您的位置:首页 > 运维架构 > Linux

linux下MTD驱动

2016-02-16 21:58 711 查看
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.

对磁盘操作最昂贵的代价总是确定读写数据开始的位置,一旦位置确定了之后,实际用于读取或者写入数据的时间几乎无关紧要的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: