MTD原始设备与FLASH硬件驱动的对话 (二) (转)
时间:10-02
整理:3721RD
点击:
SIMSun">上一个贴由下到上的介绍了FLASH硬件驱动是如何与MTD原始设备建立联系的,现在再由上到下的研究一下是如何通过MTD原始设备来访问FLASH硬件驱动的。
首先分析一下如何通过MTD原始设备进而通过FLASH硬件驱动来读取FLASH存储器的数据。
引用自<<linux系统移植>>一文:
"读Nand Flash:
当对nand flash的设备文件(nand flash在/dev下对应的文件)执行系统调用read(),或在某个文件系统中对该
设备进行读操作时. 会调用struct mtd_info中的read方法,他们缺省调用函数为nand_read(),在
drivers/mtd/nand/nand_base.c中定义.nand_read()调用nand_do_read_ecc(),执行读操作. 在
nand_do_read_ecc()函数中,主要完成如下几项工作:
1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
s3c2410_nand_select_chip()选择要操作的MTD芯片.
2. 会调用在struct nand_chip中系统缺省的方法cmdfunc发送读命令到nand flash.
3. 会调用在nand flash驱动中对struct nand_chip重载的read_buf(),即s3c2410_nand_read_buf()
从Nand Flash的控制器的数据寄存器中读出数据.
4. 如果有必要的话,会调用在nand flash驱动中对struct nand_chip重载的
enable_hwecc,correct_data以及calculate_ecc方法,进行数据ECC校验。"
下面研究一下其中的细节:
/**
* 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数据中的位置
/* Select the NAND device */
chipnr = (int)(from >> this->chip_shift);
this->select_chip(mtd, chipnr);//选择nand flash芯片(在s3c2410 nand flash控制器中为空操作)
/* 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);
/* 计算出读取到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, 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, &ecc_code);
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);
}
}
break;
}
/* read oobdata */
this->read_buf(mtd, &oob_data[mtd->oobsize - oobreadlen], oobreadlen);
//读取oob_data存放到oob_data[mtd->oobsize - oobreadlen],在这里是data_buf[end]中
/* 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 = 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[1]; i++) {
int from = oobsel->oobfree[0];
int num = oobsel->oobfree[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;
}
好的,接着研究一下如何通过MTD原始设备进而通过FLASH硬件驱动向FLASH存储器写数据。
引用自<<Linux系统移植>>一文:
写Nand Flash
当对nand flash的设备文件(nand flash在/dev下对应的文件)执行系统调用write(),或在某个文件系统中对该设备
进行读操作时, 会调用struct mtd_info中write方法,他们缺省调用函数为nand_write(),这两个函数在
drivers/mtd/nand/nand_base.c中定义. nand_write()调用nand_write_ecc(),执行写操作.在
nand_do_write_ecc()函数中,主要完成如下几项工作:
1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
s3c2410_nand_select_chip()选择要操作的MTD芯片.
2. 调用nand_write_page()写一个页.
3. 在nand_write_page()中,会调用在struct nand_chip中系统缺省的方法cmdfunc发送写命令
到nand flash.
4. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载的
write_buf(),即s3c2410_nand_write_buf()从Nand Flash的控制器的数据寄存器中写入数据.
5. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载waitfunc方法,
该方法调用系统缺省函数nand_wait(),该方法获取操作状态,并等待nand flash操作完成.等
待操作完成,是调用nand flash驱动中对struct nand_chip中重载的dev_ready方法,即
s3c2410_nand_devready()函数.
下面研究一下其中的细节:
/**
* 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);
/* Calculate chipnr */
/*
* 存在多片flash的情况下,计算出所要写的是哪片flash?
* (当然,像我的板,只用一片nand flash,所以这个操作是不必要的)
*/
chipnr = (int)(to >> this->chip_shift);
/* Select the NAND device */
/* 片选操作 */
this->select_chip(mtd, chipnr);
/* 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;
ofs = autoplace ? mtd->oobavail : mtd->oobsize;
if (eccbuf)
eccbuf += (page - startpage) * ofs;
totalpages -= page - startpage;//更新需要写的页数
numpages = min (totalpages, ppblock);//更新可以写的页数
page &= this->pagemask;//更新页码
startpage = page;//更新开始页码
oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel,
autoplace, numpages);
/* Check, if we cross a chip boundary */
if (!page) {
chipnr++;
this->select_chip(mtd, -1);
this->select_chip(mtd, chipnr);
}
}
}
/* Verify the remaining pages */
cmp:
this->data_poi = bufstart;//怀疑nand_verify_pages用到
ret = nand_verify_pages (mtd, this, startpage, totalpages,
oobbuf, oobsel, chipnr, (eccbuf != NULL));
if (!ret)
*retlen = written;
else
DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d/n", ret);
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;//存放到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;
/* 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;
}
首先分析一下如何通过MTD原始设备进而通过FLASH硬件驱动来读取FLASH存储器的数据。
引用自<<linux系统移植>>一文:
"读Nand Flash:
当对nand flash的设备文件(nand flash在/dev下对应的文件)执行系统调用read(),或在某个文件系统中对该
设备进行读操作时. 会调用struct mtd_info中的read方法,他们缺省调用函数为nand_read(),在
drivers/mtd/nand/nand_base.c中定义.nand_read()调用nand_do_read_ecc(),执行读操作. 在
nand_do_read_ecc()函数中,主要完成如下几项工作:
1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
s3c2410_nand_select_chip()选择要操作的MTD芯片.
2. 会调用在struct nand_chip中系统缺省的方法cmdfunc发送读命令到nand flash.
3. 会调用在nand flash驱动中对struct nand_chip重载的read_buf(),即s3c2410_nand_read_buf()
从Nand Flash的控制器的数据寄存器中读出数据.
4. 如果有必要的话,会调用在nand flash驱动中对struct nand_chip重载的
enable_hwecc,correct_data以及calculate_ecc方法,进行数据ECC校验。"
下面研究一下其中的细节:
/**
* 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数据中的位置
/* Select the NAND device */
chipnr = (int)(from >> this->chip_shift);
this->select_chip(mtd, chipnr);//选择nand flash芯片(在s3c2410 nand flash控制器中为空操作)
/* 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);
/* 计算出读取到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, 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, &ecc_code);
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);
}
}
break;
}
/* read oobdata */
this->read_buf(mtd, &oob_data[mtd->oobsize - oobreadlen], oobreadlen);
//读取oob_data存放到oob_data[mtd->oobsize - oobreadlen],在这里是data_buf[end]中
/* 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 = 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[1]; i++) {
int from = oobsel->oobfree[0];
int num = oobsel->oobfree[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;
}
好的,接着研究一下如何通过MTD原始设备进而通过FLASH硬件驱动向FLASH存储器写数据。
引用自<<Linux系统移植>>一文:
写Nand Flash
当对nand flash的设备文件(nand flash在/dev下对应的文件)执行系统调用write(),或在某个文件系统中对该设备
进行读操作时, 会调用struct mtd_info中write方法,他们缺省调用函数为nand_write(),这两个函数在
drivers/mtd/nand/nand_base.c中定义. nand_write()调用nand_write_ecc(),执行写操作.在
nand_do_write_ecc()函数中,主要完成如下几项工作:
1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
s3c2410_nand_select_chip()选择要操作的MTD芯片.
2. 调用nand_write_page()写一个页.
3. 在nand_write_page()中,会调用在struct nand_chip中系统缺省的方法cmdfunc发送写命令
到nand flash.
4. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载的
write_buf(),即s3c2410_nand_write_buf()从Nand Flash的控制器的数据寄存器中写入数据.
5. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载waitfunc方法,
该方法调用系统缺省函数nand_wait(),该方法获取操作状态,并等待nand flash操作完成.等
待操作完成,是调用nand flash驱动中对struct nand_chip中重载的dev_ready方法,即
s3c2410_nand_devready()函数.
下面研究一下其中的细节:
/**
* 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);
/* Calculate chipnr */
/*
* 存在多片flash的情况下,计算出所要写的是哪片flash?
* (当然,像我的板,只用一片nand flash,所以这个操作是不必要的)
*/
chipnr = (int)(to >> this->chip_shift);
/* Select the NAND device */
/* 片选操作 */
this->select_chip(mtd, chipnr);
/* 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;
ofs = autoplace ? mtd->oobavail : mtd->oobsize;
if (eccbuf)
eccbuf += (page - startpage) * ofs;
totalpages -= page - startpage;//更新需要写的页数
numpages = min (totalpages, ppblock);//更新可以写的页数
page &= this->pagemask;//更新页码
startpage = page;//更新开始页码
oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel,
autoplace, numpages);
/* Check, if we cross a chip boundary */
if (!page) {
chipnr++;
this->select_chip(mtd, -1);
this->select_chip(mtd, chipnr);
}
}
}
/* Verify the remaining pages */
cmp:
this->data_poi = bufstart;//怀疑nand_verify_pages用到
ret = nand_verify_pages (mtd, this, startpage, totalpages,
oobbuf, oobsel, chipnr, (eccbuf != NULL));
if (!ret)
*retlen = written;
else
DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d/n", ret);
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;//存放到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;
/* 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;
}