Linux设备树初始化

背景

对于Linux设备树如何转化成struct device一直心存疑问,想细揪以下其中的流程。

内核:Linux4.9

初始化流程

初始化流程如下:

start_kernel
|--- setup_arch(&command_line)
	|--- setup_machine_fdt
		|--- early_init_dt_scan_nodes
			|--- early_init_dt_scan_chosen  扫描 /chosen node, 并保存参数到boot_command_line
			|--- early_init_dt_scan_root	获取根节点{size,address}-cells信息,保存到dt_root_size_cells和dt_root_addr_cells
			|--- early_init_dt_scan_memory  扫描memory node,保存相关信息到meminfo中
	|--- unflatten_device_tree() 			生成 node
|--- rest_init
	|--- kernel_thread(kernel_init, NULL, CLONE_FS); -> do_fork -> kernel_init
		|--- kernel_init_freeable
			|--- do_basic_setup
				|--- driver_init
					|--- of_core_init							主要是创建 kset(devicetree)
				略...
				|--- do_initcalls
					|--- arch_initcall
						|--- of_platform_default_populate_init  根据 node 生成 struct platform_device

早期初始化

调用路径:

start_kernel
|--- setup_arch(&command_line)
	|--- setup_machine_fdt
		|--- early_init_dt_scan_nodes
			|--- early_init_dt_scan_chosen  扫描 /chosen node, 并保存参数到boot_command_line
			|--- early_init_dt_scan_root	获取根节点{size,address}-cells信息,保存到dt_root_size_cells和dt_root_addr_cells
			|--- early_init_dt_scan_memory  扫描memory node,保存相关信息到meminfo中

of_scan_flat_dt是遍历整个从DTB,为每个node调用传入的函数

void __init early_init_dt_scan_nodes(void)
{
    /* 扫描 /chosen node, 并保存参数到boot_command_line */
    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);

    /* 扫描memory node,保存相关信息到meminfo中 */
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

early_init_dt_scan_chosen

具体代码就不贴了,就贴重点的:

/* Retrieve command line unless forcing */
if (read_dt_cmdline)
    p = of_get_flat_dt_prop(node, "bootargs", &l);

if (p != NULL && l > 0) {
    if (concat_cmdline) {
        int cmdline_len;
        int copy_len;
        strlcat(cmdline, " ", COMMAND_LINE_SIZE);			/* 追加一个 " " */
        cmdline_len = strlen(cmdline);
        copy_len = COMMAND_LINE_SIZE - cmdline_len - 1;
        copy_len = min((int)l, copy_len);
        strncpy(cmdline + cmdline_len, p, copy_len);		/* 追加到原来的cmdline */
        cmdline[cmdline_len + copy_len] = '\0';
    } else {
        strlcpy(cmdline, p, min((int)l, COMMAND_LINE_SIZE)); /* 直接覆盖原来的cmdline */
    }
}

early_init_dt_scan_root

获取根节点{size,address}-cells信息,保存到dt_root_size_cells和dt_root_addr_cells

没啥好说的

early_init_dt_scan_memory

/* 只扫描 “memory” 节点 */
if (strcmp(type, "memory") != 0)
	return 0;

reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
    reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
     return 0;

endp = reg + (l / sizeof(__be32));
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
/* 扫描属性,为每个调用 early_init_dt_add_memory_arch 添加到memblock中 */
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
     u64 base, size;
    base = dt_mem_next_cell(dt_root_addr_cells, &reg);
     size = dt_mem_next_cell(dt_root_size_cells, &reg);
    if (size == 0)
        continue;
    pr_debug(" - %llx ,  %llx\n", (unsigned long long)base,
         (unsigned long long)size);
    early_init_dt_add_memory_arch(base, size);
}
return 0;

early_init_dt_add_memory_arch里面就是做了一堆检查,然后调用 memblock_add(base, size); 将内存添加到系统中

生成node

struct device_node 用来抽象设备树中的一个节点,定义如下:

struct device_node {
    const char *name;
    const char *type;
    phandle phandle;
    const char *full_name;			/* 从"/"开始的绝对路径name */
    struct fwnode_handle fwnode;

    struct  property *properties;
    struct  property *deadprops;    /* 如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表 */
    struct  device_node *parent;
    struct  device_node *child;
    struct  device_node *sibling;
    struct  kobject kobj;
    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
};
unflatten_device_tree
|--- __unflatten_device_tree(initial_boot_params, NULL, &of_root, alloc, false)
	|--- unflatten_dt_nodes(blob, NULL, dad, NULL) 	第一轮扫描,获取整体大小
	|--- unflatten_dt_nodes(blob, mem, dad, mynodes) 	第二轮扫描,进行实际的展开
|--- of_alias_scan

具体生成过程,略。

生成pdev

入口函数为:of_platform_default_populate_init,功能是根据 node 生成 struct platform_device

其主要调用路径为:

start_ketnel -> doinitcall -> arch_initcall
of_platform_default_populate_init (drivers/of/platform.c)
|--- of_find_node_by_path("/reserved-memory")
|--- of_find_compatible_node(... "ramoops")  # 优先处理 compatible="ramoops"的 "/reserved-memory"子节点,我们假设没有
	|--- of_platform_device_create(node,NULL,NULL);
|--- of_platform_default_populate(NULL, NULL, NULL);
	|--- of_platform_populate(root, of_default_bus_match_table, NULL, NULL);
		|--- of_platform_bus_create(child, of_default_bus_match_table, NULL, NULL, true); # 创建自己的dev并遍历子节点
			|--- for_each_child_of_node(root, child) 		# 遍历子节点
				|--- of_platform_device_create_pdata(bus, NULL, NULL, NULL)
				|--- for_each_child_of_node(bus, child)		# 遍历子节点
					|--- of_platform_bus_create(child, match, NULL, &dev->dev, true);
					|--- of_node_put(child);				# 添加引用计数
						|--- kobject_put(&child->kobj);
            |--- of_node_set_flag(bus, OF_POPULATED_BUS)
			

所创建的节点结构体为:

 struct platform_device {
    const char  *name;
    int     	id;
    bool        id_auto;
    struct device   dev;
    u32     num_resources;
    struct resource *resource;

    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;
};

根据上面的流程,我们可知 of_platform_bus_create 是最重要的函数。

它会创建自己的节点并递归处理子节点

of_platform_bus_create函数:

static int of_platform_bus_create(struct device_node *bus,
                    const struct of_device_id *matches,
                    const struct of_dev_auxdata *lookup,
                    struct device *parent, bool 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;

    /* 如果 node没有 compatible 属性就退出 */
    if (strict && (!of_get_property(bus, "compatible", NULL))) {
        pr_debug("%s() - skipping %s, no compatible prop\n",
             __func__, bus->full_name);
        return 0;
    }

    /* 如果已经创建过,则直接返回 */
    if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
        pr_debug("%s() - skipping %s, already populated\n",
            __func__, bus->full_name);
        return 0;
    }
    /* 检查是不是需要特殊处理的节点, lookup参数传进来为NULL 忽略即可 */
    auxdata = of_dev_lookup(lookup, bus);
    if (auxdata) {
        bus_id = auxdata->name;
        platform_data = auxdata->platform_data;
    }
	/* 特殊处理,略... */
    if (of_device_is_compatible(bus, "arm,primecell")) {
        /*
         * Don't return an error here to keep compatibility with older
         * device tree files.
         */
        of_amba_device_create(bus, bus_id, platform_data, parent);
        return 0;
    }

    /* 进行具体的 struct platform_device 节点创建 */
    dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
    if (!dev || !of_match_node(matches, bus))
        return 0;

    /* 递归处理子节点 */
    for_each_child_of_node(bus, child) {
        pr_debug("   create child: %s\n", child->full_name);
        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;

函数概要:

  1. of_node_check_flag(bus, OF_POPULATED_BUS):检查是否已经处理过
  2. of_dev_lookup(lookup, bus):检查是否需要特殊处理
    1. bus_id = auxdata->name;
    2. platform_data = auxdata->platform_data;
  3. of_platform_device_create_pdata(bus, bus_id, platform_data, parent):进行实际的节点从创建
  4. for_each_child_of_node(bus, child):遍历子节点
    1. of_platform_bus_create(child, matches, lookup, &dev->dev, strict):递归处理子节点
  5. of_node_set_flag(bus, OF_POPULATED_BUS):标记该节点已经处理

of_platform_device_create_pdata函数指向具体节点的创建。

代码如下:

/* 
 * 创建一个 struct platform_device 节点
 */
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 状态 */
    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;

    dev->dev.bus = &platform_bus_type;
    dev->dev.platform_data = platform_data;		/* 这个 platform_data 应该为NULL*/
    /* 
    * 扫描node的 dma-ranges 属性,并初始化 dev->coherent_dma_mask,dma_mask
    * 如果节点有 iommus 属性
    * 		dev->archdata.mapping = sunxi_mapping (arm_setup_iommu_dma_ops 和具体架构有关)
    * 		dev->archdata.dma_ops = coherent ? &iommu_coherent_ops : &iommu_ops
    * 否则:
    * 		dev->archdata.dma_ops = coherent ? &arm_coherent_dma_ops : &arm_dma_ops;
    */
    of_dma_configure(&dev->dev, dev->dev.of_node);
    /* 没配置 CONFIG_GENERIC_MSI_IRQ_DOMAIN,此函数为空 */
    of_msi_configure(&dev->dev, dev->dev.of_node);

    if (of_device_add(dev) != 0) {
        of_dma_deconfigure(&dev->dev);
        platform_device_put(dev);
        goto err_clear_flag;
    }

    return dev;

err_clear_flag:
    of_node_clear_flag(np, OF_POPULATED);
    return NULL;
}

 struct platform_device *of_device_alloc(struct device_node *np,
                  const char *bus_id,
                  struct device *parent)
{
    struct platform_device *dev;
    int rc, i, num_reg = 0, num_irq;
    struct resource *res, temp_res;

	/* 
	 * 分配并初始化一个 platform_device
	 * 会初始化以下成员:
	 *		pdev.name
	 *		pdev.id
	 *		pdev.dev(device_initialize(&pdev.dev))
	 *		pdev.dev.release = platform_device_release;
	 *		调用 arch_setup_pdev_archdata(&pdev); 该函数在ARM上为空
	 */
    dev = platform_device_alloc("", -1);
    if (!dev)
        return NULL;

    /* 统计io资源:主要是检索 'reg'和 'reg-names' 属性 */
    while (of_address_to_resource(np, num_reg, &temp_res) == 0)
        num_reg++;
    /* 统计中断资源: 主要是检索 ‘interrupts’ 属性 */
    num_irq = of_irq_count(np);

    /* 
     * 填充上述资源到 pdev->resource
     * reg属性指定的地址 res的 flags = IORESOURCE_MEM
     * interrupts属性指定的地址 res 的flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));
     */
    if (num_irq || num_reg) {
        res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
        if (!res) {
            platform_device_put(dev);
            return NULL;
        }

        dev->num_resources = num_reg + num_irq;
        dev->resource = res;
        for (i = 0; i < num_reg; i++, res++) {
            rc = of_address_to_resource(np, i, res);
            WARN_ON(rc);
        }
        if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
            pr_debug("not all legacy IRQ resources mapped for %s\n",
                 np->name);
    }
	/* 
	* 初始化 dev 的一些属性
	*/
    dev->dev.of_node = of_node_get(np);
    dev->dev.fwnode = &np->fwnode;
    dev->dev.parent = parent ? : &platform_bus;

    if (bus_id)
        dev_set_name(&dev->dev, "%s", bus_id);
    else
        of_device_make_bus_id(&dev->dev);

    return dev;
}

void of_device_make_bus_id(struct device *dev)
{
    struct device_node *node = dev->of_node;
    const __be32 *reg;
    u64 addr;
    const char __maybe_unused *name;

    /* Construct the name, using parent nodes if necessary to ensure uniqueness */
    while (node->parent) {
        /*
         * If the address can be translated, then that is as much
         * uniqueness as we need. Make it the first component and return
         */
        reg = of_get_property(node, "reg", NULL);
        if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {
            /* 
             * 一般情况都是走这里
             * 所以可看看出 Name = 'reg_addr.node_name.dev_name'
             * 此时 dev_name = ""
             */
            dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s",
                     (unsigned long long)addr, node->name,
                     dev_name(dev));
            return;
        }

        /* format arguments only used if dev_name() resolves to NULL */
        dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",
                 strrchr(node->full_name, '/') + 1, dev_name(dev));
        node = node->parent;
    }
}

小结

小结:

  1. 设备树初始化时,会给每个node创建一个结构体struct platform_device,且默认初始化以下成员
    1. dev->dev.bus = &platform_bus_type;
    2. dev_set_name(dev, name),name生成规则:reg地址.node_name
    3. dev->dev.dev.release = platform_device_release
    4. 初始化 dev->resourcedev->num_resources
      1. 统计MEM资源(检索 regreg-names 属性),可使用 platform_get_resource(pdev, IORESOURCE_MEM, index)获取
      2. 统计IRQ资源(检索 interrupts 属性),可使用 platform_get_irq(pdev, index)获取
    5. 初始化 dev->archdata.dma_ops(比较重要,影响后续的dma_alloc_xxx函数),有以下4种情况(这里以ARM架构为例,其余架构差不多)
      1. coherent属性(即使在设备树有 dma-coherent属性)&& 有iommu(即设备树中有iommus 属性)-> 赋值为 iommu_coherent_ops
      2. coherent属性(即使在设备树有 dma-coherent属性)&& 无iommu(即设备树中有iommus 属性)-> 赋值为 iommu_ops
      3. coherent属性(即使在设备树有 dma-coherent属性)&& 有iommu(即设备树中有iommus 属性)-> 赋值为 arm_coherent_dma_ops
      4. coherent属性(即使在设备树有 dma-coherent属性)&& 无iommu(即设备树中有iommus 属性)-> 赋值为 arm_dma_ops

你可能感兴趣的:(Linux,linux)