Linux 系统初始化NOR FLash芯片及驱动 韩大卫@吉林师范大学 本文旨在解析linux系统下, Nor Flash芯片的初始化大致过程, 和cfi标准类型的Flash芯片在linux系统中的底层驱动何如实现, 如何找到并调用其提供的write/read/ioctl实现函数. 在明确了上述关系后, 可用通过一个用户层的程序, 向Nor Flash芯片指定分区位置写入指定程序, 比如写入linux elf文件, 实现更新系统内核, 写入uboot, 更新引导程序, 写入 “hello ,world!”, 使得系统崩溃掉(这样做的后果是需要将Nor Flash芯片从目标板中取下来, 用烧录器烧入正确的文件后再焊到板子上). 关于linux如何在Nor FLash上建立分区, 请参考博文 <<linux下建立NOR Flash分区>> 内核启动时, 在arch具体CPU子目录下, 如 mips/cavium-octeon, nor flashd 的初始化过程如下: 在arch/mips/cavium-octeon/flash_setup.c late_initcall(flash_init); static struct map_info flash_map; static int __init flash_init(void) { union cvmx_mio_boot_reg_cfgx region_cfg; region_cfg.u64 = cvmx_read_csr(CVMX_MIO_BOOT_REG_CFGX(0)); if (region_cfg.s.en) { /* 初始化一个全局数据结构 flash_map, 定义其名为”octeon_nor0” */ flash_map.name = "octeon_nor0"; flash_map.phys = region_cfg.s.base << 16; flash_map.size = 0x1fc00000 - flash_map.phys; flash_map.bankwidth = 1; flash_map.virt = ioremap(flash_map.phys, flash_map.size); pr_notice("Bootbus flash: Setting flash for %luMB flash at " "0x%08llx\n", flash_map.size >> 20, flash_map.phys); simple_map_init(&flash_map); //调用名为cfi_probe 的驱动, 说明该CPU支持CFI标准的flash操作 mymtd = do_map_probe("cfi_probe", &flash_map); if (mymtd) { mymtd->owner = THIS_MODULE; #ifdef CONFIG_MTD_PARTITIONS nr_parts = parse_mtd_partitions(mymtd, part_probe_types, &parts, 0); if (nr_parts > 0) add_mtd_partitions(mymtd, parts, nr_parts); else add_mtd_device(mymtd); #else add_mtd_device(mymtd); #endif } else { pr_err("Failed to register MTD device for flash\n"); } } return 0; } late_initcall(flash_init); do_map_probe() 会找到并调用指定name的probe函数,定义在drivers/mtd/chips/chipreg.c 中, struct mtd_info *do_map_probe(const char *name, struct map_info *map) { struct mtd_chip_driver *drv; struct mtd_info *ret; //获取指定name的驱动 drv = get_mtd_chip_driver(name); if (!drv && !request_module("%s", name)) drv = get_mtd_chip_driver(name); if (!drv) return NULL; //成功获取指定的驱动器后, 调用其probe()函数, 实现对芯片的探测 ret = drv->probe(map); module_put(drv->module); if (ret) return ret; return NULL; } get_mtd_chip_driver()定义: static struct mtd_chip_driver *get_mtd_chip_driver (const char *name) { struct list_head *pos; struct mtd_chip_driver *ret = NULL, *this; spin_lock(&chip_drvs_lock); //遍历MTD芯片驱动器链表chip_drvs_list list_for_each(pos, &chip_drvs_list) { this = list_entry(pos, typeof(*this), list); //当当前节点name等于指定name时, 返回链表中节点指针, 退出遍历 if (!strcmp(this->name, name)) { ret = this; break; } } if (ret && !try_module_get(ret->module)) ret = NULL; spin_unlock(&chip_drvs_lock); return ret; } 总的来说, get_mtd_chip_driver(name)函数作用是遍历所有MTD芯片的驱动链表-chip_drvs_list, 并返回驱动名为name的驱动器. 在获取了驱动后, 开始调用该驱动的probe()函数, 实现探测功能. 在drivers/mtd/chips/cfi_probe.c 中, 实现了cfi标准的驱动, module_init(cfi_probe_init); late_initcall(flash_init)是在module_init(cfi_probe_init)执行之后才执行的, (注: late_initcall()优先级为7, module_init()优先级为6, 数值越低优先级越高) 这样确保了当cfi_probe驱动器添加到chip_drvs_list后, flash init才在chip_drvs_list中寻找cfi_probe的驱动, 进而调用cfi_probe驱动器的probe()函数. static int __init cfi_probe_init(void) { //将名为”cfi_probe”的驱动器添加到芯片驱动链表中 register_mtd_chip_driver(&cfi_chipdrv); return 0; } register_mtd_chip_driver()定义在drivers/mtd/chips/chipreg.c void register_mtd_chip_driver(struct mtd_chip_driver *drv) { spin_lock(&chip_drvs_lock); //向MTD芯片驱动链表chip_drvs_list中添加自己的驱动 list_add(&drv->list, &chip_drvs_list); spin_unlock(&chip_drvs_lock); } cfi_chipdrv 定义为: static struct mtd_chip_driver cfi_chipdrv = { .probe = cfi_probe, .name = "cfi_probe", .module = THIS_MODULE }; struct mtd_info *cfi_probe(struct map_info *map) { //使用gen_probe.c中的函数, cfi_chip_probe数据结构实现探测 return mtd_do_chip_probe(map, &cfi_chip_probe); } 在此看到, 在上面flash_init()中, do_map_probe()在获取了cfi_probe()的驱动后, 调用的探测函数 ret = drv->probe(map) 实际上是间接使用了mtd_do_chip_probe(), 并传入了自定义的数据结构: cfi_chip_probe cfi_chip_probe 定义为: static struct chip_probe cfi_chip_probe = { .name = "CFI", .probe_chip = cfi_probe_chip }; mtd_do_chip_probe()函数定义在 drivers/mtd/chips/gen_probe.c 中: struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp) { struct mtd_info *mtd = NULL; struct cfi_private *cfi; 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; } EXPORT_SYMBOL(mtd_do_chip_probe); genprobe_ident_chips()函数定义如下: static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chip_probe *cp) { struct cfi_private cfi; struct cfi_private *retcfi; unsigned long *chip_map; int i, j, mapsize; int max_chips; memset(&cfi, 0, sizeof(cfi)); /* 在genprobe_new_chip()中调用了cfi_chip_probe数据结构内的函数:cp->probe_chip(map, 0, NULL, cfi)), 得到返回值0 ,则直接退出, 并返回NULL. */ if (!genprobe_new_chip(map, cp, &cfi)) { return NULL; } … } 在drivers/mtd/chips/cfi_probe.c 中: static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, unsigned long *chip_map, struct cfi_private *cfi) { int i; 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; } 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(); if (!cfi_qry_mode_on(base, map, cfi)) { xip_enable(base, map, cfi); return 0; } /* 由于在mtd_do_chip_probe()没有执行到genprobe_ident_chips()的cfi.numchips = 1, 故在此进入if,并执行return语句.后面代码可以忽略. */ if (!cfi->numchips) { return cfi_chip_setup(map, cfi); } ... } 在cfi_probe_chip()调用结束后, genprobe_ident_chips()退出, 回到mtd_do_chip_probe()函数, 执行mtd = check_cmd_set(map, 1); 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 0x0001: case 0x0003: case 0x0200: return cfi_cmdset_0001(map, primary); #endif #ifdef CONFIG_MTD_CFI_AMDSTD case 0x0002: return cfi_cmdset_0002(map, primary); #endif #ifdef CONFIG_MTD_CFI_STAA case 0x0020: return cfi_cmdset_0020(map, primary); #endif default: return cfi_cmdset_unknown(map, primary); } } 引用一下网上的说法: “不同的厂商使用不同的指令集, Linux的MTD实现的命令集有AMD/Fujitsu的标准命令集和Intel/Sharp的扩展命令集(兼容Intel/Sharp标准命令集)两个,这两个命令集分别在cfi_cmdset_0002.c和cfi_cmdset_0001.c中实现。” 下面以 cfi_cmdset_0002为例: 在drivers/mtd/chips/cfi_cmdset_0002.c 中: struct mtd_info *cfi_cmdset_0002(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) { printk(KERN_WARNING "Failed to allocate memory for MTD device\n"); return NULL; } mtd->priv = map; mtd->type = MTD_NORFLASH; /* 具体的操作方法的实现函数由内核提供.这样, 用户层的ioctl/read/write等操作在系统底层真正的实现方法即为下面的函数. */ /* Fill in the default mtd operations */ mtd->erase = cfi_amdstd_erase_varsize; mtd->write = cfi_amdstd_write_words; mtd->read = cfi_amdstd_read; mtd->sync = cfi_amdstd_sync; mtd->suspend = cfi_amdstd_suspend; mtd->resume = cfi_amdstd_resume; mtd->flags = MTD_CAP_NORFLASH; mtd->name = map->name; mtd->writesize = 1; //CFI模式 if (cfi->cfi_mode==CFI_MODE_CFI){ unsigned char bootloc; /* * 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_amdstd *extp; /* 调用cfi_read_pri() 函数, 获取Amd/Fujitsu的扩展信息 */ extp = (struct cfi_pri_amdstd*)cfi_read_pri(map, adr, sizeof(*extp), "Amd/Fujitsu"); if (!extp) { kfree(mtd); return NULL; } cfi_fixup_major_minor(cfi, extp); if (extp->MajorVersion != '1' || (extp->MinorVersion < '0' || extp->MinorVersion > '4')) { printk(KERN_ERR " Unknown Amd/Fujitsu Extended Query " "version %c.%c.\n", extp->MajorVersion, extp->MinorVersion); kfree(extp); kfree(mtd); return NULL; } /* Install our own private info structure */ cfi->cmdset_priv = extp; /* Apply cfi device specific fixups */ //向mtd添加cfi修正表.此表中针对一些cfi芯片提供相应的调用函数 cfi_fixup(mtd, cfi_fixup_table); ... } else if (cfi->cfi_mode == CFI_MODE_JEDEC) //JEDEC模式下, 也有特有的jedec修正表 cfi_fixup(mtd, jedec_fixup_table); } //再向mtd添加一个通用的修正表 cfi_fixup(mtd, fixup_table); //调用cfi_amdstd_setup()初始化封装完成的数据结构struct mtd_info. return cfi_amdstd_setup(mtd); } 以cfi修正表为例: static struct cfi_fixup cfi_fixup_table[] = { { CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri, NULL }, #ifdef AMD_BOOTLOC_BUG { CFI_MFR_AMD, CFI_ID_ANY, fixup_amd_bootblock, NULL }, { MANUFACTURER_MACRONIX, CFI_ID_ANY, fixup_amd_bootblock, NULL }, #endif //针对一些芯片有特定的函数 { CFI_MFR_AMD, 0x0050, fixup_use_secsi, NULL, }, { CFI_MFR_AMD, 0x0053, fixup_use_secsi, NULL, }, { CFI_MFR_AMD, 0x0055, fixup_use_secsi, NULL, }, { CFI_MFR_AMD, 0x0056, fixup_use_secsi, NULL, }, { CFI_MFR_AMD, 0x005C, fixup_use_secsi, NULL, }, { CFI_MFR_AMD, 0x005F, fixup_use_secsi, NULL, }, { CFI_MFR_AMD, 0x0c01, fixup_s29gl064n_sectors, NULL, }, { CFI_MFR_AMD, 0x1301, fixup_s29gl064n_sectors, NULL, }, { CFI_MFR_AMD, 0x1a00, fixup_s29gl032n_sectors, NULL, }, { CFI_MFR_AMD, 0x1a01, fixup_s29gl032n_sectors, NULL, }, /* 在此可以看到: 如果你在编译时候没有定义FORCE_WORD_WRITE 这个”强制使用字节写”这个宏 , 那么对任何ID的MTD芯片, 会调用fixup_use_write_buffers 这个函数 */ #if !FORCE_WORD_WRITE { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL, }, #endif { 0, 0, NULL, NULL } }; static void fixup_use_write_buffers(struct mtd_info *mtd, void *param) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; if (cfi->cfiq->BufWriteTimeoutTyp) { DEBUG(MTD_DEBUG_LEVEL1, "Using buffer write method\n" ); /* 可以看到, mtd->write的真正实现函数是cfi_amdstd_write_buffers. */ mtd->write = cfi_amdstd_write_buffers; } } cfi_amdstd_write_words()和cfi_amdstd_write_buffers()与的参数是相同的, cfi_amdstd_write_words()调用了do_write_oneword() cfi_amdstd_write_buffers() 调用了do_write_buffer() 从函数名称上可以看到, 两者的不同可以理解为两者处理的对象分别为 word 与 buf. 综上, 用户层的write, read, ioctl 这三个函数经过系统调用, 经过mtdchar的调用, 经过chip/cfi_probe间接调用了 chip/cfi_cmdset_0002.c 或cfi_cmdset_0001.c, cfi_cmdset_0020.c中的实现函数. 在明晰了如上关系后, 我们即可在用户层向指定Nor Flash 的位置写入文件或elf程序, 即可实现烧写Bootloader或linux 内核. 前提是在linux启动前, 建立好对应的分区. 关于提到的用户程序, 请参考作者其他博文.