当前位置: 首页 > news >正文

u-boot: NAND 驱动简介

文章目录

  • 1. 前言
  • 2. NAND 初始化
  • 3. 访问 NAND 设备
    • 3.1 查看 NAND 设备信息
      • 3.1.1 查看 NAND 设备基本信息
      • 3.1.2 查看 NAND 设备 MTD 分区
      • 3.1.3 查看 NAND 设备坏块
    • 3.2 NAND 擦除操作
    • 3.3 NAND 写操作
    • 3.4 NAND 读操作
    • 3.5 其它 NAND 操作

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. NAND 初始化

下面以 Micron MT29F2G08AAD 型号的 Nand Flash 为例,说明 ARMv7 架构下 U-BootNand Flash 设备的初始化过程。首先,可以将相关代码划分为 U-Boot NAND 驱动硬件无关通用部分硬件相关的 NAND FLASH 以及其 控制器 驱动 两部分。另外,抽象层次上,U-BootNor Flash,Nand Flash 等类型设备,统一抽象为 MTD(Memory Technology Device) 类型设备,对这些类型设备的访问,都是通过 MTD 接口来间接进行。

board_init_r() /* common/board_r.c *//* 特定 板型 初始化:这里重点关注 NAND 控制器 的 初始化 */board_init() /* board/myirtech/myd_c335x/myd_c335x.c */...gpmc_init();/* putting a blanket check on GPMC based on ZeBu for now */gpmc_cfg = (struct gpmc *)GPMC_BASE;/* NAND 控制器 的一些寄存器配置 */...initr_nand()nand_init() /* drivers/mtd/nand/nand.c */nand_init_chip()struct mtd_info *mtd;#ifndef CONFIG_DM_NANDstruct nand_chip *nand = &nand_chip[i];ulong base_addr = base_address[i];#endif...mtd = &nand_info[i]; /* NAND 设备 的 MTD 数据对象 */mtd->priv = nand; /* MTD NAND 设备数据 */nand->IO_ADDR_R = nand->IO_ADDR_W = (void  __iomem *)base_addr; /* NAND 设备 IO 地址空间 *//* 1. 特定 板型 的 NAND 初始化 */if (board_nand_init(nand)) /* drivers/mtd/nand/omap_gpmc.c */return;/* 2. 扫描识别并配置 NAND 控制器 上 挂接的 NAND 设备 */if (nand_scan(mtd, maxchips))return;/* 3. 注册 扫描到的、挂接在 NAND 控制器 上 NAND 设备 */nand_register(i);/* 1. 特定 板型 的 NAND 初始化 */
board_nand_init(nand).../** xloader/Uboot's gpmc configuration would have configured GPMC for* nand type of memory. The following logic scans and latches on to the* first CS with NAND type memory.* TBD: need to make this logic generic to handle multiple CS NAND* devices.*/while (cs < GPMC_MAX_CS) {/* Check if NAND type is set */if ((readl(&gpmc_cfg->cs[cs].config1) & 0xC00) == 0x800) {/* Found it!! */break;}cs++;}...nand->IO_ADDR_R = (void __iomem *)&gpmc_cfg->cs[cs].nand_dat;nand->IO_ADDR_W = (void __iomem *)&gpmc_cfg->cs[cs].nand_cmd;...nand->priv = &omap_nand_info[cs];nand->cmd_ctrl = omap_nand_hwcontrol;nand->options |= NAND_NO_PADDING | NAND_CACHEPRG;nand->chip_delay = 100;nand->ecc.layout = &omap_ecclayout; /* NAND ECC 数据 layout */...nand->options &= ~NAND_BUSWIDTH_16; /* 8 位数据宽度 */.../* select ECC scheme */
#if defined(CONFIG_NAND_OMAP_ECCSCHEME)/* NAND ECC 模式选择 */err = omap_select_ecc_scheme(nand, CONFIG_NAND_OMAP_ECCSCHEME,CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE);...switch (ecc_scheme) {...case OMAP_ECC_BCH8_CODE_HW: /* ECC 使用 硬件 BCH8 码 */#ifdef CONFIG_NAND_OMAP_ELM.../* intialize ELM for ECC error detection */elm_init(); /* ECC 错误检测硬件模块 ELM 初始化 */.../* populate ecc specific fields */nand->ecc.mode  = NAND_ECC_HW; /* 硬件 ECC */nand->ecc.strength = 8;nand->ecc.size  = SECTOR_BYTES;nand->ecc.bytes  = 14;/* ECC 操作接口 */nand->ecc.hwctl  = omap_enable_hwecc;nand->ecc.correct = omap_correct_data_bch;nand->ecc.calculate = omap_calculate_ecc;nand->ecc.read_page = omap_read_page_bch;...#else...#endif...}...info->ecc_scheme = ecc_scheme; /* OMAP_ECC_BCH8_CODE_HW */return 0;
#else...
#endif#ifdef CONFIG_NAND_OMAP_GPMC_PREFETCHnand->read_buf = omap_nand_read_prefetch;
#else...
#endifnand->dev_ready = omap_dev_ready;return 0;/* 2. 扫描识别并配置 NAND 控制器 上 挂接的 NAND 设备 */
nand_scan(mtd, maxchips) /* drivers/mtd/nand/nand_base.c */.../* 扫描识别 NAND 设备 和 参数, 设置操作接口 等 */ret = nand_scan_ident(mtd, maxchips, NULL);...struct nand_chip *chip = mtd->priv;struct nand_flash_dev *type;/* Set the default functions */nand_set_defaults(chip, chip->options & NAND_BUSWIDTH_16); /* 设置 NAND 设备缺省操作接口(读写等) */.../* check, if a user supplied command function given */if (chip->cmdfunc == NULL)chip->cmdfunc = nand_command;/* check, if a user supplied wait function given */if (chip->waitfunc == NULL)chip->waitfunc = nand_wait;if (!chip->select_chip)chip->select_chip = nand_select_chip;.../* If called twice, pointers that depend on busw may need to be reset */if (!chip->read_byte || chip->read_byte == nand_read_byte)chip->read_byte = busw ? nand_read_byte16 : nand_read_byte;if (!chip->read_word)chip->read_word = nand_read_word;if (!chip->block_bad)chip->block_bad = nand_block_bad; /* 坏块 判定接口 */if (!chip->block_markbad)chip->block_markbad = nand_default_block_markbad;if (!chip->write_buf || chip->write_buf == nand_write_buf)chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;if (!chip->write_byte || chip->write_byte == nand_write_byte)chip->write_byte = busw ? nand_write_byte16 : nand_write_byte;if (!chip->read_buf || chip->read_buf == nand_read_buf)chip->read_buf = busw ? nand_read_buf16 : nand_read_buf;if (!chip->scan_bbt)chip->scan_bbt = nand_default_bbt; /* 坏块 选择/建立 接口 */.../* Read the flash type *//* 识别 NAND 设备类型 */type = nand_get_flash_type(mtd, chip, &nand_maf_id,&nand_dev_id, table);...u8 id_data[8];/* Select the device */chip->select_chip(mtd, 0);/** Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)* after power-up.*/chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); /* 复位 NAND 设备 ID *//* Send the command for reading device ID */chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* 发送读取 NAND 设备 ID 命令 *//* Read manufacturer and device IDs */*maf_id = chip->read_byte(mtd); /* 读取 NAND 设备 制造商 ID */*dev_id = chip->read_byte(mtd); /* 读取 NAND 设备 ID */chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* 读取 NAND 芯片 ID *//* Read entire ID string */for (i = 0; i < 8; i++)id_data[i] = chip->read_byte(mtd);...if (!type)type = nand_flash_ids; /* 预定义的 NAND 设备列表: drivers/mtd/nand/nand_ids.c *//** 对比 读取到的 NAND 设备 ID 和 预定义表 nand_flash_ids[], * 看是否能找到匹配的表项.*/for (; type->name != NULL; type++) {if (is_full_id_nand(type)) { /* 对于 全 ID 标识的设备, 进行全 ID 匹配 */if (find_full_id_nand(mtd, chip, type, id_data, &busw)) /* 如果 是 设备全 ID 匹配, */goto ident_done; /* 完成识别工作 */} else if (*dev_id == type->dev_id) { /* 如果 不是 设备全 ID 匹配, 而是 设备 ID 匹配 */break; /* 做进一步的识别工作(如符合 ONFI/JEDEC 规范的设备匹配工作) */}}/* 可能满足 ONFI/JEDEC 规范 的 设备 识别 */chip->onfi_version = 0;if (!type->name || !type->pagesize) {/* Check if the chip is ONFI compliant */if (nand_flash_detect_onfi(mtd, chip, &busw)) /* 识别到 符合 ONFI 接口规范 的 NAND 设备 */goto ident_done; /* 完成识别工作 *//* Check if the chip is JEDEC compliant */if (nand_flash_detect_jedec(mtd, chip, &busw)) /* 识别到 符合 JEDEC 接口规范 的 NAND 设备 */goto ident_done;}/* * 目前无法识别设备:* . 无法从预定义 NAND 芯片列表 nand_flash_ids[] 匹配设备* . 设备不符合 ONFI/JEDEC 接口规范*/if (!type->name)return ERR_PTR(-ENODEV);if (!mtd->name)mtd->name = type->name; /* 设置 MTD NAND 设备名称 *//* 设置 NAND 设备容量 */chip->chipsize = (uint64_t)type->chipsize << 20;/* 设置 NAND 的 page size, OOB size, erase size */if (!type->pagesize && chip->init_size) {/* Set the pagesize, oobsize, erasesize by the driver */busw = chip->init_size(mtd, chip, id_data);} else if (!type->pagesize) {/* Decode parameters from extended ID */nand_decode_ext_id(mtd, chip, id_data, &busw);} else {nand_decode_id(mtd, chip, type, id_data, &busw);}/* Get chip options */chip->options |= type->options; /* 设置 NAND 设备选项 */...ident_done:.../* 坏块管理相关配置 */nand_decode_bbm_options(mtd, chip, id_data);int maf_id = id_data[0];/* Set the bad block position */if (mtd->writesize > 512 || (chip->options & NAND_BUSWIDTH_16))chip->badblockpos = NAND_LARGE_BADBLOCK_POS;elsechip->badblockpos = NAND_SMALL_BADBLOCK_POS;/** Bad block marker is stored in the last page of each block on Samsung* and Hynix MLC devices; stored in first two pages of each block on* Micron devices with 2KiB pages and on SLC Samsung, Hynix, Toshiba,* AMD/Spansion, and Macronix.  All others scan only the first page.*/if (!nand_is_slc(chip) &&(maf_id == NAND_MFR_SAMSUNG ||maf_id == NAND_MFR_HYNIX))/* 三星 和 海力士 的 非 SLC 类型设备, 坏块标记存储在每个 block 的最后一个 page */chip->bbt_options |= NAND_BBT_SCANLASTPAGE;else if ((nand_is_slc(chip) &&(maf_id == NAND_MFR_SAMSUNG ||maf_id == NAND_MFR_HYNIX ||maf_id == NAND_MFR_TOSHIBA ||maf_id == NAND_MFR_AMD ||maf_id == NAND_MFR_MACRONIX)) ||(mtd->writesize == 2048 &&maf_id == NAND_MFR_MICRON))/* 一些厂家的 SLC 类型设备,坏块标记存储在 block 的 第1 和 第2 个 page */chip->bbt_options |= NAND_BBT_SCAN2NDPAGE;...chip->badblockbits = 8;chip->erase = single_erase; /* 设置 擦除 接口 *//* Do not replace user supplied command function! */if (mtd->writesize > 512 && chip->cmdfunc == nand_command)chip->cmdfunc = nand_command_lp; /* 覆盖命令为 大 page 接口 *//* 报告设备信息 (需要开启 CONFIG_MTD_DEBUG) */pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n",*maf_id, *dev_id);...pr_info("%d MiB, %s, erase size: %d KiB, page size: %d, OOB size: %d\n",(int)(chip->chipsize >> 20), nand_is_slc(chip) ? "SLC" : "MLC",mtd->erasesize >> 10, mtd->writesize, mtd->oobsize);return type; /* 返回是被的 NAND 设备类型 *//* 通过 NAND 控制器,选择 NAND 芯片 */chip->select_chip(mtd, -1);/* Store the number of chips and calc total size for mtd */chip->numchips = i;mtd->size = i * chip->chipsize; /* 记录 MTD NAND 设备容量 */return 0;if (!ret)/** 扫描识别收尾工作: * 从 前面 扫描识别到的 NAND 设备 和 参数,* 为 NAND 设备建立缓冲,设置 NAND 设备对应的 MTD NAND 设备对象。*/ret = nand_scan_tail(mtd);int i;struct nand_chip *chip = mtd->priv;struct nand_ecc_ctrl *ecc = &chip->ecc;struct nand_buffers *nbuf;/* 为 NAND 设备 创建 缓冲: ECC, 数据 */if (!(chip->options & NAND_OWN_BUFFERS)) {nbuf = kzalloc(sizeof(struct nand_buffers), GFP_KERNEL);chip->buffers = nbuf;} else {if (!chip->buffers)return -ENOMEM;}/* Set the internal oob buffer location, just after the page data *//* 和 NAND 页面存储一样, OOB 缓冲 紧邻 page 缓冲之后 */chip->oob_poi = chip->buffers->databuf + mtd->writesize;...switch (ecc->mode) {...case NAND_ECC_HW: /* 设置没有配置的 ECC 接口 */if (!ecc->read_page)ecc->read_page = nand_read_page_hwecc;if (!ecc->write_page)ecc->write_page = nand_write_page_hwecc;if (!ecc->read_page_raw)ecc->read_page_raw = nand_read_page_raw;if (!ecc->write_page_raw)ecc->write_page_raw = nand_write_page_raw;if (!ecc->read_oob)ecc->read_oob = nand_read_oob_std;if (!ecc->write_oob)ecc->write_oob = nand_write_oob_std;if (!ecc->read_subpage)ecc->read_subpage = nand_read_subpage;if (!ecc->write_subpage)ecc->write_subpage = nand_write_subpage_hwecc;...}/* For many systems, the standard OOB write also works for raw */if (!ecc->read_oob_raw)ecc->read_oob_raw = ecc->read_oob;if (!ecc->write_oob_raw)ecc->write_oob_raw = ecc->write_oob;// 一些其它 ECC 相关配置.../* Initialize state */chip->state = FL_READY; /* NAND 设备标记为 READY 状态 */.../* Fill in remaining MTD driver data */mtd->type = nand_is_slc(chip) ? MTD_NANDFLASH : MTD_MLCNANDFLASH; /* 设置 MTD NAND 设备类型: SLC 或 MLC */mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :MTD_CAP_NANDFLASH; /* MTD NAND 读写属性设置:只读、可读写 *//* 设置 MTD NAND 设备接口: 调用 NAND 设备接口 */mtd->_erase = nand_erase;mtd->_read = nand_read;mtd->_write = nand_write;mtd->_panic_write = panic_nand_write;mtd->_read_oob = nand_read_oob;mtd->_write_oob = nand_write_oob;mtd->_sync = nand_sync;mtd->_lock = NULL;mtd->_unlock = NULL;mtd->_block_isreserved = nand_block_isreserved;mtd->_block_isbad = nand_block_isbad;mtd->_block_markbad = nand_block_markbad;mtd->writebufsize = mtd->writesize;// MTD NAND ECC 相关设置...return 0;return ret;/* 3. 注册 扫描到的、挂接在 NAND 控制器 上 NAND 设备 */
nand_register(i);struct mtd_info *mtd;mtd = get_nand_dev_by_index(devnum);sprintf(dev_name[devnum], "nand%d", devnum);mtd->name = dev_name[devnum];#ifdef CONFIG_MTD_DEVICE/** Add MTD device so that we can reference it later* via the mtdcore infrastructure (e.g. ubi).*/add_mtd_device(mtd);
#endiftotal_nand_size += mtd->size / 1024;if (nand_curr_device == -1)nand_curr_device = devnum; /* 设置当前 NAND 设备编号 */return 0;

3. 访问 NAND 设备

3.1 查看 NAND 设备信息

3.1.1 查看 NAND 设备基本信息

U-Boot 提供一些 nand info 命令,可以查看 NAND 设备信息:

# nand infoDevice 0: nand0, sector size 128 KiBPage size       2048 bOOB size          64 bErase size    131072 bsubpagesize      512 boptions     0x4000000cbbt options 0x    8000

从上面看到,Nand Flash 设备:

o page 是 2KB 大小
o page 后跟的 OOB(Spare area)64 Bytes
o 擦除 size 是 128KB,也就是 block size

3.1.2 查看 NAND 设备 MTD 分区

前面有说过,U-Boot 将 NAND 抽象为 MTD 设备进行访问,通过 mtdparts 命令,可以查看 NAND 设备的分区信息:

# mtdpartsdevice nand0 <nand.0>, # parts = 11#: name                size            offset          mask_flags0: NAND.SPL            0x00020000      0x00000000      01: NAND.SPL.backup1    0x00020000      0x00020000      02: NAND.SPL.backup2    0x00020000      0x00040000      03: NAND.SPL.backup3    0x00020000      0x00060000      04: NAND.u-boot-spl-os  0x00040000      0x00080000      05: NAND.u-boot         0x00100000      0x000c0000      06: NAND.u-boot-env     0x00020000      0x001c0000      07: NAND.u-boot-env.backup10x00020000   0x001e0000      08: NAND.kernel         0x00800000      0x00200000      09: NAND.rootfs         0x0d600000      0x00a00000      0
10: NAND.userdata       0x02000000      0x0e000000      0active partition: nand0,0 - (NAND.SPL) 0x00020000 @ 0x00000000defaults:
mtdids  : nand0=nand.0
mtdparts: mtdparts=nand.0:128k(NAND.SPL),128k(NAND.SPL.backup1),128k(NAND.SPL.backup2),128k(NAND.SPL.backup3),256k(NAND.u-boot-spl-os),1m(NAND.u-boot),128k(NAND.u-boot-env),128k(NAND.u-boot-env.backup1),8m(NAND.kernel),214m(NAND.rootfs),-(NAND.userdata)

3.1.3 查看 NAND 设备坏块

# nand badDevice 0 bad blocks:03e40000086c0000

发现了两个坏块,数据标记了它们的字节偏移位置。

3.2 NAND 擦除操作

通过 nand erase 命令,可以对 NAND 发起对 NAND 的擦除操作:

# nand erase c0000 100000

其中参数 c0000要擦除的起始位置,相对于 NAND 设备开始位置的字节偏移100000要擦除的长度字节数。这两个参数都是十六进制数字。来看一下擦除过程的具体实现:

do_nand() /* cmd/nand.c */...nand_info_t *nand;...int dev = nand_curr_device; /* 默认选择当前 NAND 设备进行操作 */......cmd = argv[1]; /* "erase" */...nand = get_nand_dev_by_index(dev); /* 获取 NAND 设备 */if (strncmp(cmd, "erase", 5) == 0 || strncmp(cmd, "scrub", 5) == 0) {nand_erase_options_t opts;......printf("\nNAND %s: ", cmd); /* "NAND erase: " *//* 解析 擦除起始位置 和 长度 参数 到 @off 和 @size */if (mtd_arg_off_size(argc - o, argv + o, &dev, &off, &size,&maxsize, MTD_DEV_TYPE_NAND,nand->size) != 0)return 1;/* 切换到 要操作 的 目标 NAND 设备 */if (set_dev(dev))return 1;nand = get_nand_dev_by_index(dev);memset(&opts, 0, sizeof(opts));opts.offset = off;opts.length = size;...ret = nand_erase_opts(nand, &opts); /* 擦除操作 */...}ret = nand_erase_opts(nand, &opts); /* drivers/mtd/nand/nand_util.c */.../** @erase_length:要擦除的 block 数目* @erased_length: 已经擦除的 block 数目*/unsigned long erase_length, erased_length; /* in blocks */...erase_length = lldiv(opts->length + meminfo->erasesize - 1,meminfo->erasesize); /* 向上对齐到 erase size (block 大小) */...for (erased_length = 0;erased_length < erase_length;erase.addr += meminfo->erasesize) {...if (!opts->scrub) {/* 检查位于 @ofs 位置的 block 是不是坏块 */int ret = mtd_block_isbad(meminfo, erase.addr);if (ret > 0) { /* 坏块 */...if (!opts->spread)erased_length++; /* 非 nand erase.spread 命令, 坏块也计入擦除 block 数目 */continue; /* 跳过坏块: 坏块不做擦除动作,擦除坏块是非法操作 */}}erased_length++; /* 已擦除 block 数目 +1 */result = mtd_erase(meminfo, &erase); /* 擦除当前块 */...return mtd->_erase(mtd, instr);nand_erase()return nand_erase_nand(mtd, instr, 0);...struct nand_chip *chip = mtd->priv; /* MTD 转入 NAND 层操作 */.../* Grab the lock and see if the device is available */nand_get_device(mtd, FL_ERASING);/* Shift to get first page */page = (int)(instr->addr >> chip->page_shift); /* 擦除位置转换为 page 位置 */chipnr = (int)(instr->addr >> chip->chip_shift);/* Calculate pages in each block */pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);/* Select the NAND device */chip->select_chip(mtd, chipnr);/* Loop through the pages */len = instr->len;instr->state = MTD_ERASING;while (len) {...status = chip->erase(mtd, page & chip->pagemask);single_erase()struct nand_chip *chip = mtd->priv;/* Send commands to erase a block *//* 发送 page 擦除操作命令 */chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);/* 等待擦除操作完成 */return chip->waitfunc(mtd, chip);/* Increment page address and decrement length *//* 移向下一个 page */len -= (1ULL << chip->phys_erase_shift);page += pages_per_block;...}instr->state = MTD_ERASE_DONE;...}

3.3 NAND 写操作

假定以命令 nand write 0x82000000 c0000 ${filesize} 发起写操作写操作基本流程和擦除操作差不多,来看细节:

do_nand()...if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {...addr = (ulong)simple_strtoul(argv[2], NULL, 16); // 0x82000000read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */printf("\nNAND %s: ", read ? "read" : "write");s = strchr(cmd, '.');if (s && !strcmp(s, ".raw")) { // nand write.raw...}  else {// @off  = 0xc0000// @size = ${filesize}if (mtd_arg_off_size(argc - 3, argv + 3, &dev, &off,&size, &maxsize,MTD_DEV_TYPE_NAND,nand->size) != 0)return 1;if (set_dev(dev))return 1;...rwsize = size;if (!s || !strcmp(s, ".jffs2") ||!strcmp(s, ".e") || !strcmp(s, ".i")) {if (read)ret = nand_read_skip_bad(nand, off, &rwsize,NULL, maxsize,(u_char *)addr); /* 读取,会自动跳过坏块 */elseret = nand_write_skip_bad(nand, off, &rwsize,NULL, maxsize,(u_char *)addr,WITH_WR_VERIFY); /* 写入,会自动跳过坏块 */}}nand = get_nand_dev_by_index(dev);}nand_write_skip_bad()...size_t left_to_write = *length; /* 要写入的长度, 字节数 */...int need_skip;blocksize = nand->erasesize;/* 检查写入过程中, 要跳过的坏块数目 */need_skip = check_skip_len(nand, offset, *length, &used_for_write);...while (left_to_write > 0) {size_t block_offset = offset & (nand->erasesize - 1); /* 计算 block 内偏移 */.../* 写入时跳过坏块 */if (nand_block_isbad(nand, offset & ~(nand->erasesize - 1))) {printf("Skip bad block 0x%08llx\n",offset & ~(nand->erasesize - 1));offset += nand->erasesize - block_offset;continue;}/* 调整 写入 大小 */if (left_to_write < (blocksize - block_offset))write_size = left_to_write;elsewrite_size = blocksize - block_offset;/* 写入 block *//* * 注意前后的两个 nand_write() 不是同一个函数: * . 前一个 nand_write() 是 MTD 驱动层的 接口* . 后一个是 NAND 驱动层的 接口*/rval = nand_write(nand, offset, &truncated_write_size, p_buffer); /* drivers/mtd/mtcore.c */mtd_write()mtd->_write(mtd, to, len, retlen, buf);nand_write() /* drivers/mtd/nand/nand_base.c */struct mtd_oob_ops ops;nand_get_device(mtd, FL_WRITING);memset(&ops, 0, sizeof(ops));ops.len = len;ops.datbuf = (uint8_t *)buf;ops.mode = MTD_OPS_PLACE_OOB;ret = nand_do_write_ops(mtd, to, &ops); /* NAND 设备写操作 */*retlen = ops.retlen;nand_release_device(mtd);return ret;/* 写入后,再读回来验证一下数据是否正确 */if ((flags & WITH_WR_VERIFY) && !rval)rval = nand_verify(nand, offset,truncated_write_size, p_buffer);offset += write_size;p_buffer += write_size;...left_to_write -= write_size;}ret = nand_do_write_ops(mtd, to, &ops); /* NAND 设备写操作 */...struct nand_chip *chip = mtd->priv;uint32_t writelen = ops->len;......while (1) {int bytes = mtd->writesize; /* 按 page 写入 */...uint8_t *wbuf = buf; /* 数据 */...if (unlikely(oob)) {...} else {/* We still need to erase leftover OOB data */memset(chip->oob_poi, 0xff, mtd->oobsize); /* 没有指定 OOB 数据, 则将 Spare area 全部填充 0xff */}ret = chip->write_page(mtd, chip, column, bytes, wbuf,oob_required, page, cached,(ops->mode == MTD_OPS_RAW)); /* 写入一个 page 的数据 */nand_write_page()...chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);if (unlikely(raw))status = chip->ecc.write_page_raw(mtd, chip, buf, oob_required);else if (subpage)status = chip->ecc.write_subpage(mtd, chip, offset, data_len, buf, oob_required);else /* 这里只考虑这一种情形 */status = chip->ecc.write_page(mtd, chip, buf, oob_required);nand_write_page_hwecc()int i, eccsize = chip->ecc.size;int eccbytes = chip->ecc.bytes;int eccsteps = chip->ecc.steps;uint8_t *ecc_calc = chip->buffers->ecccalc;const uint8_t *p = buf; /* 要写入的数据 */uint32_t *eccpos = chip->ecc.layout->eccpos;/* 写数据到 NAND, 每次写 @eccsize 个字节 (@eccsize 是计算 ECC 的单元) */for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {chip->ecc.hwctl(mtd, NAND_ECC_WRITE);chip->write_buf(mtd, p, eccsize); /* 将数据写入到 NAND */chip->ecc.calculate(mtd, p, &ecc_calc[i]); /* 计算数据 @p 的 ECC, 记录到 ecc_calc[i] */}for (i = 0; i < chip->ecc.total; i++)chip->oob_poi[eccpos[i]] = ecc_calc[i];chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); /* 将 ECC 数据写入到 Spare area */return 0;/* 写完一个 page */cached = 0;if (!cached || !NAND_HAS_CACHEPROG(chip)) {chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);status = chip->waitfunc(mtd, chip);} else  {...}return 0;...writelen -= bytes;if (!writelen)break;...}...

3.4 NAND 读操作

可以通过 nand read 0x82000000 200000 800000 发起对 NAND 的操作:将 NAND 从 0x200000 位置开始的 0x800000 个字节,读取到内存地址 0x82000000 开始的位置。NAND 读操作入口函数为 nand_read_skip_bad() ,其逻辑和 nand_write_skip_bad() 非常相似,在此不再赘述。值得一提的是,nand_read_skip_bad() 读操作,会跳过坏块。

3.5 其它 NAND 操作

nand device // 输出当前 NAND 设备信息
nand device <dev> // 切换到 NAND 设备 dev
nand dump[.oob] <offset> // 导出 NAND page 和 OOB, 带 .oob 后缀仅导出 OOB
......

相关文章:

u-boot: NAND 驱动简介

文章目录 1. 前言2. NAND 初始化3. 访问 NAND 设备3.1 查看 NAND 设备信息3.1.1 查看 NAND 设备基本信息3.1.2 查看 NAND 设备 MTD 分区3.1.3 查看 NAND 设备坏块 3.2 NAND 擦除操作3.3 NAND 写操作3.4 NAND 读操作3.5 其它 NAND 操作 1. 前言 限于作者能力水平&#xff0c;本…...

史上最全的大数据开发八股文【自己的吐血总结】

自我介绍 我本硕都是双非计算机专业&#xff0c;从研一下开始学习大数据开发的相关知识&#xff0c;从找实习到秋招&#xff0c;我投递过100公司&#xff0c;拿到过10的offer&#xff0c;包括滴滴、字节、蚂蚁、携程、蔚来、去哪儿等大厂&#xff08;岗位都是大数据开发&#…...

数据库学习案例20240304-mysql数据库案例总结(碎片,统计信息)

1 表中的碎片 在InnoDB中删除行的时候&#xff0c;这些行只是被标记为“已删除”&#xff0c;而不是真正从物理存储上进行了删除&#xff0c;因而存储空间也没有真正被释放回收。InnoDB的Purge线程会异步地来清理这些没用的索引键和行。但是依然没有把这些释放出来的空间还给操…...

【小白友好】LeetCode 删除并获得点数

基础题 打家劫舍https://leetcode.cn/problems/house-robber/ 小白解法 删除nums[i]就会使得所有nums[i]-1和nums[i]1的值都消失&#xff0c;手写了几个&#xff0c;发现找来找去不方便&#xff0c;还不如先排个序&#xff0c;然后这样nums[i]-1和nums[i]和nums[i]1就能靠在…...

c#委托、lambda、事件

Lambda Lambda表达式是一种匿名函数&#xff0c;Lambda表达式通常以箭头“>”分隔左侧的输入和右侧的输出。 (parameter_list) > { statement_block } parameter_list 是由一个或多个参数组成的逗号分隔列表&#xff0c;每个参数都包括类型和名称&#xff0c;可以为空。…...

每日一练——9×9乘法表

#include<stdio.h>int main() {int i 0; //乘数定义for (i 1; i < 9; i) //循环1到9 {int j 0;//被乘数定义for (j 1; j < i; j) //循环被乘数1到9{printf("%d*%d%2d ", i, j, i * j); 乘法}printf("\n"); 换行} return 0; }...

大白话解析LevelDB:ShardedLRUCache

文章目录 Cache 接口定义ShardedLRUCache 的实现ShardedLRUCache 的构造函数ShardedLRUCache::Insert(const Slice& key, void* value, size_t charge, void (\*deleter)(const Slice& key, void* value))ShardedLRUCache::Lookup(const Slice& key)ShardedLRUCach…...

GDOI2024游记

Day0 中午一点钟从学校出发去东莞&#xff0c;大概坐了一个多小时车&#xff0c;两点半多到酒店。住的八方精选酒店&#xff08;ljh说他们住九方精选酒店&#xff0c;乐&#xff09;&#xff0c;说的是景区酒店&#xff0c;但打开外窗&#xff0c;近处是简陋的阳台&#xff0c…...

学编程怎么样才能更快入手,编程怎么简单易学

学编程怎么样才能更快入手&#xff0c;编程怎么简单易学 一、前言 对于初学编程建议先从简单入手&#xff0c;然后再学习其他复杂的编程语言。 今天给大家分享的中文编程开发语言工具 进度条构件的用法。 编程入门视频教程链接 https://edu.csdn.net/course/detail/39036 …...

Android 通知--判断通知是否有跳转

一. 从应用层来分析 在 Android 中&#xff0c;可以通过 PendingIntent 来实现有跳转的通知和没有跳转的通知的区别。具体来说&#xff0c;有跳转的通知会设置一个 PendingIntent&#xff0c;当用户点击通知时会触发该 PendingIntent&#xff0c;打开指定的界面或执行特…...

【计算机网络】IO多路转接之poll

文章目录 一、poll函数接口二、socket就绪条件三、poll的优点四、poll的缺点五、poll使用案例--只读取数据的server服务器1.err.hpp2.log.hpp3.sock.hpp4.pollServer.hpp5.main.cc 一、poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int t…...

性能比较:in和exists

当在Hive SQL中使用NOT IN和NOT EXISTS时&#xff0c;性能差异主要取决于底层数据的组织方式、数据量大小、索引的使用情况以及具体查询的复杂程度。下面是对这两种方法的性能分析&#xff1a; 1. NOT IN&#xff1a;- 工作原理&#xff1a;NOT IN子查询会逐个比较主查询中的值…...

【Java设计模式】五、建造者模式

文章目录 1、建造者模式2、案例&#xff1a;共享单车的创建3、其他用途 1、建造者模式 某个对象的构建复杂将复杂的对象的创建 和 属性赋值所分离&#xff0c;使得同样的构建过程可以创建不同的表示建造的过程和细节调用者不需要知道&#xff0c;只需要通过构建者去进行操作 …...

nginx代理minio教程 避坑过的教程 避开SignatureDoesNotMatch

本次教程使用的是单机minio进行演示&#xff0c;集群minio也和这个差不多。 按照这个教程&#xff0c;可以避开nginx代理minio之后&#xff0c;只能访问文件&#xff0c;但是通过预签名url上传文件就会报SignatureDoesNotMatch的坑 暂定如下&#xff1a; 你已经下载好miniom…...

Linux进程详细介绍

文章目录 Linux进程1、计算机体系结构和操作系统管理1.1、计算机体系结构 -- 硬件1.2、操作系统&#xff08;Operator System&#xff09; -- 软件 2、进程2.1、进程基本概念2.2、进程标识符2.2.1、获取当前进程标识符和当前进程的父进程标识符2.2.2、通过系统调用创建进程 -- …...

2024年3月产品认证基础考试简答题及答案

产品认证基础 46.产品认证的工厂检查有哪几种路线&#xff1f;各有什么优缺点&#xff1f; 答案&#xff1a;两种常用的检查路线&#xff1a; 1.按照要素或过程检查 按照认证规则规定的工厂应满足的要素要求&#xff08;包括质量保证能力要求&#xff09;&#xff0c;结合部…...

嵌入式蓝桥杯做题总结

第十二届省赛 按键代码 ——自认为比较巧妙&#xff0c;定时器3被设置为10ms进入一次中断&#xff0c;代替了HAL_Delay(10)的方法消抖&#xff1b; 运用状态机机思想实现检测多个按键检测——且分为两个状态&#xff0c;其中一个状态PB&#xff11;和PB&#xff12;的按键不…...

Spring Boot 常用注解大全

以下是Spring Boot中常用的注解及其详细解释以及相应的代码示例&#xff1a; SpringBootApplication: 这个注解用于标识一个Spring Boot应用的主类。它整合了 Configuration&#xff0c;EnableAutoConfiguration 和 ComponentScan。 SpringBootApplication public class Demo…...

(MATLAB)第十二章-数列与极限

目录 12.1 数列 12.1.1 数列求和 1. 累计求和函数sum() 2. 忽略NaN累计求和函数 nansum() 3. 求此元素位置之前的元素和函数cumsum() 4. 求梯形累计和函数cumtrapz() 12.1.2 数列求积 1. 元素连续相乘函数 prod() 2. 求累计积函数 cumprod() 3. 阶乘函数 ffactorial(n…...

OJ输入问题+准备

写在之前&#xff1a; 发现题目输入是这样的&#xff1a; 我的问题&#xff1a;如何通过空格分割这些输入的字符串并分别保存&#xff01;&#xff01;&#xff08;C语言scanf好解决一点但我选择C....&#xff09; C引入了ostringstream、istringstream、stringstream这三个类…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...

图解JavaScript原型:原型链及其分析 | JavaScript图解

​​ 忽略该图的细节&#xff08;如内存地址值没有用二进制&#xff09; 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么&#xff1a;保存在堆中一块区域&#xff0c;同时在栈中有一块区域保存其在堆中的地址&#xff08;也就是我们通常说的该变量指向谁&…...

OCR MLLM Evaluation

为什么需要评测体系&#xff1f;——背景与矛盾 ​​ 能干的事&#xff1a;​​ 看清楚发票、身份证上的字&#xff08;准确率>90%&#xff09;&#xff0c;速度飞快&#xff08;眨眼间完成&#xff09;。​​干不了的事&#xff1a;​​ 碰到复杂表格&#xff08;合并单元…...

6.9-QT模拟计算器

源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...

C++中vector类型的介绍和使用

文章目录 一、vector 类型的简介1.1 基本介绍1.2 常见用法示例1.3 常见成员函数简表 二、vector 数据的插入2.1 push_back() —— 在尾部插入一个元素2.2 emplace_back() —— 在尾部“就地”构造对象2.3 insert() —— 在任意位置插入一个或多个元素2.4 emplace() —— 在任意…...