Linux 系统初始化NOR FLash芯片及驱动

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启动前, 建立好对应的分区. 关于提到的用户程序, 请参考作者其他博文.

你可能感兴趣的:(linux,Flash,驱动,nor,mtd,cfi)