1. MTD chip driver模块的注册

在MTD子系统中,不管你是什么类型的存储芯片,RAM也好,ROM也好,CFI接口flash或者JEDEC接口的flash,他们的driver都是以mtd_chip_driver结构体描述的。具体类型的存储芯片的driver模块,有定义一个mtd_chip_driver结构体,并且通过调用register_mtd_chip_driver注册到MTD子系统。

struct mtd_chip_driver {struct mtd_info *(*probe)(struct map_info *map);void (*destroy)(struct mtd_info *);struct module *module;char *name;struct list_head list;
};复制代码

比如cfi接口的flash定义如下,文件位置drivers/mtd/chips/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;
}
复制代码

比如jedec接口的flash定义如下,文件位置drivers/mtd/chips/jedec_probe.c

static struct mtd_chip_driver jedec_chipdrv = {.probe    = jedec_probe,.name    = "jedec_probe",.module  = THIS_MODULE
};static int __init jedec_probe_init(void)
{register_mtd_chip_driver(&jedec_chipdrv);return 0;
}复制代码

也就是说,每种chip driver都是一个独立的模块,但是都定义了自己的mtd_chip_driver结构体,并在模块初始化函数里调用了register_mtd_chip_driver。

接下来看看register_mtd_chip_driver函数的实现,其实就是把mtd_chip_driver结构体都链接到了一个链表上。

void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{spin_lock(&chip_drvs_lock);list_add(&drv->list, &chip_drvs_list);spin_unlock(&chip_drvs_lock);
}
复制代码

2. MTD chip device的定义

如下是设备树中关于cfi-flash的配置。根据如下的定义,内核在解析设备树后,会生成一个关于cfi-flash这个节点对应的platform_device。类似的可以根据自己平台具体情况定义RAM, ROM等存储芯片。

/ {#address-cells = <1>;#size-cells = <1>;...smb {compatible = "simple-bus";#address-cells = <2>;#size-cells = <1>;// ranges配置了5个片选对应到的cpu地址空间范围//range语法:子地址(2)  父地址(1) 子地址空间长度//第一项表示:片选0  偏移0  映射到cpu的0x40000000地址, 映射长度为0x04000000//第二项表示:片选1  偏移0  映射到cpu的0x44000000地址, 映射长度为0x04000000//后面的依次类推ranges = <0 0 0x40000000 0x04000000>,<1 0 0x44000000 0x04000000>,<2 0 0x48000000 0x04000000>,<3 0 0x4c000000 0x04000000>,<7 0 0x10000000 0x00020000>;motherboard {model = "V2M-P1";arm,hbi = <0x190>;arm,vexpress,site = <0>;compatible = "arm,vexpress,v2m-p1", "simple-bus";#address-cells = <2>; /* SMB chipselect number and offset */#size-cells = <1>;#interrupt-cells = <1>;ranges;flash@0,00000000 {compatible = "arm,vexpress-flash", "cfi-flash";// norflash的片选0的 偏移0 映射到ranges定义的片选0//所以这里定义了两片norflash//第一片映射到cpu地址0x40000000,//第二片映射到cpu地址0x44000000,两片的长度都是0x04000000(64MB)//这里定义的是两个板块,每个板块64MB,//从log每块都支持x8,x16两种模式,可在硬件上通过一个BYTE# pin选择reg = <0 0x00000000 0x04000000>,<1 0x00000000 0x04000000>;//定义总线位宽为4B=32bitbank-width = <4>;};...} //end of motherboard...} //end of smb...
}
复制代码

3. 把MTD chip与MTD driver关联起来

Linux 4.x 通过drivers\mtd\mapsPhysmap_of.c这一个模块,就实现了各种类型存储器与对应驱动的关联,代码尽显灵活和抽象。从下面的代码的of_flash_match定义的表来看,在设备树中定义的"cfi-flash","jedec-flash","mtd-ram","mtd-rom","rom"等类型的节点,都使用这个模块的代码把chip与driver关联起来。从下面的代码可以看出,不管你是Norflash还是nandflash,只要是cfi接口的,都会匹配到"cfi-flash"。

TODO:这里有个疑问,"mtd-ram"是指系统中的内存吗?

static struct of_device_id of_flash_match[] = {{//compatible字段指定了mtd chip的类型,//可取值为"cfi-flash", "jedec-flash","mtd-ram","mtd-rom"任何一个..compatible    = "cfi-flash",// data字段描述的是这个设备probe的类型,最终会作为do_map_probe函数的参数.data       = (void *)"cfi_probe",},{/* FIXME: JEDEC chips can't be safely and reliably* probed, although the mtd code gets it right in* practice most of the time.  We should use the* vendor and device ids specified by the binding to* bypass the heuristic probe code, but the mtd layer* provides, at present, no interface for doing so* :(. */.compatible   = "jedec-flash",.data        = (void *)"jedec_probe",},{.compatible     = "mtd-ram",.data           = (void *)"map_ram",},{.compatible     = "mtd-rom",.data           = (void *)"map_rom",},{.type       = "rom",.compatible  = "direct-mapped"},{ },
};
MODULE_DEVICE_TABLE(of, of_flash_match);static struct platform_driver of_flash_driver = {.driver = {.name = "of-flash",.of_match_table = of_flash_match,},.probe      = of_flash_probe,.remove       = of_flash_remove,
};复制代码

3.1 of_flash_probe的实现

    // 根据设备树中定义的compatible属性来查找of_flash_match表中对应的匹配项// 找到匹配的项则返回指向of_flash_match数组项的指针match = of_match_device(of_flash_match, &dev->dev);if (!match)return -EINVAL;probe_type = match->data;// 根据flash@0,00000000节点的父亲节点的address-cells和size-cells的大小,// 来计算出reg一个尖括号<>定义的元组的大小=(2+1)*4=12字节reg_tuple_size = (of_n_addr_cells(dp) + of_n_size_cells(dp)) * sizeof(u32);of_property_read_string(dp, "linux,mtd-name", &mtd_name);/** Get number of "reg" tuples. Scan for MTD devices on area's* described by each "reg" region. This makes it possible (including* the concat support) to support the Intel P30 48F4400 chips which* consists internally of 2 non-identical NOR chips on one die.*/p = of_get_property(dp, "reg", &count);if (count % reg_tuple_size != 0) {dev_err(&dev->dev, "Malformed reg property on %s\n",dev->dev.of_node->full_name);err = -EINVAL;goto err_flash_remove;}// 定义reg的多个元组,可支持多个板块// 这里定义了2个元组,所以count=24B/12=2count /= reg_tuple_size;map_indirect = of_property_read_bool(dp, "no-unaligned-direct-access");
复制代码

分配管理结构体内存,一个reg元组,定义了一个板块,对应一个of_flash_list结构体。

    struct of_flash_list {struct mtd_info *mtd;struct map_info map;struct resource *res;};struct of_flash {struct mtd_info      *cmtd;int list_size; /* number of elements in of_flash_list */struct of_flash_list  list[0];};mtd_list = kzalloc(sizeof(*mtd_list) * count, GFP_KERNEL);if (!mtd_list)goto err_flash_remove;// 一个设备树节点分配一个of_flash结构体// of_flash结构体可能定义多个reg元组,对应多个of_flash_listinfo = devm_kzalloc(&dev->dev,sizeof(struct of_flash) +sizeof(struct of_flash_list) * count, GFP_KERNEL);if (!info)goto err_flash_remove;dev_set_drvdata(&dev->dev, info);复制代码

根据DTS中定义的板块的数量,分别读出DTS中对应的地址空间映射和总线位宽配置,然后根据这些信息初始化map_info结构体。调用do_map_probe去probe特定的硬件,返回描述闪存物理信息的mtd_info结构体。

for (i = 0; i < count; i++) {err = -ENXIO;//把在DTS中定义的地址空间范围转化为resource结构体if (of_address_to_resource(dp, i, &res)) {/** Continue with next register tuple if this* one is not mappable*/continue;}dev_dbg(&dev->dev, "of_flash device: %pR\n", &res);err = -EBUSY;res_size = resource_size(&res);//本案中定义的2个板块,定义了2个地址空间//第一块,0x40000000-0x43ffffff  64MB//第二块,0x44000000-0x47ffffff  64MB//请求resource,统一资源管理,避免资源冲突等问题info->list[i].res = request_mem_region(res.start, res_size,dev_name(&dev->dev));if (!info->list[i].res)goto err_out;err = -ENXIO;//读取总线位宽的配置width = of_get_property(dp, "bank-width", NULL);if (!width) {dev_err(&dev->dev, "Can't get bank width from device"" tree\n");goto err_out;}//初始化map_info结构体,这些可看做配置信息,后面会通过cfi读出芯片的实际配置信息。info->list[i].map.name = mtd_name ?: dev_name(&dev->dev);//板块物理地址的起始位置和大小info->list[i].map.phys = res.start;info->list[i].map.size = res_size;//设置板块的总线位宽//DTB是以大端存储的,所以需要转为cpu字节序info->list[i].map.bankwidth = be32_to_cpup(width);info->list[i].map.device_node = dp;err = -ENOMEM;//把板块的物理地址映射到内核的线性空间info->list[i].map.virt = ioremap(info->list[i].map.phys,info->list[i].map.size);if (!info->list[i].map.virt) {dev_err(&dev->dev, "Failed to ioremap() flash"" region\n");goto err_out;}//当需要支持的非线性空间的映射时,需要开启配置开关CONFIG_MTD_COMPLEX_MAPPINGS//这里我没有配置,所以该操作就是检查下从DTS读出来的总线位宽是否被内核支持simple_map_init(&info->list[i].map);/** On some platforms (e.g. MPC5200) a direct 1:1 mapping* may cause problems with JFFS2 usage, as the local bus (LPB)* doesn't support unaligned accesses as implemented in the* JFFS2 code via memcpy(). By setting NO_XIP, the* flash will not be exposed directly to the MTD users* (e.g. JFFS2) any more.*/if (map_indirect)info->list[i].map.phys = NO_XIP;if (probe_type) {info->list[i].mtd = do_map_probe(probe_type,&info->list[i].map);} else {// 兼容老式的probe接口,可以在DTS中定义"probe-type"属性,但是实际底层都是调用do_map_probe函数info->list[i].mtd = obsolete_probe(dev,&info->list[i].map);}...}
复制代码

3.2 do_map_probe函数

此函数用了典型的工厂方法设计模式,通过传入probe_type参数,指定要probe的类型,进而调用特定类型接口的probe函数,返回描述闪存信息mtd_info结构体。

struct mtd_info *do_map_probe(const char *name, struct map_info *map)
{struct mtd_chip_driver *drv;struct mtd_info *ret;//根据name查找链表chip_drvs_list,找到mtd_chip_driver结构体// 当找到定义该结构体的chip driver模块时,会增加该chip driver模块的引用计数,避免模块在使用过程中被异步卸载//比如现在name="cfi_probe",定义该接口probe的模块是cfi_probe.c,增加的引用计数是cfi_probe.c这个模块,这个模块是probe-only的,在probe完后,是可以卸载的drv = get_mtd_chip_driver(name);//如果特定接口的chip probe driver模块是编译成独立的模块,请求加载该模块驱动if (!drv && !request_module("%s", name))drv = get_mtd_chip_driver(name);if (!drv)return NULL;//调用特定接口的chip probe函数ret = drv->probe(map);/* We decrease the use count here. It may have been aprobe-only module, which is no longer required from thispoint, having given us a handle on (and increased the usecount of) the actual driver code.*/module_put(drv->module);return ret;
}
复制代码

drv->probe调用的是特定接口的chip实现的 probe函数,比如cfi接口的probe实现如下,它调用的是通用的probe函数mtd_do_chip_probe,传递了cfi_chip_probe结构体指针参数。

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);
}
复制代码

3.3 mtd_do_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);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 */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;return NULL;
}
复制代码

3.3.1 genprobe_ident_chips尝试probe一个新的CFI chip

这里注意下几个编程要点:

  1. 读写,要按照总线位宽读写,不是FLASH芯片位宽(例如背靠背)
  2. 寻址,程序要访问的地址和FLASH芯片地址引脚得到的值是不一样的,例如16位的FLASH芯片,对于CPU,0x00和0x01表示2个不同的字节,但是到了FLASH引脚得到的都是0,也就是都指向FLASH的第一个WORD。可以认为地址总线的bit0悬空,或者认为转换总线, bit0上实际输出的是bit1。这个解释了要点1
  3. 芯片手册提到偏移量都是基于WORD的,而WORD的位宽取决于芯片的位宽,因此在下命令的时候,实际偏移=手册偏移*buswidth/8。
  4. 芯片手册提到的变量长度(典型如CFI信息)例如2,指的是,变量是个16bit数,但是读的时候,要读2个WORD,然后把每个WORD的低8位拼成1个16bit数。读WORD再拼凑确实挺麻烦,尤其是读取大结构的时候,不过参照cfi_util.c的cfi_read_pri函数的做法就简单了
  5. 背靠背,也就是比方说2块16位的芯片一起接在32位的总线上。带来的就是寻址的问题,很显然,首先要按32位读写;其次就是下命令的地址,实际偏移=手册偏移interleavedevice_type/8,device_type=buswidth/interleave,而buswidth这个时候是32(总线位宽)。另外就是背靠背的时候,命令和返回的状态码是“双份的”,例如2块16位背靠背,读命令是0x00ff00ff

map_bankwidth(map) 表示flash总线位宽,1表示8bit,2表示16bit,4表示32bit cfi->interleave表示几块chip并列即背靠背,可取值1,2,4,8 cfi->device_type表示chip内部芯片位宽,即chip字长,系统定义了3种device_type

#define CFI_DEVICETYPE_X8  (8 / 8)
#define CFI_DEVICETYPE_X16 (16 / 8)
#define CFI_DEVICETYPE_X32 (32 / 8)
复制代码

总线位宽=device_type*interleave

举个例子,2块16位的芯片一起接在32位的总线上,也可以4块8位的芯片一起接在32位的总线上。

static int genprobe_new_chip(struct map_info *map, struct chip_probe *cp,struct cfi_private *cfi)
{//根据公式,总线位宽=device_type*interleave//在总线位宽确定的情况下,device_type最小取1,得到max_chips//device_type最大取4,得到min_chipsint min_chips = (map_bankwidth(map)/4?:1); /* At most 4-bytes wide. */int max_chips = map_bankwidth(map); /* And minimum 1 */int nr_chips, type;//interleave可取值1,2,4,8,nr_chips >>= 1相当于nr_chips=nr_chips/2//枚举是让尽量多的chip并列,减少对chip本身字长的要求for (nr_chips = max_chips; nr_chips >= min_chips; nr_chips >>= 1) {//检查下内核是否支持nr_chips并列if (!cfi_interleave_supported(nr_chips))continue;cfi->interleave = nr_chips;/* Minimum device size. Don't look for one 8-bit devicein a 16-bit bus, etc. */type = map_bankwidth(map) / nr_chips;for (; type <= CFI_DEVICETYPE_X32; type<<=1) {cfi->device_type = type;//一旦probe成功,退出函数if (cp->probe_chip(map, 0, NULL, cfi))return 1;}}return 0;
}复制代码
3.3.1.1 cfi_probe_chip probe第1个chip
/* check for QRY.in: interleave,type,moderet: table index, <0 for error*/static int __xipram cfi_probe_chip(struct map_info *map, __u32 base,unsigned long *chip_map, struct cfi_private *cfi)
{int i;// map->size为板块的物理地址空间大小,从DTS中读出的if ((base + 0) >= map->size) {printk(KERN_NOTICE"Probe at base[0x00](0x%08lx) past the end of the map(0x%08lx)\n",(unsigned long)base, map->size -1);return 0;}//base当前已经probe了的chip size, 一般norflash sector最小大小为256B//也就是还剩下不到1个sector的大小要probe,可以认为不用probe了if ((base + 0xff) >= map->size) {printk(KERN_NOTICE"Probe at base[0x55](0x%08lx) past the end of the map(0x%08lx)\n",(unsigned long)base + 0x55, map->size -1);return 0;}xip_disable();// cfi_qry_mode_on进入norflash的cfi查询模式,如果支持查询模式返回1if (!cfi_qry_mode_on(base, map, cfi)) {xip_enable(base, map, cfi);return 0;}//第一次调用该函数时,numchips=0if (!cfi->numchips) {/* This is the first time we're called. Set up the CFIstuff accordingly and return */return cfi_chip_setup(map, cfi);}
复制代码

cfi_chip_setup函数主要功能是probe cfi类型的chip,返回1表示probe chip成功了,0表示失败。 根据CFI查询模式和device ID等信息,初始化了cfi_ident结构体以及 cfi_private->mfr, cfi_private->id;

static int __xipram cfi_chip_setup(struct map_info *map,struct cfi_private *cfi)
{//实际偏移=手册偏移*总线位宽/8//所以偏移因子是总线位宽/8,即转化为字节为单位int ofs_factor = cfi->interleave*cfi->device_type;__u32 base = 0;//从cfi的手册知道0x2C是查询norflash有多少个擦除区(erase region)//根据手册,擦除区是指具有同样大小的连续的擦除块( Erase Block),//注意一定要是连续的擦除块,不连续的就算两个区了//本实验chip, num_erase_regions = 1int num_erase_regions = cfi_read_query(map, base + (0x10 + 28)*ofs_factor);int i;int addr_unlock1 = 0x555, addr_unlock2 = 0x2AA;xip_enable(base, map, cfi);
#ifdef DEBUG_CFIprintk("Number of erase regions: %d\n", num_erase_regions);
#endif// num_erase_regions=0表没有擦除区,或者只能整个device都擦除if (!num_erase_regions)return 0;//在这个结构体尾部给每个擦除区分配4B空间放擦除区信息。cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL);if (!cfi->cfiq)return 0;memset(cfi->cfiq,0,sizeof(struct cfi_ident));cfi->cfi_mode = CFI_MODE_CFI;cfi->sector_erase_cmd = CMD(0x30);/* Read the CFI info structure */xip_disable_qry(base, map, cfi);//读取CFI的查询结构体,一次只能读1Bfor (i=0; i<(sizeof(struct cfi_ident) + num_erase_regions * 4); i++)((unsigned char *)cfi->cfiq)[i] = cfi_read_query(map,base + (0x10 + i)*ofs_factor);/* Do any necessary byteswapping */cfi->cfiq->P_ID = le16_to_cpu(cfi->cfiq->P_ID);cfi->cfiq->P_ADR = le16_to_cpu(cfi->cfiq->P_ADR);cfi->cfiq->A_ID = le16_to_cpu(cfi->cfiq->A_ID);cfi->cfiq->A_ADR = le16_to_cpu(cfi->cfiq->A_ADR);cfi->cfiq->InterfaceDesc = le16_to_cpu(cfi->cfiq->InterfaceDesc);cfi->cfiq->MaxBufWriteSize = le16_to_cpu(cfi->cfiq->MaxBufWriteSize);#ifdef DEBUG_CFI/* Dump the information therein */print_cfi_ident(cfi->cfiq);
#endiffor (i=0; i<cfi->cfiq->NumEraseRegions; i++) {cfi->cfiq->EraseRegionInfo[i] = le32_to_cpu(cfi->cfiq->EraseRegionInfo[i]);#ifdef DEBUG_CFIprintk("  Erase Region #%d: BlockSize 0x%4.4X bytes, %d blocks\n",i, (cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff,(cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1);
#endif}
复制代码

下图是某flash的device ID地址图,该地址空间提供了关于flash的制造商ID,device ID,扇区保护状态,以及其他的一些关于flash的特性。

有两种方法知道flash的类型,一种是传统的Autoselect,即device ID,另外一种是CFI,他们使用的地址空间是不同。

   if (cfi->cfiq->P_ID == P_ID_SST_OLD) {addr_unlock1 = 0x5555;addr_unlock2 = 0x2AAA;}/** Note we put the device back into Read Mode BEFORE going into Auto* Select Mode, as some devices support nesting of modes, others* don't. This way should always work.* On cmdset 0001 the writes of 0xaa and 0x55 are not needed, and* so should be treated as nops or illegal (and so put the device* back into Read Mode, which is a nop in this case).*/cfi_send_gen_cmd(0xf0,     0, base, map, cfi, cfi->device_type, NULL);cfi_send_gen_cmd(0xaa, addr_unlock1, base, map, cfi, cfi->device_type, NULL);cfi_send_gen_cmd(0x55, addr_unlock2, base, map, cfi, cfi->device_type, NULL);cfi_send_gen_cmd(0x90, addr_unlock1, base, map, cfi, cfi->device_type, NULL);// 以上命令序列是让flash进入Auto Select Mode,目的是为了读取flash的ID.cfi->mfr = cfi_read_query16(map, base);cfi->id = cfi_read_query16(map, base + ofs_factor);/* Get AMD/Spansion extended JEDEC ID */if (cfi->mfr == CFI_MFR_AMD && (cfi->id & 0xff) == 0x7e)cfi->id = cfi_read_query(map, base + 0xe * ofs_factor) << 8 |cfi_read_query(map, base + 0xf * ofs_factor);/* Put it back into Read Mode */// 发送0xF0 reset norflash, 重新返回到read mode// 发送0xFF 退出CFI查询模式cfi_qry_mode_off(base, map, cfi);xip_allowed(base, map);printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank. Manufacturer ID %#08x Chip ID %#08x\n",map->name, cfi->interleave, cfi->device_type*8, base,map->bankwidth*8, cfi->mfr, cfi->id);return 1;
}
复制代码
3.3.1.2 cfi_probe_chip probe剩余的chip实现
    if (!cfi->numchips) {/* This is the first time we're called. Set up the CFIstuff accordingly and return */return cfi_chip_setup(map, cfi);}//从第0个大CHIP开始时,核对已经probe过的大CHI中是否有别名//如果之前probe的有别名就不用probe了//TODO: 别名? 判断别名的原理是?/* Check each previous chip to see if it's an alias */for (i=0; i < (base >> cfi->chipshift); i++) {unsigned long start;if(!test_bit(i, chip_map)) { //当前位置没有有效的大CHIP/* Skip location; no valid chip at this address */continue;}start = i << cfi->chipshift;/* This chip should be in read mode if it's onewe've already touched. */if (cfi_qry_present(map, start, cfi)) {/* Eep. This chip also had the QRY marker.* Is it an alias for the new one? */cfi_qry_mode_off(start, map, cfi);/* If the QRY marker goes away, it's an alias */if (!cfi_qry_present(map, start, cfi)) {xip_allowed(base, map);printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n",map->name, base, start);return 0;}/* Yes, it's actually got QRY for data. Most* unfortunate. Stick the new chip in read mode* too and if it's the same, assume it's an alias. *//* FIXME: Use other modes to do a proper check */cfi_qry_mode_off(base, map, cfi);if (cfi_qry_present(map, base, cfi)) {xip_allowed(base, map);printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n",map->name, base, start);return 0;}}}// 程序能跑到这里,说明之前没有别名,实际probe到的大CHIP数++/* OK, if we got to here, then none of the previous chips appear tobe aliases for the current one. */set_bit((base >> cfi->chipshift), chip_map); /* Update chip map */cfi->numchips++;/* Put it back into Read Mode */cfi_qry_mode_off(base, map, cfi);xip_allowed(base, map);printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank\n",map->name, cfi->interleave, cfi->device_type*8, base,map->bankwidth*8);return 1;复制代码
3.3.1.3 genprobe_ident_chips实际probe一个新的CFI chip后的初始化
    // cfi.cfiq->DevSize表示该chip的大小,如果DevSize=n, 则chip容量为2^n 字节cfi.chipshift = cfi.cfiq->DevSize;//考虑一个模块中 背靠背的norflash chip的个数N// 多个chip是一样的,所以总大小是N的倍数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();}// 背靠背的多块norflash chip计作一个大CHIPcfi.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) { //DTS配置的总大小小于一块的大小,算做一个大CHIPprintk(KERN_WARNING "NOR chip too large to fit in mapping. Attempting to cope...\n");max_chips = 1;}以long为单位分配bitmapmapsize = 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 */// 再次调用cfi_probe_chip 去probe其余的chip/** 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);}// probe完了所有的chip,给该norflash模块重新分配cfi_private结构体,并为每个大CHIP分配//一个flchip结构体/** Now allocate the space for the structures we need to return to* our caller, and copy the appropriate data into them.*/retcfi = kmalloc(sizeof(struct cfi_private) + cfi.numchips * sizeof(struct flchip), 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)) {// 初始化有效的大CHIP结构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.3.2 check_cmd_set

probe完chip后,就可以根据CFI查询表中定义的算法命令集去调用产商特定的初始化函数。首先会尝试首选算法命令集,如果失败会再尝试备选算法命令集。check_cmd_set就是根据primary参数来选择首选/备选算法命令集的。

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_INTELEXTcase 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_AMDSTDcase P_ID_AMD_STD:case P_ID_SST_OLD:case P_ID_WINBOND:return cfi_cmdset_0002(map, primary);
#endif
#ifdef CONFIG_MTD_CFI_STAAcase P_ID_ST_ADV:return cfi_cmdset_0020(map, primary);
#endif// 用于支持自定义的算法命令集。// 该函数会根据从cfi查询表中读出来的P_ID/A_ID加载对应的cfi_cmdset_XXXX.c模块,然后调用该模块中的cfi_cmdset_XXXX函数default:return cfi_cmdset_unknown(map, primary);}
}
复制代码

kernel当前代码支持3个算法命令集。当然也支持完全自定义的算法命令集。

下面着重分析cfi_cmdset_0001,其他的命令集类似。

struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary)
{struct cfi_private *cfi = map->fldrv_priv;struct mtd_info *mtd;int i;mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);if (!mtd)return NULL;mtd->priv = map;mtd->type = MTD_NORFLASH;// 每种算法都有自己自定义的这些API/* Fill in the default mtd operations */mtd->_erase   = cfi_intelext_erase_varsize;mtd->_read    = cfi_intelext_read;mtd->_write   = cfi_intelext_write_words;mtd->_sync    = cfi_intelext_sync;mtd->_lock    = cfi_intelext_lock;mtd->_unlock  = cfi_intelext_unlock;mtd->_is_locked = cfi_intelext_is_locked;mtd->_suspend = cfi_intelext_suspend;mtd->_resume  = cfi_intelext_resume;mtd->flags   = MTD_CAP_NORFLASH;mtd->name    = map->name;//初始化写norflash的最小size,具体可参看该结构体的说明注释mtd->writesize = 1;//当写大块的数据时,使用这个大小,一般norflashmtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize;// 重启时,调用该回调mtd->reboot_notifier.notifier_call = cfi_intelext_reboot;// TODO: CFI VS jedec规范的区别if (cfi->cfi_mode == CFI_MODE_CFI) {/** It's a real CFI chip, not one for which the probe* routine faked a CFI structure. So we read the feature* table from it.*/__u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR;struct cfi_pri_intelext *extp;// 根据基本查询表中定义的扩展查询表的地址,去读扩展查询表的内容extp = read_pri_intelext(map, adr);if (!extp) {kfree(mtd);return NULL;}/* Install our own private info structure */cfi->cmdset_priv = extp;// 给某些产品打上补丁cfi_fixup(mtd, cfi_fixup_table);#ifdef DEBUG_CFI_FEATURES/* Tell the user about it in lots of lovely detail */cfi_tell_features(extp);
#endif//erase suspend后是否支持写操作if(extp->SuspendCmdSupport & 1) {printk(KERN_NOTICE "cfi_cmdset_0001: Erase suspend on write enabled\n");}}else if (cfi->cfi_mode == CFI_MODE_JEDEC) {/* Apply jedec specific fixups */cfi_fixup(mtd, jedec_fixup_table);}/* Apply generic fixups */cfi_fixup(mtd, fixup_table);//使用CFI查询的参数设置大CHIP的管理结构for (i=0; i< cfi->numchips; i++) {if (cfi->cfiq->WordWriteTimeoutTyp)cfi->chips[i].word_write_time =1<<cfi->cfiq->WordWriteTimeoutTyp;elsecfi->chips[i].word_write_time = 50000;if (cfi->cfiq->BufWriteTimeoutTyp)cfi->chips[i].buffer_write_time =1<<cfi->cfiq->BufWriteTimeoutTyp;/* No default; if it isn't specified, we won't use it */if (cfi->cfiq->BlockEraseTimeoutTyp)cfi->chips[i].erase_time =1000<<cfi->cfiq->BlockEraseTimeoutTyp;elsecfi->chips[i].erase_time = 2000000;if (cfi->cfiq->WordWriteTimeoutTyp &&cfi->cfiq->WordWriteTimeoutMax)cfi->chips[i].word_write_time_max =1<<(cfi->cfiq->WordWriteTimeoutTyp +cfi->cfiq->WordWriteTimeoutMax);elsecfi->chips[i].word_write_time_max = 50000 * 8;if (cfi->cfiq->BufWriteTimeoutTyp &&cfi->cfiq->BufWriteTimeoutMax)cfi->chips[i].buffer_write_time_max =1<<(cfi->cfiq->BufWriteTimeoutTyp +cfi->cfiq->BufWriteTimeoutMax);if (cfi->cfiq->BlockEraseTimeoutTyp &&cfi->cfiq->BlockEraseTimeoutMax)cfi->chips[i].erase_time_max =1000<<(cfi->cfiq->BlockEraseTimeoutTyp +cfi->cfiq->BlockEraseTimeoutMax);elsecfi->chips[i].erase_time_max = 2000000 * 8;cfi->chips[i].ref_point_counter = 0;init_waitqueue_head(&(cfi->chips[i].wq));}map->fldrv = &cfi_intelext_chipdrv;return cfi_intelext_setup(mtd);
}
复制代码

从read_pri_intelext的实现可以看出,intel 扩展查询特性是向后兼容,对于新增的特性,会加在老特性的后面。 后面硬件分区表特性暂放。

总结read_pri_intelext做的工作就是读取cfi扩展查询表,根据扩展的次版本号MinorVersion,判断支持的特性,分配附加存储空间。

static inline struct cfi_pri_intelext *
read_pri_intelext(struct map_info *map, __u16 adr)
{struct cfi_private *cfi = map->fldrv_priv;struct cfi_pri_intelext *extp;unsigned int extra_size = 0;unsigned int extp_size = sizeof(*extp);again:// 调用cfi_util.c模块读取cfi扩展查询表,函数内部分配内存存储该结构,返回指向该结构体的指针extp = (struct cfi_pri_intelext *)cfi_read_pri(map, adr, extp_size, "Intel/Sharp");if (!extp)return NULL;cfi_fixup_major_minor(cfi, extp);if (extp->MajorVersion != '1' ||(extp->MinorVersion < '0' || extp->MinorVersion > '5')) {printk(KERN_ERR "  Unknown Intel/Sharp Extended Query ""version %c.%c.\n",  extp->MajorVersion,extp->MinorVersion);kfree(extp);return NULL;}// 小端字节序转为CPU字节序/* Do some byteswapping if necessary */extp->FeatureSupport = le32_to_cpu(extp->FeatureSupport);extp->BlkStatusRegMask = le16_to_cpu(extp->BlkStatusRegMask);extp->ProtRegAddr = le16_to_cpu(extp->ProtRegAddr);if (extp->MinorVersion >= '0') {extra_size = 0;// 根据保护寄存器域的个数分配附加内存/* Protection Register info */extra_size += (extp->NumProtectionFields - 1) *sizeof(struct cfi_intelext_otpinfo);}// 后面都是intel扩展的特性if (extp->MinorVersion >= '1') {/* Burst Read info */extra_size += 2;if (extp_size < sizeof(*extp) + extra_size)goto need_more;extra_size += extp->extra[extra_size - 1];}if (extp->MinorVersion >= '3') {int nb_parts, i;/* Number of hardware-partitions */extra_size += 1;if (extp_size < sizeof(*extp) + extra_size)goto need_more;nb_parts = extp->extra[extra_size - 1];/* skip the sizeof(partregion) field in CFI 1.4 */if (extp->MinorVersion >= '4')extra_size += 2;for (i = 0; i < nb_parts; i++) {struct cfi_intelext_regioninfo *rinfo;rinfo = (struct cfi_intelext_regioninfo *)&extp->extra[extra_size];extra_size += sizeof(*rinfo);if (extp_size < sizeof(*extp) + extra_size)goto need_more;rinfo->NumIdentPartitions=le16_to_cpu(rinfo->NumIdentPartitions);extra_size += (rinfo->NumBlockTypes - 1)* sizeof(struct cfi_intelext_blockinfo);}if (extp->MinorVersion >= '4')extra_size += sizeof(struct cfi_intelext_programming_regioninfo);if (extp_size < sizeof(*extp) + extra_size) {need_more:extp_size = sizeof(*extp) + extra_size;kfree(extp);if (extp_size > 4096) {printk(KERN_ERR"%s: cfi_pri_intelext is too fat\n",__func__);return NULL;}goto again;}}return extp;
复制代码

cfi_intelext_setup主要做了两件事: 一是初始化mtd_info中擦除区管理结构eraseregions。二是把cfi_cmdset_0001函数设置的reboot_notifier注册到系统。

static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd)
{struct map_info *map = mtd->priv;struct cfi_private *cfi = map->fldrv_priv;unsigned long offset = 0;int i,j;// 考虑背靠背时,一个大CHIP的大小unsigned long devsize = (1<<cfi->cfiq->DevSize) * cfi->interleave;// 设备总容量mtd->size = devsize * cfi->numchips;mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips;mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info)* mtd->numeraseregions, GFP_KERNEL);if (!mtd->eraseregions)goto setup_err;for (i=0; i<cfi->cfiq->NumEraseRegions; i++) {unsigned long ernum, ersize;//表示擦除块的大小=256*Z, 还考虑背靠背ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave;//表示该擦除区所包含的擦除块的块数ernum = (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1;// mtd->erasesize保存的是擦除块的最大值if (mtd->erasesize < ersize) {mtd->erasesize = ersize;}for (j=0; j<cfi->numchips; j++) {// .offset为该擦除区在该设备中的总偏移mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset;mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize;mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum;//为该擦除区中的擦除块分配bitmapmtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].lockmap = kmalloc(ernum / 8 + 1, GFP_KERNEL);}offset += (ersize * ernum);}//offset最后保存的是所有擦除区的大小,总和应该和设备总容量相等if (offset != devsize) {/* Argh */printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize);goto setup_err;}for (i=0; i<mtd->numeraseregions;i++){printk(KERN_DEBUG "erase region %d: offset=0x%llx,size=0x%x,blocks=%d\n",i,(unsigned long long)mtd->eraseregions[i].offset,mtd->eraseregions[i].erasesize,mtd->eraseregions[i].numblocks);}#ifdef CONFIG_MTD_OTPmtd->_read_fact_prot_reg = cfi_intelext_read_fact_prot_reg;mtd->_read_user_prot_reg = cfi_intelext_read_user_prot_reg;mtd->_write_user_prot_reg = cfi_intelext_write_user_prot_reg;mtd->_lock_user_prot_reg = cfi_intelext_lock_user_prot_reg;mtd->_get_fact_prot_info = cfi_intelext_get_fact_prot_info;mtd->_get_user_prot_info = cfi_intelext_get_user_prot_info;
#endif/* This function has the potential to distort the realitya bit and therefore should be called last. */if (cfi_intelext_partition_fixup(mtd, &cfi) != 0)goto setup_err;//产商命令集模块不能被异步卸载__module_get(THIS_MODULE);register_reboot_notifier(&mtd->reboot_notifier);return mtd;setup_err:kfree(mtd->eraseregions);kfree(mtd);kfree(cfi->cmdset_priv);return NULL;
}复制代码

3.4 mtd_concat_create

转载于:https://juejin.im/post/5c441e206fb9a049e2325053

Linux 4.x MTD源码分析-cfi-flash设备probe过程分析相关推荐

  1. linux显示启动logo源码分析以及修改显示logo

    1.linux显示启动logo整个流程分析 (1)logo图片在内核源码中是以ppm格式的文件保存,在编译内核时会把ppm格式的文件自动转换成.c文件,在c文件中会构造一个struct linux_l ...

  2. Linux brk(),mmap()系统调用源码分析3:brk()的内存申请流程

    Linux brk(),mmap()系统调用源码分析 brk()的内存申请流程 荣涛 2021年4月30日 内核版本:linux-5.10.13 注释版代码:https://github.com/Rt ...

  3. linux nDPI 协议检测 源码分析

    关于nDPI的基本功能就不在这介绍了,有兴趣了解的读者可以阅读官方的快速入门指南:https://github.com/ntop/nDPI/blob/dev/doc/nDPI_QuickStartGu ...

  4. Linux系统编程 / triggerhappy 源码分析(3.select 的应用)

    哈喽,我是老吴,继续记录我的学习心得. 一.进步的滞后性 我们期望进步是线性: 每一个人付出一些努力后,都希望它有立竿见影的效果. 现实是: 做出努力后,结果的显现往往滞后. 只有在几个月或几年后,我 ...

  5. Linux kernel SPI源码分析之SPI设备驱动源码分析(linux kernel 5.18)

    SPI基础支持此处不再赘述,直接分析linux中的SPI驱动源码. 1.SPI设备驱动架构图 2.源码分析 本次分析基于kernel5.18,linux/drivers/spi/spidev.c 设备 ...

  6. Nouveau源码分析(三):NVIDIA设备初始化之nouveau_drm_probe

    Nouveau源码分析(三) 向DRM注册了Nouveau驱动之后,内核中的PCI模块就会扫描所有没有对应驱动的设备,然后和nouveau_drm_pci_table对照. 对于匹配的设备,PCI模块 ...

  7. linux之虚拟文件系统源码分析(详解)

    文章目录 前言 基础知识 VFS的数据结构 正篇 前言 ​ 虚拟文件系统是一个很庞大的架构,如果要分析的面面俱到,会显得特别复杂而笨拙,让人看着看着,就不知所云了(当然主要还是笔者太菜),所以这篇博客 ...

  8. linux gnu binutils,binutils源码分析之准备篇

    1.什么是binutils 先写一个简单的hello_world.c程序. #include int main() { printf("Hello World!\n"); retu ...

  9. linux wifi 源代码,Linux无线认证----wifidog源码分析

    wifidog wifidog开源模块,通过iptable对报文进行重定向到端口2060接口,对报文进行拦截,利用iptable实现用户上网行为管理功能,目前市面上的无线多采用此模块进行portale ...

最新文章

  1. android平台水波效果 源码
  2. 一文图解机器学习的基本算法!
  3. Failed to find byte code for java/util/function/BiConsumer
  4. 网络工程:3.1 RIP(Routing Information Protocol)协议
  5. 【树莓派学习笔记】五、处理、自动重命名并另存为图片
  6. MyBatis复习(六):MyBatis二级缓存
  7. 理发店收银系统php,【毕业论文】基于php+mysql美发店收银系统设计与实现.doc
  8. 黑马程序员C++学习笔记(第三阶段核心:STL)--- 更新中
  9. Javascript本地存储小结
  10. excel粘贴为图片不完整_excel转PDF不完整?办公大神的压箱绝技来了!
  11. linux的源码安装步骤(以安装nginx为例)
  12. Oracle基础--PL/SQL编程基本语法
  13. 计网 | 链路层协议及大题解构
  14. 修改植物大战僵尸游戏存档——跳关并快速实现财富自由
  15. PbootCMS制作个性分页条之单页/总页数效果教程
  16. 计算机编程课程顺序_您可以在5月开始学习530项免费的在线编程和计算机科学课程
  17. 服务器文档链接电脑,服务器怎么链接电脑
  18. echarts中国地图线性流动动画js特效
  19. anthony1314的小笔记
  20. python flask项目结构_Flask项目结构

热门文章

  1. 百度AI语音SDK集成
  2. springboot和springcloud功能详细介绍
  3. 不同版本cuda对应的NVIDIA驱动版本
  4. 运营技巧|要如何提升用户留存率?
  5. 芯片验证漫游指南 pdf_更好地认识PDF 文件
  6. 算法导论(三)--分治法
  7. Linux基础命令(补充:命令行提示字符加颜色)
  8. 峰会实录 | 镜舟科技CEO孙文现:基于StarRocks打造企业级极速统一数据分析产品
  9. Springboot美食汇开放平台8ob70计算机毕业设计-课程设计-期末作业-毕设程序代做
  10. Macbook pro外接显卡实现深度学习