设备树实例(二)

在内核里,如何利用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

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);        //of_node和dev捆绑
 94         
 95     dev->dev.parent = parent;
 96 
 97     if (bus_id)        //bus_id = 0
 98         dev_set_name(&dev->dev, "%s", bus_id);
 99     else
100         of_device_make_bus_id(&dev->dev);
101 
102     return dev;
103 }

 

经过of_platform_bus_create递归调用自身,已经把每个设备节点device_add。若内核有device对应的driver,则会进入probe函数内platform_set_drvdata设置设备的数据。

posted @ 2016-07-22 01:23  Kevin_Hwang  阅读(2884)  评论(0编辑  收藏  举报