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.h
Nand基本指令:
#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 0x200
mtd->oobsize is 0x10
mtd->erasesize is 0x4000
this->page_shift is 0x9
this->bbt_erase_shift is 0xe
this->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,是为了方便移植而的设定的,其实就是unsigned int。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 0x200
mtd->oobsize is 0x10
mtd->erasesize is 0x4000
this->page_shift is 0x9
this->bbt_erase_shift is 0xe
this->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!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值