• Linux 内核:设备树(3)把device_node转换成platfrom_device


    Linux 内核:设备树(3)把device_node转换成platfrom_device

    背景

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

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

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

    原文(有删改):https://www.cnblogs.com/downey-blog/p/10486568.html

    基于arm平台,linux4.14

    设备树对于驱动

    设备树的产生就是为了替代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转换的开始

    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;
    
        if (!of_have_populated_dt())
            return -ENODEV;
    
        /*
         * Handle ramoops explicitly, since it is inside /reserved-memory,
         * which lacks a "compatible" property.
         */
        node = of_find_node_by_path("/reserved-memory");
        if (node) {
            node = of_find_compatible_node(node, NULL, "ramoops");
            if (node)
                of_platform_device_create(node, NULL, NULL);
        }
    
        /* 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);
    }
    

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

    of_default_bus_match_table

    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。

    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;
    
        // 从设备树中获取根节点的device_node结构体
        root = root ? of_node_get(root) : of_find_node_by_path("/");
        if (!root)
            return -EINVAL;
    
        pr_debug("%s()
    ", __func__);
        pr_debug(" starting at: %pOF
    ", root);
    
        //遍历所有的子节点
        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;
            }
        }
        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.

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

    of_platform_bus_create

    /**
     * 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;
    
        // ...
    
        // 创建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
    ", 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;
    }
    

    of_platform_device_create_pdata

    这个函数实现了:创建of_platform_device、赋予私有数据

    此时的参数platform_data为NULL。

    /**
     * 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;
    
        // ...
    
        // 创建实例,将传入的device_node链接到成员:dev.of_node中
        dev = of_device_alloc(np, bus_id, parent);
        if (!dev)
            goto err_clear_flag;
    
        // 登记到 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;
    }
    
    of_device_alloc
    struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
    {
        //统计reg属性的数量
        while (of_address_to_resource(np, num_reg, &temp_res) == 0)
    	    num_reg++;
        //统计中断irq属性的数量
        num_irq = of_irq_count(np);
        //根据num_irq和num_reg的数量申请相应的struct resource内存空间。
        if (num_irq || num_reg) {
            res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
            if (!res) {
                platform_device_put(dev);
                return NULL;
            }
            //设置platform_device中的num_resources成员
            dev->num_resources = num_reg + num_irq;
            //设置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);
            }
            //将device_node中的irq属性转换成platform_device中的struct resource成员。 
            if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
                pr_debug("not all legacy IRQ resources mapped for %s
    ",
                    np->name);
        }
        //将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;
    }
    

    首先,函数先统计设备树中reg属性和中断irq属性的个数,然后分别为它们申请内存空间,链入到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;获取到设备树节点中的数据。

    of_device_add
    int of_device_add(struct platform_device *ofdev){
        // ...
        return device_add(&ofdev->dev);
    }
    

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

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

    总结

    总的来说,将device_node转换为platform_device的过程还是比较简单的。

    整个转换过程的函数调用关系是这样的:

                                of_platform_default_populate_init()
                                            |
                                of_platform_default_populate();
                                            |
                                of_platform_populate();
                                            |
                                of_platform_bus_create()
                    _____________________|_________________
                    |                                      |
            of_platform_device_create_pdata()       of_platform_bus_create()
            _________________|____________________
           |                                      |
     of_device_alloc()                        of_device_add()         
    
    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    poj 2135 最小费用最大流初步
    HDU4864 贪心好题
    HDU 5643 约瑟夫环的应用
    HDU 5642 多重集排列数 递推
    HDU 5640
    HDU 2819 最大匹配
    poj 1988 多校联赛 带权并查集
    HDU 2817 多校联赛1
    HDU 2822 多校联赛1
    第二节(标识符,关键字,数据类型,运算符)
  • 原文地址:https://www.cnblogs.com/schips/p/linux_driver_device_node_to_platform_device.html
Copyright © 2020-2023  润新知