linux下MTD驱动

struct device中的platform_data是我们自己在编写驱动程序的时候自己定义的设备结构体的指针。  

因为之前不是很了解文件系统,所以要探究一下mtd来加深一下印象。

NAND FLASH每一页大小通常是512+16字节,16字节称为OOB区,通常在OOB区存放坏块标记,前面512字节的ECC校验码等。

FLASH的内部存储时MOSFET(金属半场效应管),里面有悬浮门,是真正存储数据的单元。

块是NandFlash的擦除操作的基本单元。

页是NandFlash的写入操作的基本单元。

在写数据之前,要先擦除,然后再写。

在加载完驱动之后,如果没有加入参数要求跳过坏块扫描的话,会主动扫描坏块,建立必要的BBT,以备后面坏块管理使用。

在一个块内,对每一页进行编程的话必须是顺序的,不能是随机的。

通常读取Nor速度比Nand稍快一些,而Nand写入速度快很多,NorFlash特点是芯片内执行(XIP execute in place),不需要初始化CPU取址模块就能直接从中把指令取出来。

NandFlash器件使用复杂的I/O来串行存取数据,8个引脚用来传送控制地址和数据信息。


MTD:memory technology device内存技术设备

MTD将文件系统与底层FLASH存储器进行了隔离

设备节点->MTD设备层->MTD原始设备层->硬件驱动层

MTD块设备(主设备号31) 字符设备(主设备号90)

  spi_write_then_read();函数中,先分配spi_transfer 两个内存空间,一个用于写,一个用于读,然后调用spi_message_add_tail(),将spi_transfer的地址分别放入spi_message中,之后填充spi_transfer中的tx_buf和rx_buf,最后调用spi_sync();做i/o操作。该函数是__spi_async()的封装,里面最后还是调用了spi_master->transfer()之前spi驱动注册的回调函数。

如果是spi flash,对flash的操作就是对spi的读写操作。

在xxx_flash_probe()中主要还是对mtd_info结构体(MTD原始设备)的参数赋值,和回调函数的注册。

         flash->mtd.type = MTD_NORFLASH;
         flash->mtd.writesize = 1;
         flash->mtd.flags = MTD_CAP_NORFLASH;
         flash->mtd.size = info->sector_size * info->n_sectors;
         flash->mtd.erase = m25p80_erase;
         flash->mtd.read = m25p80_read;
 
         /* sst flash chips use AAI word program */
         if (JEDEC_MFR(info->jedec_id) == CFI_MFR_SST)
                 flash->mtd.write = sst_write;
         else
                 flash->mtd.write = m25p80_write;       
        flash->mtd.dev.parent = &spi->dev;       
        flash->sector_size = info->sector_size;
        flash->page_size = info->page_size;

在每次发送命令之前都需要进行写使能,然后发送命令的内容,最后再加上地址。

struct spi_transfer t[2];
struct spi_message m;        
t.tx_buf = flash->command;
t.len = m25p_cmdsz(flash) + FAST_READ_DUMMY_BYTE;
spi_message_add_tail(&t, &m);      
t.rx_buf = buf;t.len = len;
spi_message_add_tail(&t, &m);
flash->command = OPCODE_READ;
flash->command = WR_CMD_TYPE;
spi_sync(flash->spi, &m);

最后调用mtd_device_register().如果没有分区的话就调用add_mtd_partition(),否则就调用add_mtd_partitions().

这里有一个概念的区别:struct mtd_part和struct mtd_partition.mtd_partition就是每个子分区的分区信息,mtd_part是指一个分区的实例。真正主分区的信息还是由mtd_info获得.

分配一个分区是函数allocate_partition().

  struct mtd_partition {
          char *name;                     /* identifier string */
          uint64_t size;                  /* partition size */
          uint64_t offset;                /* offset within the master MTD space */
          uint32_t mask_flags;            /* master MTD flags to mask out for this partition */
          struct nand_ecclayout *ecclayout;       /* out of band layout for this partition (NAND only) */
  }; 
  struct mtd_part {
          struct mtd_info mtd;
          struct mtd_info *master;
          uint64_t offset;
          struct list_head list;
  };

在该函数中主要是分配mtd_part内存空间和参数设置,包括一些回调函数的注册,但是这些回调函数最终还是通过指针调用一开始xxx_flash_probe注册的回调函数,当然还有一些sanity checks。

  /* set up the MTD object for this partition */
slave->mtd.type = master->type;
slave->mtd.flags = master->flags & ~part->mask_flags;
slave->mtd.size = part->size;
slave->mtd.writesize = master->writesize;
slave->mtd.writebufsize = master->writebufsize;
slave->mtd.oobsize = master->oobsize;
slave->mtd.oobavail = master->oobavail;
slave->mtd.subpage_sft = master->subpage_sft; 
slave->mtd.name = name;
slave->mtd.owner = master->owner;
slave->mtd.backing_dev_info = master->backing_dev_info;     
slave->mtd.dev.parent = master->dev.parent;
slave->mtd.read = part_read;
slave->mtd.write = part_write;

分配成功后将返回的std_part添加到mtd_partitions中去。

最后调用add_mtd_device(),注册一个mtd设备。add_mtd_device中会遍历的调用通知链mtd_notifiers中的所有注册函数。

在mtdchar.c中init_mtdchar();中相比一般字符设备注册地不同在于文件系统的注册register_filesystem,内核挂载kern_mount(),最终调用了vfs_kern_mount();

在该函数中,分配vfsmnt结构体,并且挂载文件系统mount_fs();它最终调用了file_system_type原本就已经注册号的mount回调函数。

 static struct file_system_type mtd_inodefs_type = {
        .name = "mtd_inodefs",
        .mount = mtd_inodefs_mount,
        .kill_sb = kill_anon_super,
 };
最终mtd_inodefs_mount();还是调用了mount_pseudo();//这是一个通用的挂载文件系统的函数(但是sockfs,pipefs,bdev是不能挂载的).

该函数主要是进行参数的赋值,关键的是三个数据结构struct super_block, struct inode,  struct dentry;

         s->s_flags = MS_NOUSER;
         s->s_maxbytes = MAX_LFS_FILESIZE;
         s->s_blocksize = PAGE_SIZE;
         s->s_blocksize_bits = PAGE_SHIFT;
         s->s_magic = magic;
         s->s_op = ops ? ops : &simple_super_operations;//以便之后new_inode()调用部分回调函数
         s->s_time_gran = 1; 
         root = new_inode(s);
         if (!root)
                 goto Enomem;
         /*
          * since this is the first inode, make it number 1. New inodes created
          * after this must take care not to collide with it (by passing
          * max_reserved of 1 to iunique).
          */
         root->i_ino = 1;
         root->i_mode = S_IFDIR | S_IRUSR | S_IWUSR;
         root->i_atime = root->i_mtime = root->i_ctime = CURRENT_TIME;
         dentry = d_alloc(NULL, &d_name);
         if (!dentry) {
                 iput(root);
                 goto Enomem;
         }
         dentry->d_sb = s;
         dentry->d_parent = dentry;
         d_instantiate(dentry, root);//关键是该函数,将inode信息填充到目录入口中。
         s->s_root = dentry;
         s->s_d_op = dops;
         s->s_flags |= MS_ACTIVE;
         return dget(s->s_root);
值得注意的是,几个file_operation还是要好好研究一下的

 static const struct file_operations mtd_fops = {
         .owner          = THIS_MODULE,
         .llseek         = mtd_lseek,
         .read           = mtd_read,
         .write          = mtd_write,
         .unlocked_ioctl = mtd_unlocked_ioctl,
 #ifdef CONFIG_COMPAT
         .compat_ioctl   = mtd_compat_ioctl,
 #endif
         .open           = mtd_open,
         .release        = mtd_close,
         .mmap           = mtd_mmap,
 #ifndef CONFIG_MMU
         .get_unmapped_area = mtd_get_unmapped_area,
 #endif
 };
在/mtd/mtdblock.c中初始化函数中调用了register_mtd_blktrans();最终还是调用了register_blkev();注册块设备。

 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,
 }; 

在在最后调用回调函数mtd_blktrans_ops->add_mtd();在该回调函数中调用了add_mtd_blktrans_dev();

调用alloc_disk();

block_device_operation:

 static const struct block_device_operations mtd_blktrans_ops = {
         .owner          = THIS_MODULE,
         .open           = blktrans_open,
         .release        = blktrans_release,
         .ioctl          = blktrans_ioctl,
         .getgeo         = blktrans_getgeo,
 }; 
当给磁盘分区,在分区上创建文件系统,运行文件系统检查程序,或装载一个分区时,块设备驱动程序会调用open函数。

内核认为每个磁盘都是由512字节大小的扇区所组成的线性数组。所有的I/O请求都将定位在硬件扇区的开始位置,并且每个请求的大小都将是扇区大小的整数倍

set_capacity();//设定扇区大小.

struct request代表了一个块设备的I/O执行请求.请求被表示为一系列段,每个段都对应内存中的一个缓冲区。如果多个请求都是对磁盘中相邻扇区进行操作,则内核将合并它们,但是内核不会合并在单独request结构中的读写操作。

从本质上讲,一个request结构是作为一个bio结构的链表实现的,bio结构是在底层对部分块设备I/O请求的描述。

bio结构的核心是bi_io_vec的数组

 struct bio_vec {
          struct page     *bv_page;
          unsigned int    bv_len;
          unsigned int    bv_offset;
 };
当快设置I/O请求被转换到bio结构后,它将被单独的物理内存页所销毁。驱动程序做的所有工作就是根据这个结构体数组,使用每页传输数据。

blk_init_queue();分配请求队列,传入请求参数mtd_blktrans_request,负责块设备的读写请求(本质还是唤醒之后创建的内核线程wake_up_process())。当请求队列生成的时候,request函数就与该队列绑定在一起。

在分配好请求队列后,要创建一个内核线程mtd_blktrans_thread();

在该线程中首先通过blk_fetch_request来获得请求队列中的请求(最终还是调用__elv_next_request()来获取队列中第一个未完成的请求).

接下来最核心的还是调用do_blktrans_request() 来处理mtd块设备的请求(通过switch来执行读或者写请求)。读写操作都是调用之前注册号的回调函数(最终调用的是driver/mtd/devices/xxx_flash.c中之前已经注册好的与硬件相关的读写函数),同时读写成功之后就调用rq_flush_dcache_pages().

内核使用gendisk结构来表示一个独立的磁盘设备。一旦调用了add_disk,磁盘设备将被集火,并随时会调用它提供的方法。因此在驱动程序完全被初始化并且能够响应对磁盘的请求钱,不要调用add_disk.

对磁盘操作最昂贵的代价总是确定读写数据开始的位置,一旦位置确定了之后,实际用于读取或者写入数据的时间几乎无关紧要的。

你可能感兴趣的:(linux驱动)