Linux MTD 源代码分析

Linux MTD 源代码分析

http://blogimg.chinaunix.net/blog/upfile/070511172139.pdf

MTD原始设备与FLASH硬件驱动的对话

MTD原始设备与FLASH硬件驱动的对话-续

mtd.h重要结构体:★struct erase_info  如果擦除失败,fail_addr将指示坏块地址。★struct mtd_info  mtd层函数指针存放处。

nand.hNand基本指令:#define NAND_CMD_READ0 0#define NAND_CMD_READ1 1#define NAND_CMD_PAGEPROG 0x10#define NAND_CMD_READOOB 0x50#define NAND_CMD_ERASE1 0x60#define NAND_CMD_STATUS 0x70#define NAND_CMD_STATUS_MULTI 0x71#define NAND_CMD_SEQIN 0x80#define NAND_CMD_READID 0x90#define NAND_CMD_ERASE2 0xd0#define NAND_CMD_RESET 0xff

和K9F1208指令对比

重要结构体:★struct nand_chip 具体操作Nand的函数指针都在这个结构体里面。★ struct nand_bbt_descr Nand坏块表?具体如何使用还不清楚。

nand_base.c◆int nand_scan (struct mtd_info *mtd, int maxchips){struct nand_chip *this = mtd->priv;  priv是mtd_info结构体里面的一个空指针,现在指向this。

if (this->cmdfunc == NULL)this->cmdfunc = nand_command;  判断驱动编写者是否提供了command函数,后来几个类似。

this->cmdfunc (mtd, NAND_CMD_READID, 0X00, -1);  读取Nand芯片信息,包括厂商信息的芯片ID,对于K9F1208是0xEC和0x76。  对应nand_ids.c中的{"NAND 64MiB 3,3V 8-bit", 0x76, 512, 64, 0x4000, 0}。  含义:三星的这颗Nand芯片是64MB的,3.3V供电,8bit位宽,ID为0x76,每一页大小为512Byte,64MB容量,擦除块尺寸为0x4000,操作0。对擦除块为0x4000的解释:这颗Nand芯片的容量是这样划分的,512Byte x 32 x 4096 = 64MB,一共有4096个块(block),因此每一个块的大小为512Byte x 32 = 16384Byte = 0x4000Byte。  这些信息接下来都会被MTD层获得,如果全部没有问题,则在启动时会打印:printk (KERN_INFO "NAND device: Manufacturer ID:" " 0x%02x, Chip ID: 0x%02x (%s %s)/n", nand_maf_id, nand_dev_id, nand_manuf_ids[maf_id].name , mtd->name);

/* Calculate the address shift from the page size */this->page_shift = ffs(mtd->oobblock) – 1;this->bbt_erase_shift = this->phys_erase_shift = ffs(mtd->erasesize) – 1;this->chip_shift = ffs(this->chipsize) – 1;▼这一段不太明白,翻译过来是根据页面大小计算地址变化?  我在启动时将其打印了出来:mtd->oobblock is 0x200mtd->oobsize is 0x10mtd->erasesize is 0x4000this->page_shift is 0x9this->bbt_erase_shift is 0xethis->chip_shift is 0x1a  ffs函数第一次见到,看看是什么东西:#define ffs(x) generic_ffs(x)  继续,蛮有意思的函数:static inline int generic_ffs(int x){int r = 1;

if (!x) return 0;if (!(x & 0xffff)) { x >>= 16; r += 16;}if (!(x & 0xff)) { x >>= 8; r += 8;}if (!(x & 0xf)) { x >>= 4; r += 4;}if (!(x & 3)) { x >>= 2; r += 2;}if (!(x & 1)) { x >>= 1; r += 1;}return r;}这函数人如其名,找到第一个bit位(find first bit set),比如0x80,将返回7。/* Set the bad block position */ this->badblockpos = mtd->oobblock > 512 ? NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;  确定坏块标记的位置,如果大于512,在oob区的位置0,否则是在oob区的位置5。

/* Do not replace user supplied command function ! */ if (mtd->oobblock > 512 && this->cmdfunc == nand_command) this->cmdfunc = nand_command_lp;这一段没有什么意义,因为我们的底层驱动里面提供了命令函数。

if (!nand_flash_ids[i].name) { printk (KERN_WARNING "No NAND device found!!!/n"); this->select_chip(mtd, -1); return 1;}如果没有发现芯片,会提示找不到芯片,我刚开始做u-boot驱动时,读不到正确的芯片ID,就报这个错误,并且直接返回1,下面的程序不再执行。

for (i=1; i < maxchips; i++) { this->select_chip(mtd, i);/* Send the command for reading device ID */ this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1); /* Read manufacturer and device IDs */ if (nand_maf_id != this->read_byte(mtd) || nand_dev_id != this->read_byte(mtd)) break;}  如果有多块芯片,这里会去读它们的ID信息。

/* Allocate buffers, if neccecary */if (!this->oob_buf) { size_t len; len = mtd->oobsize << (this->phys_erase_shift – this->page_shift); this->oob_buf = kmalloc (len, GFP_KERNEL); if (!this->oob_buf) { printk (KERN_ERR "nand_scan(): Cannot allocate oob_buf/n"); return -ENOMEM; } this->options |= NAND_OOBBUF_ALLOC;

}  if (!this->data_buf) { size_t len; len = mtd->oobblock + mtd->oobsize; this->data_buf = kmalloc (len, GFP_KERNEL); if (!this->data_buf) { if (this->options & NAND_OOBBUF_ALLOC) kfree (this->oob_buf); printk (KERN_ERR "nand_scan(): Cannot allocate data_buf/n"); return -ENOMEM; } this->options |= NAND_DATABUF_ALLOC;}  如果前面没有分配,在这儿分配数据区和oob区的空间。说说这个size_t,是为了方便移植而的设定的,其实就是unsignedint。oob区的大小是mtd->oobsize << (this->phys_erase_shift -this->page_shift),数据区的大小是mtd->oobblock +mtd->oobsize。这儿在计算oob区该分配多大时用到了前面定义的this->page_shift和this->phys_erase_shift。  具体计算方法?  这时候用得上前面print出来的内容:mtd->oobblock is 0x200mtd->oobsize is 0x10mtd->erasesize is 0x4000this->page_shift is 0x9this->bbt_erase_shift is 0xethis->chip_shift is 0x1a

len = mtd->oobsize << (this->phys_erase_shift – this->page_shift);这句话应该是计算oob_buf的长度,计算结果应该是(16 << 5)=512,奇怪了,oob区的大小应该是16才对,为何要左移5位变成512呢?  暂且放下,现在还没看到oob_buf的用途,继续看下面的内容。  /* Store the number of chips and calc total size for mtd */this->numchips = i;mtd->size = i * this->chipsize;/* Convert chipsize to number of pages per chip -1. */this->pagemask = (this->chipsize >> this->page_shift) – 1;/* Preset the internal oob buffer */memset(this->oob_buf, 0xff, mtd->oobsize << (this->phys_erase_shift – this->page_shift));  存储芯片的数目并计算mtd的总大小。  将芯片大小换算成页数,这时我才看懂this->page_shift的意思,就是9bit,因为便于移位操作,所以才用ffs函数将512变换为9的。  最后将oob_buf全部填充了0xff。  /* If no default placement scheme is given, select an* appropriate one */if (!this->autooob) { /* Select the appropriate default oob placement scheme for * placement agnostic filesystems */ switch (mtd->oobsize) { case 8: this->autooob = &nand_oob_8; break; case 16: this->autooob = &nand_oob_16; break; case 64: this->autooob = &nand_oob_64; break; default: printk (KERN_WARNING "No oob scheme defined for oobsize %d/n", mtd->oobsize); BUG(); }}根据oobsize填充autooob,我们的oobsize是16,填充的是nand_oob_16这个结构体的内容:static struct nand_oobinfo nand_oob_16 = {.useecc = MTD_NANDECC_AUTOPLACE,.eccbytes = 6,.eccpos = {0, 1, 2, 3, 6, 7},.oobfree = { {8, 8} }};结构体中规定了ecc校验位的位置。/* The number of bytes available for the filesystem to place fs dependend* oob data */mtd->oobavail = 0;for (i = 0; this->autooob->oobfree[i][1]; i++) mtd->oobavail += this->autooob->oobfree[i][1];文件系统的oob数据放在oob的free区里面。

/* * check ECC mode, default to software* if 3byte/512byte hardware ECC is selected and we have 256 byte pagesize* fallback to software ECC*/this->eccsize = 256; /* set default eccsize */this->eccbytes = 3;switch (this->eccmode) {case NAND_ECC_HW12_2048: if (mtd->oobblock < 2048) { printk(KERN_WARNING "2048 byte HW ECC not possible on %d byte page size, fallback to SW ECC/n", mtd->oobblock); this->eccmode = NAND_ECC_SOFT; this->calculate_ecc = nand_calculate_ecc; this->correct_data = nand_correct_data; } else this->eccsize = 2048; break;case NAND_ECC_HW3_512:case NAND_ECC_HW6_512:case NAND_ECC_HW8_512: if (mtd->oobblock == 256) { printk (KERN_WARNING "512 byte HW ECC not possible on 256 Byte pagesize, fallback to SW ECC /n"); this->eccmode = NAND_ECC_SOFT; this->calculate_ecc = nand_calculate_ecc; this->correct_data = nand_correct_data; } else this->eccsize = 512; /* set eccsize to 512 */ break;case NAND_ECC_HW3_256: break;case NAND_ECC_NONE: printk (KERN_WARNING "NAND_ECC_NONE selected by board driver. This is not recommended !!/n"); this->eccmode = NAND_ECC_NONE; break;case NAND_ECC_SOFT: this->calculate_ecc = nand_calculate_ecc; this->correct_data = nand_correct_data; break;default: printk (KERN_WARNING "Invalid NAND_ECC_MODE %d/n", this->eccmode); BUG();}默认的eccsize为256,eccbytes为3。开始判断驱动中提供的eccmode,我们以前用的是NAND_ECC_SOFT,现在为了使用yaffs,改用NAND_ECC_NONE,其他硬件的都不用看。如果是NONE的话,直接printk一个warning,如果是SOFT的,需要填充:this->calculate_ecc = nand_calculate_ecc;this->correct_data = nand_correct_data;这是两个函数哦,不是变量,mark下后面要跟。

/* Check hardware ecc function availability and adjust number of ecc bytes per* calculation step*/switch (this->eccmode) {case NAND_ECC_HW12_2048: this->eccbytes += 4;case NAND_ECC_HW8_512: this->eccbytes += 2;case NAND_ECC_HW6_512: this->eccbytes += 3;case NAND_ECC_HW3_512:case NAND_ECC_HW3_256: if (this->calculate_ecc && this->correct_data && this->enable_hwecc) break; printk (KERN_WARNING "No ECC functions supplied, Hardware ECC not possible/n"); BUG();}

mtd->eccsize = this->eccsize;没用到硬件ecc,这儿应该直接跳过了。

/* Set the number of read / write steps for one page to ensure ECC generation */switch (this->eccmode) {case NAND_ECC_HW12_2048: this->eccsteps = mtd->oobblock / 2048; break;case NAND_ECC_HW3_512:case NAND_ECC_HW6_512:case NAND_ECC_HW8_512: this->eccsteps = mtd->oobblock / 512; break;case NAND_ECC_HW3_256:case NAND_ECC_SOFT: this->eccsteps = mtd->oobblock / 256; break;

case NAND_ECC_NONE: this->eccsteps = 1; break;}设置每一页的ecc校验的steps。NAND_ECC_NONE是1,NAND_ECC_SOFT是2。

/* Initialize state, waitqueue and spinlock */this->state = FL_READY;init_waitqueue_head (&this->wq);spin_lock_init (&this->chip_lock);初始化状态机、等待列队和自旋锁。

/* Fill in remaining MTD driver data */mtd->type = MTD_NANDFLASH;mtd->flags = MTD_CAP_NANDFLASH | MTD_ECC;mtd->ecctype = MTD_ECC_SW;mtd->erase = nand_erase;mtd->point = NULL;mtd->unpoint = NULL;mtd->read = nand_read;mtd->write = nand_write;mtd->read_ecc = nand_read_ecc;mtd->write_ecc = nand_write_ecc;mtd->read_oob = nand_read_oob;mtd->write_oob = nand_write_oob;mtd->readv = NULL;mtd->writev = nand_writev;mtd->writev_ecc = nand_writev_ecc;mtd->sync = nand_sync;mtd->lock = NULL;mtd->unlock = NULL;mtd->suspend = nand_suspend;mtd->resume = nand_resume;mtd->block_isbad = nand_block_isbad;mtd->block_markbad = nand_block_markbad;填充MTD结构体的其他成员及函数,我看完nand scan如果没有突破点,就应该一个一个看这里面的内容。

/* Check, if we should skip the bad block table scan */if (this->options & NAND_SKIP_BBTSCAN) return 0;这儿比较重要,我正想u-boot在开机能不能跳过scan坏块呢,只要定义了NAND_SKIP_BBTSCAN就可以跳过坏块了。但是这个Linux下的nand_base.c,刚又看了下u-boot里面的nand_base.c,发现没有这个判断,奇怪。

/* Build bad block table */return this->scan_bbt (mtd);虽然返回,但没有结束,跳去执行scan_bbt这个函数了,下一步目标:scan_bbt!

有的旅行是为了拓宽眼界,浏览风景名胜。

Linux MTD 源代码分析

相关文章:

你感兴趣的文章:

标签云: