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!
有的旅行是为了拓宽眼界,浏览风景名胜。