[U-Boot] [PATCH 1/1]: add nand_bootupdate for i.MX6 and likes

This one adds nand_bootupdate command for i.MX6 and similar MCUs. It generates proper NAND boot structures (FCB, DBBT, etc) and writes those along with U-Boot mx image to where they belong so system would be able to boot off of raw NAND.
As of now the only way to do it is by using user-space kobs-ng utility that has lots of unnecessary bells and whistles and only runs from Linux so it is impossible to update raw NAND U-Boot from U-Boot itself.
It does not give any choice for the actual data placement in NAND but that is done deliberately -- there is no reason to put it anywhere but the only location i.MX6 Boot ROM expects it to be.
This is my THIRD attempt to get it into the main U-Boot tree. I do really hope it would get applied before I retire.
Signed-off-by: Sergey Kubushyn ksi@koi8.net --- --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -571,6 +571,16 @@ config CMD_TIME help Run commands and summarize execution time.
+config CMD_NAND_BOOTUPDATE + bool "Update NAND bootloader on iMX6 and its brethen" + depends on ARCH_MX6 && NAND_BOOT && CMD_NAND + help + Having iMX6 NAND U-Boot image in memory creates all necessary + structures and writes those where they belong along with that + U-Boot image so system is able to boot off of raw NAND. Kinda + like kobs-ng utility but simpler. + + # TODO: rename to CMD_SLEEP config CMD_MISC bool "sleep"
--- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o +obj-$(CONFIG_CMD_NAND_BOOTUPDATE) += mxs_nand_bootupdate.o
else # minimal SPL drivers
--- a/drivers/mtd/nand/mxs_nand.c +++ b/drivers/mtd/nand/mxs_nand.c @@ -26,6 +26,7 @@ #include <asm/imx-common/regs-gpmi.h> #include <asm/arch/sys_proto.h> #include <asm/imx-common/dma.h> +#include <asm/imx-common/mxs_nand.h>
#define MXS_NAND_DMA_DESCRIPTOR_COUNT 4
@@ -150,7 +151,7 @@ static uint32_t mxs_nand_aux_status_offset(void) return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3; }
-static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, +uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, uint32_t page_oob_size) { int ecc_strength; @@ -226,14 +227,14 @@ static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size, return block_mark_bit_offset; }
-static uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) +uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) { uint32_t ecc_strength; ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize); return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) >> 3; }
-static uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) +uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) { uint32_t ecc_strength; ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
--- /dev/null +++ b/arch/arm/include/asm/imx-common/mxs_nand.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 PHYTEC Messtechnik GmbH, + * Author: Stefan Christ <s.christ at phytec.de> + * + * 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. + */ + +#ifndef __NAND_MXS_H +#define __NAND_MXS_H + +/* + * Functions are definied in drivers/mtd/nand/nand_mxs.c. They are used to + * calculate the ECC Strength, BadBlockMarkerByte and BadBlockMarkerStartBit + * which are placed into the FCB structure. The i.MX6 ROM needs these + * parameters to read the firmware from NAND. + * + * The parameters depends on the pagesize and oobsize of NAND chips and are + * different for each combination. To avoid placing hardcoded values in the bbu + * update handler code, the generic calculation from the driver code is used. + */ + +uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, + uint32_t page_oob_size); + +uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd); + +uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd); + +#endif /* __NAND_MXS_H */
--- /dev/null +++ b/drivers/mtd/nand/mxs_nand_bootupdate.c @@ -0,0 +1,556 @@ +/* + * mxs_nand_bootupdate.c - write U-Boot to MXS NAND to make it bootable + * + * Copyright (c) 2016 Sergey Kubushyn ksi@koi8.net + * + * Most of the source shamelesly stolen from barebox. + * + * Here is the original copyright: + * + *=== Cut === + * Copyright (C) 2014 Sascha Hauer, Pengutronix + * + * 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. + *=== Cut === + */ + + +#include <common.h> +#include <linux/sizes.h> +#include <linux/mtd/mtd.h> +#include <linux/compat.h> +#include <command.h> +#include <console.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <jffs2/jffs2.h> +#include <nand.h> +#include <errno.h> +#include <asm/imx-common/mxs_nand.h> +#include <asm/imx-common/imximage.cfg> + + +#ifndef CONFIG_CMD_MTDPARTS +#error "CONFIG_CMD_MTDPARTS is not set, mtd partition support missing" +#endif + +static const char *uboot_tgt = "nand0,0"; + +/* partition handling routines */ +int mtdparts_init(void); +int id_parse(const char *id, const char **ret_id, u8 *dev_type, u8 *dev_num); +int find_dev_and_part(const char *id, struct mtd_device **dev, + u8 *part_num, struct part_info **part); + +struct dbbt_block { + uint32_t Checksum; /* reserved on i.MX6 */ + uint32_t FingerPrint; + uint32_t Version; + uint32_t numberBB; /* reserved on i.MX6 */ + uint32_t DBBTNumOfPages; +}; + +struct fcb_block { + uint32_t Checksum; /* First fingerprint in first byte */ + uint32_t FingerPrint; /* 2nd fingerprint at byte 4 */ + uint32_t Version; /* 3rd fingerprint at byte 8 */ + uint8_t DataSetup; + uint8_t DataHold; + uint8_t AddressSetup; + uint8_t DSAMPLE_TIME; + /* These are for application use only and not for ROM. */ + uint8_t NandTimingState; + uint8_t REA; + uint8_t RLOH; + uint8_t RHOH; + uint32_t PageDataSize; /* 2048 for 2K pages, 4096 for 4K pages */ + uint32_t TotalPageSize; /* 2112 for 2K pages, 4314 for 4K pages */ + uint32_t SectorsPerBlock; /* Number of 2K sections per block */ + uint32_t NumberOfNANDs; /* Total Number of NANDs - not used by ROM */ + uint32_t TotalInternalDie; /* Number of separate chips in this NAND */ + uint32_t CellType; /* MLC or SLC */ + uint32_t EccBlockNEccType; /* Type of ECC, can be one of BCH-0-20 */ + uint32_t EccBlock0Size; /* Number of bytes for Block0 - BCH */ + uint32_t EccBlockNSize; /* Block size in bytes for all blocks other than Block0 - BCH */ + uint32_t EccBlock0EccType; /* Ecc level for Block 0 - BCH */ + uint32_t MetadataBytes; /* Metadata size - BCH */ + uint32_t NumEccBlocksPerPage; /* Number of blocks per page for ROM use - BCH */ + uint32_t EccBlockNEccLevelSDK; /* Type of ECC, can be one of BCH-0-20 */ + uint32_t EccBlock0SizeSDK; /* Number of bytes for Block0 - BCH */ + uint32_t EccBlockNSizeSDK; /* Block size in bytes for all blocks other than Block0 - BCH */ + uint32_t EccBlock0EccLevelSDK; /* Ecc level for Block 0 - BCH */ + uint32_t NumEccBlocksPerPageSDK;/* Number of blocks per page for SDK use - BCH */ + uint32_t MetadataBytesSDK; /* Metadata size - BCH */ + uint32_t EraseThreshold; /* To set into BCH_MODE register */ + uint32_t BootPatch; /* 0 for normal boot and 1 to load patch starting next to FCB */ + uint32_t PatchSectors; /* Size of patch in sectors */ + uint32_t Firmware1_startingPage;/* Firmware image starts on this sector */ + uint32_t Firmware2_startingPage;/* Secondary FW Image starting Sector */ + uint32_t PagesInFirmware1; /* Number of sectors in firmware image */ + uint32_t PagesInFirmware2; /* Number of sector in secondary FW image */ + uint32_t DBBTSearchAreaStartAddress; /* Page address where dbbt search area begins */ + uint32_t BadBlockMarkerByte; /* Byte in page data that have manufacturer marked bad block marker, */ + /* this will be swapped with metadata[0] to complete page data. */ + uint32_t BadBlockMarkerStartBit;/* For BCH ECC sizes other than 8 and 16 the bad block marker does not */ + /* start at 0th bit of BadBlockMarkerByte. This field is used to get to */ + /* the start bit of bad block marker byte with in BadBlockMarkerByte */ + uint32_t BBMarkerPhysicalOffset;/* FCB value that gives byte offset for bad block marker on physical NAND page */ + uint32_t BCHType; + + uint32_t TMTiming2_ReadLatency; + uint32_t TMTiming2_PreambleDelay; + uint32_t TMTiming2_CEDelay; + uint32_t TMTiming2_PostambleDelay; + uint32_t TMTiming2_CmdAddPause; + uint32_t TMTiming2_DataPause; + uint32_t TMSpeed; + uint32_t TMTiming1_BusyTimeout; + + uint32_t DISBBM; /* the flag to enable (1)/disable(0) bi swap */ + uint32_t BBMarkerPhysicalOffsetInSpareData; /* The swap position of main area in spare area */ +}; + +struct fw_write_data { + int fw1_blk; + int fw2_blk; + int part_blks; + void *buf; + size_t len; +}; + +#define BF_VAL(v, bf) (((v) & bf##_MASK) >> bf##_OFFSET) +#define GETBIT(v,n) (((v) >> (n)) & 0x1) + + +static uint8_t calculate_parity_13_8(uint8_t d) +{ + uint8_t p = 0; + + p |= (GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 3) ^ GETBIT(d, 2)) << 0; + p |= (GETBIT(d, 7) ^ GETBIT(d, 5) ^ GETBIT(d, 4) ^ GETBIT(d, 2) ^ GETBIT(d, 1)) << 1; + p |= (GETBIT(d, 7) ^ GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 1) ^ GETBIT(d, 0)) << 2; + p |= (GETBIT(d, 7) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 0)) << 3; + p |= (GETBIT(d, 6) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 2) ^ GETBIT(d, 1) ^ GETBIT(d, 0)) << 4; + return p; +} + +static void encode_hamming_13_8(void *_src, void *_ecc, size_t size) +{ + int i; + uint8_t *src = _src; + uint8_t *ecc = _ecc; + + for (i = 0; i < size; i++) + ecc[i] = calculate_parity_13_8(src[i]); +} + +static uint32_t calc_chksum(void *buf, size_t size) +{ + u32 chksum = 0; + u8 *bp = buf; + size_t i; + + for (i = 0; i < size; i++) + chksum += bp[i]; + + return ~chksum; +} + + +static ssize_t raw_write_page(struct mtd_info *mtd, void *buf, loff_t offset) +{ + struct mtd_oob_ops ops; + ssize_t ret; + + ops.mode = MTD_OPS_RAW; + ops.ooboffs = 0; + ops.datbuf = buf; + ops.len = mtd->writesize; + ops.oobbuf = buf + mtd->writesize; + ops.ooblen = mtd->oobsize; + ret = mtd_write_oob(mtd, offset, &ops); + + return ret; +} + +static int fcb_create(struct fcb_block *fcb, struct mtd_info *mtd) +{ + fcb->FingerPrint = 0x20424346; + fcb->Version = 0x01000000; + fcb->PageDataSize = mtd->writesize; + fcb->TotalPageSize = mtd->writesize + mtd->oobsize; + fcb->SectorsPerBlock = mtd->erasesize / mtd->writesize; + + /* Divide ECC strength by two and save the value into FCB structure. */ + fcb->EccBlock0EccType = + mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1; + + fcb->EccBlockNEccType = fcb->EccBlock0EccType; + + /* Also hardcoded in kobs-ng */ + fcb->EccBlock0Size = 0x00000200; + fcb->EccBlockNSize = 0x00000200; + fcb->DataSetup = 80; + fcb->DataHold = 60; + fcb->AddressSetup = 25; + fcb->DSAMPLE_TIME = 6; + fcb->MetadataBytes = 10; + + /* DBBT search area starts at second page on first block */ + fcb->DBBTSearchAreaStartAddress = 1; + + fcb->BadBlockMarkerByte = mxs_nand_mark_byte_offset(mtd); + fcb->BadBlockMarkerStartBit = mxs_nand_mark_bit_offset(mtd); + + fcb->BBMarkerPhysicalOffset = mtd->writesize; + + fcb->NumEccBlocksPerPage = mtd->writesize / fcb->EccBlock0Size - 1; + + fcb->Checksum = calc_chksum((void *)fcb + 4, sizeof(*fcb) - 4); + + return 0; +} + +/* Erase entire U-Boot partition */ +static int mxs_nand_uboot_erase(struct mtd_info *mtd, struct part_info *part) +{ + uint64_t offset = 0; + struct erase_info erase; + int len = part->size; + int ret; + + while (len > 0) { + pr_debug("erasing at 0x%08llx\n", offset); + if (mtd_block_isbad(mtd, offset)) { + pr_debug("erase skip block @ 0x%08llx\n", offset); + offset += mtd->erasesize; + continue; + } + + memset(&erase, 0, sizeof(erase)); + erase.addr = offset; + erase.len = mtd->erasesize; + + ret = mtd_erase(mtd, &erase); + if (ret) + return ret; + + offset += mtd->erasesize; + len -= mtd->erasesize; + } + + return 0; +} + +/* Write the U-Boot proper (2 copies) to where it belongs. */ +/* This is U-Boot binary image itself, no FCB/DBBT here yet. */ +static int mxs_nand_uboot_write_fw(struct mtd_info *mtd, struct fw_write_data *fw) +{ + uint64_t offset; + int ret; + int blk; + size_t dummy; + size_t bytes_left; + int chunk; + void *p; + + bytes_left = fw->len; + p = fw->buf; + blk = fw->fw1_blk; + offset = blk * mtd->erasesize; + + while (bytes_left > 0) { + chunk = min(bytes_left, mtd->erasesize); + + pr_debug("fw1: writing %p at 0x%08llx, left 0x%08x\n", + p, offset, bytes_left); + + if (mtd_block_isbad(mtd, offset)) { + pr_debug("fw1: write skip block @ 0x%08llx\n", offset); + offset += mtd->erasesize; + blk++; + continue; + } + + if (blk >= fw->fw2_blk) + return -ENOSPC; + + ret = mtd_write(mtd, offset, chunk, &dummy, p); + if (ret) + return ret; + + offset += chunk; + bytes_left -= chunk; + p += chunk; + blk++; + } + + bytes_left = fw->len; + p = fw->buf; + blk = fw->fw2_blk; + offset = blk * mtd->erasesize; + + while (bytes_left > 0) { + chunk = min(bytes_left, mtd->erasesize); + + pr_debug("fw2: writing %p at 0x%08llx, left 0x%08x\n", + p, offset, bytes_left); + + if (mtd_block_isbad(mtd, offset)) { + pr_debug("fw2: write skip block @ 0x%08llx\n", offset); + offset += mtd->erasesize; + blk++; + continue; + } + + if (blk >= fw->part_blks) + return -ENOSPC; + + ret = mtd_write(mtd, offset, chunk, &dummy, p); + if (ret) + return ret; + + offset += chunk; + bytes_left -= chunk; + p += chunk; + blk++; + } + + return 0; +} + +static int dbbt_data_create(struct mtd_info *mtd, void *buf, int num_blocks) +{ + int n; + int n_bad_blocks = 0; + uint32_t *bb = buf + 0x8; + uint32_t *n_bad_blocksp = buf + 0x4; + + for (n = 0; n < num_blocks; n++) { + loff_t offset = n * mtd->erasesize; + if (mtd_block_isbad(mtd, offset)) { + n_bad_blocks++; + *bb = n; + bb++; + } + } + + *n_bad_blocksp = n_bad_blocks; + + return n_bad_blocks; +} + +/********************************************************************/ +/* This is where it is all done. Takes pointer to a U-Boot image in */ +/* RAM and image size, creates FCB/DBBT and writes everything where */ +/* it belongs into NAND. Image must be an IMX image built for NAND. */ +/********************************************************************/ +static int mxs_nand_uboot_update(const void *img, size_t len) +{ + int i, ret; + + size_t dummy; + loff_t offset = 0; + + void *fcb_raw_page; + void *dbbt_page; + void *dbbt_data_page; + void *ecc; + + uint32_t num_blocks_fcb_dbbt; + uint32_t num_blocks_fw; + + struct mtd_info *mtd; + struct fcb_block *fcb; + struct dbbt_block *dbbt; + + struct mtd_device *dev; + struct part_info *part; + u8 pnum; + + struct fw_write_data fw; + + if ((mtdparts_init() == 0) && + (find_dev_and_part(uboot_tgt, &dev, &pnum, &part) == 0)) { + if (dev->id->type != MTD_DEV_TYPE_NAND) { + puts("Not a NAND device\n"); + return -ENODEV; + } + } + + nand_curr_device = dev->id->num; + +#ifdef CONFIG_SYS_NAND_SELECT_DEVICE + board_nand_select_device(nand_info[nand_curr_device].priv, nand_curr_device); +#endif + + /* Get a pointer to mtd_info for selected device */ + + mtd = get_mtd_device_nm("nand0"); /* We always boot off of nand0 */ + + if (IS_ERR(mtd)) { + /* Should not happen */ + puts("No nand0 device...\n"); + return -ENODEV; + } + + put_mtd_device(mtd); + + /* Quick and dirty check if we have 2Mbytes of good blocks in nand0,0 */ + /* Not sure if it is needed at all but won't hurt so here it is... */ + + i = 0; + offset = 0; /* It is the first partition so it starts at block 0 */ + + while (offset < part->size) { + if (!mtd_block_isbad(mtd, offset)) { + i += mtd->erasesize; + } + offset += mtd->erasesize; + } + + if (i < SZ_2M) { + puts("Partition too small for U-Boot!\n"); + return -EINVAL; + } + + /* We will use 4 first blocks for FCB/DBBT copies. */ + /* The rest of partition is split in half and used */ + /* for two U-Boot copies. We don't care if those */ + /* start on good or bad block - bad blocks will be */ + /* skipped on write, ROM boot code will also skip */ + /* bad blocks on bootup when loading U-Boot image. */ + + fw.part_blks = part->size / mtd->erasesize; + num_blocks_fcb_dbbt = 4; + num_blocks_fw = (fw.part_blks - num_blocks_fcb_dbbt) / 2; + fw.fw1_blk = num_blocks_fcb_dbbt; + fw.fw2_blk = fw.fw1_blk + num_blocks_fw; + + /* OK, now create FCB structure for bootROM NAND boot */ + + fcb_raw_page = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + + fcb = fcb_raw_page + 12; + ecc = fcb_raw_page + 512 + 12; + + dbbt_page = kzalloc(mtd->writesize, GFP_KERNEL); + dbbt_data_page = kzalloc(mtd->writesize, GFP_KERNEL); + dbbt = dbbt_page; + + /* Write one additional page to make the ROM happy. */ + /* Maybe the PagesInFirmwarex fields are really the */ + /* number of pages - 1. kobs-ng does the same. */ + + fw.len = ALIGN(len + FLASH_OFFSET_STANDARD + mtd->writesize, mtd->writesize); + fw.buf = kzalloc(fw.len, GFP_KERNEL); + memcpy(fw.buf + FLASH_OFFSET_STANDARD, img, len); + + /* Erase entire partition */ + ret = mxs_nand_uboot_erase(mtd, part); + if (ret) + goto out; + + /* Now write 2 copies of the U-Boot proper to where they belong. */ + /* Headers (FCB, DBBT) will be generated and written after that. */ + ret = mxs_nand_uboot_write_fw(mtd, &fw); + if (ret < 0) + goto out; + + /* Create FCB, calculate ECC (we don't/can't use hardware ECC */ + /* here so we do it ourselves and then write _RAW_ pages. */ + + fcb->Firmware1_startingPage = fw.fw1_blk * mtd->erasesize / mtd->writesize; + fcb->Firmware2_startingPage = fw.fw2_blk * mtd->erasesize / mtd->writesize; + fcb->PagesInFirmware1 = + ALIGN(len + FLASH_OFFSET_STANDARD, mtd->writesize) / mtd->writesize; + fcb->PagesInFirmware2 = fcb->PagesInFirmware1; + + fcb_create(fcb, mtd); + encode_hamming_13_8(fcb, ecc, 512); + + /* + * Set the first and second byte of OOB data to 0xFF, not 0x00. These + * bytes are used as the Manufacturers Bad Block Marker (MBBM). Since + * the FCB is mostly written to the first page in a block, a scan for + * factory bad blocks will detect these blocks as bad, e.g. when + * function nand_scan_bbt() is executed to build a new bad block table. + * We will _NOT_ mark a bad block as good -- we skip the bad blocks. + */ + memset(fcb_raw_page + mtd->writesize, 0xff, 2); + + /* Now create DBBT */ + dbbt->Checksum = 0; + dbbt->FingerPrint = 0x54424244; + dbbt->Version = 0x01000000; + + if ((ret = dbbt_data_create(mtd, dbbt_data_page, fw.part_blks)) < 0) + goto out; + + if (ret > 0) + dbbt->DBBTNumOfPages = 1; + + offset = 0; + + if (mtd_block_isbad(mtd, offset)) { + puts("Block 0 is bad, NAND unusable\n"); + ret = -EIO; + goto out; + } + + /* Write FCB/DBBT to first 4 blocks. Skip bad blocks if any. */ + /* Less than 4 copies will be written if there were BBs !!! */ + for (i = 0; i < 4; i++) { + + if (mtd_block_isbad(mtd, offset)) { + pr_err("Block %d is bad, skipped\n", i); + continue; + } + + + ret = raw_write_page(mtd, fcb_raw_page, mtd->erasesize * i); + if (ret) + goto out; + + ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize, + mtd->writesize, &dummy, dbbt_page); + if (ret) + goto out; + + /* DBBTNumOfPages == 0 if no bad blocks */ + if (dbbt->DBBTNumOfPages > 0) { + ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize * 5, + mtd->writesize, &dummy, dbbt_data_page); + if (ret) + goto out; + } + } + +out: + kfree(dbbt_page); + kfree(dbbt_data_page); + kfree(fcb_raw_page); + kfree(fw.buf); + + return ret; +} + + +int mxs_do_nand_bootupdate(ulong addr, size_t len) +{ + /* KSI: Unlock NAND first if it is locked... */ + + return mxs_nand_uboot_update((const void *)addr, len); +}
--- a/cmd/nand.c +++ b/cmd/nand.c @@ -38,6 +38,11 @@ int find_dev_and_part(const char *id, struct mtd_device **dev, u8 *part_num, struct part_info **part); #endif
+#ifdef CONFIG_CMD_NAND_BOOTUPDATE +/* This comes from a separate file in drivers/mtd/nand */ +int mxs_do_nand_bootupdate(ulong addr, size_t len); +#endif + static int nand_dump(struct mtd_info *mtd, ulong off, int only_oob, int repeat) { @@ -372,6 +377,9 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) loff_t off, size, maxsize; char *cmd, *s; struct mtd_info *mtd; +#ifdef CONFIG_CMD_NAND_BOOTUPDATE + size_t cnt; +#endif #ifdef CONFIG_SYS_NAND_QUIET int quiet = CONFIG_SYS_NAND_QUIET; #else @@ -777,6 +785,48 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) } #endif
+#ifdef CONFIG_CMD_NAND_BOOTUPDATE + if (strncmp(cmd, "bootupdate", 10) == 0) { + + if (argc < 3) { + /* All default values */ + addr = getenv_ulong("fileaddr", 16, 0UL); + cnt = getenv_ulong("filesize", 16, 0UL); + } + + if (argc == 3) { + /* 'addr' only, file size from environment */ + if ((addr = simple_strtoul(argv[2], NULL, 16)) == 0UL) + addr = getenv_ulong("fileaddr", 16, 0UL); + + cnt = getenv_ulong("filesize", 16, 0UL); + } + + if (argc > 3) { + /* 'addr', 'size', and possibly more */ + if ((addr = simple_strtoul(argv[2], NULL, 16)) == 0UL) + addr = getenv_ulong("fileaddr", 16, 0UL); + + if ((cnt = simple_strtoul(argv[3], NULL, 16)) == 0UL) + cnt = getenv_ulong("filesize", 16, 0UL); + } + + + if (addr == 0 || cnt == 0) { + puts("Invalid arguments to nand bootupdate!\n"); + return 1; + } + + if (mxs_do_nand_bootupdate(addr, cnt)) { + puts("NAND bootupdate failed!\n"); + return 1; + } + + puts("NAND bootupdate successful\n"); + return 0; + } +#endif + usage: return CMD_RET_USAGE; } @@ -798,6 +848,17 @@ static char nand_help_text[] = " 'addr', skipping bad blocks and dropping any pages at the end\n" " of eraseblocks that contain only 0xFF\n" #endif +#ifdef CONFIG_CMD_NAND_BOOTUPDATE + "nand bootupdate - [addr] [size]\n" + " write U-Boot into NAND in board/SoC specific manner creating all\n" + " required headers and other bits and pieces as required for the\n" + " system to be able to boot off of NAND. 'addr' is the address\n" + " where U-Boot image has been loaded at, 'size' is its size.\n" + " If any of 'addr'/'size' is missing it is taken from environment\n" + " for the last file loaded. U-Boot image must be of a proper type\n" + " for the target platform (only IMX image supported at the moment)\n" + " binary without U-Boot image headers (e.g. u-boot.imx file.)\n" +#endif "nand erase[.spread] [clean] off size - erase 'size' bytes " "from offset 'off'\n" " With '.spread', erase enough for given file size, otherwise,\n"
--- ****************************************************************** * KSI@home KOI8 Net < > The impossible we do immediately. * * Las Vegas NV, USA < > Miracles require 24-hour notice. * ******************************************************************

Hi Sergey,
On 05/10/2016 21:57, Sergey Kubushyn wrote:
This one adds nand_bootupdate command for i.MX6 and similar MCUs. It generates proper NAND boot structures (FCB, DBBT, etc) and writes those along with U-Boot mx image to where they belong so system would be able to boot off of raw NAND.
As of now the only way to do it is by using user-space kobs-ng utility that has lots of unnecessary bells and whistles and only runs from Linux so it is impossible to update raw NAND U-Boot from U-Boot itself.
It does not give any choice for the actual data placement in NAND but that is done deliberately -- there is no reason to put it anywhere but the only location i.MX6 Boot ROM expects it to be.
This is my THIRD attempt to get it into the main U-Boot tree. I do really hope it would get applied before I retire.
Do not think twice, I would *really* like to have this feature merged. But the patch should at least be applied clean. I cannot apply it becuase there are conflicts in cmd/nand.c and mxs_nand.c (the other conflicts are marginals).
Can you please check this (before you retire, of course !) ?
Best regards, Stefano Babic
Signed-off-by: Sergey Kubushyn ksi@koi8.net
--- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -571,6 +571,16 @@ config CMD_TIME help Run commands and summarize execution time.
+config CMD_NAND_BOOTUPDATE
- bool "Update NAND bootloader on iMX6 and its brethen"
- depends on ARCH_MX6 && NAND_BOOT && CMD_NAND
- help
Having iMX6 NAND U-Boot image in memory creates all necessary
structures and writes those where they belong along with that
U-Boot image so system is able to boot off of raw NAND. Kinda
like kobs-ng utility but simpler.
# TODO: rename to CMD_SLEEP config CMD_MISC bool "sleep"
--- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o +obj-$(CONFIG_CMD_NAND_BOOTUPDATE) += mxs_nand_bootupdate.o
else # minimal SPL drivers
--- a/drivers/mtd/nand/mxs_nand.c +++ b/drivers/mtd/nand/mxs_nand.c @@ -26,6 +26,7 @@ #include <asm/imx-common/regs-gpmi.h> #include <asm/arch/sys_proto.h> #include <asm/imx-common/dma.h> +#include <asm/imx-common/mxs_nand.h>
#define MXS_NAND_DMA_DESCRIPTOR_COUNT 4
@@ -150,7 +151,7 @@ static uint32_t mxs_nand_aux_status_offset(void) return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3; }
-static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, +uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, uint32_t page_oob_size) { int ecc_strength; @@ -226,14 +227,14 @@ static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size, return block_mark_bit_offset; }
-static uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) +uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) { uint32_t ecc_strength; ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize); return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) >> 3; }
-static uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) +uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) { uint32_t ecc_strength; ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
--- /dev/null +++ b/arch/arm/include/asm/imx-common/mxs_nand.h @@ -0,0 +1,37 @@ +/*
- Copyright (C) 2015 PHYTEC Messtechnik GmbH,
- Author: Stefan Christ <s.christ at phytec.de>
- 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.
- */
+#ifndef __NAND_MXS_H +#define __NAND_MXS_H
+/*
- Functions are definied in drivers/mtd/nand/nand_mxs.c. They are
used to
- calculate the ECC Strength, BadBlockMarkerByte and
BadBlockMarkerStartBit
- which are placed into the FCB structure. The i.MX6 ROM needs these
- parameters to read the firmware from NAND.
- The parameters depends on the pagesize and oobsize of NAND chips and
are
- different for each combination. To avoid placing hardcoded values in
the bbu
- update handler code, the generic calculation from the driver code is
used.
- */
+uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size,
uint32_t page_oob_size);
+uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd);
+uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd);
+#endif /* __NAND_MXS_H */
--- /dev/null +++ b/drivers/mtd/nand/mxs_nand_bootupdate.c @@ -0,0 +1,556 @@ +/*
- mxs_nand_bootupdate.c - write U-Boot to MXS NAND to make it bootable
- Copyright (c) 2016 Sergey Kubushyn ksi@koi8.net
- Most of the source shamelesly stolen from barebox.
- Here is the original copyright:
- *=== Cut ===
- Copyright (C) 2014 Sascha Hauer, Pengutronix
- 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.
- *=== Cut ===
- */
+#include <common.h> +#include <linux/sizes.h> +#include <linux/mtd/mtd.h> +#include <linux/compat.h> +#include <command.h> +#include <console.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <jffs2/jffs2.h> +#include <nand.h> +#include <errno.h> +#include <asm/imx-common/mxs_nand.h> +#include <asm/imx-common/imximage.cfg>
+#ifndef CONFIG_CMD_MTDPARTS +#error "CONFIG_CMD_MTDPARTS is not set, mtd partition support missing" +#endif
+static const char *uboot_tgt = "nand0,0";
+/* partition handling routines */ +int mtdparts_init(void); +int id_parse(const char *id, const char **ret_id, u8 *dev_type, u8 *dev_num); +int find_dev_and_part(const char *id, struct mtd_device **dev,
u8 *part_num, struct part_info **part);
+struct dbbt_block {
- uint32_t Checksum; /* reserved on i.MX6 */
- uint32_t FingerPrint;
- uint32_t Version;
- uint32_t numberBB; /* reserved on i.MX6 */
- uint32_t DBBTNumOfPages;
+};
+struct fcb_block {
- uint32_t Checksum; /* First fingerprint in first byte */
- uint32_t FingerPrint; /* 2nd fingerprint at byte 4 */
- uint32_t Version; /* 3rd fingerprint at byte 8 */
- uint8_t DataSetup;
- uint8_t DataHold;
- uint8_t AddressSetup;
- uint8_t DSAMPLE_TIME;
- /* These are for application use only and not for ROM. */
- uint8_t NandTimingState;
- uint8_t REA;
- uint8_t RLOH;
- uint8_t RHOH;
- uint32_t PageDataSize; /* 2048 for 2K pages, 4096 for 4K
pages */
- uint32_t TotalPageSize; /* 2112 for 2K pages, 4314 for 4K
pages */
- uint32_t SectorsPerBlock; /* Number of 2K sections per block */
- uint32_t NumberOfNANDs; /* Total Number of NANDs - not used
by ROM */
- uint32_t TotalInternalDie; /* Number of separate chips in this
NAND */
- uint32_t CellType; /* MLC or SLC */
- uint32_t EccBlockNEccType; /* Type of ECC, can be one of
BCH-0-20 */
- uint32_t EccBlock0Size; /* Number of bytes for Block0 - BCH */
- uint32_t EccBlockNSize; /* Block size in bytes for all
blocks other than Block0 - BCH */
- uint32_t EccBlock0EccType; /* Ecc level for Block 0 - BCH */
- uint32_t MetadataBytes; /* Metadata size - BCH */
- uint32_t NumEccBlocksPerPage; /* Number of blocks per page for
ROM use - BCH */
- uint32_t EccBlockNEccLevelSDK; /* Type of ECC, can be one of
BCH-0-20 */
- uint32_t EccBlock0SizeSDK; /* Number of bytes for Block0 - BCH */
- uint32_t EccBlockNSizeSDK; /* Block size in bytes for all blocks
other than Block0 - BCH */
- uint32_t EccBlock0EccLevelSDK; /* Ecc level for Block 0 - BCH */
- uint32_t NumEccBlocksPerPageSDK;/* Number of blocks per page for
SDK use - BCH */
- uint32_t MetadataBytesSDK; /* Metadata size - BCH */
- uint32_t EraseThreshold; /* To set into BCH_MODE register */
- uint32_t BootPatch; /* 0 for normal boot and 1 to load patch
starting next to FCB */
- uint32_t PatchSectors; /* Size of patch in sectors */
- uint32_t Firmware1_startingPage;/* Firmware image starts on this
sector */
- uint32_t Firmware2_startingPage;/* Secondary FW Image starting
Sector */
- uint32_t PagesInFirmware1; /* Number of sectors in firmware
image */
- uint32_t PagesInFirmware2; /* Number of sector in secondary FW
image */
- uint32_t DBBTSearchAreaStartAddress; /* Page address where dbbt
search area begins */
- uint32_t BadBlockMarkerByte; /* Byte in page data that have
manufacturer marked bad block marker, */
/* this will be swapped with metadata[0] to
complete page data. */
- uint32_t BadBlockMarkerStartBit;/* For BCH ECC sizes other than 8
and 16 the bad block marker does not */
/* start at 0th bit of BadBlockMarkerByte. This
field is used to get to */
/* the start bit of bad block marker byte with in
BadBlockMarkerByte */
- uint32_t BBMarkerPhysicalOffset;/* FCB value that gives byte offset
for bad block marker on physical NAND page */
- uint32_t BCHType;
- uint32_t TMTiming2_ReadLatency;
- uint32_t TMTiming2_PreambleDelay;
- uint32_t TMTiming2_CEDelay;
- uint32_t TMTiming2_PostambleDelay;
- uint32_t TMTiming2_CmdAddPause;
- uint32_t TMTiming2_DataPause;
- uint32_t TMSpeed;
- uint32_t TMTiming1_BusyTimeout;
- uint32_t DISBBM; /* the flag to enable (1)/disable(0) bi swap */
- uint32_t BBMarkerPhysicalOffsetInSpareData; /* The swap position of
main area in spare area */ +};
+struct fw_write_data {
- int fw1_blk;
- int fw2_blk;
- int part_blks;
- void *buf;
- size_t len;
+};
+#define BF_VAL(v, bf) (((v) & bf##_MASK) >> bf##_OFFSET) +#define GETBIT(v,n) (((v) >> (n)) & 0x1)
+static uint8_t calculate_parity_13_8(uint8_t d) +{
- uint8_t p = 0;
- p |= (GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 3) ^ GETBIT(d,
2)) << 0;
- p |= (GETBIT(d, 7) ^ GETBIT(d, 5) ^ GETBIT(d, 4) ^ GETBIT(d, 2) ^
GETBIT(d, 1)) << 1;
- p |= (GETBIT(d, 7) ^ GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 1) ^
GETBIT(d, 0)) << 2;
- p |= (GETBIT(d, 7) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d,
0)) << 3;
- p |= (GETBIT(d, 6) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 2) ^
GETBIT(d, 1) ^ GETBIT(d, 0)) << 4;
- return p;
+}
+static void encode_hamming_13_8(void *_src, void *_ecc, size_t size) +{
- int i;
- uint8_t *src = _src;
- uint8_t *ecc = _ecc;
- for (i = 0; i < size; i++)
ecc[i] = calculate_parity_13_8(src[i]);
+}
+static uint32_t calc_chksum(void *buf, size_t size) +{
- u32 chksum = 0;
- u8 *bp = buf;
- size_t i;
- for (i = 0; i < size; i++)
chksum += bp[i];
- return ~chksum;
+}
+static ssize_t raw_write_page(struct mtd_info *mtd, void *buf, loff_t offset) +{
- struct mtd_oob_ops ops;
- ssize_t ret;
- ops.mode = MTD_OPS_RAW;
- ops.ooboffs = 0;
- ops.datbuf = buf;
- ops.len = mtd->writesize;
- ops.oobbuf = buf + mtd->writesize;
- ops.ooblen = mtd->oobsize;
- ret = mtd_write_oob(mtd, offset, &ops);
return ret;
+}
+static int fcb_create(struct fcb_block *fcb, struct mtd_info *mtd) +{
- fcb->FingerPrint = 0x20424346;
- fcb->Version = 0x01000000;
- fcb->PageDataSize = mtd->writesize;
- fcb->TotalPageSize = mtd->writesize + mtd->oobsize;
- fcb->SectorsPerBlock = mtd->erasesize / mtd->writesize;
- /* Divide ECC strength by two and save the value into FCB
structure. */
- fcb->EccBlock0EccType =
mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1;
- fcb->EccBlockNEccType = fcb->EccBlock0EccType;
- /* Also hardcoded in kobs-ng */
- fcb->EccBlock0Size = 0x00000200;
- fcb->EccBlockNSize = 0x00000200;
- fcb->DataSetup = 80;
- fcb->DataHold = 60;
- fcb->AddressSetup = 25;
- fcb->DSAMPLE_TIME = 6;
- fcb->MetadataBytes = 10;
- /* DBBT search area starts at second page on first block */
- fcb->DBBTSearchAreaStartAddress = 1;
- fcb->BadBlockMarkerByte = mxs_nand_mark_byte_offset(mtd);
- fcb->BadBlockMarkerStartBit = mxs_nand_mark_bit_offset(mtd);
- fcb->BBMarkerPhysicalOffset = mtd->writesize;
- fcb->NumEccBlocksPerPage = mtd->writesize / fcb->EccBlock0Size - 1;
- fcb->Checksum = calc_chksum((void *)fcb + 4, sizeof(*fcb) - 4);
- return 0;
+}
+/* Erase entire U-Boot partition */ +static int mxs_nand_uboot_erase(struct mtd_info *mtd, struct part_info *part) +{
- uint64_t offset = 0;
- struct erase_info erase;
- int len = part->size;
- int ret;
- while (len > 0) {
pr_debug("erasing at 0x%08llx\n", offset);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("erase skip block @ 0x%08llx\n", offset);
offset += mtd->erasesize;
continue;
}
memset(&erase, 0, sizeof(erase));
erase.addr = offset;
erase.len = mtd->erasesize;
ret = mtd_erase(mtd, &erase);
if (ret)
return ret;
offset += mtd->erasesize;
len -= mtd->erasesize;
- }
- return 0;
+}
+/* Write the U-Boot proper (2 copies) to where it belongs. */ +/* This is U-Boot binary image itself, no FCB/DBBT here yet. */ +static int mxs_nand_uboot_write_fw(struct mtd_info *mtd, struct fw_write_data *fw) +{
- uint64_t offset;
- int ret;
- int blk;
- size_t dummy;
- size_t bytes_left;
- int chunk;
- void *p;
- bytes_left = fw->len;
- p = fw->buf;
- blk = fw->fw1_blk;
- offset = blk * mtd->erasesize;
- while (bytes_left > 0) {
chunk = min(bytes_left, mtd->erasesize);
pr_debug("fw1: writing %p at 0x%08llx, left 0x%08x\n",
p, offset, bytes_left);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("fw1: write skip block @ 0x%08llx\n", offset);
offset += mtd->erasesize;
blk++;
continue;
}
if (blk >= fw->fw2_blk)
return -ENOSPC;
ret = mtd_write(mtd, offset, chunk, &dummy, p);
if (ret)
return ret;
offset += chunk;
bytes_left -= chunk;
p += chunk;
blk++;
- }
- bytes_left = fw->len;
- p = fw->buf;
- blk = fw->fw2_blk;
- offset = blk * mtd->erasesize;
- while (bytes_left > 0) {
chunk = min(bytes_left, mtd->erasesize);
pr_debug("fw2: writing %p at 0x%08llx, left 0x%08x\n",
p, offset, bytes_left);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("fw2: write skip block @ 0x%08llx\n", offset);
offset += mtd->erasesize;
blk++;
continue;
}
if (blk >= fw->part_blks)
return -ENOSPC;
ret = mtd_write(mtd, offset, chunk, &dummy, p);
if (ret)
return ret;
offset += chunk;
bytes_left -= chunk;
p += chunk;
blk++;
- }
- return 0;
+}
+static int dbbt_data_create(struct mtd_info *mtd, void *buf, int num_blocks) +{
- int n;
- int n_bad_blocks = 0;
- uint32_t *bb = buf + 0x8;
- uint32_t *n_bad_blocksp = buf + 0x4;
- for (n = 0; n < num_blocks; n++) {
loff_t offset = n * mtd->erasesize;
if (mtd_block_isbad(mtd, offset)) {
n_bad_blocks++;
*bb = n;
bb++;
}
- }
- *n_bad_blocksp = n_bad_blocks;
- return n_bad_blocks;
+}
+/********************************************************************/ +/* This is where it is all done. Takes pointer to a U-Boot image in */ +/* RAM and image size, creates FCB/DBBT and writes everything where */ +/* it belongs into NAND. Image must be an IMX image built for NAND. */ +/********************************************************************/ +static int mxs_nand_uboot_update(const void *img, size_t len) +{
- int i, ret;
- size_t dummy;
- loff_t offset = 0;
- void *fcb_raw_page;
- void *dbbt_page;
- void *dbbt_data_page;
- void *ecc;
- uint32_t num_blocks_fcb_dbbt;
- uint32_t num_blocks_fw;
- struct mtd_info *mtd;
- struct fcb_block *fcb;
- struct dbbt_block *dbbt;
- struct mtd_device *dev;
- struct part_info *part;
- u8 pnum;
- struct fw_write_data fw;
- if ((mtdparts_init() == 0) &&
(find_dev_and_part(uboot_tgt, &dev, &pnum, &part) == 0)) {
if (dev->id->type != MTD_DEV_TYPE_NAND) {
puts("Not a NAND device\n");
return -ENODEV;
}
- }
- nand_curr_device = dev->id->num;
+#ifdef CONFIG_SYS_NAND_SELECT_DEVICE
- board_nand_select_device(nand_info[nand_curr_device].priv,
nand_curr_device); +#endif
- /* Get a pointer to mtd_info for selected device */
- mtd = get_mtd_device_nm("nand0"); /* We always boot off of nand0 */
- if (IS_ERR(mtd)) {
/* Should not happen */
puts("No nand0 device...\n");
return -ENODEV;
- }
- put_mtd_device(mtd);
- /* Quick and dirty check if we have 2Mbytes of good blocks in
nand0,0 */
- /* Not sure if it is needed at all but won't hurt so here it
is... */
- i = 0;
- offset = 0; /* It is the first partition so it starts at block 0 */
- while (offset < part->size) {
if (!mtd_block_isbad(mtd, offset)) {
i += mtd->erasesize;
}
offset += mtd->erasesize;
- }
- if (i < SZ_2M) {
puts("Partition too small for U-Boot!\n");
return -EINVAL;
- }
- /* We will use 4 first blocks for FCB/DBBT copies. */
- /* The rest of partition is split in half and used */
- /* for two U-Boot copies. We don't care if those */
- /* start on good or bad block - bad blocks will be */
- /* skipped on write, ROM boot code will also skip */
- /* bad blocks on bootup when loading U-Boot image. */
- fw.part_blks = part->size / mtd->erasesize;
- num_blocks_fcb_dbbt = 4;
- num_blocks_fw = (fw.part_blks - num_blocks_fcb_dbbt) / 2;
- fw.fw1_blk = num_blocks_fcb_dbbt;
- fw.fw2_blk = fw.fw1_blk + num_blocks_fw;
- /* OK, now create FCB structure for bootROM NAND boot */
- fcb_raw_page = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
- fcb = fcb_raw_page + 12;
- ecc = fcb_raw_page + 512 + 12;
- dbbt_page = kzalloc(mtd->writesize, GFP_KERNEL);
- dbbt_data_page = kzalloc(mtd->writesize, GFP_KERNEL);
- dbbt = dbbt_page;
- /* Write one additional page to make the ROM happy. */
- /* Maybe the PagesInFirmwarex fields are really the */
- /* number of pages - 1. kobs-ng does the same. */
- fw.len = ALIGN(len + FLASH_OFFSET_STANDARD + mtd->writesize,
mtd->writesize);
- fw.buf = kzalloc(fw.len, GFP_KERNEL);
- memcpy(fw.buf + FLASH_OFFSET_STANDARD, img, len);
- /* Erase entire partition */
- ret = mxs_nand_uboot_erase(mtd, part);
- if (ret)
goto out;
- /* Now write 2 copies of the U-Boot proper to where they belong. */
- /* Headers (FCB, DBBT) will be generated and written after that. */
- ret = mxs_nand_uboot_write_fw(mtd, &fw);
- if (ret < 0)
goto out;
- /* Create FCB, calculate ECC (we don't/can't use hardware ECC */
- /* here so we do it ourselves and then write _RAW_ pages. */
- fcb->Firmware1_startingPage = fw.fw1_blk * mtd->erasesize /
mtd->writesize;
- fcb->Firmware2_startingPage = fw.fw2_blk * mtd->erasesize /
mtd->writesize;
- fcb->PagesInFirmware1 =
ALIGN(len + FLASH_OFFSET_STANDARD, mtd->writesize) /
mtd->writesize;
- fcb->PagesInFirmware2 = fcb->PagesInFirmware1;
- fcb_create(fcb, mtd);
- encode_hamming_13_8(fcb, ecc, 512);
- /*
* Set the first and second byte of OOB data to 0xFF, not 0x00. These
* bytes are used as the Manufacturers Bad Block Marker (MBBM). Since
* the FCB is mostly written to the first page in a block, a scan for
* factory bad blocks will detect these blocks as bad, e.g. when
* function nand_scan_bbt() is executed to build a new bad block
table.
* We will _NOT_ mark a bad block as good -- we skip the bad blocks.
*/
- memset(fcb_raw_page + mtd->writesize, 0xff, 2);
- /* Now create DBBT */
- dbbt->Checksum = 0;
- dbbt->FingerPrint = 0x54424244;
- dbbt->Version = 0x01000000;
- if ((ret = dbbt_data_create(mtd, dbbt_data_page, fw.part_blks)) < 0)
goto out;
- if (ret > 0)
dbbt->DBBTNumOfPages = 1;
- offset = 0;
- if (mtd_block_isbad(mtd, offset)) {
puts("Block 0 is bad, NAND unusable\n");
ret = -EIO;
goto out;
- }
- /* Write FCB/DBBT to first 4 blocks. Skip bad blocks if any. */
- /* Less than 4 copies will be written if there were BBs !!! */
- for (i = 0; i < 4; i++) {
if (mtd_block_isbad(mtd, offset)) {
pr_err("Block %d is bad, skipped\n", i);
continue;
}
ret = raw_write_page(mtd, fcb_raw_page, mtd->erasesize * i);
if (ret)
goto out;
ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize,
mtd->writesize, &dummy, dbbt_page);
if (ret)
goto out;
/* DBBTNumOfPages == 0 if no bad blocks */
if (dbbt->DBBTNumOfPages > 0) {
ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize * 5,
mtd->writesize, &dummy, dbbt_data_page);
if (ret)
goto out;
}
- }
+out:
- kfree(dbbt_page);
- kfree(dbbt_data_page);
- kfree(fcb_raw_page);
- kfree(fw.buf);
- return ret;
+}
+int mxs_do_nand_bootupdate(ulong addr, size_t len) +{
- /* KSI: Unlock NAND first if it is locked... */
- return mxs_nand_uboot_update((const void *)addr, len);
+}
--- a/cmd/nand.c +++ b/cmd/nand.c @@ -38,6 +38,11 @@ int find_dev_and_part(const char *id, struct mtd_device **dev, u8 *part_num, struct part_info **part); #endif
+#ifdef CONFIG_CMD_NAND_BOOTUPDATE +/* This comes from a separate file in drivers/mtd/nand */ +int mxs_do_nand_bootupdate(ulong addr, size_t len); +#endif
static int nand_dump(struct mtd_info *mtd, ulong off, int only_oob, int repeat) { @@ -372,6 +377,9 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) loff_t off, size, maxsize; char *cmd, *s; struct mtd_info *mtd; +#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- size_t cnt;
+#endif #ifdef CONFIG_SYS_NAND_QUIET int quiet = CONFIG_SYS_NAND_QUIET; #else @@ -777,6 +785,48 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) } #endif
+#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- if (strncmp(cmd, "bootupdate", 10) == 0) {
if (argc < 3) {
/* All default values */
addr = getenv_ulong("fileaddr", 16, 0UL);
cnt = getenv_ulong("filesize", 16, 0UL);
}
if (argc == 3) {
/* 'addr' only, file size from environment */
if ((addr = simple_strtoul(argv[2], NULL, 16)) == 0UL)
addr = getenv_ulong("fileaddr", 16, 0UL);
cnt = getenv_ulong("filesize", 16, 0UL);
}
if (argc > 3) {
/* 'addr', 'size', and possibly more */
if ((addr = simple_strtoul(argv[2], NULL, 16)) == 0UL)
addr = getenv_ulong("fileaddr", 16, 0UL);
if ((cnt = simple_strtoul(argv[3], NULL, 16)) == 0UL)
cnt = getenv_ulong("filesize", 16, 0UL);
}
if (addr == 0 || cnt == 0) {
puts("Invalid arguments to nand bootupdate!\n");
return 1;
}
if (mxs_do_nand_bootupdate(addr, cnt)) {
puts("NAND bootupdate failed!\n");
return 1;
}
puts("NAND bootupdate successful\n");
return 0;
- }
+#endif
usage: return CMD_RET_USAGE; } @@ -798,6 +848,17 @@ static char nand_help_text[] = " 'addr', skipping bad blocks and dropping any pages at the end\n" " of eraseblocks that contain only 0xFF\n" #endif +#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- "nand bootupdate - [addr] [size]\n"
- " write U-Boot into NAND in board/SoC specific manner creating
all\n"
- " required headers and other bits and pieces as required for the\n"
- " system to be able to boot off of NAND. 'addr' is the address\n"
- " where U-Boot image has been loaded at, 'size' is its size.\n"
- " If any of 'addr'/'size' is missing it is taken from
environment\n"
- " for the last file loaded. U-Boot image must be of a proper
type\n"
- " for the target platform (only IMX image supported at the
moment)\n"
- " binary without U-Boot image headers (e.g. u-boot.imx file.)\n"
+#endif "nand erase[.spread] [clean] off size - erase 'size' bytes " "from offset 'off'\n" " With '.spread', erase enough for given file size, otherwise,\n"
- KSI@home KOI8 Net < > The impossible we do immediately. *
- Las Vegas NV, USA < > Miracles require 24-hour notice. *
U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot

On Fri, 7 Oct 2016, Stefano Babic wrote:
Hi Sergey,
On 05/10/2016 21:57, Sergey Kubushyn wrote:
This one adds nand_bootupdate command for i.MX6 and similar MCUs. It generates proper NAND boot structures (FCB, DBBT, etc) and writes those along with U-Boot mx image to where they belong so system would be able to boot off of raw NAND.
As of now the only way to do it is by using user-space kobs-ng utility that has lots of unnecessary bells and whistles and only runs from Linux so it is impossible to update raw NAND U-Boot from U-Boot itself.
It does not give any choice for the actual data placement in NAND but that is done deliberately -- there is no reason to put it anywhere but the only location i.MX6 Boot ROM expects it to be.
This is my THIRD attempt to get it into the main U-Boot tree. I do really hope it would get applied before I retire.
Do not think twice, I would *really* like to have this feature merged. But the patch should at least be applied clean. I cannot apply it becuase there are conflicts in cmd/nand.c and mxs_nand.c (the other conflicts are marginals).
Can you please check this (before you retire, of course !) ?
What should I use a base? I did my patches against whatever is the main U-Boot git tree (doing git pull befofe generating patches) but it is a moving target so there is absolutely no guarantee it will work if I pull again and redo the patches against whatever it is today.
Any particular version/repo/commit/whatever I should use?
Best regards, Stefano Babic
Signed-off-by: Sergey Kubushyn ksi@koi8.net
--- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -571,6 +571,16 @@ config CMD_TIME help Run commands and summarize execution time.
+config CMD_NAND_BOOTUPDATE
- bool "Update NAND bootloader on iMX6 and its brethen"
- depends on ARCH_MX6 && NAND_BOOT && CMD_NAND
- help
Having iMX6 NAND U-Boot image in memory creates all necessary
structures and writes those where they belong along with that
U-Boot image so system is able to boot off of raw NAND. Kinda
like kobs-ng utility but simpler.
# TODO: rename to CMD_SLEEP config CMD_MISC bool "sleep"
--- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o +obj-$(CONFIG_CMD_NAND_BOOTUPDATE) += mxs_nand_bootupdate.o
else # minimal SPL drivers
--- a/drivers/mtd/nand/mxs_nand.c +++ b/drivers/mtd/nand/mxs_nand.c @@ -26,6 +26,7 @@ #include <asm/imx-common/regs-gpmi.h> #include <asm/arch/sys_proto.h> #include <asm/imx-common/dma.h> +#include <asm/imx-common/mxs_nand.h>
#define MXS_NAND_DMA_DESCRIPTOR_COUNT 4
@@ -150,7 +151,7 @@ static uint32_t mxs_nand_aux_status_offset(void) return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3; }
-static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, +uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, uint32_t page_oob_size) { int ecc_strength; @@ -226,14 +227,14 @@ static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size, return block_mark_bit_offset; }
-static uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) +uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) { uint32_t ecc_strength; ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize); return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) >> 3; }
-static uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) +uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) { uint32_t ecc_strength; ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
--- /dev/null +++ b/arch/arm/include/asm/imx-common/mxs_nand.h @@ -0,0 +1,37 @@ +/*
- Copyright (C) 2015 PHYTEC Messtechnik GmbH,
- Author: Stefan Christ <s.christ at phytec.de>
- 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.
- */
+#ifndef __NAND_MXS_H +#define __NAND_MXS_H
+/*
- Functions are definied in drivers/mtd/nand/nand_mxs.c. They are
used to
- calculate the ECC Strength, BadBlockMarkerByte and
BadBlockMarkerStartBit
- which are placed into the FCB structure. The i.MX6 ROM needs these
- parameters to read the firmware from NAND.
- The parameters depends on the pagesize and oobsize of NAND chips and
are
- different for each combination. To avoid placing hardcoded values in
the bbu
- update handler code, the generic calculation from the driver code is
used.
- */
+uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size,
uint32_t page_oob_size);
+uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd);
+uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd);
+#endif /* __NAND_MXS_H */
--- /dev/null +++ b/drivers/mtd/nand/mxs_nand_bootupdate.c @@ -0,0 +1,556 @@ +/*
- mxs_nand_bootupdate.c - write U-Boot to MXS NAND to make it bootable
- Copyright (c) 2016 Sergey Kubushyn ksi@koi8.net
- Most of the source shamelesly stolen from barebox.
- Here is the original copyright:
- *=== Cut ===
- Copyright (C) 2014 Sascha Hauer, Pengutronix
- 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.
- *=== Cut ===
- */
+#include <common.h> +#include <linux/sizes.h> +#include <linux/mtd/mtd.h> +#include <linux/compat.h> +#include <command.h> +#include <console.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <jffs2/jffs2.h> +#include <nand.h> +#include <errno.h> +#include <asm/imx-common/mxs_nand.h> +#include <asm/imx-common/imximage.cfg>
+#ifndef CONFIG_CMD_MTDPARTS +#error "CONFIG_CMD_MTDPARTS is not set, mtd partition support missing" +#endif
+static const char *uboot_tgt = "nand0,0";
+/* partition handling routines */ +int mtdparts_init(void); +int id_parse(const char *id, const char **ret_id, u8 *dev_type, u8 *dev_num); +int find_dev_and_part(const char *id, struct mtd_device **dev,
u8 *part_num, struct part_info **part);
+struct dbbt_block {
- uint32_t Checksum; /* reserved on i.MX6 */
- uint32_t FingerPrint;
- uint32_t Version;
- uint32_t numberBB; /* reserved on i.MX6 */
- uint32_t DBBTNumOfPages;
+};
+struct fcb_block {
- uint32_t Checksum; /* First fingerprint in first byte */
- uint32_t FingerPrint; /* 2nd fingerprint at byte 4 */
- uint32_t Version; /* 3rd fingerprint at byte 8 */
- uint8_t DataSetup;
- uint8_t DataHold;
- uint8_t AddressSetup;
- uint8_t DSAMPLE_TIME;
- /* These are for application use only and not for ROM. */
- uint8_t NandTimingState;
- uint8_t REA;
- uint8_t RLOH;
- uint8_t RHOH;
- uint32_t PageDataSize; /* 2048 for 2K pages, 4096 for 4K
pages */
- uint32_t TotalPageSize; /* 2112 for 2K pages, 4314 for 4K
pages */
- uint32_t SectorsPerBlock; /* Number of 2K sections per block */
- uint32_t NumberOfNANDs; /* Total Number of NANDs - not used
by ROM */
- uint32_t TotalInternalDie; /* Number of separate chips in this
NAND */
- uint32_t CellType; /* MLC or SLC */
- uint32_t EccBlockNEccType; /* Type of ECC, can be one of
BCH-0-20 */
- uint32_t EccBlock0Size; /* Number of bytes for Block0 - BCH */
- uint32_t EccBlockNSize; /* Block size in bytes for all
blocks other than Block0 - BCH */
- uint32_t EccBlock0EccType; /* Ecc level for Block 0 - BCH */
- uint32_t MetadataBytes; /* Metadata size - BCH */
- uint32_t NumEccBlocksPerPage; /* Number of blocks per page for
ROM use - BCH */
- uint32_t EccBlockNEccLevelSDK; /* Type of ECC, can be one of
BCH-0-20 */
- uint32_t EccBlock0SizeSDK; /* Number of bytes for Block0 - BCH */
- uint32_t EccBlockNSizeSDK; /* Block size in bytes for all blocks
other than Block0 - BCH */
- uint32_t EccBlock0EccLevelSDK; /* Ecc level for Block 0 - BCH */
- uint32_t NumEccBlocksPerPageSDK;/* Number of blocks per page for
SDK use - BCH */
- uint32_t MetadataBytesSDK; /* Metadata size - BCH */
- uint32_t EraseThreshold; /* To set into BCH_MODE register */
- uint32_t BootPatch; /* 0 for normal boot and 1 to load patch
starting next to FCB */
- uint32_t PatchSectors; /* Size of patch in sectors */
- uint32_t Firmware1_startingPage;/* Firmware image starts on this
sector */
- uint32_t Firmware2_startingPage;/* Secondary FW Image starting
Sector */
- uint32_t PagesInFirmware1; /* Number of sectors in firmware
image */
- uint32_t PagesInFirmware2; /* Number of sector in secondary FW
image */
- uint32_t DBBTSearchAreaStartAddress; /* Page address where dbbt
search area begins */
- uint32_t BadBlockMarkerByte; /* Byte in page data that have
manufacturer marked bad block marker, */
/* this will be swapped with metadata[0] to
complete page data. */
- uint32_t BadBlockMarkerStartBit;/* For BCH ECC sizes other than 8
and 16 the bad block marker does not */
/* start at 0th bit of BadBlockMarkerByte. This
field is used to get to */
/* the start bit of bad block marker byte with in
BadBlockMarkerByte */
- uint32_t BBMarkerPhysicalOffset;/* FCB value that gives byte offset
for bad block marker on physical NAND page */
- uint32_t BCHType;
- uint32_t TMTiming2_ReadLatency;
- uint32_t TMTiming2_PreambleDelay;
- uint32_t TMTiming2_CEDelay;
- uint32_t TMTiming2_PostambleDelay;
- uint32_t TMTiming2_CmdAddPause;
- uint32_t TMTiming2_DataPause;
- uint32_t TMSpeed;
- uint32_t TMTiming1_BusyTimeout;
- uint32_t DISBBM; /* the flag to enable (1)/disable(0) bi swap */
- uint32_t BBMarkerPhysicalOffsetInSpareData; /* The swap position of
main area in spare area */ +};
+struct fw_write_data {
- int fw1_blk;
- int fw2_blk;
- int part_blks;
- void *buf;
- size_t len;
+};
+#define BF_VAL(v, bf) (((v) & bf##_MASK) >> bf##_OFFSET) +#define GETBIT(v,n) (((v) >> (n)) & 0x1)
+static uint8_t calculate_parity_13_8(uint8_t d) +{
- uint8_t p = 0;
- p |= (GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 3) ^ GETBIT(d,
2)) << 0;
- p |= (GETBIT(d, 7) ^ GETBIT(d, 5) ^ GETBIT(d, 4) ^ GETBIT(d, 2) ^
GETBIT(d, 1)) << 1;
- p |= (GETBIT(d, 7) ^ GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 1) ^
GETBIT(d, 0)) << 2;
- p |= (GETBIT(d, 7) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d,
0)) << 3;
- p |= (GETBIT(d, 6) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 2) ^
GETBIT(d, 1) ^ GETBIT(d, 0)) << 4;
- return p;
+}
+static void encode_hamming_13_8(void *_src, void *_ecc, size_t size) +{
- int i;
- uint8_t *src = _src;
- uint8_t *ecc = _ecc;
- for (i = 0; i < size; i++)
ecc[i] = calculate_parity_13_8(src[i]);
+}
+static uint32_t calc_chksum(void *buf, size_t size) +{
- u32 chksum = 0;
- u8 *bp = buf;
- size_t i;
- for (i = 0; i < size; i++)
chksum += bp[i];
- return ~chksum;
+}
+static ssize_t raw_write_page(struct mtd_info *mtd, void *buf, loff_t offset) +{
- struct mtd_oob_ops ops;
- ssize_t ret;
- ops.mode = MTD_OPS_RAW;
- ops.ooboffs = 0;
- ops.datbuf = buf;
- ops.len = mtd->writesize;
- ops.oobbuf = buf + mtd->writesize;
- ops.ooblen = mtd->oobsize;
- ret = mtd_write_oob(mtd, offset, &ops);
return ret;
+}
+static int fcb_create(struct fcb_block *fcb, struct mtd_info *mtd) +{
- fcb->FingerPrint = 0x20424346;
- fcb->Version = 0x01000000;
- fcb->PageDataSize = mtd->writesize;
- fcb->TotalPageSize = mtd->writesize + mtd->oobsize;
- fcb->SectorsPerBlock = mtd->erasesize / mtd->writesize;
- /* Divide ECC strength by two and save the value into FCB
structure. */
- fcb->EccBlock0EccType =
mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1;
- fcb->EccBlockNEccType = fcb->EccBlock0EccType;
- /* Also hardcoded in kobs-ng */
- fcb->EccBlock0Size = 0x00000200;
- fcb->EccBlockNSize = 0x00000200;
- fcb->DataSetup = 80;
- fcb->DataHold = 60;
- fcb->AddressSetup = 25;
- fcb->DSAMPLE_TIME = 6;
- fcb->MetadataBytes = 10;
- /* DBBT search area starts at second page on first block */
- fcb->DBBTSearchAreaStartAddress = 1;
- fcb->BadBlockMarkerByte = mxs_nand_mark_byte_offset(mtd);
- fcb->BadBlockMarkerStartBit = mxs_nand_mark_bit_offset(mtd);
- fcb->BBMarkerPhysicalOffset = mtd->writesize;
- fcb->NumEccBlocksPerPage = mtd->writesize / fcb->EccBlock0Size - 1;
- fcb->Checksum = calc_chksum((void *)fcb + 4, sizeof(*fcb) - 4);
- return 0;
+}
+/* Erase entire U-Boot partition */ +static int mxs_nand_uboot_erase(struct mtd_info *mtd, struct part_info *part) +{
- uint64_t offset = 0;
- struct erase_info erase;
- int len = part->size;
- int ret;
- while (len > 0) {
pr_debug("erasing at 0x%08llx\n", offset);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("erase skip block @ 0x%08llx\n", offset);
offset += mtd->erasesize;
continue;
}
memset(&erase, 0, sizeof(erase));
erase.addr = offset;
erase.len = mtd->erasesize;
ret = mtd_erase(mtd, &erase);
if (ret)
return ret;
offset += mtd->erasesize;
len -= mtd->erasesize;
- }
- return 0;
+}
+/* Write the U-Boot proper (2 copies) to where it belongs. */ +/* This is U-Boot binary image itself, no FCB/DBBT here yet. */ +static int mxs_nand_uboot_write_fw(struct mtd_info *mtd, struct fw_write_data *fw) +{
- uint64_t offset;
- int ret;
- int blk;
- size_t dummy;
- size_t bytes_left;
- int chunk;
- void *p;
- bytes_left = fw->len;
- p = fw->buf;
- blk = fw->fw1_blk;
- offset = blk * mtd->erasesize;
- while (bytes_left > 0) {
chunk = min(bytes_left, mtd->erasesize);
pr_debug("fw1: writing %p at 0x%08llx, left 0x%08x\n",
p, offset, bytes_left);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("fw1: write skip block @ 0x%08llx\n", offset);
offset += mtd->erasesize;
blk++;
continue;
}
if (blk >= fw->fw2_blk)
return -ENOSPC;
ret = mtd_write(mtd, offset, chunk, &dummy, p);
if (ret)
return ret;
offset += chunk;
bytes_left -= chunk;
p += chunk;
blk++;
- }
- bytes_left = fw->len;
- p = fw->buf;
- blk = fw->fw2_blk;
- offset = blk * mtd->erasesize;
- while (bytes_left > 0) {
chunk = min(bytes_left, mtd->erasesize);
pr_debug("fw2: writing %p at 0x%08llx, left 0x%08x\n",
p, offset, bytes_left);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("fw2: write skip block @ 0x%08llx\n", offset);
offset += mtd->erasesize;
blk++;
continue;
}
if (blk >= fw->part_blks)
return -ENOSPC;
ret = mtd_write(mtd, offset, chunk, &dummy, p);
if (ret)
return ret;
offset += chunk;
bytes_left -= chunk;
p += chunk;
blk++;
- }
- return 0;
+}
+static int dbbt_data_create(struct mtd_info *mtd, void *buf, int num_blocks) +{
- int n;
- int n_bad_blocks = 0;
- uint32_t *bb = buf + 0x8;
- uint32_t *n_bad_blocksp = buf + 0x4;
- for (n = 0; n < num_blocks; n++) {
loff_t offset = n * mtd->erasesize;
if (mtd_block_isbad(mtd, offset)) {
n_bad_blocks++;
*bb = n;
bb++;
}
- }
- *n_bad_blocksp = n_bad_blocks;
- return n_bad_blocks;
+}
+/********************************************************************/ +/* This is where it is all done. Takes pointer to a U-Boot image in */ +/* RAM and image size, creates FCB/DBBT and writes everything where */ +/* it belongs into NAND. Image must be an IMX image built for NAND. */ +/********************************************************************/ +static int mxs_nand_uboot_update(const void *img, size_t len) +{
- int i, ret;
- size_t dummy;
- loff_t offset = 0;
- void *fcb_raw_page;
- void *dbbt_page;
- void *dbbt_data_page;
- void *ecc;
- uint32_t num_blocks_fcb_dbbt;
- uint32_t num_blocks_fw;
- struct mtd_info *mtd;
- struct fcb_block *fcb;
- struct dbbt_block *dbbt;
- struct mtd_device *dev;
- struct part_info *part;
- u8 pnum;
- struct fw_write_data fw;
- if ((mtdparts_init() == 0) &&
(find_dev_and_part(uboot_tgt, &dev, &pnum, &part) == 0)) {
if (dev->id->type != MTD_DEV_TYPE_NAND) {
puts("Not a NAND device\n");
return -ENODEV;
}
- }
- nand_curr_device = dev->id->num;
+#ifdef CONFIG_SYS_NAND_SELECT_DEVICE
- board_nand_select_device(nand_info[nand_curr_device].priv,
nand_curr_device); +#endif
- /* Get a pointer to mtd_info for selected device */
- mtd = get_mtd_device_nm("nand0"); /* We always boot off of nand0 */
- if (IS_ERR(mtd)) {
/* Should not happen */
puts("No nand0 device...\n");
return -ENODEV;
- }
- put_mtd_device(mtd);
- /* Quick and dirty check if we have 2Mbytes of good blocks in
nand0,0 */
- /* Not sure if it is needed at all but won't hurt so here it
is... */
- i = 0;
- offset = 0; /* It is the first partition so it starts at block 0 */
- while (offset < part->size) {
if (!mtd_block_isbad(mtd, offset)) {
i += mtd->erasesize;
}
offset += mtd->erasesize;
- }
- if (i < SZ_2M) {
puts("Partition too small for U-Boot!\n");
return -EINVAL;
- }
- /* We will use 4 first blocks for FCB/DBBT copies. */
- /* The rest of partition is split in half and used */
- /* for two U-Boot copies. We don't care if those */
- /* start on good or bad block - bad blocks will be */
- /* skipped on write, ROM boot code will also skip */
- /* bad blocks on bootup when loading U-Boot image. */
- fw.part_blks = part->size / mtd->erasesize;
- num_blocks_fcb_dbbt = 4;
- num_blocks_fw = (fw.part_blks - num_blocks_fcb_dbbt) / 2;
- fw.fw1_blk = num_blocks_fcb_dbbt;
- fw.fw2_blk = fw.fw1_blk + num_blocks_fw;
- /* OK, now create FCB structure for bootROM NAND boot */
- fcb_raw_page = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
- fcb = fcb_raw_page + 12;
- ecc = fcb_raw_page + 512 + 12;
- dbbt_page = kzalloc(mtd->writesize, GFP_KERNEL);
- dbbt_data_page = kzalloc(mtd->writesize, GFP_KERNEL);
- dbbt = dbbt_page;
- /* Write one additional page to make the ROM happy. */
- /* Maybe the PagesInFirmwarex fields are really the */
- /* number of pages - 1. kobs-ng does the same. */
- fw.len = ALIGN(len + FLASH_OFFSET_STANDARD + mtd->writesize,
mtd->writesize);
- fw.buf = kzalloc(fw.len, GFP_KERNEL);
- memcpy(fw.buf + FLASH_OFFSET_STANDARD, img, len);
- /* Erase entire partition */
- ret = mxs_nand_uboot_erase(mtd, part);
- if (ret)
goto out;
- /* Now write 2 copies of the U-Boot proper to where they belong. */
- /* Headers (FCB, DBBT) will be generated and written after that. */
- ret = mxs_nand_uboot_write_fw(mtd, &fw);
- if (ret < 0)
goto out;
- /* Create FCB, calculate ECC (we don't/can't use hardware ECC */
- /* here so we do it ourselves and then write _RAW_ pages. */
- fcb->Firmware1_startingPage = fw.fw1_blk * mtd->erasesize /
mtd->writesize;
- fcb->Firmware2_startingPage = fw.fw2_blk * mtd->erasesize /
mtd->writesize;
- fcb->PagesInFirmware1 =
ALIGN(len + FLASH_OFFSET_STANDARD, mtd->writesize) /
mtd->writesize;
- fcb->PagesInFirmware2 = fcb->PagesInFirmware1;
- fcb_create(fcb, mtd);
- encode_hamming_13_8(fcb, ecc, 512);
- /*
* Set the first and second byte of OOB data to 0xFF, not 0x00. These
* bytes are used as the Manufacturers Bad Block Marker (MBBM). Since
* the FCB is mostly written to the first page in a block, a scan for
* factory bad blocks will detect these blocks as bad, e.g. when
* function nand_scan_bbt() is executed to build a new bad block
table.
* We will _NOT_ mark a bad block as good -- we skip the bad blocks.
*/
- memset(fcb_raw_page + mtd->writesize, 0xff, 2);
- /* Now create DBBT */
- dbbt->Checksum = 0;
- dbbt->FingerPrint = 0x54424244;
- dbbt->Version = 0x01000000;
- if ((ret = dbbt_data_create(mtd, dbbt_data_page, fw.part_blks)) < 0)
goto out;
- if (ret > 0)
dbbt->DBBTNumOfPages = 1;
- offset = 0;
- if (mtd_block_isbad(mtd, offset)) {
puts("Block 0 is bad, NAND unusable\n");
ret = -EIO;
goto out;
- }
- /* Write FCB/DBBT to first 4 blocks. Skip bad blocks if any. */
- /* Less than 4 copies will be written if there were BBs !!! */
- for (i = 0; i < 4; i++) {
if (mtd_block_isbad(mtd, offset)) {
pr_err("Block %d is bad, skipped\n", i);
continue;
}
ret = raw_write_page(mtd, fcb_raw_page, mtd->erasesize * i);
if (ret)
goto out;
ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize,
mtd->writesize, &dummy, dbbt_page);
if (ret)
goto out;
/* DBBTNumOfPages == 0 if no bad blocks */
if (dbbt->DBBTNumOfPages > 0) {
ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize * 5,
mtd->writesize, &dummy, dbbt_data_page);
if (ret)
goto out;
}
- }
+out:
- kfree(dbbt_page);
- kfree(dbbt_data_page);
- kfree(fcb_raw_page);
- kfree(fw.buf);
- return ret;
+}
+int mxs_do_nand_bootupdate(ulong addr, size_t len) +{
- /* KSI: Unlock NAND first if it is locked... */
- return mxs_nand_uboot_update((const void *)addr, len);
+}
--- a/cmd/nand.c +++ b/cmd/nand.c @@ -38,6 +38,11 @@ int find_dev_and_part(const char *id, struct mtd_device **dev, u8 *part_num, struct part_info **part); #endif
+#ifdef CONFIG_CMD_NAND_BOOTUPDATE +/* This comes from a separate file in drivers/mtd/nand */ +int mxs_do_nand_bootupdate(ulong addr, size_t len); +#endif
static int nand_dump(struct mtd_info *mtd, ulong off, int only_oob, int repeat) { @@ -372,6 +377,9 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) loff_t off, size, maxsize; char *cmd, *s; struct mtd_info *mtd; +#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- size_t cnt;
+#endif #ifdef CONFIG_SYS_NAND_QUIET int quiet = CONFIG_SYS_NAND_QUIET; #else @@ -777,6 +785,48 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) } #endif
+#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- if (strncmp(cmd, "bootupdate", 10) == 0) {
if (argc < 3) {
/* All default values */
addr = getenv_ulong("fileaddr", 16, 0UL);
cnt = getenv_ulong("filesize", 16, 0UL);
}
if (argc == 3) {
/* 'addr' only, file size from environment */
if ((addr = simple_strtoul(argv[2], NULL, 16)) == 0UL)
addr = getenv_ulong("fileaddr", 16, 0UL);
cnt = getenv_ulong("filesize", 16, 0UL);
}
if (argc > 3) {
/* 'addr', 'size', and possibly more */
if ((addr = simple_strtoul(argv[2], NULL, 16)) == 0UL)
addr = getenv_ulong("fileaddr", 16, 0UL);
if ((cnt = simple_strtoul(argv[3], NULL, 16)) == 0UL)
cnt = getenv_ulong("filesize", 16, 0UL);
}
if (addr == 0 || cnt == 0) {
puts("Invalid arguments to nand bootupdate!\n");
return 1;
}
if (mxs_do_nand_bootupdate(addr, cnt)) {
puts("NAND bootupdate failed!\n");
return 1;
}
puts("NAND bootupdate successful\n");
return 0;
- }
+#endif
usage: return CMD_RET_USAGE; } @@ -798,6 +848,17 @@ static char nand_help_text[] = " 'addr', skipping bad blocks and dropping any pages at the end\n" " of eraseblocks that contain only 0xFF\n" #endif +#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- "nand bootupdate - [addr] [size]\n"
- " write U-Boot into NAND in board/SoC specific manner creating
all\n"
- " required headers and other bits and pieces as required for the\n"
- " system to be able to boot off of NAND. 'addr' is the address\n"
- " where U-Boot image has been loaded at, 'size' is its size.\n"
- " If any of 'addr'/'size' is missing it is taken from
environment\n"
- " for the last file loaded. U-Boot image must be of a proper
type\n"
- " for the target platform (only IMX image supported at the
moment)\n"
- " binary without U-Boot image headers (e.g. u-boot.imx file.)\n"
+#endif "nand erase[.spread] [clean] off size - erase 'size' bytes " "from offset 'off'\n" " With '.spread', erase enough for given file size, otherwise,\n"
- KSI@home KOI8 Net < > The impossible we do immediately. *
- Las Vegas NV, USA < > Miracles require 24-hour notice. *
U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot
--
DENX Software Engineering GmbH, Managing Director: Wolfgang Denk HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany Phone: +49-8142-66989-53 Fax: +49-8142-66989-80 Email: sbabic@denx.de =====================================================================
--- ****************************************************************** * KSI@home KOI8 Net < > The impossible we do immediately. * * Las Vegas NV, USA < > Miracles require 24-hour notice. * ******************************************************************

On Fri, Oct 7, 2016 at 12:04 PM, Sergey Kubushyn ksi@koi8.net wrote:
What should I use a base? I did my patches against whatever is the main U-Boot git tree (doing git pull befofe generating patches) but it is a moving target so there is absolutely no guarantee it will work if I pull again and redo the patches against whatever it is today.
Any particular version/repo/commit/whatever I should use?
Stefano's u-boot-imx master tree: (http://git.denx.de/?p=u-boot/u-boot-imx.git;a=summary) should work.

Hi Sergey,
On 07/10/2016 17:04, Sergey Kubushyn wrote:
Can you please check this (before you retire, of course !) ?
What should I use a base? I did my patches against whatever is the main U-Boot git tree (doing git pull befofe generating patches)
I tried on top of v2016.11-rc1, but it fails.
but it is a moving target
..yes, this is the game :-)
so there is absolutely no guarantee it will work if I pull again and redo the patches against whatever it is today.
Do not worry - I merge the small conflicts, but patches shoult at least appliable on the last tag set by Tom un -master.
Any particular version/repo/commit/whatever I should use?
Can you take then on top of u-boot-imx, -master ? This is where patches are applied first.
Best regards, Stefano

On Fri, 7 Oct 2016, Stefano Babic wrote:
Hi Sergey,
On 07/10/2016 17:04, Sergey Kubushyn wrote:
Can you please check this (before you retire, of course !) ?
What should I use a base? I did my patches against whatever is the main U-Boot git tree (doing git pull befofe generating patches)
I tried on top of v2016.11-rc1, but it fails.
but it is a moving target
..yes, this is the game :-)
so there is absolutely no guarantee it will work if I pull again and redo the patches against whatever it is today.
Do not worry - I merge the small conflicts, but patches shoult at least appliable on the last tag set by Tom un -master.
Any particular version/repo/commit/whatever I should use?
Can you take then on top of u-boot-imx, -master ? This is where patches are applied first.
Will do in a couple of hours.
--- ****************************************************************** * KSI@home KOI8 Net < > The impossible we do immediately. * * Las Vegas NV, USA < > Miracles require 24-hour notice. * ******************************************************************

+ Scott
On Thu, Oct 6, 2016 at 1:27 AM, Sergey Kubushyn ksi@koi8.net wrote:
This one adds nand_bootupdate command for i.MX6 and similar MCUs. It generates proper NAND boot structures (FCB, DBBT, etc) and writes those along with U-Boot mx image to where they belong so system would be able to boot off of raw NAND.
As of now the only way to do it is by using user-space kobs-ng utility that has lots of unnecessary bells and whistles and only runs from Linux so it is impossible to update raw NAND U-Boot from U-Boot itself.
It does not give any choice for the actual data placement in NAND but that is done deliberately -- there is no reason to put it anywhere but the only location i.MX6 Boot ROM expects it to be.
This is my THIRD attempt to get it into the main U-Boot tree. I do really hope it would get applied before I retire.
Signed-off-by: Sergey Kubushyn ksi@koi8.net
--- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -571,6 +571,16 @@ config CMD_TIME help Run commands and summarize execution time.
+config CMD_NAND_BOOTUPDATE
bool "Update NAND bootloader on iMX6 and its brethen"
depends on ARCH_MX6 && NAND_BOOT && CMD_NAND
help
Having iMX6 NAND U-Boot image in memory creates all necessary
structures and writes those where they belong along with that
U-Boot image so system is able to boot off of raw NAND. Kinda
like kobs-ng utility but simpler.
# TODO: rename to CMD_SLEEP config CMD_MISC bool "sleep"
--- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o +obj-$(CONFIG_CMD_NAND_BOOTUPDATE) += mxs_nand_bootupdate.o
The command macro couldn't be part of drivers. and also this isn't a common code or separate driver try to add the most of the code in cmd/ if possible separate file (as it is specific to imx) and if something needed to write on drivers/nand better to add in mxc or mxs driver.
Jagan.

On Wed, 2016-10-05 at 12:57 -0700, Sergey Kubushyn wrote:
This one adds nand_bootupdate command for i.MX6 and similar MCUs. It generates proper NAND boot structures (FCB, DBBT, etc) and writes those along with U-Boot mx image to where they belong so system would be able to boot off of raw NAND.
This seems like a lot of code just to write some images to various places. What are these "NAND boot structures" and do they really need to be generated by U-Boot, or can they be generated by a tool to produce an image that U-Boot writes to NAND as is? I do see there are some things to be filled in based on NAND parameters, bad blocks, etc. but can this patching up be minimized (and the necessary parts explained in the changelog)?
This is my THIRD attempt to get it into the main U-Boot tree. I do really hope it would get applied before I retire.
It helps to CC relevant people...
Signed-off-by: Sergey Kubushyn ksi@koi8.net
--- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -571,6 +571,16 @@ config CMD_TIME help Run commands and summarize execution time.
+config CMD_NAND_BOOTUPDATE
- bool "Update NAND bootloader on iMX6 and its brethen"
- depends on ARCH_MX6 && NAND_BOOT && CMD_NAND
Why does a hardware-specific option have a generic name?
- help
- Having iMX6 NAND U-Boot image in memory creates all necessary
- structures and writes those where they belong along with that
- U-Boot image so system is able to boot off of raw NAND. Kinda
- like kobs-ng utility but simpler.
# TODO: rename to CMD_SLEEP config CMD_MISC bool "sleep"
--- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o +obj-$(CONFIG_CMD_NAND_BOOTUPDATE) += mxs_nand_bootupdate.o
This looks like your mailer mangled the whitespace in the patch, or else it was generated strangely... Why not use git to generate patches? That would also help with conflicts due to the SHA1 info.
else # minimal SPL drivers
--- a/drivers/mtd/nand/mxs_nand.c +++ b/drivers/mtd/nand/mxs_nand.c @@ -26,6 +26,7 @@ #include <asm/imx-common/regs-gpmi.h> #include <asm/arch/sys_proto.h> #include <asm/imx-common/dma.h> +#include <asm/imx-common/mxs_nand.h>
#define MXS_NAND_DMA_DESCRIPTOR_COUNT 4
@@ -150,7 +151,7 @@ static uint32_t mxs_nand_aux_status_offset(void) return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3; }
-static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, +uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, uint32_t page_oob_size)
Please adjust the continuation line to align with the line you modified.
+#ifndef CONFIG_CMD_MTDPARTS +#error "CONFIG_CMD_MTDPARTS is not set, mtd partition support missing" +#endif
If this is an error then use kconfig to express the dependency.
+static const char *uboot_tgt = "nand0,0";
This is only used in one place, and you hardcode "nand0" elsewhere, so why not just put the string where it's used?
+static ssize_t raw_write_page(struct mtd_info *mtd, void *buf, loff_t offset) +{
- struct mtd_oob_ops ops;
- ssize_t ret;
- ops.mode = MTD_OPS_RAW;
- ops.ooboffs = 0;
- ops.datbuf = buf;
- ops.len = mtd->writesize;
- ops.oobbuf = buf + mtd->writesize;
- ops.ooblen = mtd->oobsize;
- ret = mtd_write_oob(mtd, offset, &ops);
+ return ret; +}
Whitespace.
Is the raw access needed because certain boot blocks use different ECC than the rest? It would be nice to handle this by having the driver apply the proper ECC to each region, rather than having the caller do the ECC manually.
+/* Erase entire U-Boot partition */ +static int mxs_nand_uboot_erase(struct mtd_info *mtd, struct part_info *part) +{
- uint64_t offset = 0;
- struct erase_info erase;
- int len = part->size;
- int ret;
- while (len > 0) {
pr_debug("erasing at 0x%08llx\n", offset);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("erase skip block @ 0x%08llx\n", offset);
offset += mtd->erasesize;
continue;
}
memset(&erase, 0, sizeof(erase));
erase.addr = offset;
erase.len = mtd->erasesize;
ret = mtd_erase(mtd, &erase);
if (ret)
return ret;
offset += mtd->erasesize;
len -= mtd->erasesize;
- }
- return 0;
+}
What's wrong with nand_erase_opts()?
+/* Write the U-Boot proper (2 copies) to where it belongs. */ +/* This is U-Boot binary image itself, no FCB/DBBT here yet. */ +static int mxs_nand_uboot_write_fw(struct mtd_info *mtd, struct fw_write_data *fw) +{
- uint64_t offset;
- int ret;
- int blk;
- size_t dummy;
- size_t bytes_left;
- int chunk;
- void *p;
- bytes_left = fw->len;
- p = fw->buf;
- blk = fw->fw1_blk;
- offset = blk * mtd->erasesize;
- while (bytes_left > 0) {
chunk = min(bytes_left, mtd->erasesize);
pr_debug("fw1: writing %p at 0x%08llx, left 0x%08x\n",
p, offset, bytes_left);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("fw1: write skip block @ 0x%08llx\n",
offset);
offset += mtd->erasesize;
blk++;
continue;
}
if (blk >= fw->fw2_blk)
return -ENOSPC;
ret = mtd_write(mtd, offset, chunk, &dummy, p);
if (ret)
return ret;
offset += chunk;
bytes_left -= chunk;
p += chunk;
blk++;
- }
- bytes_left = fw->len;
- p = fw->buf;
- blk = fw->fw2_blk;
- offset = blk * mtd->erasesize;
- while (bytes_left > 0) {
chunk = min(bytes_left, mtd->erasesize);
pr_debug("fw2: writing %p at 0x%08llx, left 0x%08x\n",
p, offset, bytes_left);
if (mtd_block_isbad(mtd, offset)) {
pr_debug("fw2: write skip block @ 0x%08llx\n",
offset);
offset += mtd->erasesize;
blk++;
continue;
}
if (blk >= fw->part_blks)
return -ENOSPC;
ret = mtd_write(mtd, offset, chunk, &dummy, p);
if (ret)
return ret;
offset += chunk;
bytes_left -= chunk;
p += chunk;
blk++;
- }
- return 0;
+}
What's wrong with nand_write_skip_bad()?
+/********************************************************************/ +/* This is where it is all done. Takes pointer to a U-Boot image in */ +/* RAM and image size, creates FCB/DBBT and writes everything where */ +/* it belongs into NAND. Image must be an IMX image built for NAND. */ +/********************************************************************/ +static int mxs_nand_uboot_update(const void *img, size_t len) +{
- int i, ret;
- size_t dummy;
- loff_t offset = 0;
- void *fcb_raw_page;
- void *dbbt_page;
- void *dbbt_data_page;
- void *ecc;
- uint32_t num_blocks_fcb_dbbt;
- uint32_t num_blocks_fw;
- struct mtd_info *mtd;
- struct fcb_block *fcb;
- struct dbbt_block *dbbt;
- struct mtd_device *dev;
- struct part_info *part;
- u8 pnum;
- struct fw_write_data fw;
- if ((mtdparts_init() == 0) &&
(find_dev_and_part(uboot_tgt, &dev, &pnum, &part)
== 0)) {
Please align continuation lines nicely and avoid unnecessary parens:
if (mtdparts_init() == 0 && find_dev_and_part(...)) { ... }
if (dev->id->type != MTD_DEV_TYPE_NAND) {
puts("Not a NAND device\n");
return -ENODEV;
}
- }
- nand_curr_device = dev->id->num;
+#ifdef CONFIG_SYS_NAND_SELECT_DEVICE
- board_nand_select_device(nand_info[nand_curr_device].priv,
nand_curr_device); +#endif
- /* Get a pointer to mtd_info for selected device */
- mtd = get_mtd_device_nm("nand0"); /* We always boot off of
nand0 */
Why do you need to do this when you just got "dev" from find_dev_and_part()?
+
- if (IS_ERR(mtd)) {
/* Should not happen */
puts("No nand0 device...\n");
return -ENODEV;
- }
+
- put_mtd_device(mtd);
Why are you calling put_mtd_device() before you're done with it?
- /* Quick and dirty check if we have 2Mbytes of good blocks in
nand0,0 */
- /* Not sure if it is needed at all but won't hurt so here it
is... */ +
- i = 0;
- offset = 0; /* It is the first partition so it starts at
block 0 */
- while (offset < part->size) {
if (!mtd_block_isbad(mtd, offset)) {
i += mtd->erasesize;
}
Unnecessary {}
offset += mtd->erasesize;
- }
+
- if (i < SZ_2M) {
puts("Partition too small for U-Boot!\n");
return -EINVAL;
- }
If you use nand_write_skip_bad() you can pass a limit that will check this for you.
- /* We will use 4 first blocks for FCB/DBBT copies. */
- /* The rest of partition is split in half and used */
- /* for two U-Boot copies. We don't care if those */
- /* start on good or bad block - bad blocks will be */
- /* skipped on write, ROM boot code will also skip */
- /* bad blocks on bootup when loading U-Boot image. */
/* * Please use standard U-Booot * multi-line comment style. */
- fw.part_blks = part->size / mtd->erasesize;
- num_blocks_fcb_dbbt = 4;
- num_blocks_fw = (fw.part_blks - num_blocks_fcb_dbbt) / 2;
- fw.fw1_blk = num_blocks_fcb_dbbt;
- fw.fw2_blk = fw.fw1_blk + num_blocks_fw;
- /* OK, now create FCB structure for bootROM NAND boot */
- fcb_raw_page = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
- fcb = fcb_raw_page + 12;
- ecc = fcb_raw_page + 512 + 12;
- dbbt_page = kzalloc(mtd->writesize, GFP_KERNEL);
- dbbt_data_page = kzalloc(mtd->writesize, GFP_KERNEL);
- dbbt = dbbt_page;
- /* Write one additional page to make the ROM happy. */
- /* Maybe the PagesInFirmwarex fields are really the */
- /* number of pages - 1. kobs-ng does the same. */
+
- fw.len = ALIGN(len + FLASH_OFFSET_STANDARD + mtd->writesize, mtd-
writesize);
- fw.buf = kzalloc(fw.len, GFP_KERNEL);
- memcpy(fw.buf + FLASH_OFFSET_STANDARD, img, len);
- /* Erase entire partition */
- ret = mxs_nand_uboot_erase(mtd, part);
- if (ret)
goto out;
- /* Now write 2 copies of the U-Boot proper to where they belong. */
- /* Headers (FCB, DBBT) will be generated and written after that. */
- ret = mxs_nand_uboot_write_fw(mtd, &fw);
- if (ret < 0)
goto out;
- /* Create FCB, calculate ECC (we don't/can't use hardware ECC */
- /* here so we do it ourselves and then write _RAW_ pages. */
+
- fcb->Firmware1_startingPage = fw.fw1_blk * mtd->erasesize / mtd-
writesize;
- fcb->Firmware2_startingPage = fw.fw2_blk * mtd->erasesize / mtd-
writesize;
- fcb->PagesInFirmware1 =
ALIGN(len + FLASH_OFFSET_STANDARD, mtd->writesize) / mtd-
writesize;
- fcb->PagesInFirmware2 = fcb->PagesInFirmware1;
- fcb_create(fcb, mtd);
- encode_hamming_13_8(fcb, ecc, 512);
- /*
- * Set the first and second byte of OOB data to 0xFF, not 0x00.
These
- * bytes are used as the Manufacturers Bad Block Marker (MBBM).
Since
- * the FCB is mostly written to the first page in a block, a scan
for
- * factory bad blocks will detect these blocks as bad, e.g. when
- * function nand_scan_bbt() is executed to build a new bad block
table.
- * We will _NOT_ mark a bad block as good -- we skip the bad
blocks.
- */
- memset(fcb_raw_page + mtd->writesize, 0xff, 2);
- /* Now create DBBT */
- dbbt->Checksum = 0;
- dbbt->FingerPrint = 0x54424244;
- dbbt->Version = 0x01000000;
- if ((ret = dbbt_data_create(mtd, dbbt_data_page, fw.part_blks)) <
goto out;
- if (ret > 0)
dbbt->DBBTNumOfPages = 1;
- offset = 0;
- if (mtd_block_isbad(mtd, offset)) {
puts("Block 0 is bad, NAND unusable\n");
ret = -EIO;
goto out;
- }
It would be nice to check for stuff like this before erasing anything, so you don't brick the board in the event that some software or config problem is causing all blocks to report as bad (I've seen this happen on other hardware).
+out:
- kfree(dbbt_page);
- kfree(dbbt_data_page);
- kfree(fcb_raw_page);
- kfree(fw.buf);
Where did this code come from that uses kfree() as anything but a compatibility wrapper?
- return ret;
+}
+int mxs_do_nand_bootupdate(ulong addr, size_t len) +{
- /* KSI: Unlock NAND first if it is locked... */
- return mxs_nand_uboot_update((const void *)addr, len);
+}
If the function takes a pointer then have it take a pointer. Don't cast it here.
--- a/cmd/nand.c +++ b/cmd/nand.c @@ -38,6 +38,11 @@ int find_dev_and_part(const char *id, struct mtd_device **dev, u8 *part_num, struct part_info **part); #endif
+#ifdef CONFIG_CMD_NAND_BOOTUPDATE +/* This comes from a separate file in drivers/mtd/nand */ +int mxs_do_nand_bootupdate(ulong addr, size_t len); +#endif
Prototypes need to come from header files that are also included by the function implementation.
static int nand_dump(struct mtd_info *mtd, ulong off, int only_oob, int repeat) { @@ -372,6 +377,9 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) loff_t off, size, maxsize; char *cmd, *s; struct mtd_info *mtd; +#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- size_t cnt;
+#endif
Why can't you use "size" that's already there?
#ifdef CONFIG_SYS_NAND_QUIET int quiet = CONFIG_SYS_NAND_QUIET; #else @@ -777,6 +785,48 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) } #endif
+#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- if (strncmp(cmd, "bootupdate", 10) == 0) {
if (argc < 3) {
/* All default values */
addr = getenv_ulong("fileaddr", 16, 0UL);
cnt = getenv_ulong("filesize", 16, 0UL);
}
if (argc == 3) {
/* 'addr' only, file size from environment */
if ((addr = simple_strtoul(argv[2], NULL, 16)) ==
0UL)
addr = getenv_ulong("fileaddr", 16, 0UL);
cnt = getenv_ulong("filesize", 16, 0UL);
}
if (argc > 3) {
/* 'addr', 'size', and possibly more */
if ((addr = simple_strtoul(argv[2], NULL, 16)) ==
0UL)
addr = getenv_ulong("fileaddr", 16, 0UL);
if ((cnt = simple_strtoul(argv[3], NULL, 16)) ==
0UL)
cnt = getenv_ulong("filesize", 16, 0UL);
}
Don't put assignments in if-statements.
This is more simply written as:
if (strncmp(cmd, "bootupdate", 10) == 0) { if (argc >= 3) addr = simple_strtoul(argv[2], NULL, 16); else addr = getenv_ulong("fileaddr", 16, 0UL);
if (argc >= 4) size = simple_strtoul(argv[3], NULL, 16); else size = getenv_ulong("filesize", 16, 0UL);
... }
Is the undocumented "get the value from the environment if zero is explicitly passed" semantic really necessary? If it is, then:
if (strncmp(cmd, "bootupdate", 10) == 0) { unsigned long val;
addr = getenv_ulong("fileaddr", 16, 0UL); size = getenv_ulong("filesize", 16, 0UL);
if (argc >= 3) { val = simple_strtoul(argv[2], NULL, 16); if (addr) addr = val; }
if (argc >= 4) { val = simple_strtoul(argv[3], NULL, 16); if (val) size = val; } }
if (addr == 0 || cnt == 0) {
puts("Invalid arguments to nand bootupdate!\n");
return 1;
}
Is there a reason you're more worried about these invalid values than the many other possibilities?
+
if (mxs_do_nand_bootupdate(addr, cnt)) {
The help text says "in board/SoC specific manner" -- not "hardcoded to mxs".
@@ -798,6 +848,17 @@ static char nand_help_text[] = " 'addr', skipping bad blocks and dropping any pages at the end\n" " of eraseblocks that contain only 0xFF\n" #endif +#ifdef CONFIG_CMD_NAND_BOOTUPDATE
- "nand bootupdate - [addr] [size]\n"
- " write U-Boot into NAND in board/SoC specific manner creating
all\n"
- " required headers and other bits and pieces as required for
the\n"
- " system to be able to boot off of NAND. 'addr' is the
address\n"
- " where U-Boot image has been loaded at, 'size' is its size.\n"
- " If any of 'addr'/'size' is missing it is taken from
environment\n"
- " for the last file loaded. U-Boot image must be of a proper
type\n"
- " for the target platform (only IMX image supported at the
moment)\n"
- " binary without U-Boot image headers (e.g. u-boot.imx file.)\n"
+#endif
This is too verbose. Most of this info should go in a README.
-Scott

On Fri, 7 Oct 2016, Scott Wood wrote:
On Wed, 2016-10-05 at 12:57 -0700, Sergey Kubushyn wrote:
This one adds nand_bootupdate command for i.MX6 and similar MCUs. It generates proper NAND boot structures (FCB, DBBT, etc) and writes those along with U-Boot mx image to where they belong so system would be able to boot off of raw NAND.
This seems like a lot of code just to write some images to various places. What are these "NAND boot structures" and do they really need to be generated by U-Boot, or can they be generated by a tool to produce an image that U-Boot writes to NAND as is? I do see there are some things to be filled in based on NAND parameters, bad blocks, etc. but can this patching up be minimized (and the necessary parts explained in the changelog)?
The entire idea is to _REPLACE_ the only existing tool, kobs-ng. It only works from Linux so one has to get everything up and running before he could actually write U-Boot in NAND that is catch 22.
It can _NOT_ be generated with an external tool somewhere else because it is specific to the NAND chip installed on a particular board i.e. it MUST be generated in-place. Furthermore it is NOT a static info as it can change as time goes and NAND develops new bad blocks so it MUST be re-generated each and every time U-Boot is written to a particular board's NAND.
Those NAND boot structures are headers required by i.MX6 and their brethen Boot ROM to boot off of raw NAND. It won't boot if any of those headers is missing or incorrect.
There is no other method for putting i.MX6 NAND U-boot for bootup from raw NAND at present time other than using kobs-ng utility under Linux _RAN ON THAT PARTICULAR BOARD_ where U-Boot needs to be written to NAND.
This is my THIRD attempt to get it into the main U-Boot tree. I do really hope it would get applied before I retire.
It helps to CC relevant people...
Making another patch with proper CCs now so the ritual will be performed properly.
--- ****************************************************************** * KSI@home KOI8 Net < > The impossible we do immediately. * * Las Vegas NV, USA < > Miracles require 24-hour notice. * ******************************************************************
participants (5)
-
Fabio Estevam
-
Jagan Teki
-
Scott Wood
-
Sergey Kubushyn
-
Stefano Babic