下面研究一下其中的细节: /** * nand_read - [MTD Interface] MTD compability function for nand_do_read_ecc * @mtd: MTD device structure * @from: offset to read from * @len: number of bytes to read * @retlen: pointer to variable to store the number of read bytes * @buf: the databuffer to put data * * This function simply calls nand_do_read_ecc with oob buffer and oobsel = NULL * and flags = 0xff */ static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf) { return nand_do_read_ecc (mtd, from, len, retlen, buf, NULL, &mtd->oobinfo, 0xff); } 注: 以参数oob_buf为NULL,flags为0xff调用nand_do_read_ecc函数。
/** * nand_do_read_ecc - [MTD Interface] Read data with ECC * @mtd: MTD device structure * @from: offset to read from * @len: number of bytes to read * @retlen: pointer to variable to store the number of read bytes * @buf: the databuffer to put data * @oob_buf: filesystem supplied oob data buffer (can be NULL) * @oobsel: oob selection structure * @flags: flag to indicate if nand_get_device/nand_release_device should be preformed * and how many corrected error bits are acceptable: * bits 0..7 - number of tolerable errors * bit 8 - 0 == do not get/release chip, 1 == get/release chip * * NAND read with ECC */ int nand_do_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf, u_char * oob_buf, struct nand_oobinfo *oobsel, int flags) {
int i, j, col, realpage, page, end, ecc, chipnr, sndcmd = 1; int read = 0, oob = 0, ecc_status = 0, ecc_failed = 0; struct nand_chip *this = mtd->priv; u_char *data_poi, *oob_data = oob_buf;//目前oob_data指针为空,以后会去修改它。 u_char ecc_calc[32];//该数组用于存放计算出来的ecc结果 u_char ecc_code[32];//该数组用于存放oob中ecc部分的数据 int eccmode, eccsteps;//eccmode存放ecc的类型(ECC_SOFT); eccsteps用于记录一个page所需的ecc校验次数(2)。 int *oob_config, datidx; int blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1; int eccbytes; int compareecc = 1;//是否需要ecc标志(如果设置成ECC_NONE,这个标志将被清0) int oobreadlen;
DEBUG (MTD_DEBUG_LEVEL3, "nand_read_ecc: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
/* Do not allow reads past end of device */ /* 不允许超越设备容量的读操作 */ if ((from + len) > mtd->size) { DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: Attempt read beyond end of device\n"); *retlen = 0; return -EINVAL; }
/* Grab the lock and see if the device is available */ /* 获取自旋锁,等待设备可用并获取其控制权 */ if (flags & NAND_GET_DEVICE) nand_get_device (this, mtd, FL_READING);
/* Autoplace of oob data ? Use the default placement scheme */ if (oobsel->useecc == MTD_NANDECC_AUTOPLACE) oobsel = this->autooob; /* * 感觉这一步有点多余,因为nand_scan中已经调用了以下代码: * memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo)); * 把this->autooob的内容拷贝到mtd->oobinfo中了 */ eccmode = oobsel->useecc ? this->eccmode : NAND_ECC_NONE; oob_config = oobsel->eccpos;//记录ecc在oob数据中的位置
/* First we calculate the starting page */ /* 首先,我们计算出开始页码 */ realpage = (int) (from >> this->page_shift); page = realpage & this->pagemask;
/* Get raw starting column */ /* 其次,我们计算页内偏址 */ col = from & (mtd->oobblock - 1);
end = mtd->oobblock;//页大小(512) ecc = this->eccsize;//ecc保护下的数据大小(256) eccbytes = this->eccbytes;//ecc所占的字节数(3) if ((eccmode == NAND_ECC_NONE) || (this->options & NAND_HWECC_SYNDROME)) compareecc = 0;//如果设置为关闭ECC或写操作才需要ECC,那把ecc给禁用(现在可是读操作^_^)
oobreadlen = mtd->oobsize;//16 if (this->options & NAND_HWECC_SYNDROME) oobreadlen -= oobsel->eccbytes;
/* Loop until all data read */ while (read < len) { int aligned = (!col && (len - read) >= end); /* * If the read is not page aligned, we have to read into data buffer * due to ecc, else we read into return buffer direct * 如果要读的位置不是页对齐都话,那么只要先把整页读出来, * 取出所需要读取的数据,然后修改读位置,那么以后的读操作都是页对齐的了。 */ if (aligned) data_poi = &buf[read]; else data_poi = this->data_buf; /* Check, if we have this page in the buffer * * FIXME: Make it work when we must provide oob data too, * check the usage of data_buf oob field * 如果我们所需要的数据还存在于缓冲中都话: * 1 如果读位置页对齐,我们只要把缓冲中的数据直接拷贝到data_poi(buf[read])中即可(因为数据存在与缓存中,所以也无需要考虑ecc问题) * 2 如果读位置不是页对齐,什么读不要作,让其继续留在缓存(data_buf)中,以后会从data_poi(指向缓存data_buf)中提取所需要的数据。 */ if (realpage == this->pagebuf && !oob_buf) { /* aligned read ? */ if (aligned) memcpy (data_poi, this->data_buf, end); goto readdata; }
/* Check, if we must send the read command */ /* 发送读命令,页地址为page,列地址为0x00 */ if (sndcmd) { this->cmdfunc (mtd, NAND_CMD_READ0, 0x00, page); sndcmd = 0; }
/* get oob area, if we have no oob buffer from fs-driver */ if (!oob_buf || oobsel->useecc == MTD_NANDECC_AUTOPLACE || oobsel->useecc == MTD_NANDECC_AUTOPL_USR) oob_data = &this->data_buf[end];//以上情况,oob_data暂存在data_buf缓存中
eccsteps = this->eccsteps;//2 switch (eccmode) { case NAND_ECC_NONE: { /* No ECC, Read in a page */ static unsigned long lastwhinge = 0; if ((lastwhinge / HZ) != (jiffies / HZ)) { printk (KERN_WARNING "Reading data from NAND FLASH without ECC is not recommended\n"); lastwhinge = jiffies; } this->read_buf(mtd, data_poi, end); break; } case NAND_ECC_SOFT: /* Software ECC 3/256: Read in a page + oob data */ this->read_buf(mtd, data_poi, end);//读取数据到data_poi for (i = 0, datidx = 0; eccsteps; eccsteps--, i+=3, datidx += ecc) this->calculate_ecc(mtd, &data_poi[datidx], &ecc_calc[i]); /* 计算出读取到data_poi的数据的ecc值,并存放到ecc_calc数组中。 * 因为读都数据有一页大小(512),需要分别对其上半部和下半部分计算一次ecc值,并分开存放到ecc_calc数组相应都位置中。 */ break;
default: for (i = 0, datidx = 0; eccsteps; eccsteps--, i+=eccbytes, datidx += ecc) { this->enable_hwecc(mtd, NAND_ECC_READ); this->read_buf(mtd, &data_poi[datidx], ecc);
/* HW ecc with syndrome calculation must read the * syndrome from flash immidiately after the data */ if (!compareecc) { /* Some hw ecc generators need to know when the * syndrome is read from flash */ this->enable_hwecc(mtd, NAND_ECC_READSYN); this->read_buf(mtd, &oob_data[i], eccbytes); /* We calc error correction directly, it checks the hw * generator for an error, reads back the syndrome and * does the error correction on the fly */ ecc_status = this->correct_data(mtd, &data_poi[datidx], &oob_data[i], &ecc_code[i]); if ((ecc_status == -1) || (ecc_status > (flags && 0xff))) { DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: " "Failed ECC read, page 0x%08x on chip %d\n", page, chipnr); ecc_failed++; } } else { this->calculate_ecc(mtd, &data_poi[datidx], &ecc_calc[i]); } } break; }
/* Skip ECC check, if not requested (ECC_NONE or HW_ECC with syndromes) */ /* 跳过ecc检测 */ if (!compareecc) goto readoob; /* Pick the ECC bytes out of the oob data */ /* 从刚读出来都oob_data中取出ecc数据(在这里是前三个字节) */ for (j = 0; j < oobsel->eccbytes; j++) ecc_code[j] = oob_data[oob_config[j]];
/* correct data, if neccecary */ for (i = 0, j = 0, datidx = 0; i < this->eccsteps; i++, datidx += ecc) { ecc_status = this->correct_data(mtd, &data_poi[datidx], &ecc_code[j], &ecc_calc[j]); /* 拿前面计算出来都ecc_cal数组都数据与读出来的ecc数据作比较,并尝试修正错误(但不保证能修复,具体看返回值) */ /* Get next chunk of ecc bytes */ j += eccbytes; /* Check, if we have a fs supplied oob-buffer, * This is the legacy mode. Used by YAFFS1 * Should go away some day */ if (oob_buf && oobsel->useecc == MTD_NANDECC_PLACE) { int *p = (int *)(&oob_data[mtd->oobsize]); p[i] = ecc_status; } /* 很不幸,ecc检测发现错误且未能修复,报告错误 */ if ((ecc_status == -1) || (ecc_status > (flags && 0xff))) { DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: " "Failed ECC read, page 0x%08x\n", page); ecc_failed++; } }
readoob: /* check, if we have a fs supplied oob-buffer */ if (oob_buf) { /* without autoplace. Legacy mode used by YAFFS1 */ switch(oobsel->useecc) { case MTD_NANDECC_AUTOPLACE: case MTD_NANDECC_AUTOPL_USR: /* Walk through the autoplace chunks */ for (i = 0; oobsel->oobfree[i][1]; i++) { int from = oobsel->oobfree[i][0]; int num = oobsel->oobfree[i][1]; memcpy(&oob_buf[oob], &oob_data[from], num); oob += num; } break; case MTD_NANDECC_PLACE: /* YAFFS1 legacy mode */ oob_data += this->eccsteps * sizeof (int); default: oob_data += mtd->oobsize; } } readdata: /* Partial page read, transfer data into fs buffer * 读位置不是页对齐,从data_poi(data_buf中)提取所需要都数据 */ if (!aligned) { for (j = col; j < end && read < len; j++) buf[read++] = data_poi[j];//read自增 this->pagebuf = realpage; } else read += mtd->oobblock;//整页读取,计数值加上整页的数目(512)
/* Apply delay or wait for ready/busy pin * Do this before the AUTOINCR check, so no problems * arise if a chip which does auto increment * is marked as NOAUTOINCR by the board driver. */ if (!this->dev_ready) udelay (this->chip_delay); else nand_wait_ready(mtd); if (read == len)//所需数据读完都情况,退出读循环。 break;
/* For subsequent reads align to page boundary. */ col = 0;//对于读位置不是页对齐都情况,前面已对其进行林相应都处理,现在读位置变得页对齐了。 /* Increment page address */ realpage++;//页地址加1,读取下一页。
page = realpage & this->pagemask; /* Check, if we cross a chip boundary */ if (!page) { chipnr++; this->select_chip(mtd, -1); this->select_chip(mtd, chipnr); } /* Check, if the chip supports auto page increment * or if we have hit a block boundary. * 如果芯片支持页自增操作,且未到block boundary(15)的话,不用再发送读命令 */ if (!NAND_CANAUTOINCR(this) || !(page & blockcheck)) sndcmd = 1; }
/* Deselect and wake up anyone waiting on the device */ if (flags & NAND_GET_DEVICE) nand_release_device(mtd);//放弃对设备都控制权,好让其它进程获取并占有它
/* * Return success, if no ECC failures, else -EBADMSG * fs driver will take care of that, because * retlen == desired len and result == -EBADMSG */ *retlen = read; return ecc_failed ? -EBADMSG : 0; }
下面研究一下其中的细节: /** * nand_write - [MTD Interface] compability function for nand_write_ecc * @mtd: MTD device structure * @to: offset to write to * @len: number of bytes to write * @retlen: pointer to variable to store the number of written bytes * @buf: the data to write * * This function simply calls nand_write_ecc with oob buffer and oobsel = NULL * */ static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf) { return (nand_write_ecc (mtd, to, len, retlen, buf, NULL, NULL)); } 注: 以参数eccbuf、oobsel为NULL,调用nand_write_ecc函数。
/** * nand_write_ecc - [MTD Interface] NAND write with ECC * @mtd: MTD device structure * @to: offset to write to * @len: number of bytes to write * @retlen: pointer to variable to store the number of written bytes * @buf: the data to write * @eccbuf: filesystem supplied oob data buffer * @oobsel: oob selection structure * * NAND write with ECC */ static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel) { int startpage, page, ret = -EIO, oob = 0, written = 0, chipnr; int autoplace = 0, numpages, totalpages; struct nand_chip *this = mtd->priv; u_char *oobbuf, *bufstart; int ppblock = (1 << (this->phys_erase_shift - this->page_shift));//page/block
DEBUG (MTD_DEBUG_LEVEL3, "nand_write_ecc: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
/* Initialize retlen, in case of early exit */ *retlen = 0;
/* Do not allow write past end of device */ /* 超越nand flash容量的写操作是不允许的 */ if ((to + len) > mtd->size) { DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: Attempt to write past end of page\n"); return -EINVAL; }
/* reject writes, which are not page aligned */ /* 不按页对齐的写操作同样是不允许的 */ if (NOTALIGNED (to) || NOTALIGNED(len)) { printk (KERN_NOTICE "nand_write_ecc: Attempt to write not page aligned data\n"); return -EINVAL; }
/* Grab the lock and see if the device is available */ /* 获取设备的控制权 */ nand_get_device (this, mtd, FL_WRITING);
/* Check, if it is write protected */ /* 如果nand flash写保护,当然不能再写了 */ if (nand_check_wp(mtd)) goto out;
/* if oobsel is NULL, use chip defaults */ if (oobsel == NULL) oobsel = &mtd->oobinfo; /* Autoplace of oob data ? Use the default placement scheme */ if (oobsel->useecc == MTD_NANDECC_AUTOPLACE) { oobsel = this->autooob; autoplace = 1; } if (oobsel->useecc == MTD_NANDECC_AUTOPL_USR) autoplace = 1;
/* Setup variables and oob buffer */ totalpages = len >> this->page_shift;//计算所要读取的数据长度共有多少页 page = (int) (to >> this->page_shift);//计算数据所要写到的开始页码 /* Invalidate the page cache, if we write to the cached page */ /* 如果缓存保存的数据在我们要写数据的范围内,把缓存里的数据设置为不可用???? */ if (page <= this->pagebuf && this->pagebuf < (page + totalpages)) this->pagebuf = -1; /* Set it relative to chip */ page &= this->pagemask; startpage = page; /* Calc number of pages we can write in one go */ numpages = min (ppblock - (startpage & (ppblock - 1)), totalpages);//计算出本block中允许被写的页数 oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel, autoplace, numpages);//先不深入研究~_~ bufstart = (u_char *)buf;//获取所要写数据的地址
/* Loop until all data is written */ /* 循环进行写操作 */ while (written < len) {
this->data_poi = (u_char*) &buf[written];//先把所要写的数据缓冲到data_poi下 /* Write one page. If this is the last page to write * or the last page in this block, then use the * real pageprogram command, else select cached programming * if supported by the chip. * 如果这是所写数据的最后一个页或许这是所写block的最后一个页,调用nand flash的 * pageprogram指令,真正把数据写入nand flash中(nand flash的最小擦除单元为block) */ ret = nand_write_page (mtd, this, page, &oobbuf[oob], oobsel, (--numpages > 0)); if (ret) { DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: write_page failed %d\n", ret); goto out; } /* Next oob page */ oob += mtd->oobsize; /* Update written bytes count */ /* 更新写入计数值 */ written += mtd->oobblock; if (written == len)//写入完毕,退出 goto cmp; /* Increment page address */ page++;//下一页
/* Have we hit a block boundary ? Then we have to verify and * if verify is ok, we have to setup the oob buffer for * the next pages. * 暂时不是很明白,需要先搞明白nand_prepare_oobbuf函数的作用 */ if (!(page & (ppblock - 1))){ int ofs; this->data_poi = bufstart;//怀疑nand_verify_pages用到 ret = nand_verify_pages (mtd, this, startpage, page - startpage, oobbuf, oobsel, chipnr, (eccbuf != NULL));//一页写完,检查数据 if (ret) { DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d\n", ret); goto out; } *retlen = written;
out: /* Deselect and wake up anyone waiting on the device */ nand_release_device(mtd);//放弃对设备的控制权
return ret; }
/** * nand_write_page - [GENERIC] write one page * @mtd: MTD device structure * @this: NAND chip structure * @page: startpage inside the chip, must be called with (page & this->pagemask) * @oob_buf: out of band data buffer * @oobsel: out of band selecttion structre * @cached: 1 = enable cached programming if supported by chip * * Nand_page_program function is used for write and writev ! * This function will always program a full page of data * If you call it with a non page aligned buffer, you're lost :) * * Cached programming is not supported yet. */ static int nand_write_page (struct mtd_info *mtd, struct nand_chip *this, int page, u_char *oob_buf, struct nand_oobinfo *oobsel, int cached) { int i, status; u_char ecc_code[32]; int eccmode = oobsel->useecc ? this->eccmode : NAND_ECC_NONE; int *oob_config = oobsel->eccpos; int datidx = 0, eccidx = 0, eccsteps = this->eccsteps; int eccbytes = 0;
/* FIXME: Enable cached programming */ cached = 0;//在高版本的内核下找到这样的解释: /* * Cached progamming disabled for now, Not sure if its worth the * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s) */
/* Send command to begin auto page programming */ /* 发送页编程指令 */ this->cmdfunc (mtd, NAND_CMD_SEQIN, 0x00, page);
/* Write out complete page of data, take care of eccmode */ switch (eccmode) { /* No ecc, write all */ case NAND_ECC_NONE: printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not recommended\n"); this->write_buf(mtd, this->data_poi, mtd->oobblock); break;
/* Software ecc 3/256, write all */ case NAND_ECC_SOFT: for (; eccsteps; eccsteps--) { this->calculate_ecc(mtd, &this->data_poi[datidx], ecc_code);//计算出一页的ecc数据 for (i = 0; i < 3; i++, eccidx++) oob_buf[oob_config[eccidx]] = ecc_code[i];//存放到ecc_code数组中 datidx += this->eccsize; } this->write_buf(mtd, this->data_poi, mtd->oobblock);//调用FLASH硬件驱动层进行写操作 break; default: eccbytes = this->eccbytes; for (; eccsteps; eccsteps--) { /* enable hardware ecc logic for write */ this->enable_hwecc(mtd, NAND_ECC_WRITE); this->write_buf(mtd, &this->data_poi[datidx], this->eccsize); this->calculate_ecc(mtd, &this->data_poi[datidx], ecc_code); for (i = 0; i < eccbytes; i++, eccidx++) oob_buf[oob_config[eccidx]] = ecc_code[i]; /* If the hardware ecc provides syndromes then * the ecc code must be written immidiately after * the data bytes (words) */ if (this->options & NAND_HWECC_SYNDROME) this->write_buf(mtd, ecc_code, eccbytes); datidx += this->eccsize; } break; }
/* Write out OOB data */ if (this->options & NAND_HWECC_SYNDROME) this->write_buf(mtd, &oob_buf[oobsel->eccbytes], mtd->oobsize - oobsel->eccbytes); else this->write_buf(mtd, oob_buf, mtd->oobsize);//写oob data,主要把上面计算的ecc值写进去
/* Send command to actually program the data */ this->cmdfunc (mtd, cached ? NAND_CMD_CACHEDPROG : NAND_CMD_PAGEPROG, -1, -1);
if (!cached) { /* call wait ready function */ status = this->waitfunc (mtd, this, FL_WRITING);//等待写入完成
/* See if operation failed and additional status checks are available */ if ((status & NAND_STATUS_FAIL) && (this->errstat)) { status = this->errstat(mtd, this, FL_WRITING, status, page); }
/* See if device thinks it succeeded */ if (status & NAND_STATUS_FAIL) { DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write, page 0x%08x, ", __FUNCTION__, page); return -EIO; } } else { /* FIXME: Implement cached programming ! */ /* wait until cache is ready*/ // status = this->waitfunc (mtd, this, FL_CACHEDRPG);//cached的写操作暂时没用 } return 0; }