linux块驱动程序分析 之 nand flash 驱动编写过程分析

内核版本号:linux-2.6.22
块设备程序: driver/mtd/mtdblock.c

1 从驱动的注册函数开始分析
  a.先定义并初始化一个全局变量 mtdblock_tr
  static struct mtd_blktrans_ops mtdblock_tr = {
   .name  = "mtdblock",
   .major  = 31,
   .part_bits = 0,
   .blksize  = 512,
   .open  = mtdblock_open,
   .flush  = mtdblock_flush,
   .release = mtdblock_release,
   .readsect = mtdblock_readsect,
   .writesect = mtdblock_writesect,
   .add_mtd = mtdblock_add_mtd,
   .remove_dev = mtdblock_remove_dev,
   .owner  = THIS_MODULE,
  };

  驱动程序的init入口函数init_mtdblock中调用register_mtd_blktrans函数
  register_mtd_blktrans(&mtdblock_tr)
 
  b.在driver/mtd/mtd_blkdevs.c中定义了 register_mtd_blktrans函数和如下的结构体变量
   static struct mtd_notifier blktrans_notifier = {
   .add = blktrans_notify_add,
   .remove = blktrans_notify_remove,
   };

  
 register_mtd_blktrans
    >register_mtd_user(&blktrans_notifier)
    + >blktrans_notifier->add() =也就是调用了=>blktrans_notify_add()
    +   >list_for_each(this, &blktrans_majors) //遍历某一主设备号下所用的磁盘分区
    +     struct mtd_blktrans_ops *tr->add_mtd(tr, mtd); =也就是调用了=>mtdblock_add_mtd()
    +      >add_mtd_blktrans_dev
    +      >list_add_tail(&new->list, &tr->devs);//把设备或设备分区加入链表
    +      >gd = alloc_disk(1 << tr->part_bits);//分配一个gendisk 磁盘结构体
    +      >set_capacity(gd, (new->size * tr->blksize) >> 9);//设置磁盘的容量
    +      >gd->queue = tr->blkcore_priv->rq;//该队列是由 blk_init_queue 函数生成的队列
    +      >add_disk(gd);//添加磁盘
    >register_blkdev(tr->major, tr->name);//注册块设备
    >tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock)//初始化队列
    >tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,"%sd", tr->name);//生成一个内核线程
    >list_add(&tr->list, &blktrans_majors);//把设备分区加入链表
  
  c.当磁盘有数据读写请求时,会调用blk_init_queue函数中指定义的处理函数: mtd_blktrans_request
  mtd_blktrans_request
    >wake_up_process(tr->blkcore_priv->thread)//唤醒内核线程
    ||    =也就是调用=> 上面kthread_run函数的第一个参数指针函数 mtd_blktrans_thread
    >mtd_blktrans_thread
       >do_blktrans_request(tr, dev, req);//处理请求
       +   //根据req标识来选择 读/写 操作。
       + >mtd_blktrans_ops *tr->readsect(dev, block, buf)  //读操作
     ||  =也就是调用了=> mtdblock_readsect。可参看上面定义的结构体 struct mtd_blktrans_ops mtdblock_tr
     >mtdblock_readsect
       >do_cached_read
         >mtd->read(mtd, pos, size, &retlen, buf)//在这个地方调用了底层结构体mtd_info的 .read 函数        
       + >mtd_blktrans_ops *tr->writesect(dev, block, buf) //写操作
         ||
         >mtdblock_writesect
           >do_cached_write
             >erase_write(mtd, pos, size, buf)
               >mtd->erase(mtd, &erase)  //调用mtd_info的 .erase与.write 函数
               >mtd->write(mtd, pos, len, &retlen, buf);
       >end_request(req, res);//完成处理
 
  d.由c项可知,最后驱动程序会调用mtd_info结构体的 .erase .write .read 函数,那么这些函数又是在哪里初始化的呢?
  对于nand flash,可参看内核驱动driver/mtd/nand/nand_base.c
   EXPORT_SYMBOL_GPL(nand_scan);
   EXPORT_SYMBOL_GPL(nand_scan_ident);
   EXPORT_SYMBOL_GPL(nand_scan_tail);
   EXPORT_SYMBOL_GPL(nand_release);
   导出了四个函数,下面分析这几个函数。
 nand_scan(struct mtd_info *mtd, int maxchips) //扫描 nand
   >nand_scan_ident(mtd, maxchips);
     struct nand_chip *chip = mtd->priv;
     >nand_set_defaults(chip, busw);//设备默认的操作函数
   +  chip->chip_delay    = 20;
   +  chip->cmdfunc       = nand_command;
   +  chip->waitfunc      = nand_wait;
   +  chip->select_chip   = nand_select_chip;
   +  chip->read_byte     = nand_read_byte;
   +  chip->read_word     = nand_read_word;
   +  chip->block_bad     = nand_block_bad;
   +  chip->block_markbad = nand_default_block_markbad;
   +  chip->write_buf     =  nand_write_buf;
   +  chip->read_buf      = nand_read_buf;
   +  chip->verify_buf    =  nand_verify_buf;
   +  chip->scan_bbt      = nand_default_bbt;
   +  chip->controller    = &chip->hwcontrol;
     >type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id);//读设备ID,并比较
   >nand_scan_tail(mtd);
      struct nand_chip *chip = mtd->priv;
      >chip->write_page = nand_write_page
      >chip->ecc.read_page_raw = nand_read_page_raw;
      >chip->ecc.write_page_raw = nand_write_page_raw;
     
     case NAND_ECC_SOFT:  //根据chip->ecc.mode 的值来设备软件ECC操作函数
    chip->ecc.calculate = nand_calculate_ecc;
    chip->ecc.correct = nand_correct_data;
    chip->ecc.read_page = nand_read_page_swecc;
    chip->ecc.write_page = nand_write_page_swecc;
    chip->ecc.read_oob = nand_read_oob_std;
    chip->ecc.write_oob = nand_write_oob_std;
    chip->ecc.size = 256;
    chip->ecc.bytes = 3;
    
    /* Fill in remaining MTD driver data */
   >接下来填充mtd_info结体体mtd变量对应的成员,在这里设备了上面分析过程中用到的三个重要的函数
    mtd->erase 与 mtd->read 与 mtd->write,这三个函数在c项分析中提到过。当发生磁盘的读写操作时
    会调用这三个函数,那么下来,再分析这三个函数是怎么读写nand flash的。
    mtd->type = MTD_NANDFLASH;
    mtd->flags = MTD_CAP_NANDFLASH;
    mtd->erase = nand_erase;
    mtd->point = NULL;
    mtd->unpoint = NULL;
    mtd->read = nand_read;
    mtd->write = nand_write;
    mtd->read_oob = nand_read_oob;
    mtd->write_oob = nand_write_oob;
    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->read 函数来分析
      mtd->read() ==> nand_read()
      >nand_get_device
      >nand_do_read_ops(mtd, from, &chip->ops);
      +  struct nand_chip *chip = mtd->priv;
      +  >chip->select_chip(mtd, chipnr);
      +  >chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
      +  ||
      +  nand_command
      +     >chip->cmd_ctrl(mtd, command, ctrl);//用来设置发命令还是发地址,需要用户来提供
      +  >ret = chip->ecc.read_page(mtd, chip, bufpoi);
     +   >chip->read_buf(mtd, buf, mtd->writesize);
     +   >chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
     +         >buf[i] = readb(chip->IO_ADDR_R);
      +  >nand_transfer_oob(chip,buf, ops, mtd->oobsize); //实现从nand读取到的数据拷贝
      +  >nand_wait_ready(mtd);
            >“死循环” 调用到等待函数chip->dev_ready(mtd);
             这其中不会让系统真的等待,会通过内核调度函数来调度其他线程的。
      >nand_release_device

   >再从mtd->write 函数来分析
      mtd->write() ==> nand_write()
      >nand_get_device
      >nand_do_write_ops(mtd, to, &chip->ops);
      +  struct nand_chip *chip = mtd->priv;
      +  >chip->select_chip(mtd, chipnr);
      +  >chip->write_page(mtd, chip, wbuf, page, cached,(ops->mode == MTD_OOB_RAW));
     + ||
     +  >nand_write_page
     +     >chip->ecc.write_page(mtd, chip, buf);
     +     +  >chip->ecc.write_page_raw(mtd, chip, buf);
     +     +      >chip->write_buf(mtd, buf, mtd->writesize);
        +     +      >chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
        +     +          >nand_write_buf
         +     +             >writeb(buf[i], chip->IO_ADDR_W);
     +     >chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1);
     +     ||
     +     nand_command
     +        >chip->cmd_ctrl(mtd, command, ctrl);//用来设置发命令还是发地址,需要用户来提供 
     +     >status = chip->waitfunc(mtd, chip);  
     +     ||
     +      nand_wait
     +          >chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
      >nand_release_device

  e.由前面的分析可知,nand flash 块设备的操作流程都是由mtd核心写好了,用户的驱动只需要提供相应的
    mtd_info结构体,nand_chip结构体,以及他相关的硬件操作函数就可以啦。
    这些完成后,调用分区相关的函数,所以还需要提供磁盘分区表数组。
    先会调用driver/mtd/mtdcore.c中的
    >add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)
      >进行一大堆相关的数据结构体设置
      >add_mtd_device(struct mtd_info *mtd)  
          >   //遍历mtd_notifiers链表的每一个成员,
          list_for_each(this, &mtd_notifiers) {
       struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
       not->add(mtd);
      }
         //调用mtd_notifier结构体的成员 .add 指针函数,这就回到了b项里的分析,这里再把这些分析
         //过程贴过来。
             + >blktrans_notifier->add() =也就是调用了=>blktrans_notify_add()
          +   >list_for_each(this, &blktrans_majors) //遍历某一主设备号下所用的磁盘分区
          +     struct mtd_blktrans_ops *tr->add_mtd(tr, mtd); =也就是调用了=>mtdblock_add_mtd()
          +      >add_mtd_blktrans_dev
          +      >list_add_tail(&new->list, &tr->devs);//把设备或设备分区加入链表
          +      >gd = alloc_disk(1 << tr->part_bits);//分配一个gendisk 磁盘结构体
          +      >set_capacity(gd, (new->size * tr->blksize) >> 9);//设置磁盘的容量
          +      >gd->queue = tr->blkcore_priv->rq;//该队列是由 blk_init_queue 函数生成的队列
          +      >add_disk(gd);//添加磁盘
   >至此,整个nand flash的驱动程序分析过程就结束了。
   小结:
   通过对nand flash驱动程序的分析,发现:用户根本就不需要编写很多代码来实现flash block驱动的程序,因为
   块核心:block_core已经完美实现了gendisk、queue、请求处理函数等操作,用户代码需要实现就是根据不同的CPU
   的nand 控制器、以及不同的nand flash实现flash的读、写、控制函数。也就是核心层驱动知道:发的流程,但它
   不知道具体的操作方法;而用户提供的驱动程序知道:具体的操作方法;所以两都结合就可以实现了nand flash的
   读写了。
   用户驱动编写过程:
   1 分配结构体nand_chip 、mtd_info
   2 定义 nand flash 的分区表数组
   3 映射 nand 控制器寄存器
   4 设置 nand 控制器寄存器
  5 设置 nand_chip 、 mtd_info结构体,实现相关的操作函数
  6 扫描、识别nand flash: nand_scan
  6 添加分区 :add_mtd_partitions

代码附上:

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/mtd/mtd.h>
#include<linux/mtd/nand.h>
#include<linux/mtd/partitions.h>
#include<linux/slab.h>
#include<asm/sizes.h>
#include<asm/arch/regs-nand.h>
#include<asm/arch/nand.h>
#include<linux/clk.h>
#include<asm/io.h>

struct nand_ctl_regs {
unsigned long nfconf;
unsigned long nfcont;
unsigned long nfcmmd;
unsigned long nfaddr;
unsigned long nfdata;
unsigned long nfmeccd0;
unsigned long nfmeccd1;
unsigned long nfseccd;
unsigned long nfstat;
unsigned long nfestat0;
unsigned long nfestat1;
unsigned long nfmecc0;
unsigned long nfmecc1;
unsigned long nfsecc;
unsigned long nfsblk;
unsigned long nfeblk;
};

static struct mtd_info *gq_nand_mtd;
static struct nand_chip *gq_nand_chip;
static struct nand_ctl_regs *nand_regs;
static struct mtd_partition nand_parts[] ={
 [0] = {
  .name = "u-boot",
  .offset = 0,
  .size = SZ_256K,
 }, 
 [1] = {  
        .name = "params",
  .offset = MTDPART_OFS_APPEND,
  .size = SZ_128K,
 },
 [2] = {
  .name = "kernel",
        .offset = MTDPART_OFS_APPEND,
  .size = SZ_2M,
 },
 [3] = {
  .name = "rootfs",
  .offset = MTDPART_OFS_APPEND,
  .size = MTDPART_SIZ_FULL,
 }
};

static int gq_dev_ready(struct mtd_info *mtd)
{
 return (nand_regs->nfstat & (1<<0) );
}
static void gq_select_chip(struct mtd_info *mtd, int chip)
{/* if chip==-1 取消片选*/
 if(-1==chip){
  nand_regs->nfcont |= (1<<1);
 }
 else{
  nand_regs->nfcont &= ~(1<<1);
 }
}
static void gq_cmd_ctrl(struct mtd_info *mtd, int dat,unsigned int ctrl)
{
 if(ctrl & NAND_CLE)/*发命令*/
 {
  nand_regs->nfcmmd = dat;
 }
 else /*发地址*/
 {
  nand_regs->nfaddr = dat;
 }
}
static int gq_nand_drv_init(void)
{
 struct clk *n_clk;
 /*打开nand控制器的时钟*/
 n_clk = clk_get(NULL,"nand");
 clk_enable(n_clk);
 
 nand_regs = ioremap(0x4E000000,sizeof(struct nand_ctl_regs));
 if(nand_regs == NULL){
  printk("nand_chip struct alloc failed\n");
  return -1;
 }
 
 /* nfconf[13-12]:发出CLE/ALE后 (Hclk*TACLS) 时间后能发出nWE,手册上说能同时发出,故可设为0
     * nfconf[10- 8]:nWE的脉冲宽度 (Hclk*(TWRPH0+1)),手册上说必须 >=12ns,故可设为1
     * nfconf[ 6- 4]:nWE变成高电平后,(Hclk*(TWRPH1+1))时间后,CLE/ALE才能变成低电平,由手册可知
     *                  必须>5ns,故可设为0
  */
 #define TACLS  0
 #define TWRPH0 1
 #define TWRPH1 0
 nand_regs->nfconf = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
 /* nfcont[0]:1->enable nand flash controller BUT:0->disable nand flash controller
  * nfcont[1]:1->disable chip select. BUT:0->enable chip select.
  */
 nand_regs->nfcont = (1<<1)|(1<<0);/*禁止片选,使能nand flash controller*/

 gq_nand_chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
 if(gq_nand_chip == NULL){
  printk("nand_chip struct alloc failed\n");
  return -1;
 } 
 gq_nand_chip->IO_ADDR_R   = &nand_regs->nfdata;
 gq_nand_chip->IO_ADDR_W   = &nand_regs->nfdata;
 gq_nand_chip->select_chip = gq_select_chip;
 gq_nand_chip->cmd_ctrl    = gq_cmd_ctrl;
 gq_nand_chip->dev_ready   = gq_dev_ready;
 gq_nand_chip->ecc.mode    = NAND_ECC_SOFT;

 gq_nand_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
 if(gq_nand_mtd == NULL){
  printk("mtd_info struct alloc failed\n");
  return -1;
 }
 gq_nand_mtd->priv   = gq_nand_chip;
 gq_nand_mtd->owner  = THIS_MODULE;

 if(!nand_scan(gq_nand_mtd,1))
  add_mtd_partitions(gq_nand_mtd, nand_parts, 4);
 return 0;
}

static void gq_nand_drv_exit(void)
{
 del_mtd_partitions(gq_nand_mtd);
 kfree(gq_nand_mtd);
 kfree(gq_nand_chip);
 iounmap(nand_regs);
}

module_init(gq_nand_drv_init);
module_exit(gq_nand_drv_exit);
MODULE_LICENSE("GPL");

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值