内核版本:linux-4.19
之前系统的学习了有关设备树的一些知识,时间长了总会有忘记的时候,所以现在把所学到的知识记录下来。
系统启动后,内核会执行一段汇编代码,汇编代码暂不分析,我们从 start_kernel 开始。
优先被初始化的信息
调用流程:
start_kernel -->setup_arch -->setup_machine_fdt -->early_init_dt_verify /* 验证设备树文件 */ -->of_flat_dt_match_machine /* 与内核中注册的 machine_desc 进行比较, 最终获取到与之匹配的 machine_desc */ -->arch_get_next_mach /* 获取到 machine_desc 的 dt_compat 属性 */ -->early_init_dt_scan_nodes /* 获取到设备树中 chosen、{size,address}-cells、memory 信息 */
early_init_dt_verify 代码:
bool __init early_init_dt_verify(void *params){ if (!params) return false; /* 验证设备树的 magic */ if (fdt_check_header(params)) return false; /* 设置 device-tree 指针 */ initial_boot_params = params; of_fdt_crc32 = crc32_be(~0, initial_boot_params, fdt_totalsize(initial_boot_params)); return true;}
of_flat_dt_match_machine 代码:
获取到最为匹配的 machine_desc。
const void * __init of_flat_dt_match_machine(const void *default_match, const void * (*get_next_compat)(const char * const**)){ ... while ((data = get_next_compat(&compat))) { score = of_flat_dt_match(dt_root, compat); if (score > 0 && score < best_score) { best_data = data; best_score = score; } } ... return best_data;}
early_init_dt_scan_nodes 代码:
/* 扫描 /chosen 节点,处理 bootargs 并保存至 boot_command_line */of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);/* 获取 {size,address}-cells 信息 */of_scan_flat_dt(early_init_dt_scan_root, NULL);/* 设置 memeory */of_scan_flat_dt(early_init_dt_scan_memory, NULL);
通过 early_init_dt_scan_memory 函数,最后调用 memblock_add 来完成 memory 的设置。
设备树展开
接下来,内核会展开设备树,并将节点构建为 device_node。便于系统管理、使用。
调用流程:
start_kernel -->setup_arch -->unflatten_device_tree -->__unflatten_device_tree -->unflatten_dt_nodes(blob, NULL, dad, NULL); /* First pass, scan for size */ -->fdt_next_node /* 获取每个 node 的 offsize, 并统计整体大小 */ -->unflatten_dt_nodes(blob, mem, dad, mynodes); /* Second pass, do actual unflattening */ -->populate_node
device_node 结构:
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;#if defined(CONFIG_OF_KOBJ) struct kobject kobj;#endif unsigned long _flags; void *data;#if defined(CONFIG_SPARC) const char *path_component_name; unsigned int unique_id; struct of_irq_controller *irq_trans;#endif};
property 结构:
struct property { char *name; /* 属性名字 */ int length; /* 属性值长度 */ void *value; /* 属性值指针 */ struct property *next;#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC) unsigned long _flags;#endif#if defined(CONFIG_OF_PROMTREE) unsigned int unique_id;#endif#if defined(CONFIG_OF_KOBJ) struct bin_attribute attr;#endif};
这些 device_node 构成一棵树,根节点为: of_root。
device_node 转换为 platform_device
调用流程:
arch_initcall_sync(of_platform_default_populate_init); -->of_platform_default_populate -->of_platform_populate -->of_platform_bus_create -->of_platform_device_create_pdata -->of_device_alloc -->of_device_add
这时涉及到 initcall 调用问题,应该会在总结。
of_device_alloc 代码:
struct platform_device *of_device_alloc(struct device_node *np, const char *bus_id, struct device *parent){ ... platform_device_alloc /* 分配 platform_device */ ... of_address_to_resource /* 解析 address 资源 */ ... of_irq_to_resource_table /* 解析 irq 资源 */ ...}
of_device_add 代码:
int of_device_add(struct platform_device *ofdev){ BUG_ON(ofdev->dev.of_node == NULL); ofdev->name = dev_name(&ofdev->dev); ofdev->id = PLATFORM_DEVID_NONE; set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node)); return device_add(&ofdev->dev); /* 添加 device */}
哪些 device_node 可以转换为 platform_device? 1. 根节点下含有 compatile 属性的子节点 2. 如果一个结点的 compatile 属性含有这些特殊的值 ("simple-bus", "simple-mfd", "isa", "arm,amba-bus") 之一, 那么它的子结点(需含 compatile 属性)也可以转换为 platform_device 3. i2c, spi 等总线节点下的子节点,应该交给对应的总线驱动程序来处理,它们不应该被转换为 platform_device。
of_default_bus_match_table 表:
const struct of_device_id of_default_bus_match_table[] = { { .compatible = "simple-bus", }, { .compatible = "simple-mfd", }, { .compatible = "isa", },#ifdef CONFIG_ARM_AMBA { .compatible = "arm,amba-bus", },#endif /* CONFIG_ARM_AMBA */ {} /* Empty terminated list */};