u-boot给内核传参说明
众所周知,u-boot传递给内核三个参数,即通过r0、r1、r2寄存器传递给内核。并且,r0的值暂时没有用到,缺省放0。
那么本篇文章主要来讲一下r1和r2的值
寄存器 | 含义 |
---|---|
R1 | 机器号,标识计算机系统的型号 |
R2 | ATAGS or dtb |
说明:关于内核从head.S汇编执行到start_kernel部分这里就不介绍了。最终执行到start_kernel时。r1被赋值给__machine_arch_type
全局变量,r2被赋值给__atags_pointer
全局变量。
内核提供了一个重要的结构体struct machine_desc ,这个结构体在内核移植中起到相当重要的作用,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。
内核编译时维护了一份machine_desc的全局数据结构,用于和uboot传参匹配,做一些芯片相关的初始化工作。
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
phys_addr_t 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 */
enum reboot_mode reboot_mode; /* default restart mode */
unsigned l2c_aux_val; /* L2 cache aux value */
unsigned l2c_aux_mask; /* L2 cache aux mask */
void (*l2c_write_sec)(unsigned long, unsigned);
const struct smp_operations *smp; /* SMP operations */
bool (*smp_init)(void);
void (*fixup)(struct tag *, char **);
void (*dt_fixup)(void);
long long (*pv_fixup)(void);
void (*reserve)(void);/* reserve mem blocks */
void (*map_io)(void);/* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
void (*init_time)(void);
void (*init_machine)(void);
void (*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
void (*handle_irq)(struct pt_regs *);
#endif
void (*restart)(enum reboot_mode, const char *);
}
nr用于和uboot传参的machine id进行匹配。
dt_compat字符串数组,用于与使用设备树的uboot传参,进行比对字符串。
剩下一些回调函数,基本名字翻译过来就是相关的功能。可根据芯片实际情况选择实现or不实现。
struct machine_desc
通过以下两个宏来定义
/*
* Set of macros to define architecture features. This is built into
* a table by the linker.
*/
#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 \
};
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
顾名思义,上面的是传统的machine_desc
定义方式,需要定义machine id。下面的是dtb的定义方式,不需要严格定义machine_id。直接置位0xFFFFFFFF。设备树通过上面讲的“dt_compat”字符串数组匹配。
machine_desc数据结构,存储在内核特定的section中。可见宏定义中的__attribute__((__section__(".arch.info.init")))
。内核编译后可以通过System.map查看。全志平台示例:
c1035e1c T __arch_info_begin
c1035e1c t __mach_desc_GENERIC_DT.31656
c1035e84 t __mach_desc_SUN9I_DT
c1035eec t __mach_desc_SUN8I_DT
c1035f54 t __mach_desc_SUN7I_DT
c1035fbc t __mach_desc_SUN6I_DT
c1036024 t __mach_desc_SUNXI_DT
c103608c T __arch_info_end
因为数据结构大小固定,所以内核可以通过__arch_info_begin
地址,加上数据结构大小偏移,逐一访问这些结构体。
代码初始化
start_kernel->setup_arch
setup_arch与arch体系有关,这里以arm32代码为例进行说明。
/*
* __atags_pointer: 设备树orATAGS
* __machine_arch_type:machine id
*/
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
dump_stack_set_arch_desc("%s", mdesc->name);
......
__atags_pointer可能是dtb,也可能是ATAGS,所以需要逐一判断,优先判断dtb情况。(这个变量名字应该是历史遗留吧,最开始只有ATAGS)。
先看扫描设备树setup_machine_fdt
函数
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;
#if defined(CONFIG_ARCH_MULTIPLATFORM) || defined(CONFIG_ARM_SINGLE_ARMV7M)
DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
.l2c_aux_val = 0x0,
.l2c_aux_mask = ~0x0,
MACHINE_END
mdesc_best = &__mach_desc_GENERIC_DT;
#endif
if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys))) (1)
return NULL;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); (2)
if (!mdesc) { (3)
const char *prop;
int size;
unsigned long dt_root;
early_print("\nError: unrecognized/unsupported "
"device tree compatible list:\n[ ");
dt_root = of_get_flat_dt_root();
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
while (size > 0) {
early_print("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
early_print("]\n\n");
dump_machine_table(); /* does not return */
}
/* We really don't want to do this, but sometimes firmware provides buggy data */
if (mdesc->dt_fixup) (4)
mdesc->dt_fixup();
early_init_dt_scan_nodes(); (5)
/* Change machine number to match the mdesc we're using */
__machine_arch_type = mdesc->nr;
return mdesc;
}
(1)判断指针是否为空,非空情况下再判断是否为dtb数据结构,即check dtb header。如果是ATAGS情况这里就应该直接返回了。如果是dtb,这里会将全局变量initial_boot_params
指向dtb所在位置。
(2)of_flat_dt_match_machine
作用是遍历所有machine_desc
数据结构,并拿到每个数据结构内的dt_compat
字符串数组,与dtb root节点的compatible进行匹配。匹配上即可拿到对应的machine_desc
。
(3)如果遍历了所有machine_desc
,没有找到合适的,dump所有machine_desc
(4)如果定义了机器的dt_fixup回调,则执行。
(5)扫描设备树,获取 1)commandline 2)root节点的一些参数 3)memory参数(内存大小,起始物理地址等)
上文(1)(3)情况退出时,就需要继续判断ATAGS情况了,即setup_machine_tags
函数
const struct machine_desc * __init
setup_machine_tags(phys_addr_t __atags_pointer, unsigned int machine_nr)
{
struct tag *tags = (struct tag *)&default_tags;
const struct machine_desc *mdesc = NULL, *p;
char *from = default_command_line;
default_tags.mem.start = PHYS_OFFSET;
/*
* locate machine in the list of supported machines.
*/
for_each_machine_desc(p) (1)
if (machine_nr == p->nr) {
pr_info("Machine: %s\n", p->name);
mdesc = p;
break;
}
if (!mdesc) { (2)
early_print("\nError: unrecognized/unsupported machine ID"
" (r1 = 0x%08x).\n\n", machine_nr);
dump_machine_table(); /* does not return */
}
if (__atags_pointer) (3)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->atag_offset) (4)
tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);
#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
#endif
if (tags->hdr.tag != ATAG_CORE) {
early_print("Warning: Neither atags nor dtb found\n");
tags = (struct tag *)&default_tags;
}
if (mdesc->fixup)
mdesc->fixup(tags, &from); (5)
if (tags->hdr.tag == ATAG_CORE) {
if (memblock_phys_mem_size())
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
}
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE); (6)
return mdesc;
}
(1)遍历machine_desc
,与传参的machine_nr
进行匹配。(使用ATAGS的内核,machine_desc必须定义nr,且必须与uboot传递的保持一致)
(2)找不到dump所有描述符
(3)传参了ATAGS物理地址,则转换为虚拟地址
(4)没传地址,且机器描述符定义了ATAGS偏移地址情况下,使用改地址的ATAGS。当3、4情况都不满足时,使用default_tags。
(5)通过fixup
回调做machine初始化操作,并且填充command line
。如果没填充,则使用default_command_line。
注:剩下的一些操作都是ATAG相关的了。
小结
综上,通过dtb传参的方式,可以不需要machine id。而通过ATAGS传参的方式,则需要machine id来匹配machine_desc
。
在引入dts的内核版本后,基本都是用dtb得到方式传参了。当然也有一些芯片厂家,为了兼容老的sdk,还是有使用atags的方式。这里就引申出了一个问题,同时需要穿atags和dtb的情况。
讲一下我了解到的几种方式
1)内核menuconfig支持,dtb append在内核后面,也就是在内核后面拼接一个dtb。然后uboot传参时,r2照样传递atags,dtb则从内核镜像后面去获取。存在内核和dtb绑定了,不能灵活兼容不同dtb的问题,不过这个问题可以在u-boot中规避,jump前根据板级信息,选择不同的dtb拼接在内核后面即可。海思安防芯片sdk就是通过这种方式同时传递atags和dtb的。
2)atags增加dtb的tag。将dtb作为atags的一部分,传递给内核,内核解析atags类型,获取dtb内容。
3)八仙过海,各显神通,瞎jb魔改就完事了。
完!