linux驱动移植-linux块设备驱动Nor Flash
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
在进行Mini2440 uboot移植的时候,我们介绍了Nor Flash相关的硬件知识,当时我们使用的Nor Flash型号为S29AL016D70TF102,大小为2MB。
后来由于开发板的网卡坏了,所以换了一块板子,这块板子芯片型号为SST39VF1601,大小2MB。因此在阅读这篇文章之前,参考Mini2440之uboot移植之实践NOR FLASH支持(二)修改uboot源码,使其支持该款型号的Nor Flash。
修改drivers/mtd/jedec_flash.c文件jedec_table数组,在最后添加:
{ .mfr_id = (u16)SST_MANUFACT, // 生产厂商id 0xBF .dev_id = SST39VF1601, // 设备id 0X234B .name = "SST 39VF1601", .uaddr = { [1] = MTD_UADDR_0x5555_0x2AAA /* x16 */ }, .DevSize = SIZE_2MiB, .CmdSet = P_ID_AMD_STD, .NumEraseRegions= 4, .regions = { ERASEINFO(0x1000,96), // 由于该款芯片扇区擦除采用统一的2Kword,所以这里我就自己分了4个区域 ERASEINFO(0x1000,160), ERASEINFO(0x1000,240), ERASEINFO(0x1000,16), } },
修改include/configs/smdk2440.h文件,这里使用CONFIG_SYS_MAX_FLASH_SECT定义了我们NOR FLASH最大扇区数量,我们修改为512:
#define CONFIG_SYS_MAX_FLASH_SECT (512)
编译u-boot并下载到Nor Flash中,并以NOR方式启动(必须以NOR方式启动,这样Nor Flash地址才能映射到0x00000000):
Flash: fwc addr 00000000 cmd f0 00f0 16bit x 16 bit fwc addr 0000aaaa cmd aa 00aa 16bit x 16 bit fwc addr 00005554 cmd 55 0055 16bit x 16 bit fwc addr 0000aaaa cmd 90 0090 16bit x 16 bit fwc addr 00000000 cmd f0 00f0 16bit x 16 bit JEDEC PROBE: ID bf 234b 0 // 设备id Found JEDEC Flash: SST 39VF1601 unlock address index 3 unlock addresses are 0x5555/0x2aaa erase_region_count = 96 erase_region_size = 4096 erase_region_count = 160 erase_region_size = 4096 erase_region_count = 240 erase_region_size = 4096 erase_region_count = 16 erase_region_size = 4096 flash_protect ON: from 0x00000000 to 0x0004083B protect on 0 protect on 1 ...... protect on 63 protect on 64 2 MiB
一、Nor Flash介绍
Nor Flash存储器接口标准包含CFI和JEDEC:
- CFI为公共Flash接口(Common Flash Interface),CFI是一个公开的标准的从Flash Memory器件中读取数据的接口。它可以使系统软件查询已安装的Flash Memory器件的各种参数,包括器件阵列结构参数、电气和时间参数以及器件支持的功能等。利用CFI可以不用修改系统软件就可以用新型的和改进的产品代替旧版本的产品。例如:如果新型的Flash Memory的擦除时间只有旧版本的一半,系统软件只要通过CFI读取新器件的擦除时间等参数,修改一下定时器的时间参数即可。
- JEDEC是由生产厂商们制定的国际性协议,主要为计算机内存制定。JEDEC用来帮助程序读取Flash的制造商ID和设备ID,以确定Flash的大小和算法,如果芯片不支持CFI,就需使用JEDEC了。工业标准的内存通常指的是符合JEDEC标准的一组内存。
老式的Nor Flash一般是JEDEC规范,其一般只包含识别 ID、擦除芯片、烧写数据的命令。要想知道其容量大小等信息,就需要先读出其芯片id,然后向Nand Flash一样,根据芯片id匹配内核中drivers/mtd/chips/jedec_probe.c里的的jedec_table数组,来确定Nor Flash的哥哥参数(名称、容量、位宽)等,比较麻烦。另外如果内核jedec_table数组中事先没有对应芯片id的信息,还需要先在该数组中添加。
目前的Nor Flash一般都支持CFI规范,其除了提供识别 ID、擦除芯片、烧写数据的命令之后,还提供了进入CFI模式的命令,进入CFI模式后就可以通过读取相应地址的数据获取芯片属性信息,如容量、电压等信息。 进入CFI模式(使用CFI Query Entry命令)。
1.1 SST39VF1601
Mini2440开发板就是将2MB的Nor Flash(型号SST39VF1601)焊接在了Bank0上,SST39VF1601这款芯片具有以下性质:
- 扇区擦除能力:统一的2Kword,即4KB;
- 块擦除能力:统一的32Kword,即64KB;
- 与 JEDEC 标准的兼容性;
- CFI(通用闪存接口)兼容;进入CFI模式,需要写入三个字节的时序,与获取设备ID的前两个字节一致,只是第三个字节是向地址0x5555写入0x98。一旦设备进入CFI查询模式下,系统可以读取地址处的CFI数据,此外系统必须写入CFI退出命令,从CFI返回到数据读取状态。
SST39VF1601支持16位模式;这里我们Mini2440采用的是16位模式,S3C2440的A1~A20连接Nor Flash的A0~A19,SST39VF1601地址线一共20位,数据线一共16位。
需要错位连接的原因是:S3C2440处理器的每个地址对应的是一个字节的数据单元,而16位模式下,Nor Flash的每个地址对应的是一个HALF-WORD(16 bit)的数据单元。
为了保持匹配,所以必须错位连接。这样,从S3C2440处理器发送出来的地址信号的最低位A0对16位Flash来说就被屏蔽掉了。
补充说明:
- 一般来说,ARM处理器内部要设置相应的寄存器,告诉处理器外部扩展的Flash的位宽(8 bit/16 bit/32 bit)。这样,处理器才知道在访问的时候如何从Flash正确的读取数据;
- 有些ARM处理器内部可以设置地址的错位,对于支持软件选择地址错位的处理器,在连接16 bit Flash的时候,硬件上可以不需要把地址线错位;
- 如果处理器支持内部设置地址错位,在实际访问的时候,送出的地址实际上是在MCU内部做了错位处理,其作用是等效于硬件连接上的错位的;
生产厂家以及设备ID:
Address | Data | |
Manufacture‘s ID | 0000H | 00BFH |
Device ID | 0001H | 234BH |
1.2 指令集
以Software ID Entry为例,先向地址“5555”处写入“AA”,再向地址“2AAA”处写 入“55”,接着向地址“5555”处写入“90” ,最后从“0000”地址读到厂家ID“00BF”,从“0001”地址读取到设备ID"234B"。
周期“First”和“Second”是“解锁”,周期“Third”是发出命令。 需要注意的是,以地址"5555"为例,每个地址对应的数据长度为2个字节,对应8位位宽地址5555<<1=AAAA。
以读取软件ID为例,编写代码时序如下:
- 解锁:向地址AAAAH(5555<<1)写入AAH,向地址5554(2AAA<<1)写入55H;
- 命令:向地址AAAAH(5555<<1)写入90H;
- 读地址0x0000得到厂家ID;
- 读地址0x0001得到设备ID
由于Mini2440的A1接到Nor Flash的A0,所以2440发出(5555<<1),Nor Flash才能收到“5555”这个地址;同理,只要是2440发出的地址都需要在Nor Flash的地址基础上<<1。
关于如何对Nor Flash进行读写、擦除,我就简单说一下:
- 读:Nor Flash上电后处于数据读取状态(Reading Array Data)。此状态可以进行正常的读,这和读取SDRAM/SRAM/ROM一样(要是不一样的话,芯片上电后如何从Nor Flash中读取启动代码)。当芯片进入软件ID标识模式,必须通过发出软件ID退出命令来完成退出序列,将设备返回到读取模式。
- 擦除:在完成信息获取后一般就要擦除数据。Nor Flash支持扇区擦除(Sector Erase)、块擦除(Block Erase)和整片擦除(Chip Erase),这3种模式都有对应的命令序列,在完成擦除命令后会自动返回到数据读取状态。
- 编程(写):完成擦除后就需要对芯片进行写入操作也就是编程,这就需要进入编程(Program)状态。在完成编程命令后会自动返回到数据读取状态。注意:编程前一定要先擦除。因为编程只能将'1'改写为'0',通过擦写可以将数据全部擦写为'1'。
二、Nor Flash驱动分析
实际上编写Nor Flash驱动和Nand Flash驱动是类似的,在Nand Flash驱动编写过程中,我们主要就是分配nand_chip,初始化nand_chip,并以mtd_info和mtd_partition为参数调用mtd_device_register进行MTD设备注册。
同理对于Nor Flash,我们需要定义具体内存映射结构体map_info,然后通过接口类型后调用do_map_probe。
这一节我们分析内核自带的Nor Flash驱动,位于drivers/mtd/maps/physmap-core.c。
2.1 相关结构体
2.1.1 struct physmap_flash_data
physmap_flash_data定义在include/linux/mtd/physmap.h,用于存储Nor Flash的配置数据,比如位宽,分区数量,分区表信息等;
struct physmap_flash_data { unsigned int width; // 位宽 int (*init)(struct platform_device *); void (*exit)(struct platform_device *); void (*set_vpp)(struct platform_device *, int); unsigned int nr_parts; // 分区个数 unsigned int pfow_base; char *probe_type; struct mtd_partition *parts; // 分区表 const char * const *part_probe_types; };
2.1.2 physmap_flash_info
physmap_flash_info定义在drivers/mtd/maps/physmap-core.c,用于描述Nor Flash信息:
struct physmap_flash_info { unsigned int nmaps; // I/O 内存地址资源数量 => Nor Flash芯片数量 struct mtd_info **mtds; // 指针数组,实际上为 stuct mtd_info *mtds[nmaps],每个元素都指向一个strcut mtd_info, 数组长度和nmaps一样 实现对MTD设备读、写、擦除等操作 struct mtd_info *cmtd; // 统一的mtd_info struct map_info *maps; // 数组结构,数组长度和nmaps一样 spinlock_t vpp_lock; // 自旋锁 int vpp_refcnt; const char *probe_type; // 类型 const char * const *part_types; unsigned int nparts; // 分区个数 const struct mtd_partition *parts; // 分区表 struct gpio_descs *gpios; unsigned int gpio_values; unsigned int win_order; };
与linux 2.6版本相比,该结构体发生了较大的变化,我理解的是这里应该是考虑多个Nor Flash芯片,每个Nor FLash都有自己的内存地址空间。比如:
- Nor Flash 1:0x00000000~0x00010000;
- Nor Flash 2:0x00080000~0x00090000;
- ...
然后将每一个Nor Flash芯片都使用一个strcut mtd_info、struct map_info描述。
2.2 模块入口函数
static struct platform_driver physmap_flash_driver = { // platform驱动 .probe = physmap_flash_probe, .remove = physmap_flash_remove, .shutdown = physmap_flash_shutdown, .driver = { .name = "physmap-flash", // 驱动名称 .of_match_table = of_flash_match, }, }; #ifdef CONFIG_MTD_PHYSMAP_COMPAT static struct physmap_flash_data physmap_flash_data = { .width = CONFIG_MTD_PHYSMAP_BANKWIDTH, // Nor Flash位宽 }; static struct resource physmap_flash_resource = { // 资源定义 .start = CONFIG_MTD_PHYSMAP_START, // Nor Flash物理基地址 .end = CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1, //Nor Flash结束地址 .flags = IORESOURCE_MEM, // 内存资源 }; static struct platform_device physmap_flash = { // platform设备 .name = "physmap-flash", // 设备名称 .id = 0, .dev = { // strcut device .platform_data = &physmap_flash_data, }, .num_resources = 1, // 资源数量 .resource = &physmap_flash_resource, // 详细资源信息 }; #endif static int __init physmap_init(void) { int err; err = platform_driver_register(&physmap_flash_driver); // 注册platform驱动 #ifdef CONFIG_MTD_PHYSMAP_COMPAT if (err == 0) { err = platform_device_register(&physmap_flash); // 注册platform设备 if (err) platform_driver_unregister(&physmap_flash_driver); } #endif return err; }
可以看到这里如果我们内核配置了CONFIG_MTD_PHYSMAP_COMPAT,将会进行platform设备的注册。
在platform设备定义中,引入了三个宏常量:
- CONFIG_MTD_PHYSMAP_BANKWIDTH:Nor Flash位宽;
- CONFIG_MTD_PHYSMAP_START:Nor Flash的物理基地址;
- CONFIG_MTD_PHYSMAP_LEN :Nor Flash的容量大小;
如果platform设备和驱动匹配成功,将会执行physmap_flash_probe函数。
2.3 physmap_flash_probe
physmap_flash_info定义在drivers/mtd/maps/physmap-core.c,
static int physmap_flash_probe(struct platform_device *dev) { struct physmap_flash_info *info; int err = 0; int i; if (!dev->dev.of_node && !dev_get_platdata(&dev->dev)) // dev->dev.platform_data return -EINVAL; info = devm_kzalloc(&dev->dev, sizeof(*info), GFP_KERNEL); // 为info动态申请内存,dev->dev释放时自动回收 if (!info) return -ENOMEM; while (platform_get_resource(dev, IORESOURCE_MEM, info->nmaps)) // 统计IORESOURCE_MEM 内存资源数量 info->nmaps++; if (!info->nmaps) // 如果没有I/O内存地址资源 return -ENODEV; info->maps = devm_kzalloc(&dev->dev, // 动态申请struct map_info数组 sizeof(*info->maps) * info->nmaps, GFP_KERNEL); if (!info->maps) // 内存申请失败 return -ENOMEM; info->mtds = devm_kzalloc(&dev->dev, // 为指针数组申请内存,数组长度为nmaps,dev->dev释放时自动回收 sizeof(*info->mtds) * info->nmaps, GFP_KERNEL); if (!info->mtds) // 内存申请失败 return -ENOMEM; platform_set_drvdata(dev, info); // dev->dev.driver_data = info info->gpios = devm_gpiod_get_array_optional(&dev->dev, "addr", GPIOD_OUT_LOW); if (IS_ERR(info->gpios)) return PTR_ERR(info->gpios); if (info->gpios && info->nmaps > 1) { dev_err(&dev->dev, "addr-gpios only supported for nmaps == 1\n"); return -EINVAL; } if (dev->dev.of_node) err = physmap_flash_of_init(dev); else err = physmap_flash_pdata_init(dev); if (err) return err; if (err) return err; for (i = 0; i < info->nmaps; i++) { // 遍历每一个内存资源,依次初始化info->maps[i]、info->mtds[i] struct resource *res; res = platform_get_resource(dev, IORESOURCE_MEM, i); // 获取内存资源信息 info->maps[i].virt = devm_ioremap_resource(&dev->dev, res); // 初始化对应的虚拟地址 if (IS_ERR(info->maps[i].virt)) { err = PTR_ERR(info->maps[i].virt); goto err_out; } dev_notice(&dev->dev, "physmap platform flash device: %pR\n", res); info->maps[i].name = dev_name(&dev->dev); // 设置名称 if (!info->maps[i].phys) info->maps[i].phys = res->start; // 设置物理基地址 info->win_order = get_bitmask_order(resource_size(res)) - 1; info->maps[i].size = BIT(info->win_order + // 设置容量 (info->gpios ? info->gpios->ndescs : 0)); info->maps[i].map_priv_1 = (unsigned long)dev; // 存储platform设备 if (info->gpios) { err = physmap_addr_gpios_map_init(&info->maps[i]); if (err) goto err_out; } #ifdef CONFIG_MTD_COMPLEX_MAPPINGS /* * Only use the simple_map implementation if map hooks are not * implemented. Since map->read() is mandatory checking for its * presence is enough. */ if (!info->maps[i].read) simple_map_init(&info->maps[i]); #else simple_map_init(&info->maps[i]); #endif if (info->probe_type) { info->mtds[i] = do_map_probe(info->probe_type, &info->maps[i]); } else { int j; for (j = 0; j < ARRAY_SIZE(rom_probe_types); j++) { info->mtds[i] = do_map_probe(rom_probe_types[j], // 通过do_map_probe()来识别芯片 &info->maps[i]); if (info->mtds[i]) break; } } if (!info->mtds[i]) { // 最终还是没找到芯片,进行退出操作 dev_err(&dev->dev, "map_probe failed\n"); err = -ENXIO; goto err_out; } info->mtds[i]->dev.parent = &dev->dev; } if (info->nmaps == 1) { // 只有一个Nor Flash芯片 info->cmtd = info->mtds[0]; } else { /* * We detected multiple devices. Concatenate them together. */ info->cmtd = mtd_concat_create(info->mtds, info->nmaps, // 如果存在多个Nor Flash芯片,将他们连接在一起,设置统一的mtd_info结构体 dev_name(&dev->dev)); if (!info->cmtd) err = -ENXIO; } if (err) goto err_out; spin_lock_init(&info->vpp_lock); // 初始化自旋锁 mtd_set_of_node(info->cmtd, dev->dev.of_node); err = mtd_device_parse_register(info->cmtd, info->part_types, NULL, // 设置分区范围、注册mtd_info结构体,尝试以指定文件系统类型去解析Nor Flash分区 info->parts, info->nparts); if (err) goto err_out; return 0; err_out: physmap_flash_remove(dev); return err; }
这段代码是在太长了,我直接挑重点说:
- 分配一个physmap_flash_info 结构体变量info;
- 设置info:
- 初始化info->nmaps为设置I/O 内存地址资源数量;
- 动态申请struct map_info数组,赋值给info->maps;
- 动态申请struct *mtd_info指针数组,赋值给info->mtds;
- 初始化info->gpios;
- 遍历每一个I/O内存地址资源模块:
- 初始化info->maps[i]成员,名称、物理地址、虚拟地址、容量、字节宽度等;
- 通过函数simple_map_init初始化info->maps[i]成员函数read、write、copy_from、copy_to;
- 通过do_map_probe()来识别芯片,并初始化info->mtds[i];
- 通过mtd_device_parse_register注册MTD设备;
2.3.1 devm_gpiod_get_array_optional
devm_gpiod_get_array_optional定义在drivers/gpio/gpiolib-devres.c:
/** * devm_gpiod_get_array_optional - Resource-managed gpiod_get_array_optional() * @dev: GPIO consumer * @con_id: function within the GPIO consumer * @flags: optional GPIO initialization flags * * Managed gpiod_get_array_optional(). GPIO descriptors returned from this * function are automatically disposed on driver detach. * See gpiod_get_array_optional() for detailed information about behavior and * return values. */ struct gpio_descs *__must_check devm_gpiod_get_array_optional(struct device *dev, const char *con_id, enum gpiod_flags flags) { struct gpio_descs *descs; descs = devm_gpiod_get_array(dev, con_id, flags); if (IS_ERR(descs) && (PTR_ERR(descs) == -ENOENT)) return NULL; return descs; }
2.3.2 devm_gpiod_get_array
/** * devm_gpiod_get_array - Resource-managed gpiod_get_array() * @dev: GPIO consumer * @con_id: function within the GPIO consumer * @flags: optional GPIO initialization flags * * Managed gpiod_get_array(). GPIO descriptors returned from this function are * automatically disposed on driver detach. See gpiod_get_array() for detailed * information about behavior and return values. */ struct gpio_descs *__must_check devm_gpiod_get_array(struct device *dev, const char *con_id, enum gpiod_flags flags) { struct gpio_descs **dr; struct gpio_descs *descs; dr = devres_alloc(devm_gpiod_release_array, sizeof(struct gpio_descs *), GFP_KERNEL); if (!dr) return ERR_PTR(-ENOMEM); descs = gpiod_get_array(dev, con_id, flags); if (IS_ERR(descs)) { devres_free(dr); return descs; } *dr = descs; devres_add(dev, dr); return descs; }
2.3.3 simple_map_init
simple_map_init定义在include/linux/mtd/map.h文件中:
static inline int map_bankwidth_supported(int w) { switch (w) { #ifdef CONFIG_MTD_MAP_BANK_WIDTH_1 case 1: #endif #ifdef CONFIG_MTD_MAP_BANK_WIDTH_2 case 2: #endif #ifdef CONFIG_MTD_MAP_BANK_WIDTH_4 case 4: #endif #ifdef CONFIG_MTD_MAP_BANK_WIDTH_8 case 8: #endif #ifdef CONFIG_MTD_MAP_BANK_WIDTH_16 case 16: #endif #ifdef CONFIG_MTD_MAP_BANK_WIDTH_32 case 32: #endif return 1; default: return 0; } } #define simple_map_init(map) BUG_ON(!map_bankwidth_supported((map)->bankwidth))
2.3.4 do_map_probe
do_map_probe函数是physmap_flash_probe中的关键步骤。do_map_probe函数根据传入的参数去调用相关规范的probe函数,判断该Nor Flash支持哪种规范去访问。
rom_probe_types是一个字符指针数组,定义在drivers/mtd/maps/physmap-core.c:
static const char * const rom_probe_types[] = { "cfi_probe", "jedec_probe", "qinfo_probe", "map_rom", };
这个数组里每一项都指向一个字符串,do_map_types函数本质上就是根据传入的字符串名称在链表中找到相应的probe函数。
如cif_probe将尝试以CFI规范去解析Nor Flash,如果Nor Flash支持CFI协议,将解析成功,读取Nor Falsh信息,分配、设置mtd_info结构体。
do_map_probe定义在drivers/mtd/chips/chipreg.c:
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); /* We decrease the use count here. It may have been a probe-only module, which is no longer required from this point, having given us a handle on (and increased the use count of) the actual driver code. */ module_put(drv->module); return ret; }
这里主要做了以下事情:
- 根据传入的字符串名称找到一个mtd_chip_driver结构体变量;
- 调用找到的mtd_chip_driver结构体的probe函数;
由于do_map_probe比较复杂,所以单独一个小节分析这一块代码,如果对这块代码不感兴趣,看到这里就可以了。
2.3.5 mtd_device_parse_register
mtd_device_parse_register这个函数在前面MTD子系统分析时已经介绍了,该函数对add_mtd_device、add_mtd_partitions进行了包装,根据分区参数决定执行哪个函数。
三、do_map_probe
3.1 get_mtd_chip_driver
static struct mtd_chip_driver *get_mtd_chip_driver (const char *name) { struct list_head *pos; struct mtd_chip_driver *ret = NULL, *this; spin_lock(&chip_drvs_lock); // 获取锁 list_for_each(pos, &chip_drvs_list) { // 遍历链表 this = list_entry(pos, typeof(*this), list); if (!strcmp(this->name, name)) { ret = this; break; } } if (ret && !try_module_get(ret->module)) ret = NULL; spin_unlock(&chip_drvs_lock); // 释放锁 return ret; }
该函数遍历chip_drvs_list链表,找到链表中挂载的所有内容,如果和传入名称匹配,将返回匹配成功链表对象的地址。
3.1.1 chip_drvs_list链表
chip_drvs_list链表在哪里被设置呢?在内核源码中查找,找到了往链表中注册成员的register_mtd_chip_driver函数。
该函数又被很多驱动的入口函数调用:
如cfi_probe.c、jedec_probe.c、map_ram.c、map_rom.c等文件的入口函数:
- jedec_probe.c中定义了JEDEC标准的Flash驱动;
- cfi_probe.c中定义CFI标准的Flash驱动;
- map_ram.c定义了以RAM作为MTD存储介质的驱动;
- map_rom.c定义了以ROM作为MTD存储介质的驱动;
这里以cfi_probe.c文件入口函数为例进行分析。
3.1.2 CFI协议层(cfi_probe.c)
static struct mtd_chip_driver cfi_chipdrv = { .probe = cfi_probe, .name = "cfi_probe", .module = THIS_MODULE }; static int __init cfi_probe_init(void) { register_mtd_chip_driver(&cfi_chipdrv); return 0; } static void __exit cfi_probe_exit(void) { unregister_mtd_chip_driver(&cfi_chipdrv); } module_init(cfi_probe_init); // 模块入口函数 module_exit(cfi_probe_exit); // 模块出口函数
调用register_mtd_chip_driver函数,注册了一个名为cfi_chipdrv的mtd_chip_driver结构体,该结构体name成员名为“cfi_probe”,probe指针指向cfi_probe函数。
由此可见,在对于Nor Flash这类存储设备进行访问时,驱动程序会调用do_map_probe函数,使用内核中支持的mtd_chip_driver的probe函数去检测硬件的类型。
如果内核中注册的mtd_chip_driver支持该芯片的访问,将填充mtd_info结构体,设置mtd_info结构体的设备读写函数。
同时,我们可以使用内核中注册的mtd_chip_driver,去尝试匹配一个新的类内存接口的Flash设备,可以仿照内核physmap.c文件,将mtd_chip_driver名称放在一个指针数组中,使用时遍历指针数组中每一项内容进行匹配,直到匹配完成返回。
3.2 drv->probe(map)
这里我们依然以cfi_chipdrv 为例,其对应的probe函数为cfi_probe:
static struct chip_probe cfi_chip_probe = { .name = "CFI", .probe_chip = cfi_probe_chip }; struct mtd_info *cfi_probe(struct map_info *map) { /* * Just use the generic probe stuff to call our CFI-specific * chip_probe routine in all the possible permutations, etc. */ return mtd_do_chip_probe(map, &cfi_chip_probe); } struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp) { struct mtd_info *mtd = NULL; struct cfi_private *cfi; /* First probe the map to see if we have CFI stuff there. */ cfi = genprobe_ident_chips(map, cp); // 这里通过命令进入CFI模式,读取芯片信息 if (!cfi) return NULL; map->fldrv_priv = cfi; /* OK we liked it. Now find a driver for the command set it talks */ mtd = check_cmd_set(map, 1); /* First the primary cmdset 根据芯片信息中的P_ID来嗲用对应接口申请并设置mtd_info */ if (!mtd) mtd = check_cmd_set(map, 0); /* Then the secondary */ if (mtd) { if (mtd->size > map->size) { printk(KERN_WARNING "Reducing visibility of %ldKiB chip to %ldKiB\n", (unsigned long)mtd->size >> 10, (unsigned long)map->size >> 10); mtd->size = map->size; } return mtd; } printk(KERN_WARNING"gen_probe: No supported Vendor Command Set found\n"); kfree(cfi->cfiq); kfree(cfi); map->fldrv_priv = NULL;
先调用genprobe_ident_chips得到cfi_private结构,再判断是哪种指令集。Flash的指令集有Intel、AMD、STAA指令,根据不同的指令,需要设置不同的操作。
关于该函数的细节这里不去深究了,有兴趣的可以参考博客《Linux驱动:Nor flash驱动看这一篇就够了》。
3.2.1 genprobe_ident_chips
genprobe_ident_chip定义在drivers/mtd/chips/gen_probe.c:
static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chip_probe *cp) { struct cfi_private cfi; struct cfi_private *retcfi; unsigned long *chip_map; int i, j, mapsize; int max_chips; memset(&cfi, 0, sizeof(cfi)); /* Call the probetype-specific code with all permutations of interleave and device type, etc. */ if (!genprobe_new_chip(map, cp, &cfi)) { /* The probe didn't like it */ pr_debug("%s: Found no %s device at location zero\n", cp->name, map->name); return NULL; } #if 0 /* Let the CFI probe routine do this sanity check. The Intel and AMD probe routines won't ever return a broken CFI structure anyway, because they make them up themselves. */ if (cfi.cfiq->NumEraseRegions == 0) { printk(KERN_WARNING "Number of erase regions is zero\n"); kfree(cfi.cfiq); return NULL; } #endif cfi.chipshift = cfi.cfiq->DevSize; if (cfi_interleave_is_1(&cfi)) { ; } else if (cfi_interleave_is_2(&cfi)) { cfi.chipshift++; } else if (cfi_interleave_is_4((&cfi))) { cfi.chipshift += 2; } else if (cfi_interleave_is_8(&cfi)) { cfi.chipshift += 3; } else { BUG(); } cfi.numchips = 1; /* * Allocate memory for bitmap of valid chips. * Align bitmap storage size to full byte. */ max_chips = map->size >> cfi.chipshift; if (!max_chips) { printk(KERN_WARNING "NOR chip too large to fit in mapping. Attempting to cope...\n"); max_chips = 1; } mapsize = sizeof(long) * DIV_ROUND_UP(max_chips, BITS_PER_LONG); chip_map = kzalloc(mapsize, GFP_KERNEL); if (!chip_map) { kfree(cfi.cfiq); return NULL; } set_bit(0, chip_map); /* Mark first chip valid */ /* * Now probe for other chips, checking sensibly for aliases while * we're at it. The new_chip probe above should have let the first * chip in read mode. */ for (i = 1; i < max_chips; i++) { cp->probe_chip(map, i << cfi.chipshift, chip_map, &cfi); } /* * Now allocate the space for the structures we need to return to * our caller, and copy the appropriate data into them. */ retcfi = kmalloc(struct_size(retcfi, chips, cfi.numchips), GFP_KERNEL); if (!retcfi) { kfree(cfi.cfiq); kfree(chip_map); return NULL; } memcpy(retcfi, &cfi, sizeof(cfi)); memset(&retcfi->chips[0], 0, sizeof(struct flchip) * cfi.numchips); for (i = 0, j = 0; (j < cfi.numchips) && (i < max_chips); i++) { if(test_bit(i, chip_map)) { struct flchip *pchip = &retcfi->chips[j++]; pchip->start = (i << cfi.chipshift); pchip->state = FL_READY; init_waitqueue_head(&pchip->wq); mutex_init(&pchip->mutex); } } kfree(chip_map); return retcfi; }
3.2.2 check_cmd_set
check_cmd_set定义在drivers/mtd/chips/gen_probe.c:
static struct mtd_info *check_cmd_set(struct map_info *map, int primary) { struct cfi_private *cfi = map->fldrv_priv; __u16 type = primary?cfi->cfiq->P_ID:cfi->cfiq->A_ID; if (type == P_ID_NONE || type == P_ID_RESERVED) return NULL; switch(type){ /* We need these for the !CONFIG_MODULES case, because symbol_get() doesn't work there */ #ifdef CONFIG_MTD_CFI_INTELEXT case P_ID_INTEL_EXT: case P_ID_INTEL_STD: case P_ID_INTEL_PERFORMANCE: return cfi_cmdset_0001(map, primary); #endif #ifdef CONFIG_MTD_CFI_AMDSTD // menuconfig需要配置该项 case P_ID_AMD_STD: case P_ID_SST_OLD: case P_ID_WINBOND: return cfi_cmdset_0002(map, primary); #endif #ifdef CONFIG_MTD_CFI_STAA case P_ID_ST_ADV: return cfi_cmdset_0020(map, primary); #endif default: return cfi_cmdset_unknown(map, primary); } }
四、测试内核Nor Flash驱动
我们需要重新配置内核,使用内核自带的Nor Flash驱动,具体步骤如下。我们切换到linux内核目录下:
root@zhengyang:~# cd /work/sambashare/linux-5.2.8/
在linux内核根目录下执行,生成默认配置文件.config:
make distclean make s3c2440_defconfig # 这个是我之前配置的
进行内核配置,系统首先读取arch/arm/Kconfig生成整个配置界面:
root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig
配置如下:
Device Drivers ---> <*> Memory Technology Device (MTD) support ---> RAM/ROM/Flash chip drivers ---> // 会将gen_probe.ko、jedec_probe.ko、cfi_probe.ko等编译进内核 drivers/mtd/chips/Kconfig文件中配置 [*] Detect flash chips by Common Flash Interface (CFI) probe // cfi_probe.ko CFI标准Flash驱动 [*] Detect non-CFI AMD/JEDEC-compatible flash chips // jedec_probe.ko JEDEC标准Flash驱动 [*] Supprot for CFI command set 0002 (AMD/Fujitsu/Spansion chips) // 支持CFI命令集0002 我们所使用芯片为AMD Mapping drivers for chip access ---> <M> Flash device in physical memory map // 这里并没有编译进入内核,而是编译为模块文件 [*] Physmap compat support (0x00000000) Physical start address of flash mapping(New) // 设置物理基地址 (0x00200000) Physical length of flash mapping (New) // 设置容量长度,必须大于等于自身Nor Flash的2MB (2) Bank width in octets (News) // 因为字节位宽为16,所以设置为2
如果选中了Physmap compat support,还需要设置它下面的三项内容,分别是Flash映射的物理起始地址、映射的长度、以及位宽。
这些配置信息最终在编译内核时自动生成宏定义,存放在.config文件中,当执行make时,各层的Makefile会根据.config文件中的编译选项来决定哪些文件会被编译到内核中,或者编译成模块。
注:
- 我们知道如果定义了CONFIG_MTD_PHYSMAP_COMPAT宏,将会进行platform设备的注册。如果你好奇CONFIG_MTD_PHYSMAP_COMPAT宏是怎么生成的,不妨研究一下drivers/mtd/maps/Kconfig文件。
- CONFIG_MTD_PHYSMAP_XXXX配置项的生成同上;
修改完配置后,保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:
mv s3c2440_defconfig ./arch/arm/configs/
4.1 编译内核
编译内核:
make s3c2440_defconfig make V=1 uImage
将uImage复制到tftp服务器路径下:
root@zhengyang:/work/sambashare/linux-5.2.8# cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/
4.2 烧录内核
以NOR方式启动,这样Nor Flash地址才能映射到0x00000000。开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。
设置开发板ip地址,从而可以使用网络服务:
SMDK2440 # set ipaddr 192.168.0.105 SMDK2440 # save Saving Environment to NAND... Erasing NAND... Erasing at 0x40000 -- 100% complete. Writing to NAND... OK SMDK2440 # ping 192.168.0.200 dm9000 i/o: 0x20000000, id: 0x90000a46 DM9000: running in 16 bit mode MAC: 08:00:3e:26:0a:5b operating at unknown: 0 mode Using dm9000 device host 192.168.0.200 is alive
设置tftp服务器地址,也就是我们ubuntu服务器地址:
set serverip 192.168.0.200 save
下载内核到内存,并写NAND FLASH:
tftp 30000000 uImage nand erase.part kernel nand write 30000000 kernel bootm
4.3 编译模块
编译模块:
make modules
如下图所示,可以看到physmap.c编译成.ko模块了:
CC drivers/mtd/maps/physmap.mod.o LD [M] drivers/mtd/maps/physmap.ko
将驱动模块复制到nfs文件系统:
root@zhengyang:/work/sambashare/linux-5.2.8# cp drivers/mtd/maps/physmap.ko /work/nfs_root/rootfs/
4.4 测试
以NOR方式重启开发板,加载驱动:
[root@zy:/]# insmod physmap.ko physmap-flash physmap-flash.0: physmap platform flash device: [mem 0x00000000-0x001fffff] physmap-flash.0: Found 1 x16 devices at 0x0 in 16-bit bank. Manufacturer ID 0x0000bf Chip ID 0x00234b number of CFI chips: 1
这里我们发现Nor Flash芯片使用CFI探测到了,因此就不会使用JEDEC进行探测了,这里输出了探测到的芯片的id信息。
如果我们在编译内核时没有配置cfi_probe.ko驱动,那么将会进行JEDEC探测,并且在jedec_table数组中找到匹配的芯片项目。jedec_table数组定义在drivers/mtd/chips/jedec_probe.c,我们定位到芯片型号为SST39VF1601的数组项:
{ .mfr_id = CFI_MFR_SST, /* should be CFI */ .dev_id = SST39VF1601, .name = "SST 39VF1601", .devtypes = CFI_DEVICETYPE_X16, .uaddr = MTD_UADDR_0xAAAA_0x5555, .dev_size = SIZE_2MiB, .cmd_set = P_ID_AMD_STD, // 0x0002 .nr_regions = 2, .regions = { ERASEINFO(0x1000,256), ERASEINFO(0x1000,256) } }
- 厂家ID:mfr_id为0xBF,即CFI_MFR_SST;
- 设备ID:dev_id为0x234B,即SST39VF1601;
- 名字:name为SST 39VF1601;
- uaddr:解锁地址改为MTD_UADDR_0xAAAA_0x5555。Nor Flash的指令集中有一些命令在写入命令时序之前,需要先进行解锁,即向一些地址写入固定值,解锁地址可以查阅手册。
- 大小:DevSize为SIZE_2MiB;
- NumEraseRegions:NOR的结构不同,擦除块是不同的。可以查阅手册,有多少种块,就写几。
- regions:说明擦除块的分布情况,从上往下分别是NOR中地址从低到高排列;
4.4.1 查看MTD块设备
查看MTD设备文件:
[root@zy:/]# ls -l /dev/mtd* crw-rw---- 1 0 0 90, 0 Jan 1 00:00 /dev/mtd0 crw-rw---- 1 0 0 90, 1 Jan 1 00:00 /dev/mtd0ro brw-rw---- 1 0 0 31, 0 Jan 1 00:00 /dev/mtdblock0
可以看到2个mtd0字符设备,一个mtd0块设备,这是因为我们并没有对Nor Flash进行分区。
五、编写Nor Flash驱动
在之前的章节,我们已经分析了内核自带的Nor Flash驱动的源码。这里我们将总结一下驱动编写的步骤。
由于MTD设备驱动已经帮我实现了MTD块设备、以及MTD字符设备驱动的编写。而我们要做的主要就是:
- 动态申请struct map_info map;
- 动态申请struct mtd_info mtd;
- 初始化map成员,名称、物理地址、虚拟地址、容量、字节宽度等;
- 通过do_map_probe()来识别芯片,并初始化mtd;
- 注册MTD设备;
注:这里没有向Nand Flash驱动一样定义读、写等函数,这是因为Nor Flash可以向内存一样进行读写,因此其读写函数是通用的,使用内核默认的即可。
5.1 项目结构
我们在/work/sambashare/drivers路径下创建项目18.nor_flash_dev,创建为nor_flash_dev.c。
5.2 模块入口函数
- 使用kzalloc函数为map_info、mtd_info动态申请内存;
- 设置map_info结构体成员;
- 设置mtd_info结构体成员;
- 以map_info为参数调用do_map_probe探测Nor Flash;
- mtd_info和mtd_partition为参数调用mtd_device_parse_register()进行MTD设备注册;
5.3 模块出口函数
- 卸载MTD设备;
- 释放mtd_info、map_info;
5.4 nor_flash_dev.c
#include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/device.h> #include <linux/platform_device.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> #include <linux/mtd/partitions.h> /* 全局变量 */ static struct mtd_info *s3c_mtd; static struct map_info *s3c_map; /* 指针数组,数组每个元素都指向一个字符串 */ static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL}; static const char *part_probe_types[] = { "cmdlinepart", "RedBoot", NULL }; /* 分区信息 */ static struct mtd_partition s3c_nor_part[] = { [0] = { .name = "bootloader", .size = 0x00040000, .offset = 0, }, [1] = { .name = "root", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, } }; /* *init入口函数 */ static int s3c_nor_init(void) { const char **probe_type = rom_probe_types; const char **part_types = part_probe_types; /* 1. 分配map_info 结构体 */ s3c_map = kzalloc(sizeof(struct map_info), GFP_KERNEL); /* 2. 设置map_info 结构体 */ s3c_map->name = "nor_flash"; s3c_map->phys = 0x0; //物理地址 s3c_map->size = 0x1000000; //长度必须大于等于norflash的2M容量 s3c_map->bankwidth = 2; //16位宽 s3c_map->virt = ioremap(s3c_map->phys, s3c_map->size); //虚拟地址 simple_map_init(s3c_map); /* 3. 设置mtd_info 结构体 */ for (; s3c_mtd == NULL && *probe_type != NULL; probe_type++) s3c_mtd = do_map_probe(*probe_type, s3c_map); if (!s3c_mtd){ printk("not available norflash !\r\n"); goto err_out; } s3c_mtd->owner = THIS_MODULE; /* 4.使用mtd_device_parse_register来注册MTD字符/块设备 */ mtd_device_parse_register(s3c_mtd, part_types, NULL, s3c_nor_part, ARRAY_SIZE(s3c_nor_part)); return 0; err_out: iounmap(s3c_map->virt); //取消虚拟地址映射 kfree(s3c_map); kfree(s3c_mtd); return 0; } /* * exit出口函数 */ static void s3c_nor_exit(void) { mtd_device_unregister(s3c_mtd); //卸载MTD设备 iounmap(s3c_map->virt); //取消虚拟地址映射 kfree(s3c_map); kfree(s3c_mtd); } module_init(s3c_nor_init); module_exit(s3c_nor_exit); MODULE_LICENSE("GPL");
5.5 Makefile
KERN_DIR :=/work/sambashare/linux-5.2.8 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += nor_flash_dev.o
5.6 测试
5.6.1 编译Nor Flash驱动
编译Nor Flash驱动,将 nor_flash_dev.ko拷贝到nfs根文件系统。
root@zhengyang:/work/sambashare/drivers/18.nor_flash_dev# cp /work/sambashare/drivers/18.nor_flash_dev/nor_flash_dev.ko /work/nfs_root/rootfs
以NOR方式重启开发板,加载驱动:
[root@zy:/]# insmod nor_flash_dev.ko nor_flash_dev: loading out-of-tree module taints kernel. nor_flash: Found 1 x16 devices at 0x0 in 16-bit bank. Manufacturer ID 0x0000bf Chip ID 0x00234b number of CFI chips: 1 Creating 2 MTD partitions on "nor_flash": 0x000000000000-0x000000040000 : "bootloader" 0x000000040000-0x000000200000 : "root"
可以看到创建了两个分区,bootloader和root。
5.6.2 挂载MTD块设备
查看MDT设备文件:
[root@zy:/]# ls -l /dev/mtd* random: crng init done crw-rw---- 1 0 0 90, 0 Jan 1 00:00 /dev/mtd0 crw-rw---- 1 0 0 90, 1 Jan 1 00:00 /dev/mtd0ro crw-rw---- 1 0 0 90, 2 Jan 1 00:00 /dev/mtd1 crw-rw---- 1 0 0 90, 3 Jan 1 00:00 /dev/mtd1ro brw-rw---- 1 0 0 31, 0 Jan 1 00:00 /dev/mtdblock0 brw-rw---- 1 0 0 31, 1 Jan 1 00:00 /dev/mtdblock1
对于Nor Flash设备一般采用jffs2文件系统,这里我们采用flash_eraseall -j /dev/mtd1命令格式化分区,注意格式化分区是以MTD的字符设备进行的(每个分区的字符设备,其实就是对应每个分区块设备,flash_eraseall命令是以ioctl等基础实现的,而块设备不支持ioctl,只有字符设备支持):
[root@zy:/]# flash_eraseall -j /dev/mtd1 Erasing 4 Kibyte @ 1c0000 - 100% complete.Cleanmarker written at 1bf000.
-j代表格式化为jffs2文件类型,可通过flash_eraseall --help查看应用程序的帮助信息。
[root@zy:/]# flash_eraseall --help BusyBox v1.25.0 (2022-02-12 01:26:48 CST) multi-call binary. Usage: flash_eraseall [-jNq] MTD_DEVICE Erase an MTD device -j Format the device for jffs2 -N Don't skip bad blocks -q Don't display progress messages
注:
- 如果根文件系统没有flash_eraseall命令,需要配置busybox(Miscellaneous Utilities配置项 flash_eraseall),并重新编译。可参考Mini2440之linux内核移植之yaffs2根文件系统移植;
- 编译linux内核时,需要支持jffs2文系统(File systems->Miscellaneous filesystems->Journaling Flash File System V2 (JFFS2)support);
挂载块设备到/mnt目录,执行如下命令,-t代表挂载文件系统的类型:
[root@zy:/]# mount -t jffs2 /dev/mtdblock1 /mnt [root@zy:/]# ls -l /mnt total 0
接下来就可以在/mnt文件夹下任意读写文件了,最终会保存在Nor Flash的mtdblock1块设备上:
[root@zy:/]# cd /mnt [root@zy:/mnt]# touch test [root@zy:/mnt]# echo "111" > test [root@zy:/mnt]# cat test 111 [root@zy:/mnt]# ls -l total 1 -rw-r--r-- 1 0 0 4 Jan 1 00:03 test
卸载块设备:
[root@zy:/mnt]# cd .. [root@zy:/]# umount /dev/mtdblock1
六、代码下载
Young / s3c2440_project[drivers]
参考文章