- 简介
- 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.03
Linux-版本
VERSION = 4
PATCHLEVEL = 0
SUBLEVEL = 0
u-boot 启动内核
首先是u-boot初始化的操作,我们这里首先不做分析。直接分析void main_loop(void) 。因为这个函数是u-boot初始化好了以后,一直循环这个函数来等待命令。直到等待超时,来启动内核。我的目的是分析这个自动启动内核所使用的命令以及参数。
main_loop
s = 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_ARM64
void (*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会递增的,先存r4
ldmia r3!, {r4, r5, r6, r7}
# 这里bss段之类的鬼东西
# 还有代码的重定位
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
# 前面提到了 r3 地址每一次会增加
# 在这里再一次读取数据,
# r5 = &__machine_arch_type
# r6 = &__atags_pointer
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
# 执行到这里的时候
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
cmp r7, #0
strne r0, [r7] @ Save control register values
# 执行到这里我们就可以确定
# __machine_arch_type 保存了R1寄存器的数据,也就是机器ID
# __atags_pointer 保存了R2寄存器的数据,早期版本(没有设备树以前)这里存放Atags,现在存放设备树地址
b start_kernel
ENDPROC(__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, and
3) device population.
这一节的目的主要是为了讲述匹配machine_desc
结构体的过程。这个结构体描述了系统体系架构相关部分的内容。
arch\arm\include\asm\mach
struct 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.c
DT_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_END
static 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.c
start_kernel
setup_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_machine
mdesc = 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 越小越好。最小就是为1
best_score = score;
}
}
return best_data;
of_flat_dt_match
这个函数一直深入下去会匹配compatible
参数。然后作比较返回匹配值。
of_flat_dt_match -> of_fdt_match -> of_fdt_is_compatible
int 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.c
start_kernel
setup_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_dt
const 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_root
prop = 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_memory
const 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_reserve
early_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 + l
np = 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 ` 。每一个节点都可能包含子节点和父节点以及兄弟节点。当然,除了根节点没有父节点。在这里,树的概念就形成了。
```c
struct 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。
- 转化这个过程是怎样的?
```c
platform_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_init
kernel_init_freeable
do_basic_setup();
do_initcalls();
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
# 我们需要分析的是level 3
do_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以及SPI
of_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_register
bus_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_attach
static 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.c
static 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.h
include/linux/of_pci.h
include/linux/of_device.h
include/linux/of_address.h
include/linux/of_graph.h
include/linux/of_iommu.h
include/linux/of_mdio.h
include/linux/of_reserved_mem.h
include/video/of_videomode.h
include/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)