- 简介
- u-boot 启动内核
- 内核对设备树的处理
- arm\kernel\head-nommu.S
- 我们分析 head-nommu.S 这个汇编文件可以确定一件事,那就是r0,r1,r2三个寄存器都没有被使用到。
- ifdef CONFIG_CPU_THUMBONLY
- else
- address-cells
- size-cells
- create tree of device_nodes from flat blob
- 在这里创建 device_nodes
- dtb 里面包含了各个设备节点,现在要将他设置为一棵树
- 遍历每一个节点,填充struct device_node 结构体
- 构造每一个device_node 节点,填充 struct device_node 结构体
- 内核中的设备树文件以及函数
- 总结
- 参考资料
简介
这一篇文章的主要目的是为了理清U-boot和设备树之间的关系。我以前查到的资料一直以为U-boot会传递参数确定内核使用哪一个设备树。有一次我去一家公司面试的时候,被指导说U-boot似乎是不能这么操作。因此想要求证一下。首先需要确定一件事,就是U-boot也在使用设备树。我之前以为设备树是只有Linux会使用的,但是今天面试的时候,经过大佬提醒才反应过来。U-boot也已经有那么庞大了,开始使用设备树是很正常的事。
Uboot-2017.03Linux-版本VERSION = 4PATCHLEVEL = 0SUBLEVEL = 0
u-boot 启动内核
首先是u-boot初始化的操作,我们这里首先不做分析。直接分析void main_loop(void) 。因为这个函数是u-boot初始化好了以后,一直循环这个函数来等待命令。直到等待超时,来启动内核。我的目的是分析这个自动启动内核所使用的命令以及参数。
main_loops = bootdelay_process();s = getenv("bootcmd"); # 返回得到的bootcmd 命令# 前面讲到u-boot也是用了设备树,这个参数一般会设置到设备树里面# 查看设备树里面的# "bootm ${tee_addr} ${initrd_addr} ${fdt_addr}; "# 在这里我们就可以确定使用的是 bootm 确实是指定了 设备树地址autoboot_command(s);run_command_list(s, -1, 0);
上面确定了bootm将会传递三个参数,然后会根据启动内核,调用对应的os启动函数,那么怎么知道是调用的哪一个函数了?
boot_os_fn 函数
/** Continue booting an OS image; caller already has:* - copied image header to global variable `header'* - checked header magic number, checksums (both header & image),* - verified image architecture (PPC) and type (KERNEL or MULTI),* - loaded (first part of) image to header load address,* - disabled interrupts.** @flag: Flags indicating what to do (BOOTM_STATE_...)* @argc: Number of arguments. Note that the arguments are shifted down* so that 0 is the first argument not processed by U-Boot, and* argc is adjusted accordingly. This avoids confusion as to how* many arguments are available for the OS.* @images: Pointers to os/initrd/fdt* @return 1 on error. On success the OS boots so this function does* not return.*/typedef int boot_os_fn(int flag, int argc, char * const argv[],bootm_headers_t *images);
在u-boot的bootm_os.c 文件中定义以下几个函数。
static boot_os_fn *boot_os[] = {[IH_OS_U_BOOT] = do_bootm_standalone,#ifdef CONFIG_BOOTM_LINUX[IH_OS_LINUX] = do_bootm_linux,#endif#ifdef CONFIG_BOOTM_NETBSD[IH_OS_NETBSD] = do_bootm_netbsd,#endif...}
显然我们只需要分析do_bootm_linux 函数就可以了。此函数位于arch\arm\lib\bootm.c
/* Main Entry point for arm bootm implementation** Modeled after the powerpc implementation* DIFFERENCE: Instead of calling prep and go at the end* they are called if subcommand is equal 0.*/int do_bootm_linux(int flag, int argc, char * const argv[],bootm_headers_t *images)boot_jump_linux(images, flag);/* Subcommand: GO */static void boot_jump_linux(bootm_headers_t *images, int flag)#ifdef CONFIG_ARM64void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,void *res2);#else/*原来arm64和arm32启动参数多了一个*/void (*kernel_entry)(int zero, int arch, uint params);/*最重要的在这里*/kernel_entry = (void (*)(int, int, uint))images->ep;/*r2有两种赋赋值方式*/if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)/*根据镜像里面指定的地址*/r2 = (unsigned long)images->ft_addr; # 设备树的地址else/***********************************//*根据启动命令参数的地址*/r2 = gd->bd->bi_boot_params;# 参考AAPCS规则# r0 = 0# r1 = machid# r2 = r2# 现在我们就进入内核了,这一步和内联汇编操作没什么区别kernel_entry(0, machid, r2);
上面最重要的就是kernel_entry的处理。其中r2 = gd->bd->bi_boot_params; 就表示我们u-boot启动的时候可以命令指定设备树。
内核对设备树的处理
- 首先确定内核入口
```bash
arm\kernel\head-nommu.S
我们分析 head-nommu.S 这个汇编文件可以确定一件事,那就是r0,r1,r2三个寄存器都没有被使用到。
/*- Kernel startup entry point.
*- This is normally called from the decompressor code. The requirements
- are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
- r1 = machine nr. *
- See linux/arch/arm/tools/mach-types for the complete list of machine
- numbers for r1. /
ifdef CONFIG_CPU_THUMBONLY
.thumb
ENTRY(stext)
else
.arm
ENTRY(stext) … /记住r9是在内核启动时修改的,并非是u-boot传递进来的/ ldr r9, =CONFIG_PROCESSOR_ID … 1: bl after_proc_init b mmap_switched
在u-boot启动内核时,首先判断是否开启mmu。否则将会调用__mmap_switched。此函数在head-common.S中。再调用__mmap_switched。需要注意此时R0,R1,R2寄存器都没有被修改,还是u-boot传入进来的value。<a name="AjBQo"></a>## head-common.S 对dtb的简单处理从__mmap_switched入手分析。```bash/** The following fragment of code is executed with the MMU on in MMU mode,* and uses absolute addresses; this is not position independent.** r0 = cp#15 control register* r1 = machine ID* r2 = atags/dtb pointer* r9 = processor ID*/__INIT# 内核启动以后判断到没有开启MUU,调用这个函数__mmap_switched:adr r3, __mmap_switched_data# 将地址上的值加载到寄存器上# r3的value会递增的,先存r4ldmia r3!, {r4, r5, r6, r7}# 这里bss段之类的鬼东西# 还有代码的重定位cmp r4, r5 @ Copy data segment if needed1: cmpne r5, r6ldrne fp, [r4], #4strne fp, [r5], #4bne 1bmov fp, #0 @ Clear BSS (and zero fp)1: cmp r6, r7strcc fp, [r6],#4bcc 1b# 前面提到了 r3 地址每一次会增加# 在这里再一次读取数据,# r5 = &__machine_arch_type# r6 = &__atags_pointerARM( ldmia r3, {r4, r5, r6, r7, sp})THUMB( ldmia r3, {r4, r5, r6, r7} )THUMB( ldr sp, [r3, #16] )# 执行到这里的时候str r9, [r4] @ Save processor IDstr r1, [r5] @ Save machine typestr r2, [r6] @ Save atags pointercmp r7, #0strne r0, [r7] @ Save control register values# 执行到这里我们就可以确定# __machine_arch_type 保存了R1寄存器的数据,也就是机器ID# __atags_pointer 保存了R2寄存器的数据,早期版本(没有设备树以前)这里存放Atags,现在存放设备树地址b start_kernelENDPROC(__mmap_switched).align 2.type __mmap_switched_data, %object__mmap_switched_data:.long __data_loc @ r4.long _sdata @ r5.long __bss_start @ r6.long _end @ r7.long processor_id @ r4.long __machine_arch_type @ r5.long __atags_pointer @ r6#ifdef CONFIG_CPU_CP15.long cr_alignment @ r7
分析到这里,我认为结论已经相当清晰了,u-boot通过r2寄传递设备树地址,
上面的代码可以分析到,在调用start_kernel以前。机器ID和设存器备树地址已经存放在下面两个地址了。以后就可以通过这两个地址访问。
# __machine_arch_type 保存了R1寄存器的数据,也就是机器ID# __atags_pointer 保存了R2寄存器的数据,早期版本(没有设备树以前)这里存放Atags,现在存放设备树地址
对设备树平台信息的处理
设备树信息,参考Documentation\devicetree\usage-model.txt文档
Linux uses DT data for three major purposes:1) platform identification,2) runtime configuration, and3) device population.
这一节的目的主要是为了讲述匹配machine_desc 结构体的过程。这个结构体描述了系统体系架构相关部分的内容。
arch\arm\include\asm\machstruct machine_desc {unsigned int nr; /* architecture number */const char *name; /* architecture name */unsigned long atag_offset; /* tagged list (relative) */...}
我们编译的一个UImage很有可能支持多种板子,我们可以通过compatible 来匹配对应的machine_desc。
首先查看我们的设备树文件,确定compatible
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
搜索上面的compatible然后我们就可以确定,匹配的数据了。
arch\arm\mach-imx\mach-imx6ul.cDT_MACHINE_START(IMX6UL, "Freescale i.MX6 UltraLite (Device Tree)").map_io = imx6ul_map_io,.init_irq = imx6ul_init_irq,.init_machine = imx6ul_init_machine,.init_late = imx6ul_init_late,.dt_compat = imx6ul_dt_compat,MACHINE_ENDstatic const char * const imx6ul_dt_compat[] __initconst = {"fsl,imx6ul","fsl,imx6ull",NULL,
DT_MACHINE_START 定义了一个machine_desc结构体。其中的imx6ul_dt_compat 用来指定匹配的dts。那么内核是怎么找到这一个结构体的了?这一切一切的根源又要从start_kernel说起。
start_kernel 匹配machine_desc
init\main.cstart_kernelsetup_arch(&command_line);# __atags_pointer 就是保存的设备树的地址mdesc = setup_machine_fdt(__atags_pointer);early_init_dt_verify(phys_to_virt(dt_phys)# 检测设备树有效性# params 就是虚拟地址fdt_check_header(params)/* Setup flat device-tree pointer */# initial_boot_params 也是一个全局的空指针函数initial_boot_params = params;# 比较重要的就是这一步# 在这里找到了匹配的match_machinemdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);# machine_desc 是全局的指针函数,machine_desc = mdesc;
在这里将全局变量 initial_boot_params初始化了。此变量保存的是 设备树的虚拟地址。
同时还保存了machine_desc指针。这个指针保存了匹配到的machine_desc结构体。
of_flat_dt_match_machine
查找machine_desc结构体最重要的of_flat_dt_match_machine 函数。此函数将会找到最合适的machine_desc结构体。
/*** of_flat_dt_match_machine - Iterate match tables to find matching machine.** @default_match: A machine specific ptr to return in case of no match.* @get_next_compat: callback function to return next compatible match table.** Iterate through machine match tables to find the best match for the machine* compatible string in the FDT.*/const void * __init of_flat_dt_match_machine(const void *default_match,const void * (*get_next_compat)(const char * const**))dt_root = of_get_flat_dt_root();# 以 score 为依据,while ((data = get_next_compat(&compat))) {score = of_flat_dt_match(dt_root, compat);if (score > 0 && score < best_score) {/*保存最匹配的数据*/best_data = data;# 最后返回的score 越小越好。最小就是为1best_score = score;}}return best_data;
of_flat_dt_match
这个函数一直深入下去会匹配compatible 参数。然后作比较返回匹配值。
of_flat_dt_match -> of_fdt_match -> of_fdt_is_compatibleint of_fdt_is_compatible(const void *blob,unsigned long node, const char *compat)cp = fdt_getprop(blob, node, "compatible", &cplen);while (cplen > 0) {score++;if (of_compat_cmp(cp, compat, strlen(compat)) == 0)/*匹配 compatible 里面的每一个value score 为1 肯定是最匹配的, 也就是第一个compatible里面的value*/return score;l = strlen(cp) + 1;cp += l;cplen -= l;}
对设备树中运行时的配置信息的处理
上面我们已经知道内核匹配到了machine_desc结构体。然后将其保存在了machine_desc指针中。下面我们来查查看内核是怎么来操作这些信息的。
init\main.cstart_kernelsetup_arch(&command_line);# __atags_pointer 就是保存的设备树的地址mdesc = setup_machine_fdt(__atags_pointer);mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);early_init_dt_scan_nodes();void __init early_init_dt_scan_nodes(void)/* Retrieve various information from the /chosen node */of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);/* Initialize {size,address}-cells info */of_scan_flat_dt(early_init_dt_scan_root, NULL);/* Setup memory, calling early_init_dt_add_memory_arch */of_scan_flat_dt(early_init_dt_scan_memory, NULL);
of_scan_flat_dt 函数的处理方式
of_scan_flat_dtconst void *blob = initial_boot_params; // 设备树的虚拟地址/*遍历寻找所有节点,每找到一个节点,就调用一下回调函数,直到返回1*/for (offset = fdt_next_node(blob, -1, &depth);offset >= 0 && depth >= 0 && !rc;offset = fdt_next_node(blob, offset, &depth)) {pathp = fdt_get_name(blob, offset, NULL);if (*pathp == '/')pathp = kbasename(pathp);rc = it(offset, pathp, depth, data);}
chosen 节点的处理
将/chosen 节点中的bootargs 属性中的value存入全局变量boot_command_line char数组中。
确定根节点的下面的两个属性值
address-cells
size-cells
保存到了dt_root_size_cells以及dt_root_addr_cellsearly_init_dt_scan_rootprop = of_get_flat_dt_prop(node, "#size-cells", NULL);if (prop)dt_root_size_cells = be32_to_cpup(prop);prop = of_get_flat_dt_prop(node, "#address-cells", NULL);if (prop)dt_root_addr_cells = be32_to_cpup(prop);
解析/memory节点中的reg属性
early_init_dt_scan_memoryconst char *type = of_get_flat_dt_prop(node, "device_type", NULL);.../*运行到这里的时候,已经能确定是处于memory节点下面*//*获得reg参数,l就是参数长度*/reg = of_get_flat_dt_prop(node, "reg", &l);/*获取这节点数据的结束地址*/endp = reg + (l / sizeof(__be32));/*得到base 以及size*/base = dt_mem_next_cell(dt_root_addr_cells, ®);size = dt_mem_next_cell(dt_root_size_cells, ®);/*早期初始化memory*/early_init_dt_add_memory_arch(base, size);...memblock_add(base, size);
这个函数最终调用了
memblock_add函数来添加内存块。dtb转化为device_node
函数调用过程 ```c init\main.c start_kernel setup_arch(&command_line);
/* Register the kernel text, kernel data and initrd with memblock. */arm_memblock_init(mdesc); //arch\arm\kernel\devtree.c/*保留设备树的这一块内存*/early_init_fdt_reserve_self();//drivers\of\fdt.c/*保留设备树自身的这一块区域*/# 使得系统运行中可以随时访问这一块区域early_init_dt_reserve_memory_arch(__pa(initial_boot_params),fdt_totalsize(initial_boot_params),0);# 根据dtb中的memreserver中的信息调用memblock_reserveearly_init_fdt_scan_reserved_mem();
create tree of device_nodes from flat blob
在这里创建 device_nodes
dtb 里面包含了各个设备节点,现在要将他设置为一棵树
unflatten_device_tree();
# early_init_dt_alloc_memory_arch 是一个回调函数__unflatten_device_tree(initial_boot_params, NULL, &of_root,early_init_dt_alloc_memory_arch, false);# 获取大小size = unflatten_dt_nodes(blob, NULL, dad, NULL);/* Allocate memory for the expanded device tree */# 分配空间,# 但是为什么这里还需要加上4了?是为了加上魔数占用的空间mem = dt_alloc(size + 4, __alignof__(struct device_node));# 填充对应的魔数#*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);/* Second pass, do actual unflattening */# 在这里就是真正的处理设备树节点了unflatten_dt_nodes(blob, mem, dad, mynodes);
遍历每一个节点,填充struct device_node 结构体
unflatten_dt_nodes
# 遍历每一个节点,然后调用下面的函数,# 构造 struct device_node 以及 struct property 的属性populate_node(blob, offset, &mem,nps[depth],fpsizes[depth],&nps[depth+1], dryrun);
构造每一个device_node 节点,填充 struct device_node 结构体
populate_node
# 获取节点 name属性pathp = fdt_get_name(blob, offset, &l);# 分配空间 sizeof(struct device_node) + allocl# allocl 大小是 fpsize + lnp = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,__alignof__(struct device_node));# 现在这里可以看到 struct device_node 是怎么填充的了np->full_name = fn = ((char *)np) + sizeof(*np);np->name = of_get_property(np, "name", NULL);np->type = of_get_property(np, "device_type", NULL);
这里主要讲述了struct device_node 的分配以及填充<a name="j08qn"></a>### struct device_node在设备树文件里面,每一个节点对应下面的一个`struct device_node ` 。每一个节点都可能包含子节点和父节点以及兄弟节点。当然,除了根节点没有父节点。在这里,树的概念就形成了。```cstruct device_node {const char *name; /*来源于节点的name属性,并非节点的名称*/const char *type; /*来源于节点的device_type属性*/phandle phandle;const char *full_name; /*这个才是节点的名字*/struct fwnode_handle fwnode;struct property *properties;struct property *deadprops; /* removed properties */struct device_node *parent;struct device_node *child;struct device_node *sibling;struct kobject kobj;unsigned long _flags;void *data;/*数据都放放在这个地方的,比如 name 以及 type的内容*/};
我们还可以看到两个struct property 结构体指针。
struct property {char *name; /*属性名称*/int length; /*表示value的长度。(1个字节为单位)*/void *value;struct property *next; # 组成了链表unsigned long _flags;unsigned int unique_id;struct bin_attribute attr;};
device_node转化为platform_device
上面我们讲述了dtb怎么转化为device_node节点。现在我们需要来分析device_node节点转化为platform_device。
有两个问题,
1. 那些device_node可以转化成为platform_device?2. 转化这个过程是怎样的?
- 首先分析第一个问题。 ```c
- 节点必须含有 compatible 属性(不包含根节点)
- 属于根节点的子节点(必须含有compatible 属性)
I2C以及SPI等子节点应该交给对应的总线驱动来处理,不应该被转化为platform_device。
- 转化这个过程是怎样的?```cplatform_device 中包含有 resource 数组。里面包含了 IO 资源,内存资源。中断资源,platform_device.dev 中包含了 of_node 结构体,这里包含了设备树的属性
platform_device
struct platform_device {const char *name;int id;bool id_auto;# struct device 结构体里面 包含了一个# struct device_node *of_node;# 这个结构体里面都是包含了读取设备树节点的属性值struct device dev;u32 num_resources; # resource 数组的长度# 资源包含了 IO 资源,内存资源。中断资源, 这些资源都是从device node里面得到struct resource *resource; # 指向了一个数组,这些资源来自设备树中的reg属性const struct platform_device_id *id_entry;char *driver_override; /* Driver name to force a match *//* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */struct pdev_archdata archdata;};
初始化步骤
arch_initcall_sync(of_platform_default_populate_init);#define arch_initcall_sync(fn) __define_initcall(fn, 3s)#define __define_initcall(fn, id) \static initcall_t __initcall_##fn##id __used \__attribute__((__section__(".initcall" #id ".init"))) = fn;static initcall_t __initcall_of_platform_default_populate_init3s__used __attribute__((__section__(".initcall3s.init"))) =of_platform_default_populate_init;;
我们可以看到 of_platform_default_populate_init 函数被放到了.initcall3s.init段落中。此时我们查看链接脚本
# arch/arm/kernel/vmlinux.lds__initcall3_start = .; KEEP(*(.initcall3.init)) KEEP(*(.initcall3s.init)) __initcall4_start = .;
也就是说所有的.initcall3s.init中的数据都是放在initcall3_start以后的内存里面。直到initcall4_start。
下面我们来跟踪这个的调用过程
start_kernel/* Do the rest non-__init'ed, we're now alive */rest_init();# 新建了一个线程kernel_thread(kernel_init, NULL, CLONE_FS);kernel_initkernel_init_freeabledo_basic_setup();do_initcalls();for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)do_initcall_level(level);# 我们需要分析的是level 3do_initcall_level(int level)for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)do_one_initcall(*fn);
上面我们确定了 of_platform_default_populate_init 函数将会被调用。那么现在就分析此函数
of_platform_default_populate_init
此函数的大致作用是遍历整个设备树节点分别为每一个可以转化为platform_device的device_node创建平台设备。
of_platform_default_populate_init/* Populate everything else. */of_platform_default_populate(NULL, NULL, NULL);of_platform_populate(root, of_default_bus_match_table, lookup,parent);for_each_child_of_node(root, child)# 默认根节点下面的子节点都是总线节点,比如 i2C以及SPIof_platform_bus_create(child, matches, lookup, parent, true);of_platform_bus_create/* Make sure it has a compatible property */# 印证了我们的第一个问题,必须要有 compatible 属性of_get_property(bus, "compatible", NULL)# 创建平台设备数据dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);for_each_child_of_node(bus, child)# 递归循环下去of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
platform_device和platform_driver的匹配
上面我们讨论了platform_device的被创建的过程。那么现在我们需要讨论一下platform_device和platform_driver匹配过程。
内核探查到了设备,但是设备对应的驱动还需要匹配才行。因此我们需要来分析这个匹配过程。
大致可以分为两个主题,
platform_driver 的注册过程platform_device 的注册过程换句话说就是 驱动的注册过程,以及设备的注册过程。
首先明确一点,设备就是一个又一个struct platform_device结构体,设备驱动就是一个又一个
struct platform_driver结构体。
platform_driver 的注册过程
platform_driver_register__platform_driver_register# 比较重要的就是 probe函数drv->driver.probe = platform_drv_probe;driver_registerbus_add_driver(drv);# 很重要的一步,将platform_driver 放入 platform_bus_driver 链表中去klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);# 尝试绑定驱动到设备上面# try to bind driver to devices.driver_attach(drv);bus_for_each_dev # 包含一个回调函数 __driver_attachstatic int __driver_attach(struct device *dev, void *data)# 就是这个,相当重要的函数,驱动和设备的匹配ret = driver_match_device(drv, dev);
上面大致分析了 platform_driver_register函数的浅层调用过程。直到driver_match_device函数的调用。
三个平台设备相关的结构体
总线类型 bus_type platform_bus_type设备 platform_driver驱动 platform_device
首先我们查看设备树文件中的
gpio_spi: gpio_spi@0 {compatible = "fairchild,74hc595";gpio-controller;#gpio-cells = <2>;reg = <0>;registers-number = <1>;registers-default = /bits/ 8 <0x57>;spi-max-frequency = <10000>;};
搜索对应的”fairchild,74hc595”
# drivers\gpio\gpio-74x164.cstatic const struct of_device_id gen_74x164_dt_ids[] = {{ .compatible = "fairchild,74hc595" },{ .compatible = "nxp,74lvc594" },{},};MODULE_DEVICE_TABLE(of, gen_74x164_dt_ids);# 从这里我们可以观察到static struct spi_driver gen_74x164_driver = {.driver = {.name = "74x164",.of_match_table = gen_74x164_dt_ids,},.probe = gen_74x164_probe,.remove = gen_74x164_remove,};
以gen_74x164_probe为入口来分析数据,待续
内核中的设备树文件以及函数
内核中与设备树有关的文件
include/linux/of_fdt.h # 设备树的一般处理文件include/linux/of_irq.h # 地址相关文件include/linux/of_net.h #include/linux/of_gpio.h # GPIO 相关函数include/linux/of_platform.h # 平台相关函数# 将device_node 转化为 platform_device的函数include/linux/of_dma.h # 设备树中dma相关函数include/linux/of_pdt.hinclude/linux/of_pci.hinclude/linux/of_device.hinclude/linux/of_address.hinclude/linux/of_graph.hinclude/linux/of_iommu.hinclude/linux/of_mdio.hinclude/linux/of_reserved_mem.hinclude/video/of_videomode.hinclude/video/of_display_timing.h
总结
- bootloader 启动内核的时候将会设置三个寄存器- r0 一般设置为0- r1 一般设置为机器ID- r2 设置为设备树地址或者是早期的ATAGS(没有设备树以前使用,现在已经丢弃了)- 按照以下的dtb -> device_node -> platform_device (最后匹配platform_driver)
有关于 platform device 和 platform_driver 之间的匹配可以参考 驱动匹配过程
参考资料
第01节_传递dtb给内核
What does __init mean in the Linux kernel code?
Linux启动过程分析(head.s)
