您的位置:首页 > 运维架构 > Linux

Linux系统建立Nor Flash分区

2013-04-22 15:02 681 查看
Linux建立Nor Flash分区

韩大卫@吉林师范大学

接上文章<<linux系统Nor Flash芯片初始化及驱动>>, 当Nor Flash 芯片在flash芯片驱动器链表chip_drvs_list中找到并调用名为”cfi_probe”的驱动后, 完成芯片初始化阶段, 接着进入linux对Flash建立分区阶段.

在 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_info是linux描述MTD类型设备的数据结构, 里面有mtd设备等待初始化的信息(变量)和一些设备操作方法(函数指针).

经过do_map_probe()的初始化, 其中有一些成员已经得到赋值, 请参考drivers/mtd/chips/cfi_probe.c 中的cfi_probe()函数.

parse_mtd_partitions(mymtd,
part_probe_types,&parts, 0);

解析器类型: 有cmdlinepart和 RedBoot两中, 如没有定义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->name为octeon_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(), 都是传入一个指定参数, 之后的工作都交给linux的mtd层去实现.

另一方面,  在late_initcall(flash_init)调用之前, linux已经向内核注册了名为”cmdlinepart”的解析器, 等待被调用, 完成使命, 体现出自身价值.

在drivers/mtd/cmdlinepart.c中:

module_init(cmdline_parser_init);

注: module_init()优先级为6, 高于优先级为7的late_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.c的 start_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 添加到linux的mtd核心中.后面会看到这样的操作.
*/

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传给内核的配置命令字符串所代表的配置. 根据配置命令字符串建立了相应的分区.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息