在MTD子系統中,無論你是什麼類型的存儲芯片,RAM也好,ROM也好,CFI接口flash或者JEDEC接口的flash,他們的driver都是以mtd_chip_driver結構體描述的。具體類型的存儲芯片的driver模塊,有定義一個mtd_chip_driver結構體,而且經過調用register_mtd_chip_driver註冊到MTD子系統。node
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.clinux
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。express
接下來看看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);
}
複製代碼
以下是設備樹中關於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
...
}
複製代碼
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"是指系統中的內存嗎?app
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,
};
複製代碼
// 根據設備樹中定義的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結構體。ide
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);
}
...
}
複製代碼
此函數用了典型的工廠方法設計模式,經過傳入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);
}
複製代碼
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;
}
複製代碼
這裏注意下幾個編程要點:
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;
}
複製代碼
/* 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的特性。
有兩種方法知道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;
}
複製代碼
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;
複製代碼
// 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;
複製代碼
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;
}
複製代碼