Linux DeviceTree学习(三)

Linux DeviceTree学习(三)

4 Linux Kernel匹配设备树

4.1 Kernel使能device tree

Boot options  --->
[*] Flattened Device Tree support  
(0) Compressed ROM boot loader base address
(0) Compressed ROM boot loader BSS address
[ ] Use appended device tree blob to zImage (EXPERIMENTAL)

4.2 Kernel 解析Device Tree

Kernel会根据Device Tree的struct fdt_header、struct fdt_node_header及struct fdt_property三个结构体的信息填充至Kernel能够使用的struct property结构体,该结构体描述如下:

struct property  {
    char  *name;          	/* property full name */
    int  length;            	/* property  value length */
    void  *value;            /* property  value */
    struct  property *next;     /* next property  under the same node */
    unsigned  long _flags;
    unsigned  int unique_id;
    struct  bin_attribute attr;  	/* 属性文件,与sysfs文件系统挂接 */
};

4.2.1 Kernel解析流程

kernel的C语言阶段的入口函数是init/main.c/stsrt_kernel()函数,在early_init_dt_scan_nodes()中会做以下三件事:

  • (1) 扫描/chosen或者/chose@0节点下面的bootargs属性值到boot_command_line,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中;

  • (2) 扫描根节点下面,获取{size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中;

  • (3) 扫描具有device_type = “memory”属性的/memory或者/memory@0节点下面的reg属性值,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息。
    Linux DeviceTree学习(三)_第1张图片

  • Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体,struct device_node最终一般会被挂接到具体的struct device结构体。struct device_node结构体描述如下:

      struct device_node  {
          const  char *name;        /* node的名称,取最后一次“/”和“@”之间子串 */
          const  char *type;       /* device_type的属性名称,没有为 */
      phandle  phandle;      /* phandle属性值 */
      /* 指向该结构体结束的位置,存放node的路径全名,例如:/chosen */
      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;  /* 姊妹节点,与自己同等级的node */
          struct kobject kobj;         /* sysfs文件系统目录体现 */
          unsigned  long _flags;   /* 当前node状态标志位,见/include/linux/of.h line124-127 */
          void   *data;
      };
    

4.2.2 setup_machine_fdt( )

在Linux4.14.14内核中,首先使用setup_machine_fdt来setup machine描述符,如果返回NULL,才使用传统的方法setup_machine_tags来setup machine描述符。传统的方法需要给出__machine_arch_type(bootloader通过r1寄存器传递给kernel的)和tag list的地址(用来进行tag parse)。__machine_arch_type用来寻找machine描述符;tag list用于运行时参数的传递。

/**
 * setup_machine_fdt - Machine setup when an dtb was passed to the kernel
 * @dt_phys: physical address of dt blob
 *
 * If a dtb was passed to the kernel in r2, then use it to choose the
 * correct machine_desc and to setup the system.
 */
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
	const struct machine_desc *mdesc, *mdesc_best = NULL;

#if defined(CONFIG_ARCH_MULTIPLATFORM) || defined(CONFIG_ARM_SINGLE_ARMV7M)
	DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
		.l2c_aux_val = 0x0,
		.l2c_aux_mask = ~0x0,
	MACHINE_END

	mdesc_best = &__mach_desc_GENERIC_DT;
#endif

	if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
		return NULL;

	//获取machine描述符
	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

	if (!mdesc) {
		const char *prop;
		int size;
		unsigned long dt_root;

		early_print("\nError: unrecognized/unsupported "
			    "device tree compatible list:\n[ ");

		dt_root = of_get_flat_dt_root();
		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
		while (size > 0) {
			early_print("'%s' ", prop);
			size -= strlen(prop) + 1;
			prop += strlen(prop) + 1;
		}
		early_print("]\n\n");

		dump_machine_table(); /* does not return */
	}

	/* We really don't want to do this, but sometimes firmware provides buggy data */
	if (mdesc->dt_fixup)
		mdesc->dt_fixup();

	//两个功能:为后续的DTB Scan进行准备工作,获取运行时参数bootarg传递
	early_init_dt_scan_nodes();

	/* Change machine number to match the mdesc we're using */
	__machine_arch_type = mdesc->nr;

	return mdesc;
}

4.2.3 early_init_dt_scan_nodes( )

运行时参数bootarg是在扫描DTB的chosen node时候完成的,具体的工作就是获取chosen node的bootargs、initrd等属性的value,并将其保存到全局变量(boot_command_line、initrd_start、initrd_end)中。

void __init early_init_dt_scan_nodes(void)
{
    /* 扫描 /chosen node,保存运行时参数(bootargs)
    到boot_command_line, 此外,还处理initrd相关的property,
    并保存在initrd_start和initrd_end这两个全局变量中 */
	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

	/* 扫描根节点,获取 {size,address}-cells信息,
	并保存在dt_root_size_cells和dt_root_addr_cells全局变量中 */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);

   	/* 扫描DTB中的memory node,并把相关信息保存在meminfo中,
   	全局变量meminfo保存了系统内存相关的信息。*/
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

4.2.4 unflatten_device_tree( )

Device Tree的解析首先从unflatten_device_tree()开始,代码列出如下:

/**
 * unflatten_device_tree - create tree of  device_nodes from flat blob
 *
 * unflattens the device-tree passed by the  firmware, creating the
 * tree of struct device_node. It also fills  the "name" and "type"
 * pointers of the nodes so the normal  device-tree walking functions
 * can be used.
 */
void  __init unflatten_device_tree(void)
{
    __unflatten_device_tree(initial_boot_params,  &of_root,
              early_init_dt_alloc_memory_arch);
 
    /*  Get pointer to "/chosen" and "/aliases" nodes for use  everywhere */
    of_alias_scan(early_init_dt_alloc_memory_arch);
}
  • 在unflatten_device_tree()中,调用函数__unflatten_device_tree(),参数initial_boot_params指向Device Tree在内存中的首地址,of_root在经过该函数处理之后,会指向根节点,early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback)。

4.2.5 __unflatten_device_tree( )

/**
 * __unflatten_device_tree - create tree of  device_nodes from flat blob
 *
 * unflattens a device-tree, creating the
 * tree of struct device_node. It also fills  the "name" and "type"
 * pointers of the nodes so the normal  device-tree walking functions
 * can be used.
 * @blob: The blob to expand
 * @mynodes: The device_node tree created by  the call
 * @dt_alloc: An allocator that provides a  virtual address to memory
 * for the resulting tree
 */
static void __unflatten_device_tree(const  void *blob,	//需要扫描的DTB
                struct device_node **mynodes,		//global list指针
                void * (*dt_alloc)(u64 size, u64 align))	//内存分配函数
{
    unsigned  long size;
    int  start;
    void  *mem;
 
     /* 省略部分不重要部分 */
    /*  First pass, scan for size */
    start  = 0;
    size  = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0,  true);
    size  = ALIGN(size, 4);
 
    /*  Allocate memory for the expanded device tree */
    mem  = dt_alloc(size + 4, __alignof__(struct device_node));
    memset(mem,  0, size);
 
    /*  Second pass, do actual unflattening */
    start  = 0;
    unflatten_dt_node(blob,  mem, &start, NULL, mynodes, 0, false);
}
  • 在__unflatten_device_tree()函数中,两次调用unflatten_dt_node()函数,第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小,第二次调用才是具体填充每一个struct device_node和struct property结构体。

4.2.6 unflatten_dt_node( )

/**
 * unflatten_dt_node - Alloc and populate a  device_node from the flat tree
 * @blob: The parent device tree blob
 * @mem: Memory chunk to use for allocating  device nodes and properties
 * @poffset: pointer to node in flat tree
 * @dad: Parent struct device_node
 * @nodepp: The device_node tree created by  the call
 * @fpsize: Size of the node path up at the  current depth.
 * @dryrun: If true, do not allocate device  nodes but still calculate needed
 * memory size
 */
static void * unflatten_dt_node(const  void *blob,
              void  *mem,
              int  *poffset,
              struct  device_node *dad,
              struct  device_node **nodepp,
              unsigned  long fpsize,
              bool  dryrun)
{
    const  __be32 *p;
    struct  device_node *np;
    struct  property *pp, **prev_pp = NULL;
    const  char *pathp;
    unsigned  int l, allocl;
    static  int depth;
    int  old_depth;
    int  offset;
    int  has_name = 0;
    int  new_format = 0;
 
    /*  获取node节点的name指针到pathp中 */
    pathp  = fdt_get_name(blob, *poffset, &l);
    if  (!pathp)
       return  mem;
 
    allocl  = ++l;
 
    /*  version 0x10 has a more compact unit name here instead of the full
     * path. we accumulate the full path size  using "fpsize", we'll rebuild
     * it later. We detect this because the first  character of the name is
     * not '/'.
     */
    if  ((*pathp) != '/') {
       new_format  = 1;
       if  (fpsize == 0) {
           /*  root node: special case. fpsize accounts for path
            * plus terminating zero. root node only has  '/', so
            * fpsize should be 2, but we want to avoid  the first
            * level nodes to have two '/' so we use  fpsize 1 here
            */
           fpsize  = 1;
           allocl  = 2;
           l  = 1;
           pathp  = "";
       }  else {
           /*  account for '/' and path size minus terminal 0
            * already in 'l'
            */
           fpsize  += l;
           allocl  = fpsize;
       }
    }
 
    /*  分配struct device_node内存,包括路径全称大小 */
    np  = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
              __alignof__(struct  device_node));
    if  (!dryrun) {
       char  *fn;
       of_node_init(np);
 
       /*  填充full_name,full_name指向该node节点的全路径名称字符串 */
       np->full_name  = fn = ((char *)np) + sizeof(*np);
       if  (new_format) {
           /*  rebuild full path for new format */
           if  (dad && dad->parent) {
              strcpy(fn,  dad->full_name);
              fn  += strlen(fn);
           }
           *(fn++)  = '/';
       }
       memcpy(fn,  pathp, l);
 
       /*  节点挂接到相应的父节点、子节点和姊妹节点 */
       prev_pp  = &np->properties;
       if  (dad != NULL) {
           np->parent  = dad;
           np->sibling  = dad->child;
           dad->child  = np;
       }
    }
    
    /*  处理该node节点下面所有的property */
    for  (offset = fdt_first_property_offset(blob, *poffset);
         (offset >= 0);
         (offset = fdt_next_property_offset(blob,  offset))) {
       const  char *pname;
       u32  sz;
 
       if  (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) {
           offset  = -FDT_ERR_INTERNAL;
           break;
       }
 
       if  (pname == NULL) {
           pr_info("Can't  find property name in list !\n");
           break;
       }
       
       if  (strcmp(pname, "name") == 0)
           has_name  = 1;
           
       pp  = unflatten_dt_alloc(&mem, sizeof(struct property),
                  __alignof__(struct  property));
                  
       if  (!dryrun) {
           /*  We accept flattened tree phandles either in
            * ePAPR-style "phandle"  properties, or the
            * legacy "linux,phandle"  properties.  If both
            * appear and have different values, things
            * will get weird.  Don't do that. */
          
           /*  处理phandle,得到phandle值 */
           if  ((strcmp(pname, "phandle") == 0) ||
               (strcmp(pname, "linux,phandle")  == 0)) {
              if  (np->phandle == 0)
                  np->phandle  = be32_to_cpup(p);
           }
           
           /*  And we process the "ibm,phandle" property
            * used in pSeries dynamic device tree
            * stuff */
           if  (strcmp(pname, "ibm,phandle") == 0)
              np->phandle  = be32_to_cpup(p);
               pp->name  = (char *)pname;
               pp->length  = sz;
               pp->value  = (__be32 *)p;
               *prev_pp  = pp;
               prev_pp  = &pp->next;
       }
    }
    
    /*  with version 0x10 we may not have the name property, recreate
     * it here from the unit name if absent
     */
     
    /*  为每个node节点添加一个name的属性 */
    if  (!has_name) {
       const  char *p1 = pathp, *ps = pathp, *pa = NULL;
       int  sz;
 
       /*  属性name的value值为node节点的名称,取“/”和“@”之间的子串 */
       while  (*p1) {
           if  ((*p1) == '@')
              pa  = p1;
           if  ((*p1) == '/')
              ps  = p1 + 1;
           p1++;
       }
       
       if  (pa < ps)
           pa  = p1;
           
       sz  = (pa - ps) + 1;
       pp  = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
                  __alignof__(struct  property));
       if  (!dryrun) {
           pp->name  = "name";
           pp->length  = sz;
           pp->value  = pp + 1;
           *prev_pp  = pp;
           prev_pp  = &pp->next;
           memcpy(pp->value,  ps, sz - 1);
           ((char  *)pp->value)[sz - 1] = 0;
       }
    }
    
    /*  填充device_node结构体中的name和type成员 */
    if  (!dryrun) {
       *prev_pp  = NULL;
       np->name  = of_get_property(np, "name", NULL);
       np->type  = of_get_property(np, "device_type", NULL);
 
       if  (!np->name)
           np->name  = "";
       if  (!np->type)
           np->type  = "";
    }
 
    old_depth  = depth;
    *poffset  = fdt_next_node(blob, *poffset, &depth);
    if  (depth < 0)
       depth  = 0;
       
    /*  递归调用node节点下面的子节点 */
    while  (*poffset > 0 && depth > old_depth)
       mem  = unflatten_dt_node(blob, mem, poffset, np, NULL,
                  fpsize,  dryrun);
 
    if  (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
       pr_err("unflatten:  error %d processing FDT\n", *poffset);
 
    /*
     * Reverse the child list. Some drivers  assumes node order matches .dts
     * node order
     */
    if  (!dryrun && np->child) {
       struct  device_node *child = np->child;
       np->child  = NULL;
       while  (child) {
           struct  device_node *next = child->sibling;
           child->sibling  = np->child;
           np->child  = child;
           child  = next;
       }
    }
 
    if  (nodepp)
       *nodepp  = np;
 
    return  mem;
}
  • 通过以上函数处理就得到了所有的struct device_node结构体,为每一个node都会自动添加一个名称为“name”的property,property.length的值为当前node的名称取最后一个“/”和“@”之间的子串(包括‘\0’)。例如:/serial@e2900800,则length = 7,property.value = device_node.name = “serial”。

4.3 platform_device和device_node绑定

machine初始化的代码可以沿着start_kernel->rest_init->kernel_init->kernel_init_freeable->do_basic_setup->do_initcalls路径寻找。在do_initcalls函数中,kernel会依次执行各个initcall函数,在这个过程中,会调用customize_machine,具体如下:

static int __init customize_machine(void)
{
	/*
	 * customizes platform devices, or adds new ones
	 * On DT based machines, we fall back to populating the
	 * machine from the device tree, if no callback is provided,
	 * otherwise we would always need an init_machine callback.
	 */
	if (machine_desc->init_machine)
		machine_desc->init_machine();

	return 0;
}
  • 经过以上解析,DeviceTree的数据已经全部解析出具体的struct device_node和struct property结构体,下面需要和具体的device进行绑定。首先讲解platform_device和device_node的绑定过程。在arch/arm/kernel/setup.c文件中,customize_machine()函数负责填充struct platform_device结构体。platform_device生成函数调用过程如下图所示:
    Linux DeviceTree学习(三)_第2张图片
onst struct of_device_id  of_default_bus_match_table[ ] = {
    {  .compatible = "simple-bus", },
    {  .compatible = "simple-mfd", },
#ifdef CONFIG_ARM_AMBA
    {  .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
    {}  /* Empty terminated list */
};

4.3.1 of_platform_populate( )
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;

/*  获取根节点 */
root  = root ? of_node_get(root) : of_find_node_by_path("/");
if  (!root)
   return  -EINVAL;

/*  为根节点下面的每一个节点创建platform_device结构体 */
for_each_child_of_node(root,  child) {
   rc  = of_platform_bus_create(child, matches, lookup, parent, true);
   if  (rc) {
       of_node_put(child);
       break;
   }
}
/*  更新device_node flag标志位 */
of_node_set_flag(root,  OF_POPULATED_BUS);

of_node_put(root);
return  rc;

}

4.3.2 of_platform_bus_create( )

static int of_platform_bus_create(struct  device_node *bus,	//要创建的那个device node
                const struct of_device_id *matches,		//要匹配的list
                const struct of_dev_auxdata *lookup,		//附属数据
             struct device *parent, bool strict)//parent指向父节点,strict是否要求完全匹配
{
    const  struct of_dev_auxdata *auxdata;
    struct  device_node *child;
    struct  platform_device *dev;
    const  char *bus_id = NULL;
    void  *platform_data = NULL;
    int  rc = 0;
 
    /*  只有包含"compatible"属性的node节点才会生成相应的platform_device结构体 */
    /*  Make sure it has a compatible property */
    if  (strict && (!of_get_property(bus, "compatible", NULL))) {
       return  0;
    }
    /*  省略部分代码 */
    /*  
     * 针对节点下面得到status = "ok" 或者status = "okay"或者不存在status属性的
     * 节点分配内存并填充platform_device结构体
     */
    dev  = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
    if  (!dev || !of_match_node(matches, bus))
       return  0;
 
/* 递归调用节点解析函数,为子节点继续生成platform_device结构体,前提是父节点
* 的”compatible”=”simple-bus”,也就是匹配of_default_bus_match_table结构体中的数据
*/
    for_each_child_of_node(bus,  child) {
       rc  = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
       if  (rc) {
           of_node_put(child);
           break;
       }
    }
    of_node_set_flag(bus,  OF_POPULATED_BUS);
    return  rc;
}

4.3.3 of_platform_device_create_pdata( )

/**
 * of_platform_device_create_pdata - Alloc, initialize and register an of_device
 * @np: pointer to node to create device for
 * @bus_id: name to assign device
 * @platform_data: pointer to populate platform_data pointer with
 * @parent: Linux device model parent device.
 *
 * Returns pointer to created platform device, or NULL if a device was not
 * registered.  Unavailable devices will not get registered.
 */
static struct platform_device *of_platform_device_create_pdata(
					struct device_node *np,
					const char *bus_id,
					void *platform_data,
					struct device *parent)
{
	struct platform_device *dev;
/*  
     * 针对节点下面得到status = "ok" 或者status = "okay"或者不存在status属性的
     * 节点分配内存并填充platform_device结构体
     */
	if (!of_device_is_available(np) ||
	    of_node_test_and_set_flag(np, OF_POPULATED))
		return NULL;

	dev = of_device_alloc(np, bus_id, parent);
	if (!dev)
		goto err_clear_flag;

	//配置platform_device中的其他成员
	dev->dev.bus = &platform_bus_type;
	dev->dev.platform_data = platform_data;
	of_msi_configure(&dev->dev, dev->dev.of_node);

	//把platform_device加入统一的设备模型系统中
	if (of_device_add(dev) != 0) {
		platform_device_put(dev);
		goto err_clear_flag;
	}

	return dev;

err_clear_flag:
	of_node_clear_flag(np, OF_POPULATED);
	return NULL;
}
  • 总的来说,当of_platform_populate()函数执行完毕,kernel就为DTB中所有包含compatible属性名的第一级node创建platform_device结构体,并向平台设备总线注册设备信息。如果第一级node的compatible属性值等于“simple-bus”、“simple-mfd”或者"arm,amba-bus"的话,kernel会继续为当前node的第二级包含compatible属性的node创建platform_device结构体,并注册设备。Linux系统下的设备大多都是挂载在平台总线下的,因此在平台总线被注册后,会根据of_root节点的树结构,去寻找该总线的子节点,所有的子节点将被作为设备注册到该总线上。

你可能感兴趣的:(#,Linux,设备树)