kernel启动流程_DTS解析(源码层面)
此篇博客有很多参考其他文章的内容,由于参考内容繁杂,不一一标注角标了,在末尾会贴上所有参考博客的link,如有侵权,请联系本人处理,谢谢。
深入,并且广泛
-沉默犀牛
我认为作为初学者去学习kernel代码的一个重要方法就是:先知道这些代码是干嘛的,然后再找代码来验证想法。这样的探索顺序会变得事半功倍,让我们直接去看繁杂的代码来分析出代码用途,是非人道主义的。所以此篇博客会先用文字描述一下大致流程,再带着读者到代码中去验证。
执行流程
从dts文件的内容来看,系统平台上挂载了很多总线,i2c,spi,uart等等,每一个总线都被描述为一个节点,Linux启动到kernel 入口后,会执行以下操作来加载系统平台上的总线和设备:start_kernel() ==> setup_arch() ==> unflatten_device_tree()
,执行完unflatten_device_tree()
后,dts的节点信息被解析出来,保存到allnodes链表中。随后启动到board文件时,调用.init_machine,再调用of_platform_populate(....)
接口,加载平台总线和平台设备。至此,系统平台上的所有已配置的总线和设备将被注册到系统中。(对这句话更加正确的解释是:此时所说的设备指的是platform device,此时的总线指的是i2c,spi等,因为i2c总线和spi总线可以理解为注册在platform总线上的device)
注意:不是dtsi文件中所有的节点都会被注册,在注册总线和设备时,会对dts节点的状态作一个判断,如果节点里面的status属性没有被定义,或者status属性被定义了并且值被设为“ok”或者“okay”,其他情况则不被注册到系统中。
那么其他设备,例如i2c、spi设备是怎样注册到系统中的呢?下面我们就以i2c设备为例,看看Linux怎样注册i2c设备到系统中。以高通平台为例,在注册i2c总线时,会调用到qup_i2c_probe()接口,该接口用于申请总线资源和添加i2c适配器。在成功添加i2c适配器后,会调用of_i2c_register_devices()接口。此接口会解析i2c总线节点的子节点(挂载在该总线上的i2c设备节点),获取i2c设备的地址、中断号等硬件信息。然后调用request_module()加载设备对应的驱动文件,调用i2c_new_device(),生成i2c设备。此时设备和驱动都已加载,于是drvier里面的probe方法将被调用。后面流程就和之前一样了。
代码验证
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
mdesc = setup_machine_fdt(__atags_pointer); //根据Device Tree的信息,找到最适合的machine描述符
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);
if (mdesc->reboot_mode != REBOOT_HARD)
reboot_mode = mdesc->reboot_mode;
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
early_fixmap_init();
early_ioremap_init();
parse_early_param();
#ifdef CONFIG_MMU
early_paging_init(mdesc);
#endif
setup_dma_zone(mdesc);
xen_early_init();
efi_init();
/*
* Make sure the calculation for lowmem/highmem is set appropriately
* before reserving/allocating any mmeory
*/
adjust_lowmem_bounds();
arm_memblock_init(mdesc);
/* Memory may have been removed so recalculate the bounds. */
adjust_lowmem_bounds();
early_ioremap_reset();
paging_init(mdesc);
request_standard_resources(mdesc);
if (mdesc->restart)
arm_pm_restart = mdesc->restart;
unflatten_device_tree(); //将DTB转换成节点是device_node的树状结构
我注释的两行代码就是有关DTS解析的重要代码,如注释所述,它们分别做了:
1.根据Device Tree的信息,找到最适合的machine描述符
2.将DTB转换成节点是device_node的树状结构
现在分别仔细看一下实现过程:
1.根据Device Tree的信息,找到最适合的machine_desc,先了解一下结构体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; /*!!!!本文中最重要的成员!!!!!
用于匹配DTS文件*/
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 *);
};
接下来看一看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))) /*early_init_dt_verify()检查fdt头部的合法性,然后设置fdt全局变量以
及计算crc,赋值了一个initial_boot_params变量后边在访问设备树的时候还会用到*/
return NULL;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); //重要函数,下面详细介绍
if (!mdesc) {
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)
mdesc->dt_fixup();
early_init_dt_scan_nodes();
/* Change machine number to match the mdesc we're using */
__machine_arch_type = mdesc->nr;
return mdesc;
}
为了讲清楚mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
这一句话的作用,我们先分析这个函数的第二个参数arch_get_next_match
:
static const void * __init arch_get_next_mach(const char *const **match