Linux 4.x MTD原始碼分析-cfi-flash裝置probe過程分析

rexnie發表於2019-01-20

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<cfi->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的特性。

AutoSelectID

有兩種方法知道flash的型別,一種是傳統的Autoselect,即device ID,另外一種是CFI,他們使用的地址空間是不同。

Linux 4.x MTD原始碼分析-cfi-flash裝置probe過程分析

	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<<cfi->cfiq->WordWriteTimeoutTyp;
		else
			cfi->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;
		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<<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;
			//為該擦除區中的擦除塊分配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<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_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

相關文章