/* * Copyright (C) 2013 Texas Instruments * * Author : Pankaj Bharadiya * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * * Fastboot is implemented using gadget stack, many of the ideas are * derived from fastboot implemented in OmapZoom by * Tom Rix and Sitara 2011 u-boot by * Mohammed Afzal M A * * Part of OmapZoom was copied from Android project, Android source * (legacy bootloader) was used indirectly here by using OmapZoom. * * This is Android's Copyright: * * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include "g_fastboot.h" #include #include #define MAX_PTN 11 /* Initialize the name of fastboot flash name mappings */ fastboot_ptentry omap3h1_nand_ptn[MAX_PTN] = { { .name = "X-Loader", .start = 0x0000000, .length = 0x0020000, /* 256 K */ /* Written into the first 0x40000 blocks Use HW ECC */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_HW_ECC /* FASTBOOT_PTENTRY_FLAGS_REPEAT_4, */ }, { .name = "uboot", .start = 0x0080000, .length = 0x01E0000, /* 1.875 M */ /* Skip bad blocks on write Use HW ECC */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_HW_ECC, }, { .name = "U-Boot Env", .start = NAND_ENV_OFFSET, /* set in config file */ .length = 0x0020000, .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_ENV | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, { .name = "kernel", .start = 0x0280000, .length = 0x0500000, /* 5 M */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, { .name = "initramfs", .start = 0x0780000, .length = 0x0A00000, /* 10 M */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, { .name = "devicetree", .start = 0x1180000, .length = 0x020000, /* 128k */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, { .name = "ramdisk", .start = 0x1180000, .length = 0x0C80000, /* 13 M */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, { .name = "system", .start = 0x1E80000, .length = 0x800 * 0x20000, /* >256 M */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, { .name = "cache", .start = 0x11E80000, .length = 0x400 * 0x20000, /* ~50Mb */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, { .name = "recovery", .start = 0x14A80000, .length = 0x200 * 0x20000, /* ~26Mb */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, { .name = "userdata", .start = 0x2f80000, .length = 0x28CC0000, /* ~660 Mb */ .flags = FASTBOOT_PTENTRY_FLAGS_WRITE_I | FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC, }, }; struct fastboot_config fastboot_cfg; extern int do_env_save (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]); extern int do_switch_ecc(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[]); static void set_env(char *var, char *val) { char *setenv[4] = { "setenv", NULL, NULL, NULL, }; setenv[1] = var; setenv[2] = val; do_env_set(NULL, 0, 3, setenv); } static void set_ecc(int hw) { char ecc_type[2]; char ecc_provider[3]; /* make sure there's space */ char *ecc[4] = { "nandecc", "hw", "0" , NULL }; ecc[2] = ecc_type; ecc[1] = ecc_provider; if (hw) { /*for hardware ecc : set BCH8*/ sprintf(ecc_provider, "hw"); } else { sprintf(ecc_provider, "sw"); } /* OLIO: HW = 1bit ECC, SW = BCH8 */ do_switch_ecc(NULL, 0, 2, ecc); } static void save_env(struct fastboot_ptentry *ptn, char *var, char *val) { char *saveenv[2] = { "setenv", NULL, }; set_env (var, val); if ((ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_HW_ECC) && (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC)) { /* Both can not be true */ FBTWARN("can not do hw and sw ecc for partition '%s'\n", ptn->name); FBTWARN("Ignoring these flags\n"); #ifdef OLIO_INVALID } else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_HW_ECC) { setecc(1); } else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC) { set_ecc(0); #endif /* OLIO_INVALID */ } /* OLIO: env always saved with SW */ set_ecc(0); do_env_save(NULL, 0, 1, saveenv); } static void save_block_values(struct fastboot_ptentry *ptn, unsigned int offset, unsigned int size) { struct fastboot_ptentry *env_ptn; char var[64], val[32]; char start[32], length[32]; char *setenv[4] = { "setenv", NULL, NULL, NULL, }; char *saveenv[2] = { "setenv", NULL, }; setenv[1] = var; setenv[2] = val; FBTINFO ("saving it..\n"); if (size == 0) { /* The error case, where the variables are being unset */ sprintf (var, "%s_nand_offset", ptn->name); sprintf (val, ""); do_env_set (NULL, 0, 3, setenv); sprintf (var, "%s_nand_size", ptn->name); sprintf (val, ""); do_env_set (NULL, 0, 3, setenv); } else { /* Normal case */ sprintf (var, "%s_nand_offset", ptn->name); sprintf (val, "0x%x", offset); FBTINFO("%s %s %s\n", setenv[0], setenv[1], setenv[2]); do_env_set (NULL, 0, 3, setenv); sprintf(var, "%s_nand_size", ptn->name); sprintf (val, "0x%x", size); FBTINFO("%s %s %s\n", setenv[0], setenv[1], setenv[2]); do_env_set (NULL, 0, 3, setenv); } /* Warning : The environment is assumed to be in a partition named 'enviroment'. It is very possible that your board stores the enviroment someplace else. */ env_ptn = fastboot_flash_find_ptn("environment"); if (env_ptn) { /* Some flashing requires the nand's ecc to be set */ if ((env_ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_HW_ECC) && (env_ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC)) { /* Both can not be true */ FBTWARN("can not do hw and sw ecc for partition '%s'\n", ptn->name); FBTWARN("Ignoring these flags\n"); } else if (env_ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_HW_ECC) { set_ecc(1); } else if (env_ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC) { set_ecc(0); } sprintf (start, "0x%x", env_ptn->start); sprintf (length, "0x%x", env_ptn->length); } do_env_save (NULL, 0, 1, saveenv); } /* When save = 0, just parse. The input is unchanged When save = 1, parse and do the save. The input is changed */ static int parse_env(void *ptn, char *err_string, int save, int debug) { int ret = 1; unsigned int sets = 0; unsigned int comment_start = 0; char *var = NULL; char *var_end = NULL; char *val = NULL; char *val_end = NULL; unsigned int i; char *buff = (char *)fastboot_cfg.transfer_buffer; unsigned int size = fastboot_cfg.download_bytes_unpadded; /* The input does not have to be null terminated. This will cause a problem in the corner case where the last line does not have a new line. Put a null after the end of the input. WARNING : Input buffer is assumed to be bigger than the size of the input */ if (save) buff[size] = 0; for (i = 0; i < size; i++) { if (NULL == var) { /* * Check for comments, comment ok only on * mostly empty lines */ if (buff[i] == '#') comment_start = 1; if (comment_start) { if ((buff[i] == '\r') || (buff[i] == '\n')) { comment_start = 0; } } else { if (!((buff[i] == ' ') || (buff[i] == '\t') || (buff[i] == '\r') || (buff[i] == '\n'))) { /* * Normal whitespace before the * variable */ var = &buff[i]; } } } else if (((NULL == var_end) || (NULL == val)) && ((buff[i] == '\r') || (buff[i] == '\n'))) { /* This is the case when a variable is unset. */ if (save) { /* Set the var end to null so the normal string routines will work WARNING : This changes the input */ buff[i] = '\0'; save_env(ptn, var, val); FBTDBG("Unsetting %s\n", var); } /* Clear the variable so state is parse is back to initial. */ var = NULL; var_end = NULL; sets++; } else if (NULL == var_end) { if ((buff[i] == ' ') || (buff[i] == '\t')) var_end = &buff[i]; } else if (NULL == val) { if (!((buff[i] == ' ') || (buff[i] == '\t'))) val = &buff[i]; } else if (NULL == val_end) { if ((buff[i] == '\r') || (buff[i] == '\n')) { /* look for escaped cr or ln */ if ('\\' == buff[i - 1]) { /* check for dos */ if ((buff[i] == '\r') && (buff[i+1] == '\n')) buff[i + 1] = ' '; buff[i - 1] = buff[i] = ' '; } else { val_end = &buff[i]; } } } else { sprintf(err_string, "Internal Error"); FBTDBG("Internal error at %s %d\n", __FILE__, __LINE__); return 1; } /* Check if a var / val pair is ready */ if (NULL != val_end) { if (save) { /* Set the end's with nulls so normal string routines will work. WARNING : This changes the input */ *var_end = '\0'; *val_end = '\0'; save_env(ptn, var, val); FBTDBG("Setting %s %s\n", var, val); } /* Clear the variable so state is parse is back to initial. */ var = NULL; var_end = NULL; val = NULL; val_end = NULL; sets++; } } /* Corner case Check for the case that no newline at end of the input */ if ((NULL != var) && (NULL == val_end)) { if (save) { /* case of val / val pair */ if (var_end) *var_end = '\0'; /* else case handled by setting 0 past the end of buffer. Similar for val_end being null */ save_env(ptn, var, val); if (var_end) FBTDBG("Trailing Setting %s %s\n", var, val); else FBTDBG("Trailing Unsetting %s\n", var); } sets++; } /* Did we set anything ? */ if (0 == sets) sprintf(err_string, "No variables set"); else ret = 0; return ret; } static int saveenv_to_ptn(struct fastboot_ptentry *ptn, char *err_string) { int ret = 1; int save = 0; int debug = 0; /* err_string is only 32 bytes Initialize with a generic error message. */ sprintf(err_string, "%s", "Unknown Error"); /* Parse the input twice. Only save to the enviroment if the entire input if correct */ save = 0; if (0 == parse_env(ptn, err_string, save, debug)) { save = 1; ret = parse_env(ptn, err_string, save, debug); } return ret; } static void set_ptn_ecc(struct fastboot_ptentry *ptn) { if (((ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_HW_BCH8_ECC) || (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_HW_ECC)) && (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC)) { /* Both can not be true */ FBTERR("can not do hw and sw ecc for partition '%s'\n", ptn->name); FBTERR("Ignoring these flags\n"); } else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_HW_BCH8_ECC) { set_ecc(1); } else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_HW_ECC) { set_ecc(1); } else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_SW_ECC) { set_ecc(0); } } static int write_to_ptn(struct fastboot_ptentry *ptn) { int ret = 1; char start[32], length[32]; char wstart[32], wlength[32], addr[32]; char write_type[32]; int repeat, repeat_max; char *write[6] = { "nand", "write", NULL, NULL, NULL, NULL, }; char *erase[5] = { "nand", "erase", NULL, NULL, NULL, }; erase[2] = start; erase[3] = length; write[1] = write_type; write[2] = addr; write[3] = wstart; write[4] = wlength; FBTINFO("flashing '%s'\n", ptn->name); /* Which flavor of write to use */ if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_I) sprintf(write_type, "write.i"); else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_JFFS2) sprintf(write_type, "write.jffs2"); else sprintf(write_type, "write"); /* Some flashing requires writing the same data in multiple, consecutive flash partitions */ repeat_max = 1; if (FASTBOOT_PTENTRY_FLAGS_REPEAT_MASK(ptn->flags)) { if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_CONTIGUOUS_BLOCK) { FBTWARN("can not do both 'contiguous block' and 'repeat' writes for for partition '%s'\n", ptn->name); FBTWARN("Ignoring repeat flag\n"); } else { repeat_max = (FASTBOOT_PTENTRY_FLAGS_REPEAT_MASK(ptn->flags)); } } sprintf(length, "0x%x", ptn->length); for (repeat = 0; repeat < repeat_max; repeat++) { set_ptn_ecc(ptn); sprintf(start, "0x%x", ptn->start + (repeat * ptn->length)); do_nand(NULL, 0, 4, erase); if ((ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_NEXT_GOOD_BLOCK) && (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_CONTIGUOUS_BLOCK)) { /* Both can not be true */ FBTWARN("can not do 'next good block' and 'contiguous block' for partition '%s'\n", ptn->name); FBTWARN("Ignoring these flags\n"); } else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_NEXT_GOOD_BLOCK) { /* Keep writing until you get a good block transfer_buffer should already be aligned */ if (fastboot_cfg.nand_block_size) { unsigned int blocks = fastboot_cfg.download_bytes / fastboot_cfg.nand_block_size; unsigned int i = 0; unsigned int offset = 0; sprintf(wlength, "0x%x", fastboot_cfg.nand_block_size); while (i < blocks) { /* Check for overflow */ if (offset >= ptn->length) break; /* download's address only advance if last write was successful */ sprintf(addr, "0x%x", fastboot_cfg.transfer_buffer + (i * fastboot_cfg.nand_block_size)); /* nand's address always advances */ sprintf(wstart, "0x%x", ptn->start + (repeat * ptn->length) + offset); ret = do_nand(NULL, 0, 5, write); if (ret) break; else i++; /* Go to next nand block */ offset += fastboot_cfg.nand_block_size; } } else { FBTWARN("nand block size can not be 0 when using 'next good block' for partition '%s'\n", ptn->name); FBTWARN("Ignoring write request\n"); } } else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_CONTIGUOUS_BLOCK) { /* Keep writing until you get a good block transfer_buffer should already be aligned */ if (fastboot_cfg.nand_block_size) { if (0 == nand_curr_device) { nand_info_t *nand; unsigned long off; unsigned int ok_start; nand = &nand_info[nand_curr_device]; FBTINFO("\nDevice %d bad blocks:\n", nand_curr_device); /* Initialize the ok_start to the start of the partition Then try to find a block large enough for the download */ ok_start = ptn->start; /* It is assumed that the start and length are multiples of block size */ for (off = ptn->start; off < ptn->start + ptn->length; off += nand->erasesize) { if (nand_block_isbad(nand, off)) { /* Reset the ok_start to the next block */ ok_start = off + nand->erasesize; } /* Check if we have enough blocks */ if ((ok_start - off) >= fastboot_cfg.download_bytes) break; } /* Check if there is enough space */ if (ok_start + fastboot_cfg.download_bytes <= ptn->start + ptn->length) { sprintf(addr, "0x%x", fastboot_cfg.transfer_buffer); sprintf(wstart, "0x%x", ok_start); sprintf(wlength, "0x%x", fastboot_cfg.download_bytes); ret = do_nand(NULL, 0, 5, write); /* Save the results into an environment variable on the format ptn_name + 'offset' ptn_name + 'size' */ if (ret) { /* failed */ save_block_values(ptn, 0, 0); } else { /* success */ save_block_values(ptn, ok_start, fastboot_cfg.download_bytes); } } else { FBTERR("could not find enough contiguous space in partition '%s' \n", ptn->name); FBTERR("Ignoring write request\n"); } } else { /* TBD : Generalize flash handling */ FBTERR("only handling 1 NAND per board"); FBTERR("Ignoring write request\n"); } } else { FBTWARN("nand block size can not be 0 when using 'continuous block' for partition '%s'\n", ptn->name); FBTWARN("Ignoring write request\n"); } } else { /* Normal case */ sprintf(addr, "0x%x", fastboot_cfg.transfer_buffer); sprintf(wstart, "0x%x", ptn->start + (repeat * ptn->length)); sprintf(wlength, "0x%x", fastboot_cfg.download_bytes); if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_JFFS2) sprintf(wlength, "0x%x", fastboot_cfg.download_bytes_unpadded); ret = do_nand(NULL, 0, 5, write); if (0 == repeat) { if (ret) /* failed */ save_block_values(ptn, 0, 0); else /* success */ save_block_values(ptn, ptn->start, fastboot_cfg.download_bytes); } } if (ret) break; } return ret; } int handle_flash(char *part_name , char *response) { int status = 0; fastboot_cfg.download_bytes_unpadded = fastboot_cfg.download_size; /* XXX: Revisit padding handling */ if (fastboot_cfg.nand_block_size) { if (fastboot_cfg.download_bytes % fastboot_cfg.nand_block_size) { unsigned int pad = fastboot_cfg.nand_block_size - (fastboot_cfg.download_bytes % fastboot_cfg.nand_block_size); unsigned int i; for (i = 0; i < pad; i++) { if (fastboot_cfg.download_bytes >= fastboot_cfg.transfer_buffer_size) break; fastboot_cfg.transfer_buffer[fastboot_cfg.download_bytes] = 0; fastboot_cfg.download_bytes++; } } } if (fastboot_cfg.download_bytes) { struct fastboot_ptentry *ptn; ptn = fastboot_flash_find_ptn(part_name); if (ptn == 0) { sprintf(response, "FAILpartition does not exist"); } else if ((fastboot_cfg.download_bytes > ptn->length) && !(ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_ENV)) { sprintf(response, "FAILimage too large for partition"); } else { /* Check if this is not really a flash write but rather a saveenv */ if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_WRITE_ENV) { /* Since the response can only be 64 bytes, there is no point in having a large error message. */ char err_string[32]; if (saveenv_to_ptn(ptn, &err_string[0])) { FBTINFO("savenv '%s' failed : %s\n", ptn->name, err_string); sprintf(response, "FAIL%s", err_string); } else { FBTINFO("partition '%s' saveenv-ed\n", ptn->name); sprintf(response, "OKAY"); } } else { /* Normal case */ if (write_to_ptn(ptn)) { FBTINFO("flashing '%s' failed\n", ptn->name); sprintf(response, "FAILfailed to flash partition"); } else { FBTINFO("partition '%s' flashed\n", ptn->name); sprintf(response, "OKAY"); } } } } else { sprintf(response, "FAILno image downloaded"); } return status; } /*This function does nothing with NAND partitioning. It only adds partition info to fastboot partition table. */ int do_format() { int indx; fastboot_flash_reset_ptn(); for (indx = 0; indx < MAX_PTN; indx++) { fastboot_flash_add_ptn(&omap3h1_nand_ptn[indx]); } fastboot_flash_dump_ptn(); return 0; } static int do_format_nand(void) { struct ptable *ptbl = NULL; //&the_ptable; unsigned next; int n; block_dev_desc_t *dev_desc; unsigned long blocks_to_write, result; dev_desc = get_dev_by_name(FASTBOOT_BLKDEV); if (!dev_desc) { printf("error getting device %s\n", FASTBOOT_BLKDEV); return -1; } if (!dev_desc->lba) { printf("device %s has no space\n", FASTBOOT_BLKDEV); return -1; } printf("blocks %lu\n", dev_desc->lba); #if 0 start_ptbl(ptbl, dev_desc->lba); for (n = 0, next = 0; fbt_partitions[n].name; n++) { u64 sz = fbt_partitions[n].size_kb * 2; if (fbt_partitions[n].name[0] == '-') { next += sz; continue; } if (sz == 0) sz = dev_desc->lba - next; if (add_ptn(ptbl, next, next + sz - 1, fbt_partitions[n].name)) return -1; next += sz; } end_ptbl(ptbl); blocks_to_write = DIV_ROUND_UP(sizeof(struct ptable), dev_desc->blksz); result = dev_desc->block_write(dev_desc->dev, 0, blocks_to_write, ptbl); if (result != blocks_to_write) { printf("\nFormat failed, block_write() returned %lu instead of %lu\n", result, blocks_to_write); return -1; } printf("\nnew partition table of %lu %lu-byte blocks\n", blocks_to_write, dev_desc->blksz); fbt_reset_ptn(); #endif return 0; }