在linux系统中,提供了MTD(内存技术设备)系统来建立Flash针对linux的统一,抽象接口,MTD将文件系统与底层的Flash存取器进行了隔离,使得Flash驱动工程师无需关心Flash作为字符设备和块设备与Linux内核接口(由MTD层完成) 在引入MTD后,Linux系统中Flash设备驱动及接口可分四层 (1)硬件驱动层,Flash硬件驱动层负责Flash硬件设备读,写,擦除 LInux MTD 设备的 nor Flash 芯片驱动位于 drivers/mtd/chips 子目录下, NAND Flash的驱动程序则 位于 drivers/mtd/nand 子目录下。 (2)MTD原始设备层:MTD原始设备层有两部分组成,一部分是MTD原始设备的通用代码,另一部分是特定Flash数据,如分区等 (3)MTD设备层:基于MTD原始设备层,Linux系统可以定义出MTD的块设备(主设备号31)和字符设备(主设备号90)MTD字符设备定义在mtdchar.c中实现,通过注册一系列file_operations的函数(lseek,open,clos,read,write等),可实现设备读写控制,MTD 块设备则是定义在一个描述MTD 块设备的结构 mtdblk_dev ,并声明了一个名为 mtdblks 的指针数组,这个数组 中的每个mtdblk_dev 和 mtd_table 中的每一个mtd_info 一一对应。 (4)块设备节点:通过mknod在/dev子目录下建立MTD字符设备节点和MTD块设备节点 LInux MTD系统接口 在引入MTD后,底层Flash驱动直接与MTD原始设备层交互,利用其提供的接口注册设备和分区 用于描述MTD原始设备的数据结构是mtd_info,这其中定义了大量关于MTD的数据和操作函数,mtd_info是表示MTD原始设备结构体,每个分区也被认为是一个mtd_info,这些mtd_info指针被存放在mtd_table的数组里,这个结构体定义如下: struct mtd_info { u_char type;//内存技术类型 uint32_t flags;//标志位 uint64_t size; // MTD设备大小Total size of the MTD unsigned int erasesize_shift; unsigned int writesize_shift; unsigned int erasesize_mask; unsigned int writesize_mask; // Kernel-only stuff starts here. const char *name; int index; struct nand_ecclayout *ecclayout; int numeraseregions; struct mtd_erase_region_info *eraseregions; int (*erase) (struct mtd_info *mtd,struct erase_info *instr); int (*point) (struct mtd_info *mtd,loff_t from,size_t len, size_t *retlen,void **virt,resource_size_t *phys); void (*unpoint) (struct mtd_info *mtd,size_t len); unsigned long (*get_unmapped_area) (struct mtd_info *mtd, unsigned long len, unsigned long offset, unsigned long flags); struct backing_dev_info *backing_dev_info; int (*read) (struct mtd_info *mtd,size_t *retlen,u_char *buf); int (*write) (struct mtd_info *mtd,loff_t to,const u_char *buf); int (*panic_write) (struct mtd_info *mtd,const u_char *buf); int (*read_oob) (struct mtd_info *mtd, struct mtd_oob_ops *ops); int (*write_oob) (struct mtd_info *mtd, struct mtd_oob_ops *ops); int (*get_fact_prot_info) (struct mtd_info *mtd,struct otp_info *buf,size_t len); int (*read_fact_prot_reg) (struct mtd_info *mtd,u_char *buf); int (*get_user_prot_info) (struct mtd_info *mtd,size_t len); int (*read_user_prot_reg) (struct mtd_info *mtd,u_char *buf); int (*write_user_prot_reg) (struct mtd_info *mtd,u_char *buf); int (*lock_user_prot_reg) (struct mtd_info *mtd,size_t len); int (*writev) (struct mtd_info *mtd,const struct kvec *vecs,unsigned long count,size_t *retlen); void (*sync) (struct mtd_info *mtd); int (*lock) (struct mtd_info *mtd,loff_t ofs,uint64_t len); int (*unlock) (struct mtd_info *mtd,uint64_t len); int (*suspend) (struct mtd_info *mtd); void (*resume) (struct mtd_info *mtd); int (*block_isbad) (struct mtd_info *mtd,loff_t ofs); int (*block_markbad) (struct mtd_info *mtd,loff_t ofs); struct notifier_block reboot_notifier; struct mtd_ecc_stats ecc_stats; int subpage_sft; void *priv; struct module *owner; struct device dev; int usecount; int (*get_device) (struct mtd_info *mtd); void (*put_device) (struct mtd_info *mtd); }; mtd_info的type字段给出了底层物理设备的类型,包括MTD_RAM,MTD_ROM,MTD_norFLASH,MTD_NANSFLASH等 flags字段标志可以是MTD_WRITEABLE,MTD_BIT_WRITEABLE,MTD_NO_ERASE,MTD_POWERUP_LOCK等的组合 mtd_info中的read(),write(),read_oob(),write_oob(),erase()是MTD设备驱动要实现的主要函数,在Linux中MTD下层实现了针对nor Flash和NAND Flash通用的mtd_info成员函数 某些内存技术带有额外数据(OOB),例如NAND Flash没512字节就会有16个字节的“额外数据” Flash驱动中使用如下两个函数注册和注销MTD设备 int add_mtd_device(struct mtd_info *mtd); int del_mtd_device(struct mtd_info *mtd); 下面代码中mtd_part结构体用于表示分区,其mtd_info成员结构体用于描述分区,它会被加入到mtd_table(定义为struct mtd_info *mtd_table[MAX_MTD_DEVICES])中,其大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区相应的函数 struct mtd_part { struct mtd_info mtd;//分区信息(大部分由master决定) struct mtd_info *master;//该分区的主分区 uint64_t offset;//该分区的偏移地址 int index;// 主分区号 struct list_head list; int registered; }; mtd_partition(分区数组)会在MTD原始设备层调用add_mtd_partitions()时传递分区信息用,定义如下: struct mtd_partition { char *name; //标示字符串 uint64_t size; //分区大小 uint64_t offset; //主MTD内偏移地址 uint32_t mask_flags; //掩码标志 struct nand_ecclayout *ecclayout; //OOB布局out of band layout for this partition (NAND only) struct mtd_info **mtdp; }; Flash驱动中使用如下两个函数注册和注销分区 int add_mtd_partitions(struct mtd_info *master,struct mtd_partition *parts,int nbparts) int del_mtd_partitions(struct mtd_info *master); add_mtd_partitions()会对每一个新建立的分区建立一个mtd_part结构体,并将其加入mtd_partions(分区数组)中,并调用add_mtd_device()将此分区作为MTD设备键入mtd_table,成功时返回0,否则返回-ENOMEM del_mtd_partitions()的作用是对于mtd_partitions上的每一个分区,如果它的主分区是master(要被删除的分区号),则将它从mtd_partitions和mtd_table中删除,这是要调用del_mtd_device; add_mtd_partitions()中新建的mtd_part需要依赖于传进的mtd_partition(分区数组)参数对其进行初始化 int add_mtd_partitions(struct mtd_info *master, const struct mtd_partition *parts, int nbparts) { struct mtd_part *slave; uint64_t cur_offset = 0; int i; printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n",nbparts,master->name); for (i = 0; i < nbparts; i++) { slave = add_one_partition(master,parts + i,i,cur_offset); if (!slave) return -ENOMEM; cur_offset = slave->offset + slave->mtd.size; } return 0; } static struct mtd_part *add_one_partition(struct mtd_info *master, const struct mtd_partition *part,int partno, uint64_t cur_offset) { struct mtd_part *slave; slave = kzalloc(sizeof(*slave),GFP_KERNEL); if (!slave) { printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n", master->name); del_mtd_partitions(master); return NULL; } list_add(&slave->list,&mtd_partitions); 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.oobsize = master->oobsize; slave->mtd.oobavail = master->oobavail; slave->mtd.subpage_sft = master->subpage_sft; slave->mtd.name = part->name; slave->mtd.owner = master->owner; slave->mtd.backing_dev_info = master->backing_dev_info; ... } MTD用户空间编程 drives/mtd/mtdchar.c文件实现了MTD字符设备接口,通过它用户可以直接操作Flash设备,通过read(),write系统调用可以读写Flash,通过一系列IOCTL命令可以获得Flash设备信息,擦除Flash,读写NAND的OOB等 nor Flash驱动 在Linux中,实现了针对CFI(公共Flash接口),JEDEC等接口的通用nor驱动,这一层驱动直接面对mtd_info的成员函数,这使得nor的芯片级驱动变得十分简单,只需定义具体内存映射情况结构体mao_info并使用指定调用do_mao_probe()探测mtd_info()就可以了 nor Flash驱动的核心是定义map_info结构体,他指定nor Flash的基址,位宽,大小等信息,然后调用do_map_probe()探测芯片就可以了,map_info原型如下: struct map_info { const char *name; unsigned long size; resource_size_t phys; #define NO_XIP (-1UL) void __iomem *virt; void *cached; int bankwidth; #ifdef CONfig_MTD_COMPLEX_MAPPINGS map_word (*read)(struct map_info *,unsigned long); void (*copy_from)(struct map_info *,void *,unsigned long,ssize_t); void (*write)(struct map_info *,const map_word,unsigned long); void (*copy_to)(struct map_info *,const void *,ssize_t); #endif void (*inval_cache)(struct map_info *,ssize_t); void (*set_vpp)(struct map_info *,int); unsigned long pfow_base; unsigned long map_priv_1; unsigned long map_priv_2; void *fldrv_priv; struct mtd_chip_driver *fldrv; }; nor Flash驱动在Linux中实现,主要工作如下: (1)定义map_info的实例,初始化其中的成员,根据目标板的情况name,size,bankwidth和phys赋值 (2)如果Flash需要分区,则定义mtd_partition数组,将实际电路板中Flash分区信息记录在其中 (3)以map_info和探测的接口类型(如“ch_probe”,"jedec_probe"等)为参数调用do_map_probe()探测Flash得到mtd_info do_map_probe()函数原型如下: struct mtd_info *do_map_probe(const char *name,struct map_info *map); 第一个为探测接口类型,常见的如下 do_map_probe("cfi_probe",&xxx_map_info); do_map_probe("jedec_probe",&xxx_map_info); do_map_probe("map_rom",&xxx_map_info); do_map_probe()会根据传入参数name(接口类型),通过get_mtd_chip_driver()得到具体的MTD驱动,如下所示: struct mtd_info *do_map_probe(const char *name,struct map_info *map) { struct mtd_chip_driver *drv; struct mtd_info *ret; drv = get_mtd_chip_driver(name); if (!drv && !request_module("%s",name)) drv = get_mtd_chip_driver(name);//通过接口类型找到驱动 if (!drv) return NULL; ret = drv->probe(map); module_put(drv->module); if (ret) return ret; return NULL; } (4)在模块初始化时以mtd_info为参数调用add_mtd_device()或以mtd_info,partition数组为参数调用add_mtd_partitions()注册设备或分区 (5)在模块卸载时调用“反函数”来删除设备或分区 下面是一个nor Flash的驱动 #define WINDOW_SIZE ... #define WINDOW_ADDR ... static struct map_info xxx_map = {//定义并初始化map_info .name = "xxx_Flash",.size = WINDOW_SIZE,//大小 .bankwidth = 1,//总线宽度 .phys = WINDOW_ADDR//物理地址 }; static struct mtd_partition xxx_partitions[] = {//定义数组用于分区 { .name = "Drive A",.offset = 0,//分区偏移地址 .size = 0x0e0000//分区大小 }, ... }; #define NUM_PARTITIONS ARRAY_SIZE(xxx_partitions) static struct mtd_info *mymtd; static int __init init_xxx_map(void) { int rc = 0; xxx_map.virt = ioremap_nocache(xxx_map.phys,xxx_map.size);//物理地址映射为虚拟地址 ... mymtd = do_map_probe("jedec_probe",&xxx_map);//探测nor Flash得到mtd_info mymtd->owner = THIS_MODULE; add_mtd_partitions(mymtd,xxx_partitions,NUM_PARTTITIONS);//添加分区信息 ... } struct void __exit cleanup_xxx_map(void0 { if(mymtd){ del_mtd_partitions(mymtd);//删除分区 map_destroy(mymtd); } ... } NAND Flash驱动 和nor Flash非常类似,在Linux内核MTD的下层已经实现了通用的NAND驱动(在driver/mtd/nand/nand_bash.c中)即实现了mtd_info结构体的成员函数 MTD使用nand_chip数据结构表示一个NAND Flash芯片,这个结构体中包含了关于NAND Flash的地址信息,读写方法,ECC模式等,这个结构体原型如下: struct nand_chip { void __iomem *IO_ADDR_R; void __iomem *IO_ADDR_W; uint8_t (*read_byte)(struct mtd_info *mtd); u16 (*read_word)(struct mtd_info *mtd); void (*write_buf)(struct mtd_info *mtd,const uint8_t *buf,int len); void (*read_buf)(struct mtd_info *mtd,uint8_t *buf,int len); int (*verify_buf)(struct mtd_info *mtd,int len); void (*select_chip)(struct mtd_info *mtd,int chip); int (*block_bad)(struct mtd_info *mtd,int getchip); int (*block_markbad)(struct mtd_info *mtd,loff_t ofs); void (*cmd_ctrl)(struct mtd_info *mtd,int dat, unsigned int ctrl); int (*dev_ready)(struct mtd_info *mtd); void (*cmdfunc)(struct mtd_info *mtd,unsigned command,int column,int page_addr); int (*waitfunc)(struct mtd_info *mtd,struct nand_chip *this); void (*erase_cmd)(struct mtd_info *mtd,int page); int (*scan_bbt)(struct mtd_info *mtd); int (*errstat)(struct mtd_info *mtd,struct nand_chip *this,int state,int status,int page); int (*write_page)(struct mtd_info *mtd,struct nand_chip *chip, const uint8_t *buf,int page,int cached,int raw); int chip_delay; unsigned int options; int page_shift; int phys_erase_shift; int bbt_erase_shift; int chip_shift; int numchips; uint64_t chipsize; int pagemask; int pagebuf; int subpagesize; uint8_t cellinfo; int badblockpos; nand_state_t state; uint8_t *oob_poi; struct nand_hw_control *controller; struct nand_ecclayout *ecclayout; struct nand_ecc_ctrl ecc; struct nand_buffers *buffers; struct nand_hw_control hwcontrol; struct mtd_oob_ops ops; uint8_t *bbt; struct nand_bbt_descr *bbt_td; struct nand_bbt_descr *bbt_md; struct nand_bbt_descr *badblock_pattern; void *priv; }; 与nor Flash一样,由于有了MTD层,完成一个NAND Flash驱动在Linux中的工作量也很少,主要工作如下: (1)如果Flash需要分区,则定义mtd_partition数组,将实际电路板中Flash分区信息记录以其中 (2)在模块加载是分配nand_chip的内存,根据目标板NAND控制器的情况初始化nand_chip中的cmd_ctrl(),dev_ready(),ready_byte(),write_buf()等成员(如果不赋值就使用nand_base.c中的默认函数),注意将mtd_info的priv指向nand_chip (3)以mtd_info为参数调用nand_scan()函数探测NAND Flash的存在得到mtd_info,nand_scan()函数原型如下: int nand_scan(struct mtd_info *mtd,int maxchips); (4)如果要分区则以mtd_info,mtd_partition为参数调用add_mtd_partitions()函数,添加分区 下面是一个NAND Flash设备驱动模块 #define CHIP_PHYSICAL_ADDRESS ... #define NUM_PARTITIONS 2 static const struct mtd_partition partition_info[] = { { .name = "NAND FS 0", .offset = 0, .size = 8 * 1024 * 1024}, { .name = "NAND FS 1", .offset = MTDPART_OFS_APPEND,//表示接着上面结束地址 .size = MTDPART_SIZ_FULL} }; static int __init au1xxx_nand_init(void) { struct nand_chip *this; u16 boot_swapboot = 0; int retval; u32 mem_staddr; u32 nand_phys; //初始化结构体(分配内存) au1550_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip),GFP_KERNEL); if (!au1550_mtd) { printk("Unable to allocate NAND MTD dev structure.\n"); return -ENOMEM; } this = (struct nand_chip *)(&au1550_mtd[1]); //初始化成员函数 memset(au1550_mtd,sizeof(struct mtd_info)); memset(this,sizeof(struct nand_chip)); au1550_mtd->priv = this; au1550_mtd->owner = THIS_MODULE; au_writel(0,MEM_STNDCTL); #ifdef CONfig_MIPS_PB1550 au_writel(au_readl(GPIO2_DIR) & ~(1 << 6),GPIO2_DIR); boot_swapboot = (au_readl(MEM_STSTAT) & (0x7 << 1)) | ((bcsr->status >> 6) & 0x1); switch (boot_swapboot) { case 0: case 2: case 8: case 0xC: case 0xD: nand_width = 0; break; case 1: case 9: case 3: case 0xE: case 0xF: nand_width = 1; break; default: printk("Pb1550 NAND: bad boot:swap\n"); retval = -EINVAL; goto outmem; } #endif #ifdef NAND_STCFG if (NAND_CS == 0) { au_writel(NAND_STCFG, MEM_STCFG0); au_writel(NAND_STTIME,MEM_STTIME0); au_writel(NAND_STADDR,MEM_STADDR0); } if (NAND_CS == 1) { au_writel(NAND_STCFG, MEM_STCFG1); au_writel(NAND_STTIME,MEM_STTIME1); au_writel(NAND_STADDR,MEM_STADDR1); } if (NAND_CS == 2) { au_writel(NAND_STCFG, MEM_STCFG2); au_writel(NAND_STTIME,MEM_STTIME2); au_writel(NAND_STADDR,MEM_STADDR2); } if (NAND_CS == 3) { au_writel(NAND_STCFG, MEM_STCFG3); au_writel(NAND_STTIME,MEM_STTIME3); au_writel(NAND_STADDR,MEM_STADDR3); } #endif mem_staddr = 0x00000000; if (((au_readl(MEM_STCFG0) & 0x7) == 0x5) && (NAND_CS == 0)) mem_staddr = au_readl(MEM_STADDR0); else if (((au_readl(MEM_STCFG1) & 0x7) == 0x5) && (NAND_CS == 1)) mem_staddr = au_readl(MEM_STADDR1); else if (((au_readl(MEM_STCFG2) & 0x7) == 0x5) && (NAND_CS == 2)) mem_staddr = au_readl(MEM_STADDR2); else if (((au_readl(MEM_STCFG3) & 0x7) == 0x5) && (NAND_CS == 3)) mem_staddr = au_readl(MEM_STADDR3); if (mem_staddr == 0x00000000) { printk("Au1xxx NAND: ERROR WITH NAND CHIP-SELECT\n"); kfree(au1550_mtd); return 1; } nand_phys = (mem_staddr << 4) & 0xFFFC0000; p_nand = (void __iomem *)ioremap(nand_phys,0x1000); if (NAND_CS == 0) nand_width = au_readl(MEM_STCFG0) & (1 << 22); if (NAND_CS == 1) nand_width = au_readl(MEM_STCFG1) & (1 << 22); if (NAND_CS == 2) nand_width = au_readl(MEM_STCFG2) & (1 << 22); if (NAND_CS == 3) nand_width = au_readl(MEM_STCFG3) & (1 << 22); this->dev_ready = au1550_device_ready; this->select_chip = au1550_select_chip; this->cmdfunc = au1550_command; this->chip_delay = 30; this->ecc.mode = NAND_ECC_SOFT; this->options = NAND_NO_AUTOINCR; if (!nand_width) this->options |= NAND_BUSWIDTH_16; this->read_byte = (!nand_width) ? au_read_byte16 : au_read_byte; au1550_write_byte = (!nand_width) ? au_write_byte16 : au_write_byte; this->read_word = au_read_word; this->write_buf = (!nand_width) ? au_write_buf16 : au_write_buf; this->read_buf = (!nand_width) ? au_read_buf16 : au_read_buf; this->verify_buf = (!nand_width) ? au_verify_buf16 : au_verify_buf; if (nand_scan(au1550_mtd,1)) {//探测NAND Flash retval = -ENXIO; goto outio; } //注册分区 add_mtd_partitions(au1550_mtd,partition_info,ARRAY_SIZE(partition_info)); return 0; outio: IoUnmap((void *)p_nand); outmem: kfree(au1550_mtd); return retval; } 最后强调的是,在NAND芯片驱动中,如果nand_chip中没有赋值,将使用默认值(nand_base.c中的默认值),下面是nand_chip中的nand_ecc_ctrl结构体类型成员ecc赋值,定义OOB的分布模式 static struct nand_ecclayout nand_oob_8 = { .eccbytes = 3, .eccpos = {0,1,2}, .oobfree = { {.offset = 3, .length = 2}, {.offset = 6, .length = 2}} }; static struct nand_ecclayout nand_oob_16 = { .eccbytes = 6,2,3,6,7}, .oobfree = { {.offset = 8, . length = 8}} }; static struct nand_ecclayout nand_oob_64 = { .eccbytes = 24, .eccpos = { 40,41,42,43,44,45,46,47, 48,49,50,51,52,53,54,55, 56,57,58,59,60,61,62,63}, .oobfree = { {.offset = 2, .length = 38}} }; static struct nand_ecclayout nand_oob_128 = { .eccbytes = 48, .eccpos = { 80,81,82,83,84,85,86,87, 88,89,90,91,92,93,94,95, 96,97,98,99,100,101,102,103, 104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119, 120,121,122,123,124,125,126,127}, .length = 78}} }; nor Flash驱动实例 针对S3C2410等平台而言。外接nor Flash的情况下,由于nor Flash直接映射到cpu的内存空间,为了使用nor Flash驱动,我们只需要在BSP的板文件中添加相应的信息, 例如nor Flash所在的物理地址和到校,分区信息,总线宽度等,这些信息以platform资源和数据的形式呈现,如下: static struct resource nor_resource = { .start = OMAP_CS0_PHYS,.end = OMAP_CS0_PHYS + SZ_32M - 1,.flags = IORESOURCE_MEM,//表示地址 }; static struct mtd_partition nor_partitions[] = {//分区结构体 { .name = "bootloader", .offset = 0, .size = SZ_128K, .mask_flags = MTD_WRITEABLE,},{ .name = "params", .offset = MTDPART_OFS_APPEND, .mask_flags = 0,{ .name = "kernel", .size = SZ_2M, .mask_flags = 0 },{ .name = "rootfs", .size = MTDPART_SIZ_FULL,}; static struct flash_platform_data nor_data = { .map_name = "cfi_probe",.width = 2,.parts = nor_partitions,.nr_parts = ARRAY_SIZE(nor_partitions),}; static struct platform_device nor_device = { .name = "omapflash",//应该和驱动里的(physmap.c)一致 .id = 0,.dev = { .platform_data = &nor_data,.num_resources = 1,//资源数 .resource = &nor_resource,//资源结构体 }; 下面来总结一下: 由于引入了MTD系统以及MTD下层的通用nor和NAND驱动,Linux中nor和NAND Flash芯片级驱动的设计难度大大降低,对于nor驱动工作仅仅只需在BSP中添加相关的platform信息。 在串口驱动中,讲解了tty_driver到uart_driver的角色转换,在Flash驱动中,讲解了mtd_info向map_info/nand_chip的转移,可以说,Linux驱动这种分层设计思想是贯穿个中Linux驱动框架的。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。