博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux 4.x MTD源码分析-cfi-flash设备probe过程分析
阅读量:6094 次
发布时间:2019-06-20

本文共 31263 字,大约阅读时间需要 104 分钟。

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=32bit            			bank-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=2	count /= 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_list info = 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 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;}复制代码

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_chips	int 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 device		   in 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,mode   ret: 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查询模式,如果支持查询模式返回1	if (!cfi_qry_mode_on(base, map, cfi)) {		xip_enable(base, map, cfi);		return 0;	}    //第一次调用该函数时,numchips=0	if (!cfi->numchips) {		/* This is the first time we're called. Set up the CFI		   stuff 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 = 1	int 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_CFI	printk("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的查询结构体,一次只能读1B	for (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);#endif	for (i=0; i
cfiq->NumEraseRegions; i++) { cfi->cfiq->EraseRegionInfo[i] = le32_to_cpu(cfi->cfiq->EraseRegionInfo[i]);#ifdef DEBUG_CFI printk(" 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 CFI		   stuff 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 one		   we'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 to	   be 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计作一个大CHIP	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) { //DTS配置的总大小小于一块的大小,算做一个大CHIP		printk(KERN_WARNING "NOR chip too large to fit in mapping. Attempting to cope...\n");		max_chips = 1;	}    以long为单位分配bitmap	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 */    // 再次调用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_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	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    // 用于支持自定义的算法命令集。    // 该函数会根据从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;	//当写大块的数据时,使用这个大小,一般norflash	mtd->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<
cfiq->WordWriteTimeoutTyp; else cfi->chips[i].word_write_time = 50000; if (cfi->cfiq->BufWriteTimeoutTyp) cfi->chips[i].buffer_write_time = 1<
cfiq->BufWriteTimeoutTyp; /* No default; if it isn't specified, we won't use it */ if (cfi->cfiq->BlockEraseTimeoutTyp) cfi->chips[i].erase_time = 1000<
cfiq->BlockEraseTimeoutTyp; else cfi->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); else cfi->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); else cfi->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<
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
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
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; //为该擦除区中的擦除块分配bitmap mtd->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
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_OTP mtd->_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 reality a 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

你可能感兴趣的文章
java设计模式之单例模式
查看>>
地震频发
查看>>
有状态服务和没状态服务和keepalive
查看>>
lvs fullnat+ECMP【8】session同步模型讨论
查看>>
MVC中的三个模块
查看>>
第三方登录之QQ登录(二)——OAuth2.0处理流程介绍(以QQ登录为例)
查看>>
利用反射访问私有方法,私有属性
查看>>
回文数
查看>>
JSON格式转换,String,Array,List,Map
查看>>
不用制作启动盘安装Windows操作系统的最简单办法
查看>>
centos 5.6 linux安装系统后的基本优化
查看>>
我的友情链接
查看>>
zabbix 3.0 脚本调用
查看>>
解决“eth0设备的MAC 址与预想的不符,忽略” 追加
查看>>
第11章 表单
查看>>
售前工程师的成长---一个老员工的经验之谈
查看>>
Get到的优秀博客网址
查看>>
centos7.3编译安装php-7.1.10
查看>>
用Java将Excel的xls和xlsx文件转换成csv文件的方法
查看>>
解决java.net.SocketException: Too many open files
查看>>