diff options
-rw-r--r-- | common/imx-bbu-nand-fcb.c | 372 |
1 files changed, 341 insertions, 31 deletions
diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 5ded45ae59..b3dea37067 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -31,6 +31,7 @@ #include <linux/mtd/mtd-abi.h> #include <linux/mtd/nand_mxs.h> #include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> #include <linux/stat.h> #include <io.h> #include <mach/generic.h> @@ -794,27 +795,218 @@ static int imx_bbu_write_fcbs_dbbts(struct mtd_info *mtd, struct fcb_block *fcb) return valid > 0 ? 0 : -EIO; } +static int block_is_empty(struct mtd_info *mtd, int block) +{ + int rawsize = mtd->writesize + mtd->oobsize; + u8 *rawpage = xmalloc(rawsize); + int ret; + loff_t offset = (loff_t)block * mtd->erasesize; + + ret = raw_read_page(mtd, rawpage, offset); + if (ret) + goto err; + + ret = nand_check_erased_buf(rawpage, rawsize, 4 * 13); + + if (ret == -EBADMSG) + ret = 0; + else if (ret >= 0) + ret = 1; + +err: + free(rawpage); + return ret; +} + +static int read_firmware(struct mtd_info *mtd, int first_page, int num_pages, + void **firmware) +{ + void *buf, *pos; + int pages_per_block = mtd->erasesize / mtd->writesize; + int now, size, block, ret, need_cleaning = 0; + + pr_debug("%s: reading %d pages from page %d\n", __func__, num_pages, first_page); + + buf = pos = malloc(num_pages * mtd->writesize); + if (!buf) + return -ENOMEM; + + if (first_page % pages_per_block) { + pr_err("Firmware does not begin on eraseblock boundary\n"); + ret = -EINVAL; + goto err; + } + + block = first_page / pages_per_block; + size = num_pages * mtd->writesize; + + while (size) { + if (block >= mtd_num_pebs(mtd)) { + ret = -EIO; + goto err; + } + + if (mtd_peb_is_bad(mtd, block)) { + block++; + continue; + } + + now = min_t(unsigned int , size, mtd->erasesize); + + ret = mtd_peb_read(mtd, pos, block, 0, now); + if (ret == -EUCLEAN) { + pr_info("Block %d needs cleaning\n", block); + need_cleaning = 1; + } else if (ret < 0) { + pr_err("Reading PEB %d failed with %d\n", block, ret); + goto err; + } + + if (mtd_buf_all_ff(pos, now)) { + /* + * At this point we do not know if this is a + * block that contains only 0xff or if it is + * really empty. We test this by reading a raw + * page and check if it's empty + */ + ret = block_is_empty(mtd, block); + if (ret < 0) + goto err; + if (ret) { + ret = -EINVAL; + goto err; + } + } + + pos += now; + size -= now; + block++; + } + + ret = 0; + + *firmware = buf; + + pr_info("Firmware @ page %d, size %d pages has crc32: 0x%08x\n", + first_page, num_pages, crc32(0, buf, num_pages * mtd->writesize)); + +err: + if (ret < 0) { + free(buf); + pr_warn("Firmware at page %d is not readable\n", first_page); + return ret; + } + + if (need_cleaning) { + pr_warn("Firmware at page %d needs cleanup\n", first_page); + return -EUCLEAN; + } + + return 0; +} + +static void read_firmware_all(struct mtd_info *mtd, struct fcb_block *fcb, void **data, int *len, + int *used_refresh, int *unused_refresh, int *used) +{ + void *primary = NULL, *secondary = NULL; + int pages_per_block = mtd->erasesize / mtd->writesize; + int fw0 = imx_bbu_firmware_start_block(mtd, 0) * pages_per_block; + int fw1 = imx_bbu_firmware_start_block(mtd, 1) * pages_per_block; + int first, ret, primary_refresh = 0, secondary_refresh = 0; + + *used_refresh = 0; + *unused_refresh = 0; + + if (fcb->Firmware1_startingPage == fw0 && + fcb->Firmware2_startingPage == fw1) { + first = 0; + } else if (fcb->Firmware1_startingPage == fw1 && + fcb->Firmware2_startingPage == fw0) { + first = 1; + } else { + pr_warn("FCB is not what we expect. Update will not be robust"); + *used = 0; + return; + } + + if (fcb->PagesInFirmware1 != fcb->PagesInFirmware2) { + pr_warn("FCB is not what we expect. Update will not be robust"); + return; + } + + *len = fcb->PagesInFirmware1 * mtd->writesize; + + ret = read_firmware(mtd, fcb->Firmware1_startingPage, fcb->PagesInFirmware1, &primary); + if (ret > 0) + primary_refresh = 1; + + ret = read_firmware(mtd, fcb->Firmware2_startingPage, fcb->PagesInFirmware2, &secondary); + if (ret > 0) + secondary_refresh = 1; + + if (!primary && !secondary) { + *unused_refresh = 1; + *used_refresh = 1; + *used = 0; + *data = NULL; + } else if (primary && !secondary) { + *used_refresh = primary_refresh; + *unused_refresh = 1; + *used = first; + *data = primary; + return; + } else if (secondary && !primary) { + *used_refresh = secondary_refresh; + *unused_refresh = 1; + *used = !first; + *data = secondary; + } else { + if (memcmp(primary, secondary, fcb->PagesInFirmware1 * mtd->writesize)) + *unused_refresh = 1; + + *used_refresh = primary_refresh; + *used = first; + *data = primary; + free(secondary); + } + + pr_info("Primary firmware is on pages %d-%d, %svalid, %s\n", fcb->Firmware1_startingPage, + fcb->Firmware1_startingPage + fcb->PagesInFirmware1, primary ? "" : "in", + primary_refresh ? "needs cleanup" : "clean"); + + pr_info("secondary firmware is on pages %d-%d, %svalid, %s\n", fcb->Firmware2_startingPage, + fcb->Firmware2_startingPage + fcb->PagesInFirmware2, secondary ? "" : "in", + secondary_refresh ? "needs cleanup" : "clean"); + + pr_info("ROM uses slot %d\n", *used); +} + static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *data) { struct imx_nand_fcb_bbu_handler *imx_handler = container_of(handler, struct imx_nand_fcb_bbu_handler, handler); struct cdev *bcb_cdev; struct mtd_info *mtd; - int ret; - struct fcb_block fcb = {}; - void *fw; + int ret, i; + struct fcb_block *fcb = NULL; + void *fw = NULL, *fw_orig = NULL; unsigned fw_size, partition_size; enum filetype filetype; unsigned num_blocks_fw; int pages_per_block; + int used = 0; + int fw_orig_len; + int used_refresh = 0, unused_refresh = 0; - filetype = file_detect_type(data->image, data->len); + if (data->image) { + filetype = file_detect_type(data->image, data->len); - if (filetype != imx_handler->filetype && + if (filetype != imx_handler->filetype && !bbu_force(data, "Image is not of type %s but of type %s", file_type_to_string(imx_handler->filetype), file_type_to_string(filetype))) - return -EINVAL; + return -EINVAL; + } bcb_cdev = cdev_by_name(handler->devicefile); if (!bcb_cdev) { @@ -826,48 +1018,166 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat partition_size = mtd->size; pages_per_block = mtd->erasesize / mtd->writesize; + for (i = 0; i < 4; i++) { + read_fcb(mtd, i, &fcb); + if (fcb) + break; + } + /* - * We have to write one additional page to make the ROM happy. - * Maybe the PagesInFirmwarex fields are really the number of pages - 1. - * kobs-ng has the same. + * This code uses the following layout in the Nand flash: + * + * fwmaxsize = (n_blocks - 4) / 2 + * + * block + * + * 0 ---------------------- + * | FCB/DBBT 0 | + * 1 ---------------------- + * | FCB/DBBT 1 | + * 2 ---------------------- + * | FCB/DBBT 2 | + * 3 ---------------------- + * | FCB/DBBT 3 | + * 4 ---------------------- + * | Firmware slot 0 | + * 4 + fwmaxsize ---------------------- + * | Firmware slot 1 | + * ---------------------- + * + * We want a robust update in which a power failure may occur + * everytime without bricking the board, so here's the strategy: + * + * The FCBs contain pointers to the firmware slots in the + * Firmware1_startingPage and Firmware2_startingPage fields. Note that + * Firmware1_startingPage doesn't necessarily point to slot 0. We + * exchange the pointers during update to atomically switch between the + * old and the new firmware. + * + * - We read the first valid FCB and the firmware slots. + * - We check which firmware slot is currently used by the ROM: + * - if no FCB is found or its layout differs from the above layout, + * continue without robust update + * - if only one firmware slot is readable, the ROM uses it + * - if both slots are readable, the ROM will use slot 0 + * - Step 1: erase/update the slot currently unused by the ROM + * - Step 2: Update FCBs/DBBTs, thereby letting Firmware1_startingPage + * point to the slot we just updated. From this moment + * on the new firmware will be used and running a + * refresh/repair after a power failure after this + * step will complete the update. + * - Step 3: erase/update the other firmwre slot + * - Step 4: Eventually write FCBs/DBBTs again. This may become + * necessary when step 3 revealed new bad blocks. + * + * This robust update only works when the original FCBs on the device + * uses the same layout as this code does. In other cases update will + * also work, but it won't be robust against power failures. + * + * Refreshing the firmware which is needed when blocks become unreadable + * due to read disturbance works the same way, only that the new firmware + * is the same as the old firmware and that it will only be written when + * reading from the device returns -EUCLEAN indicating that a block needs + * to be rewritten. */ - fw_size = ALIGN(data->len + mtd->writesize, mtd->writesize); - fw = xzalloc(fw_size); - memcpy(fw, data->image, data->len); + if (fcb) + read_firmware_all(mtd, fcb, &fw_orig, &fw_orig_len, + &used_refresh, &unused_refresh, &used); + + if (data->image) { + /* + * We have to write one additional page to make the ROM happy. + * Maybe the PagesInFirmwarex fields are really the number of pages - 1. + * kobs-ng has the same. + */ + fw_size = ALIGN(data->len + mtd->writesize, mtd->writesize); + fw = xzalloc(fw_size); + memcpy(fw, data->image, data->len); + free(fw_orig); + used_refresh = 1; + unused_refresh = 1; + + free(fcb); + fcb = xzalloc(sizeof(*fcb)); + fcb->Firmware1_startingPage = imx_bbu_firmware_start_block(mtd, !used) * pages_per_block; + fcb->Firmware2_startingPage = imx_bbu_firmware_start_block(mtd, used) * pages_per_block; + fcb->PagesInFirmware1 = fw_size / mtd->writesize; + fcb->PagesInFirmware2 = fcb->PagesInFirmware1; + + fcb_create(imx_handler, fcb, mtd); + } else { + if (!fcb) { + pr_err("No FCB found on device, cannot refresh\n"); + ret = -EINVAL; + goto out; + } - num_blocks_fw = imx_bbu_firmware_max_blocks(mtd); + if (!fw_orig) { + pr_err("No firmware found on device, cannot refresh\n"); + ret = -EINVAL; + goto out; + } + + fw = fw_orig; + fw_size = fw_orig_len; + pr_info("Refreshing existing firmware\n"); + } - pr_info("maximum size per firmware: 0x%08x bytes\n", - num_blocks_fw * mtd->erasesize); + num_blocks_fw = imx_bbu_firmware_max_blocks(mtd); - if (num_blocks_fw * mtd->erasesize < fw_size) + if (num_blocks_fw * mtd->erasesize < fw_size) { + pr_err("Not enough space for update\n"); return -ENOSPC; + } ret = bbu_confirm(data); if (ret) goto out; - ret = imx_bbu_write_firmware(mtd, 0, fw, fw_size); - if (ret < 0) - goto out; + /* Step 1: write firmware which is currently unused by the ROM */ + if (unused_refresh) { + pr_info("%sing slot %d\n", data->image ? "updat" : "refresh", !used); + ret = imx_bbu_write_firmware(mtd, !used, fw, fw_size); + if (ret < 0) + goto out; + } else { + pr_info("firmware slot %d still ok, nothing to do\n", !used); + } - ret = imx_bbu_write_firmware(mtd, 1, fw, fw_size); + /* + * Step 2: Write FCBs/DBBTs. This will use the firmware we have + * just written as primary firmware. From now on the new + * firmware will be booted. + */ + ret = imx_bbu_write_fcbs_dbbts(mtd, fcb); if (ret < 0) goto out; - fcb.Firmware1_startingPage = imx_bbu_firmware_start_block(mtd, 0) * pages_per_block; - fcb.Firmware2_startingPage = imx_bbu_firmware_start_block(mtd, 1) * pages_per_block; - fcb.PagesInFirmware1 = ALIGN(data->len, mtd->writesize) / mtd->writesize; - fcb.PagesInFirmware2 = fcb.PagesInFirmware1; - - fcb_create(imx_handler, &fcb, mtd); + /* Step 3: Write the secondary firmware */ + if (used_refresh) { + pr_info("%sing slot %d\n", data->image ? "updat" : "refresh", used); + ret = imx_bbu_write_firmware(mtd, used, fw, fw_size); + if (ret < 0) + goto out; + } else { + pr_info("firmware slot %d still ok, nothing to do\n", used); + } - ret = imx_bbu_write_fcbs_dbbts(mtd, &fcb); - if (ret < 0) - goto out; + /* + * Step 4: If writing the secondary firmware discovered new bad + * blocks, write the FCBs/DBBTs again with updated bad block + * information. + */ + if (ret > 0) { + pr_info("New bad blocks detected, writing FCBs/DBBTs again\n"); + ret = imx_bbu_write_fcbs_dbbts(mtd, fcb); + if (ret < 0) + goto out; + } out: free(fw); + free(fcb); return ret; } @@ -896,7 +1206,7 @@ int imx6_bbu_nand_register_handler(const char *name, unsigned long flags) handler = &imx_handler->handler; handler->devicefile = "nand0.barebox"; handler->name = name; - handler->flags = flags; + handler->flags = flags | BBU_HANDLER_CAN_REFRESH; handler->handler = imx_bbu_nand_update; ret = bbu_register_handler(handler); @@ -973,7 +1283,7 @@ int imx28_bbu_nand_register_handler(const char *name, unsigned long flags) handler = &imx_handler->handler; handler->devicefile = "nand0.barebox"; handler->name = name; - handler->flags = flags; + handler->flags = flags | BBU_HANDLER_CAN_REFRESH; handler->handler = imx_bbu_nand_update; ret = bbu_register_handler(handler); |