在内核里,如何利用dtb?以下以全志a64为实例讲解。
解析dtb的流程如下:
start_kernel // init/main.c
----setup_arch // arch/arm64/kernel/setup.c
--------setup_machine_fdt(__fdt_pointer)
--------unflatten_device_tree()
*Note:__fdt_pointer是dtb数据结构的头指针。
在查看setup_machine_fdt之前需要知道fdt数据结构。
scripts/dtc/libfdt/fdt.h:
1 struct fdt_header { 2 uint32_t magic; /* magic word FDT_MAGIC */ 3 uint32_t totalsize; /* total size of DT block */ 4 uint32_t off_dt_struct; /* offset to structure */ 5 uint32_t off_dt_strings; /* offset to strings */ 6 uint32_t off_mem_rsvmap; /* offset to memory reserve map */ 7 uint32_t version; /* format version */ 8 uint32_t last_comp_version; /* last compatible version */ 9 10 /* version 2 fields below */ 11 uint32_t boot_cpuid_phys; /* Which physical CPU id we‘re 12 booting on */ 13 /* version 3 fields below */ 14 uint32_t size_dt_strings; /* size of the strings block */ 15 16 /* version 17 fields below */ 17 uint32_t size_dt_struct; /* size of the structure block */ 18 }; 19 20 struct fdt_reserve_entry { 21 uint64_t address; 22 uint64_t size; 23 }; 24 25 struct fdt_node_header { 26 uint32_t tag; 27 char name[0]; 28 }; 29 30 struct fdt_property { 31 uint32_t tag; 32 uint32_t len; 33 uint32_t nameoff; 34 char data[0]; 35 };
dtb由三个区域dt_struct、dt_strings和mem_rsvmap组成,这里暂且忽略mem_rsvmap。
dt_struct表示dtb的结构,其实由若干片数据组成,这里是5个token:FDT_BEGIN_NODE、FDT_END_NODE、FDT_PROP、FDT_NOP和FDT_END,片数据以末端的‘\0‘表示结束。
dt_strings表示dtb的字符串库,利用fdt_property.nameoff+fdt_header.ff_dt_strings+__fdt_pointer获取字符串指针。
dtb的结构可能是:
FDT_BEGIN_NODE
----FDT_PROP x N
----FDT_BEGIN_NODE
--------FDT_PROP x N
------------FDT_BEGIN_NODE
----------------FDT_PROP x N
------------FDT_END_NODE
----FDT_END_NODE
FDT_END_NODE
FDT_END
在内核里,需要把dtb(非树操作不便)转化成device tree,其由device node组成。
include/linux/of.h:
1 struct property { 2 char *name; 3 int length; 4 void *value; 5 struct property *next; 6 unsigned long _flags; 7 unsigned int unique_id; 8 }; 9 10 #if defined(CONFIG_SPARC) 11 struct of_irq_controller; 12 #endif 13 14 struct device_node { 15 const char *name; 16 const char *type; 17 phandle phandle; 18 const char *full_name; 19 20 struct property *properties; 21 struct property *deadprops; /* removed properties */ 22 struct device_node *parent; 23 struct device_node *child; 24 struct device_node *sibling; 25 struct device_node *next; /* next device of same type */ 26 struct device_node *allnext; /* next in list of all nodes */ 27 struct proc_dir_entry *pde; /* this node‘s proc directory */ 28 struct kref kref; 29 unsigned long _flags; 30 void *data; 31 #if defined(CONFIG_SPARC) 32 const char *path_component_name; 33 unsigned int unique_id; 34 struct of_irq_controller *irq_trans; 35 #endif 36 };
device_node只有一个child,通过child->sibling得到其他child。对于child而言,只有一个parent。
device_node通过一个properties获得一个property,而多个property->next即能获得所有property。
了解dtb和device_node的数据结构,查看setup_machine_fdt(__fdt_pointer)和unflatten_device_tree()问题不大
以下忽略部分代码的setup_machine_fdt:
1 static void __init setup_machine_fdt(phys_addr_t dt_phys) 2 { 3 struct boot_param_header *devtree; 4 unsigned long dt_root; 5 6 devtree = phys_to_virt(dt_phys); //转化虚拟地址 7 8 initial_boot_params = devtree; 9 dt_root = of_get_flat_dt_root(); //这里return 0 10 11 machine_name = of_get_flat_dt_prop(dt_root, "model", NULL); 12 if (!machine_name) 13 machine_name = of_get_flat_dt_prop(dt_root, "compatible", NULL); 14 if (!machine_name) 15 machine_name = "<unknown>"; 16 pr_info("Machine: %s\n", machine_name); 17 18 /* Retrieve various information from the /chosen node */ 19 of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); 20 /* Initialize {size,address}-cells info */ 21 of_scan_flat_dt(early_init_dt_scan_root, NULL); 22 /* Setup memory, calling early_init_dt_add_memory_arch */ 23 of_scan_flat_dt(early_init_dt_scan_memory, NULL); 24 }
of_scan_flat_dt函数内容是循环所有node(通过tag是否等于FDT_BEGIN_NODE)调用argv[1]函数,argv[2]为argv[1]的data。由注释可以得知以上三个函数的实质内容。
以下忽略部分代码的unflatten_device_tree和__unflatten_device_tree:
1 void __init unflatten_device_tree(void) 2 { 3 __unflatten_device_tree(initial_boot_params, &of_allnodes, 4 early_init_dt_alloc_memory_arch); 5 6 /* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */ 7 of_alias_scan(early_init_dt_alloc_memory_arch); 8 } 9 10 static void __unflatten_device_tree(struct boot_param_header *blob, 11 struct device_node **mynodes, 12 void * (*dt_alloc)(u64 size, u64 align)) 13 { 14 unsigned long size; 15 int start; 16 void *mem; 17 struct device_node **allnextp = mynodes; 18 19 /* First pass, scan for size */ 20 start = 0; 21 size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0); 22 size = ALIGN(size, 4); 23 24 /* Allocate memory for the expanded device tree */ 25 mem = dt_alloc(size + 4, __alignof__(struct device_node)); 26 27 memset((void *)mem, 0, size); 28 29 /* Second pass, do actual unflattening */ 30 start = 0; 31 unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0); 32 33 *allnextp = NULL; 34 }
unflatten_device_tree函数内容把dtb解析成device node。
__unflatten_device_tree中两次unflatten_dt_node,第一次轮询可能递归调用自身(如有子node的话),获得device tree的容量。
第二次调用通过allnextp获得property的内容(NULL则忽略)。
到此,我们的dtb文件已经完全解析成device node,添加设备十分方便。
以下讨论利用device tree如何初始化device。
start_kernel----rest_init----kernel_init----kernel_init_freeable----do_basic_setup----do_initcalls_sync
arch/arm64/kernel/setup.c
1 static int __init arm64_device_init(void) 2 { 3 of_clk_init(NULL); 4 of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); 5 return 0; 6 } 7 arch_initcall_sync(arm64_device_init);
of_clk_init通过__clk_of_table初始化一系列时钟,__clk_of_table在drivers/clk/sunxi/clk-sun50iw1.c通过CLK_OF_DECLARE初始化。
of_platform_populate把device node里获取platform资源并add device
以下忽略部分代码的drivers/of/platform.c
1 int of_platform_populate(struct device_node *root, 2 const struct of_device_id *matches, 3 const struct of_dev_auxdata *lookup, 4 struct device *parent) 5 { 6 struct device_node *child; 7 int rc = 0; 8 9 root = root ? of_node_get(root) : of_find_node_by_path("/"); 10 11 12 for_each_child_of_node(root, child) 13 rc = of_platform_bus_create(child, matches, lookup, parent, true); 14 15 of_node_put(root); 16 return rc; 17 } 18 19 static int of_platform_bus_create(struct device_node *bus, 20 const struct of_device_id *matches, 21 const struct of_dev_auxdata *lookup, 22 struct device *parent, bool strict) 23 { 24 const struct of_dev_auxdata *auxdata; 25 struct device_node *child; 26 struct platform_device *dev; 27 const char *bus_id = NULL; 28 void *platform_data = NULL; 29 int rc = 0; 30 31 dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); 32 33 for_each_child_of_node(bus, child) 34 rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); 35 36 return rc; 37 } 38 39 struct platform_device *of_platform_device_create_pdata( 40 struct device_node *np, 41 const char *bus_id, 42 void *platform_data, 43 struct device *parent) 44 { 45 struct platform_device *dev; 46 47 dev = of_device_alloc(np, bus_id, parent); 48 49 dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); 50 dev->dev.bus = &platform_bus_type; 51 dev->dev.platform_data = platform_data; 52 53 /* We do not fill the DMA ops for platform devices by default. 54 * This is currently the responsibility of the platform code 55 * to do such, possibly using a device notifier 56 */ 57 58 if (of_device_add(dev) != 0) { //device_add 59 platform_device_put(dev); 60 return NULL; 61 } 62 63 return dev; 64 } 65 66 struct platform_device *of_device_alloc(struct device_node *np, 67 const char *bus_id, 68 struct device *parent) 69 { 70 struct platform_device *dev; 71 int rc, i, num_reg = 0, num_irq; 72 struct resource *res, temp_res; 73 74 dev = platform_device_alloc("", -1); 75 76 /* count the io and irq resources */ 77 if (of_can_translate_address(np)) 78 while (of_address_to_resource(np, num_reg, &temp_res) == 0) 79 num_reg++; //设备树reg属性的个数 80 num_irq = of_irq_count(np); //主要调用irq_of_parse_and_map解析和映射中断号 81 82 /* Populate the resource table */ 83 if (num_irq || num_reg) { 84 res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL); 85 86 dev->num_resources = num_reg + num_irq; 87 dev->resource = res; 88 for (i = 0; i < num_reg; i++, res++) { 89 rc = of_address_to_resource(np, i, res); //若range属性为空则1:1映射地址 90 } 91 } 92 93 dev->dev.of_node = of_node_get(np); 94 dev->dev.parent = parent; 95 96 if (bus_id) //bus_id = 0 97 dev_set_name(&dev->dev, "%s", bus_id); 98 else 99 of_device_make_bus_id(&dev->dev); 100 101 return dev; 102 }
经过of_platform_bus_create递归调用自身,已经把每个设备节点device_add。若内核有device对应的driver,则会进入probe函数内platform_set_drvdata设置设备的数据。