Linux设备树详解(四) 内核解析DTB成platfrom_device

Linux设备树详解(四) 内核解析DTB成platfrom_device Debug 2023-08-14 16:26:07 1048

一、背景

在上一节中讲到设备树dtb文件中的各个节点转换成device_node的过程(dtb转换成device_node ),每个设备树子节点都将转换成一个对应的device_node节点。

设备树dts文件最终在linux内核中会转化成platform_device:dts->dtb ->device_node->platform_device。

那么,接下来,我们就来看看linux内核如何把device_node转换成platfrom_device。

平台:arm

内核版本:Linux 5.19

二、device_node转换为platform_device的过程

易百纳社区

三、设备树对于驱动

设备树的产生就是为了替代driver中过多的platform_device部分的静态定义,将硬件资源抽象出来,由系统统一解析,这样就可以避免各驱动中对硬件资源大量的重复定义。

这样一来,几乎可以肯定的是,设备树中的节点最终目标是转换成platform device结构,在驱动开发时就只需要添加相应的platform driver部分进行匹配即可。

四、device_node转换为platform_device是有条件的

首先,对于所有的device_node,如果要转换成platform_device,除了节点中必须有compatible属性以外,必须满足以下条件:

  • 一般情况下,只对设备树中根的第1级节点(/xx)注册成platform device,也就是对它们的子节点(/xx/*)并不处理。
  • 如果一个结点的 compatile 属性含有这些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一,并且自己成功注册成了platform_device,, 那么它的子结点(需含compatile属性)也可以转换为platform_device(当成总线看待)。
  • 根节点(/)是例外的,生成platfrom_device时,即使有compatible属性也不会处理

五、设备树结点的什么属性会被转换?

如果是device_node转换成platform device,这个转换过程又是怎么样的呢?在老版本的内核中,platform_device部分是静态定义的,其实最主要的部分就是resources部分,这一部分描述了当前驱动需要的硬件资源,一般是IO,中断等资源。

在设备树中,这一类资源通常通过reg属性来描述,中断则通过interrupts来描述;

所以,设备树中的reg和interrupts资源将会被转换成platform_device内的struct resources资源。

那么,设备树中其他属性是怎么转换的呢?

答案是:不需要转换,在platform_device中有一个成员struct device dev,这个dev中又有一个指针成员struct device_node *of_node。

linux的做法就是将这个of_node指针直接指向由设备树转换而来的device_node结构;留给驱动开发者自行处理。例如,有这么一个struct platform_device* of_test.我们可以直接通过of_test->dev.of_node来访问设备树中的信息。

六、platform_device转换的开始

6.1 of_platform_default_populate_init

函数的执行入口是,在系统启动的早期进行的 of_platform_default_populate_init, 该函数在文件drivers/of/platform.c中定义,具体代码如下:

static int __init of_platform_default_populate_init(void)
{
    struct device_node *node;

    device_links_supplier_sync_state_pause();

    if (!of_have_populated_dt())
        return -ENODEV;

    if (IS_ENABLED(CONFIG_PPC)) {
        struct device_node *boot_display = NULL;
        struct platform_device *dev;
        int ret;

        /* Check if we have a MacOS display without a node spec */
        if (of_get_property(of_chosen, "linux,bootx-noscreen", NULL)) {
            /*
             * The old code tried to work out which node was the MacOS
             * display based on the address. I'm dropping that since the
             * lack of a node spec only happens with old BootX versions
             * (users can update) and with this code, they'll still get
             * a display (just not the palette hacks).
             */
            dev = platform_device_alloc("bootx-noscreen", 0);
            if (WARN_ON(!dev))
                return -ENOMEM;
            ret = platform_device_add(dev);
            if (WARN_ON(ret)) {
                platform_device_put(dev);
                return ret;
            }
        }

        /*
         * For OF framebuffers, first create the device for the boot display,
         * then for the other framebuffers. Only fail for the boot display;
         * ignore errors for the rest.
         */
        for_each_node_by_type(node, "display") {
            if (!of_get_property(node, "linux,opened", NULL) ||
                !of_get_property(node, "linux,boot-display", NULL))
                continue;
            dev = of_platform_device_create(node, "of-display", NULL);
            if (WARN_ON(!dev))
                return -ENOMEM;
            boot_display = node;
            break;
        }
        for_each_node_by_type(node, "display") {
            if (!of_get_property(node, "linux,opened", NULL) || node == boot_display)
                continue;
            of_platform_device_create(node, "of-display", NULL);
        }

    } else {
        /*
         * Handle certain compatibles explicitly, since we don't want to create
         * platform_devices for every node in /reserved-memory with a
         * "compatible",
         */
        for_each_matching_node(node, reserved_mem_matches)
            of_platform_device_create(node, NULL, NULL);

        node = of_find_node_by_path("/firmware");
        if (node) {
            of_platform_populate(node, NULL, NULL, NULL);
            of_node_put(node);
        }

        node = of_get_compatible_child(of_chosen, "simple-framebuffer");
        of_platform_device_create(node, NULL, NULL);
        of_node_put(node);

        /* Populate everything else. */
        of_platform_default_populate(NULL, NULL, NULL);
    }

    return 0;
}
arch_initcall_sync(of_platform_default_populate_init);

在函数of_platform_default_populate_init()中,调用了 of_platform_default_populate(NULL, NULL, NULL);,传入三个空指针:

int of_platform_default_populate(struct device_node *root,
                 const struct of_dev_auxdata *lookup,
                 struct device *parent)
{
    return of_platform_populate(root, of_default_bus_match_table, lookup,
                    parent);
}
EXPORT_SYMBOL_GPL(of_platform_default_populate);

of_platform_default_populate()调用了of_platform_populate(),我们注意下 of_default_bus_match_table。

6.2 of_default_bus_match_table

of_default_bus_match_table()在文件drivers/of/platform.c中定义,具体代码如下:

/* drivers/of/platform.c */
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 */
};

如果节点的属性值为 "simple-bus","simple-mfd","isa","arm,amba-bus "之一的话,那么它子节点就可以转化成platform_device。

6.3 of_platform_populate

of_platform_populate()在文件drivers/of/platform.c中定义,具体代码如下:

/**
 * of_platform_populate() - Populate platform_devices from device tree data
 * @root: parent of the first level to probe or NULL for the root of the tree
 * @matches: match table, NULL to use the default
 * @lookup: auxdata table for matching id and platform_data with device nodes
 * @parent: parent to hook devices from, NULL for toplevel
 *
 * Similar to of_platform_bus_probe(), this function walks the device tree
 * and creates devices from nodes.  It differs in that it follows the modern
 * convention of requiring all device nodes to have a 'compatible' property,
 * and it is suitable for creating devices which are children of the root
 * node (of_platform_bus_probe will only create children of the root which
 * are selected by the @matches argument).
 *
 * New board support should be using this function instead of
 * of_platform_bus_probe().
 *
 * Return: 0 on success, < 0 on failure.
 */
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;

    pr_debug("%s()\n", __func__);
    pr_debug(" starting at: %pOF\n", root);

    //遍历所有的子节点
    device_links_supplier_sync_state_pause();
    for_each_child_of_node(root, child) {
        // 然后对每个根目录下的一级子节点 创建 bus
        // 例如, /r1 , /r2,而不是 /r1/s1
        rc = of_platform_bus_create(child, matches, lookup, parent, true);
        if (rc) {
            of_node_put(child);
            break;
        }
    }
    device_links_supplier_sync_state_resume();

    of_node_set_flag(root, OF_POPULATED_BUS);

    of_node_put(root);
    return rc;
}
EXPORT_SYMBOL_GPL(of_platform_populate);

在调用of_platform_populate()时传入了的matches参数是of_default_bus_match_table[];

这个table是一个静态数组,这个静态数组中定义了一系列的compatible属性:"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"。

按照我们上文中的描述,当某个根节点下的一级子节点的compatible属性为这些属性其中之一时,它的一级子节点也将由device_node转换成platform_device。

到底是不是这样呢?接着往下看。

6.4 of_platform_bus_create

of_platform_bus_create()在文件drivers/of/platform.c中定义,具体代码如下:

/**
 * of_platform_bus_create() - Create a device for a node and its children.
 * @bus: device node of the bus to instantiate
 * @matches: match table for bus nodes
 * @lookup: auxdata table for matching id and platform_data with device nodes
 * @parent: parent for new device, or NULL for top level.
 * @strict: require compatible property
 *
 * Creates a platform_device for the provided device_node, and optionally
 * recursively create devices for all the child nodes.
 */
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;

    /* Make sure it has a compatible property */
    if (strict && (!of_get_property(bus, "compatible", NULL))) {
        pr_debug("%s() - skipping %pOF, no compatible prop\n",
             __func__, bus);
        return 0;
    }
    
    ....
    
    // 创建of_platform_device、赋予私有数据
    dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
    // 判断当前节点的compatible属性是否包含上文中compatible静态数组中的元素
    // 如果不包含,函数返回0,即,不处理子节点。
    if (!dev || !of_match_node(matches, bus))
        return 0;

    for_each_child_of_node(bus, child) {
        pr_debug("   create child: %pOF\n", child);
        // 创建 of_platform_bus
        /* 
           如果当前compatible属性中包含静态数组中的元素,
           即当前节点的compatible属性有"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"其中一项,
           把子节点当作对应的总线来对待,递归地对当前节点调用`of_platform_bus_create()`
           即,将符合条件的子节点转换为platform_device结构。
        */
        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;
}

6.5 of_platform_device_create_pdata

这个函数实现了:创建of_platform_device、赋予私有数据 此时的参数platform_data为NULL。

of_platform_device_create_pdata()在文件drivers/of/platform.c中定义,具体代码如下:

/**
 * 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.
 *
 * Return: 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;
    ...
    // 创建实例,将传入的device_node链接到成员:dev.of_node中
    dev = of_device_alloc(np, bus_id, parent);
    if (!dev)
        goto err_clear_flag;
    
    dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
    if (!dev->dev.dma_mask)
        dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
    // 登记到 platform 中
    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;
}

6.6 of_device_alloc

of_device_alloc()在文件drivers/of/platform.c中定义,具体代码如下:

/**
 * of_device_alloc - Allocate and initialize an of_device
 * @np: device node to assign to device
 * @bus_id: Name to assign to the device.  May be null to use default name.
 * @parent: Parent device.
 */
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;
    struct resource *res, temp_res;

    dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
    if (!dev)
        return NULL;

    /* count the io resources */
    //统计reg属性的数量
    while (of_address_to_resource(np, num_reg, &temp_res) == 0)
        num_reg++;

    /* Populate the resource table */
    //根据num_reg的数量申请相应的struct resource内存空间。
    if (num_reg) {
        res = kcalloc(num_reg, sizeof(*res), GFP_KERNEL);
        if (!res) {
            platform_device_put(dev);
            return NULL;
        }

        //设置platform_device中的num_resources成员
        dev->num_resources = num_reg;
        //设置platform_device中的resource成员
        dev->resource = res;
        
        //将device_node中的reg属性转换成platform_device中的struct resource成员。
        for (i = 0; i < num_reg; i++, res++) {
            rc = of_address_to_resource(np, i, res);
            WARN_ON(rc);
        }
    }
    
    //将platform_device的dev.of_node成员指针指向device_node
    dev->dev.of_node = of_node_get(np);
    //将platform_device的dev.fwnode成员指针指向device_node的fwnode成员
    dev->dev.fwnode = &np->fwnode;
    //设备parent为platform_bus
    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;
}
EXPORT_SYMBOL(of_device_alloc);

首先,函数先统计设备树中reg属性的个数,然后分别为它们申请内存空间,链入到platform_device中的struct resources成员中。

除了设备树中"reg"和"interrupt"属性之外,还有可选的"reg-names"和"interrupt-names"这些io中断资源相关的设备树节点属性也在这里被转换。

将相应的设备树节点生成的device_node节点链入到platform_device的dev.of_node中。

最终,我们能够通过在自己的驱动中,使用struct device_node *node = pdev->dev.of_node;获取到设备树节点中的数据。

6.7 of_device_add

of_device_add()在文件drivers/of/device.c中定义,具体代码如下:

int of_device_add(struct platform_device *ofdev)
{
    BUG_ON(ofdev->dev.of_node == NULL);

    /* name and id have to be set so that the platform bus doesn't get
     * confused on matching */
    ofdev->name = dev_name(&ofdev->dev);
    ofdev->id = PLATFORM_DEVID_NONE;

    /*
     * If this device has not binding numa node in devicetree, that is
     * of_node_to_nid returns NUMA_NO_NODE. device_add will assume that this
     * device is on the same node as the parent.
     */
    set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));

    return device_add(&ofdev->dev);
}

将当前platform_device中的struct device成员注册到系统device中,并为其在用户空间创建相应的访问节点。

这一步会调用platform_match,因此最终也会执行设备树的match,以及probe。

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Debug
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Debug
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区