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传给内核的配置命令字符串所代表的配置. 根据配置命令字符串建立了相应的分区.


你可能感兴趣的:(linux)