zoukankan      html  css  js  c++  java
  • Linux系统建立Nor Flash分区

    Linux建立Nor Flash分区
    
    
    韩大卫@吉林师范大学
    
    接上文章<<linux系统Nor Flash芯片初始化及驱动>>, Nor Flash 芯片在flash芯片驱动器里链表chip_drvs_list中找到并调用名为”cfi_probe”的驱动后, 完成芯片初始化阶段, 接着进入linuxFlash建立分区阶段.arch/mips/cavium-octeon/flash_setup.c static struct map_info flash_map;
    
    
    static int __init flash_init(void)
    
    {                         
                     
        union cvmx_mio_boot_reg_cfgx region_cfg;
    
    //bootbus总线上获取flash的基地址.
        region_cfg.u64 = cvmx_read_csr(CVMX_MIO_BOOT_REG_CFGX(0));
    
        if (region_cfg.s.en) {
                
        //将全局数据结构struct map_info 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;
    
    
    	//使用ioremap()32M 大小的Flash的物理地址映射到虚拟地址上.
            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);
    
    
    		/*
    调用do_map_probe()进入Nor Flash芯片初始化阶段,该函数会在Flash芯片驱动器列表中找到名为cfi_probe的驱动器, 并调用其probe()函数, 准备好read/wirte/ioctl等函数的实现方法.
    		*/
            mymtd = do_map_probe("cfi_probe", &flash_map);
    
            if (mymtd) {      
    
                mymtd->owner = THIS_MODULE;
    
         
    #ifdef CONFIG_MTD_PARTITIONS
    
    /*FLash芯片成功探测(调用过probe), linux进入处理Flash分区阶段
    */
    nr_parts = parse_mtd_partitions(mymtd,
    part_probe_types,
    &parts, 0);
    
                if (nr_parts > 0)
    
    
    /*
    	nr_parts>0 ,说明解析到存在多个分区, 那么添加各个分区
    */
                    add_mtd_partitions(mymtd, parts, nr_parts);
    
                else          
    
                    add_mtd_device(mymtd);
    
    #else             
    
    		//由于定义了CONFIG_MTD_PARTITIONS
    , 不执行该函数        
                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(), 成功的话返回一个重要的数据结构struct mtd_info.
    
    Struct mtd_infolinux描述MTD类型设备的数据结构, 里面有mtd设备等待初始化的信息(变量)和一些设备操作方法(函数指针).
    
    经过do_map_probe()的初始化, 其中有一些成员已经得到赋值, 请参考drivers/mtd/chips/cfi_probe.c 中的cfi_probe()函数.
    
    
    
    parse_mtd_partitions(mymtd,
     part_probe_types,&parts, 0); 
    
    
    解析器类型: cmdlinepartRedBoot两中, 如没有定义CONFIG_MTD_REDBOOT_PARTS, 那只将cmdlinepart编译在内, 最后只连接cmdlinepart.o.
    
    static const char *part_probe_types[] = { 
    
        "cmdlinepart",
    
    #ifdef CONFIG_MTD_REDBOOT_PARTS
    
        "RedBoot",
    
    #endif
    
        NULL
    
    };
    
    
    parse_mtd_partitions()定义在drivers/mtd/mtdpart.c :
    
    int parse_mtd_partitions(struct mtd_info *master, const char **types,
    
                 struct mtd_partition **pparts, unsigned long origin)
    
    {
    
        struct mtd_part_parser *parser;
    
        int ret = 0;
    
     
    	//循环次数: 解析器的个数, 这里只有一个解析器cmdlinepart, 故只循环一次
        for ( ; ret <= 0 && *types; types++) {
    
    
    		//获取cmdlinepart解析器
            parser = get_partition_parser(*types);
    
            if (!parser && !request_module("%s", *types))
    
                    parser = get_partition_parser(*types);
    
            if (!parser) {
    
                printk(KERN_NOTICE "%s partition parsing not available\n",
    
                       *types);
    
                continue;
    
            }   
    
    
    		/*
    获取成功的话, 调用其解析函数 parse_fn(), 类似于do_map_probe()
    后者是获取驱动器cfi_probe, 获取成功的话, 调用其探测函数Probe()
    		*/
            ret = (*parser->parse_fn)(master, pparts, origin);
    
    
    		/*
    			parse_fn()返回解析到的分区个数.在此打印出相关信息,此信息可在dmesg中看到.
    		 parser->name cmdlinepart,  master->nameocteon_nor0
    		*/
            if (ret > 0) {
    
                printk(KERN_NOTICE "%d %s partitions found on MTD device %s\n",
    
                       ret, parser->name, master->name);
    
            }  
    
    		/*
    减少调用get_partition_parser ()时增加模块使用计数器个数, 释放解析器模块使用权
     #define put_partition_parser(p) do { module_put((p)->owner); } while(0) 
    		*/ 
    
            put_partition_parser(parser);
    
        }   
    
        return ret;
    
    }
    
    get_partition_parser() 定义如下:
    
    static struct mtd_part_parser *get_partition_parser(const char *name)
    
    {   
    
        struct mtd_part_parser *p, *ret = NULL;
    
    
        spin_lock(&part_parser_lock);
    
           
    	//遍历分区解析器链表part_parsers
    
        list_for_each_entry(p, &part_parsers, list) 
    
    
    		//在链表中获取解析器名为name的节点, 并增加其模块使用计数, 取得解析器模块使用权
            if (!strcmp(p->name, name) && try_module_get(p->owner)) {
    
                ret = p;
    
                break;
    
            }   
    
    
        spin_unlock(&part_parser_lock);
           
    
        return ret;
    
    }   
    
        
    总的来说, parse_mtd_partitions()函数类似do_map_probe(), 都是传入一个指定参数, 之后的工作都交给linuxmtd层去实现.
    
    
    另一方面,  late_initcall(flash_init)调用之前, linux已经向内核注册了名为”cmdlinepart”的解析器, 等待被调用, 完成使命, 体现出自身价值.drivers/mtd/cmdlinepart.c:
    
    
    module_init(cmdline_parser_init);
    : module_init()优先级为6, 高于优先级为7late_initcall(). 数值越低优先级越高.
                 
    static int __init cmdline_parser_init(void)
    
    {                
    
    		//向分区解析器链表中添加名为”cmdlinepart” 的解析器
        return register_mtd_parser(&cmdline_parser);
    
    }             
    
       
    /*: 这是driver/mtd/mtdpart.c提供的API:
    int register_mtd_parser(struct mtd_part_parser *p)               
    {   
    
        spin_lock(&part_parser_lock);
    
    	//向分区解析器链表中添加成员
        list_add(&p->list, &part_parsers);
    
        spin_unlock(&part_parser_lock);
    
        
        return 0;
    
    }  
    */
    
    cmdline_parser 定义如下:
                 
    
    static int mtdpart_setup(char *s) 
    
    {
    
        cmdline = s;
    
        return 1;
    
    }
    
     
    __setup("mtdparts=", mtdpart_setup);                                                                                                         
    
    static struct mtd_part_parser cmdline_parser = { 
    
        .owner = THIS_MODULE,
    
    
    	// cmdlinepart的 解析函数为: parse_cmdline_partitions
        .parse_fn = parse_cmdline_partitions,
    
        .name = "cmdlinepart",
    
    };            
    
        
    
    关于__setup("mtdparts=", mtdpart_setup)解释一下:
    
    __setup()宏 定义在include/linux/init.h :
    
    #define __setup(str, fn)                    \    
        __setup_param(str, fn, fn, 0)
    
    
    #define __setup_param(str, unique_id, fn, early)            \
    
        static const char __setup_str_##unique_id[] __initconst \
    
            __aligned(1) = str; \ 
    
        static struct obs_kernel_param __setup_##unique_id  \
    
            __used __section(.init.setup)           \
    
            __attribute__((aligned((sizeof(long)))))    \
    
            = { __setup_str_##unique_id, fn, early }                                                                                             
    
                                  
    __setup宏是linux用来注册关键字及提供处理函数. Str即为关键字, fn为对应的处理函数.
    
    经过上面的展开后, __setup()宏会将传进来的的函数指针放到.init.setup..init.setup是在.init.text下的.
    
    /* :
    mips架构下: arch/mips/kernel/vmlinux.lds.S
    
    __init_begin = .
    INIT_TEXT_SECTION(PAGE_SIZE)
    INIT_DATA_SECTION()
    
    #define INIT_DATA_SECTION(initsetup_align)              \
    
        .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {       \
    
            INIT_DATA                       \
    
            INIT_SETUP(initsetup_align)             \    
            INIT_CALLS                      \
    
            CON_INITCALL                        \
    
            SECURITY_INITCALL                   \
    
            INIT_RAM_FS                     \
    
        }
    INIT_SETUP() 宏中, 
    #define INIT_SETUP(initsetup_align)                 \         
            . = ALIGN(initsetup_align);             \
    
            VMLINUX_SYMBOL(__setup_start) = .;          \
    
            *(.init.setup)                      \
    
            VMLINUX_SYMBOL(__setup_end) = .;
    
     
    
    可以看到:  经过层层包装,.init.setup字段放在了.init.text字段中. 
    
    同样, arm架构中:arch/arm/kernel/vmlinux.lds.S
    
        .init : {           /* Init code and data       */
    
                INIT_TEXT
    
    ...
            __setup_start = .;
    
                *(.init.setup)
    
            __setup_end = .;
    
    		//直接就可以看到__setup的位置.
    
    可以看到, __setup()宏将传进来的函数指针放到.init.setup字段中. 
    
    linux定义了parse_args()函数处理由__setup()传进来参数, pa	rse_args()解析uboot传给内核的启动参数, 匹配__setup()传进来的str, 匹配成功后调用其fn函数.init/main.c __init start_kernel(void) :
    
    parse_args("Booting kernel", static_command_line, __start___param,
    
               __stop___param - __start___param,              
    
               &unknown_bootoption);parse_args()解析到配置命令字符串中有"mtdparts=" ,mtdpart_setup()函数便会自动得到调用:boot_command_line 字符串保存到本地变量static char *cmdline.
    
    另外, start_kernel()中有这样的语句:
    
    printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
    通过这个打印可以看到Uboot传给内核的配置命令字符串:
    
    在我的系统中的打印信息是:
    
    Kernel command line:  bootoctlinux 0x1dd00000 coremask=0x1 mtdparts=octeon_nor0:1m(BOOT),7m(LINUX),21m(CONFIG),3m(MD) console=ttyS0,115200
    
    
    其中”bootoctlinux 0x1dd00000 coremask=0x1 mtdparts=octeon_nor0:1m(BOOT),7m(LINUX),21m(CONFIG),3m(MD) console=ttyS0,115200”
    就是 boot_command_line配置命令字符串.
    
    
    在此可能有些疑问, 这样的配置命令字符串是怎样产生的,以及内核在哪里得到这个配置命令字符串static_command_line?
    
     
    第一个问题跟uboot的配置有关, 需要一大堆文字来解释说明, 请参考作者其他博文, 这里只简单说个大概:
    
    	uboot启动时,  Uboot环境变量(linux_args, loadaddr, bootcmd,numcores, uboot环境里使用printenv命令可以看到)作为编译参数, 传给Uboot的启动linux时的调用参数, 在该调用函数中, 会获取并处理这些环境变量, 得到我们见到的配置命令字符串, 最后调用一段用汇编语言写的代码, 将其存入特定寄存器中.linux内核启动时, init/main.cstart_kernel(): setup_arch(&command_line)这里获取到了Uboot传下来的配置命令字符串, 并调用setup_command_line(command_line)command_line 复制static_command_line, 之后调用parse_args()对其进行处理. 
    
    
    总的来说, 可以简单理解为: cmdlinepart通过mtdpart_setup()函数保存了内核启动时由uboot传进来的含有”mtdparts=”的配置命令字符串, 并该字符串传给本地指针static char *cmdline, 在接下来的parse_cmdline_partitions(), 根据此配置命令字符串作出解析.最后实现该配置. parse_mtd_partitions() 中使用的(*parser->parse_fn)(master, pparts, origin)的实现函数就是drivers/mtd/cmdlinepart.c 中的 parse_cmdline_partitions, 定义如下:
    
    static int parse_cmdline_partitions(struct mtd_info *master,                                        
                                 struct mtd_partition **pparts,
    
                                 unsigned long origin)
    
    {
    
        unsigned long offset;
    
        int i;
    
        struct cmdline_mtd_partition *part;
    
        const char *mtd_id = master->name;
    
       
        /* parse command line */
    
        if (!cmdline_parsed)
    
    	/*
    在此函数中解析了linux启动时的mtdparts配置命令字符串.即”bootoctlinux 0x1dd00000 coremask=0x1 mtdparts=octeon_nor0:1m(BOOT),7m(LINUX),21m(CONFIG),3m(MD) console=ttyS0,115200”, 
    初始化并返回一个全局的分区描述数据结构static struct cmdline_mtd_partition *partitions
    
    struct cmdline_mtd_partition {
    
        struct cmdline_mtd_partition *next;
    	//下个分区的指针
        char *mtd_id;								//mtd_id
        int num_parts;
    							//分区编号
        struct mtd_partition *parts;
    			//包含了分区名,大小, 偏移等信息
    };                
    
       
    	*/
            mtdpart_setup_real(cmdline);
    
       
    /*
    在获得了分区描述信息partitions,  将每一个分区的partitions传给参数
     struct mtd_partition **pparts, 完成解析函数真正的作用. 等解析函数退出后, flash_init()便可以调用add_mtd_partitions()将每个 struct mtd_partition 添加到linuxmtd核心中.后面会看到这样的操作.
    */
    
        for(part = partitions; part; part = part->next)
    
        {
    
            if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))
    
            {
    
                for(i = 0, offset = 0; i < part->num_parts; i++)
    
                {
    
                    if (part->parts[i].offset == OFFSET_CONTINUOUS)
    
                      part->parts[i].offset = offset;
    
                    else
    
                      offset = part->parts[i].offset;
    
                    if (part->parts[i].size == SIZE_REMAINING)
    
                      part->parts[i].size = master->size - offset;
    
                    if (offset + part->parts[i].size > master->size)
    
                    {
    
                        printk(KERN_WARNING ERRP
    
                               "%s: partitioning exceeds flash size, truncating\n",
    
                               part->mtd_id);
    
                        part->parts[i].size = master->size - offset;
    
                        part->num_parts = i;
    
                    }
    
                    offset += part->parts[i].size;
    
                }
    
    			//将每个分区的struct cmdline_mtd_partition 传给函数参数pparts.
    
                *pparts = kmemdup(part->parts,
    
                        sizeof(*part->parts) * part->num_parts,
    
                        GFP_KERNEL);
    
                if (!*pparts)
    
                    return -ENOMEM;
    
    
    			//在最后一次循环时,返回分区号, 即分区个数   
             return part->num_parts;
    
            }
    
        }
    
        return 0;
    
    }   
    
        
    parse_cmdline_partitions()调用结束后, 返回系统存在多少个分区, parse_mtd_partitions()函数也随之调用完成.  flash_init() 走到add_mtd_partitions(mymtd, parts, nr_parts);  drivers/mtd/mtdpart.c  :
    
    int add_mtd_partitions(struct mtd_info *master,
    
                   const struct mtd_partition *parts,                                                             
                   int nbparts)
    
    {   
    
        struct mtd_part *slave;
    
        uint64_t cur_offset = 0;
    
        int i;
    
           
        printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
    
           
    	//nbparts 为前面函数解析到的分区个数
        for (i = 0; i < nbparts; i++) {
    
    
    	/*
    	parts为每个分区的数据结构数组指针
    	add_one_partition()初始化并填充一个linux描述mtd分区的数据结构:struct mtd_part, 
    主要是填充struct mtd_part 内的 struct mtd_info, mtd_info内的函数指针填充实现函数等. 
    比如给每个分区的mtd_info 填充好read, write, lock/unlock, erase等函数.
    最后把指针交给slave, slave可利用地址偏移offset进行下个分区处理.add_one_partition() , 会打印出该分区的地址偏移值, 大小, slave分区的名字.dmesg里可以看到这样的信息:
    
    [   33.891465] 0x000000000000-0x000000100000 : "BOOT"
    
    [   33.896708] 0x000000100000-0x000000800000 : "LINUX"
    
    [   33.901975] 0x000000800000-0x000001d00000 : "CONFIG"
    
    [   33.907329] 0x000001d00000-0x000002000000 : "MD"
    
    	*/
            slave = add_one_partition(master, parts + i, i, cur_offset);
    
            if (!slave)
    
                return -ENOMEM;
    
    
    		//当前偏移值
            cur_offset = slave->offset + slave->mtd.size;
    
        }  
         
        return 0;
    
    }   
    
    EXPORT_SYMBOL(add_mtd_partitions);
    
    
    总的来说, 就是经过了以上步骤, 实现了uboot传给内核的配置命令字符串所代表的配置. 根据配置命令字符串建立了相应的分区.
    
    
    
  • 相关阅读:
    jmeter之三种参数化
    linux(centos6.5)常用命令
    win10+jdk+mysql+tomcat+jpress环境搭建与部署
    [剑指Offer] 29.最小的K个数
    [C/C++] C++中new的语法规则
    [C/C++] 深拷贝和浅拷贝
    [C/C++] #ifdef和#endif
    [C/C++] C++声明和定义的区别
    [C/C++] extern关键字详解以及与static、const区别
    [C/C++] static在C和C++中的用法和区别
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3036491.html
Copyright © 2011-2022 走看看