1.首先要解决的问题就是内核如何进行多平台的适配,内核是如何认识这些板子的,在linux-3.4.2\arch\arm\kernel\vmlinux.lds.S 中定义了很多很多的段,如下就是其中一段
.init.arch.info : { __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .; }
搜索.arch.info.init 的引用会查看到如下的宏定义代码
#define MACHINE_START(_type,_name) \ static const struct machine_desc __mach_desc_##_type \ __used \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = MACH_TYPE_##_type, \ .name = _name, #define MACHINE_END \ };
搜索MACHINE_START这个宏定义的引用会发现会有很多地方引用,这些即是linux中不同板子对应的宏定义调用。
我们打开其中一个板子对应的宏定义调用查看
MACHINE_START(AFEB9260, "Custom afeb9260 board") /* Maintainer: Sergey Lapin <slapin@ossfans.org> */ .timer = &at91sam926x_timer, .map_io = at91_map_io, .init_early = afeb9260_init_early, .init_irq = at91_init_irq_default, .init_machine = afeb9260_board_init, MACHINE_END
将宏定义和具体的某个板子的信息结合一下就可以得到如下代码
#define MACHINE_START(_type,_name) static const struct machine_desc __mach_desc_CPUAT9G20 __used __attribute__((__section__(".arch.info.init"))) = { //系统指定将这个结构体放在代码段arch.info.init上 .nr = MACH_TYPE_CPUAT9G20 .name = "Eukrea CPU9G20", .timer = &at91rm9200_timer, .map_io = at91_map_io, .init_early = carmeva_init_early, .init_irq = at91_init_irq_default, .init_machine = carmeva_board_init, #define MACHINE_END };
如上可知系统创建了一个 machine_desc结构体对应的变量叫__mach_desc_CPUAT9G20
, 系统指定将这个结构体放在代码段arch.info.init上(使用attribute)。linux就是这样,在vmlinux.lds.S链接文件中定义不同段,将要用到的信息放进段里去。
下面我们来看看machine_desc这个结构体:
struct machine_desc { unsigned int nr; /* architecture number */ const char *name; /* architecture name */ unsigned long atag_offset; /* tagged list (relative) */ const char *const *dt_compat; /* array of device tree * 'compatible' strings */ unsigned int nr_irqs; /* number of IRQs */ #ifdef CONFIG_ZONE_DMA unsigned long dma_zone_size; /* size of DMA-able area */ #endif unsigned int video_start; /* start of video RAM */ unsigned int video_end; /* end of video RAM */ unsigned char reserve_lp0 :1; /* never has lp0 */ unsigned char reserve_lp1 :1; /* never has lp1 */ unsigned char reserve_lp2 :1; /* never has lp2 */ char restart_mode; /* default restart mode */ void (*fixup)(struct tag *, char **, struct meminfo *); void (*reserve)(void);/* reserve mem blocks */ void (*map_io)(void);/* IO mapping function */ void (*init_early)(void); void (*init_irq)(void); struct sys_timer *timer; /* system tick timer */ void (*init_machine)(void); #ifdef CONFIG_MULTI_IRQ_HANDLER void (*handle_irq)(struct pt_regs *); #endif void (*restart)(char, const char *); };
这里面存放着大量的针对板子的信息,会对uboot传过来的参数进行结构体配置(atag_offset; /* tagged list (relative) */)。并且在linux移植的时候也要对该结构体的变量进行赋值。并且在之后的启动或其他函数中,可以对该结构体的变量进行调用。
在arch.h中有如下的宏定义
#define for_each_machine_desc(p) \ for (p = __arch_info_begin; p < __arch_info_end; p++)
上面代码的_arch_info_begin和 _arch_info_end会指向链接文件vmlinux.lds.S中的一段代码
.init.arch.info : { __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .; }
由此可知这些板子的结构体machine_desc都放到了arch_info_begin到_arch_info_end这段内存当中。
我们可以查看一下arch.info.init这段内存到底存了些什么,搜索arch.info.init会找到上面提到过的宏定义#define MACHINE_START(_type,_name) 。
#define MACHINE_START(_type,_name) \ static const struct machine_desc __mach_desc_##_type \ __used \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = MACH_TYPE_##_type, \ .name = _name, #define MACHINE_END \ };
找到for_each_machine_desc宏定义的引用setup_machine_fd函数如下:
struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) { 。。。。。。。。。。。。。。。。。。。。。。 for_each_machine_desc(mdesc) { score = of_flat_dt_match(dt_root, mdesc->dt_compat); if (score > 0 && score < mdesc_score) { mdesc_best = mdesc; mdesc_score = score; } } 。。。。。。。。。。。。。。。。。。。。。。 return mdesc_best; }
该代码会找到一个移植Linux时,写的最适合的machine desc结构体mdesc_best;并且返回。
查看setup_machine_fd是被谁调用时会找到如下代码:
void __init setup_arch(char **cmdline_p){ 。。。。。。。。。。。。。。。。。。。。。 mdesc = setup_machine_fdt(__atags_pointer); machine_desc = mdesc; //定义各种全局变量 machine_name = mdesc->name; 。。。。。。。。。。。。。。。。。。。。。。。 }
atags_pointer是uboot传过来的参数列表,Linux将这个数据通过setup_machine_fd函数对板子的信息进行参数解析。将返回的值,即mdesc_best适配的结构体赋值给各种全局变量,然后在其他函数中调用。