of_parse_phandle_with_args函数详解

本文深入解析Linux内核中的of_parse_phandle_with_args函数,用于从设备树节点的phandle列表中获取指定索引的节点及其参数。详细介绍了函数的工作原理,包括参数解释、源码分析及实际应用示例。通过对DTS文件的编写和驱动程序的实现,展示了如何利用该函数解析设备树信息。
摘要由CSDN通过智能技术生成

源码分析

of_parse_phandle_with_args

函数作用:获得节点 phandle 列表中的某个节点

int of_parse_phandle_with_args(const struct device_node *np, const char *list_name,
                const char *cells_name, int index,
                struct of_phandle_args *out_args)
{
    if (index < 0)
        return -EINVAL;
    return __of_parse_phandle_with_args(np, list_name, cells_name, index, out_args);
}
EXPORT_SYMBOL(of_parse_phandle_with_args);

参数 np 指向当前节点;list_name 指向节点中 phandle 列表的属性名; cells_name 参数指明 phandle 指向的节点所含的 cells 个数;index 表示 phandle 列 表的索引,0 代表第一个 phandle,1 代表第二个 phandle;out_args 参数用于存储 phandle 中的参数。

函数首先检查 index 的值,小于 0 直接返回错误。检查通过之后直接调用 __of_parse_phandle_with_args() 函数,然后返回。

__of_parse_phandle_with_args

下面分段分析__of_parse_phandle_with_args函数。

/**
* of_parse_phandle_with_args() - Find a node pointed by phandle in a list
* @np:        pointer to a device tree node containing a list
* @list_name:    property name that contains a list
* @cells_name:    property name that specifies phandles' arguments count
* @index:    index of a phandle to parse out
* @out_args:    optional pointer to output arguments structure (will be filled)
*
* This function is useful to parse lists of phandles and their arguments.
* Returns 0 on success and fills out_args, on error returns appropriate
* errno value.
*
* Caller is responsible to call of_node_put() on the returned out_args->node
* pointer.
*
* Example:
*
* phandle1: node1 {
*     #list-cells = <2>;
* }
*
* phandle2: node2 {
*     #list-cells = <1>;
* }
*
* node3 {
*     list = <&phandle1 1 2 &phandle2 3>;
* }
*
* To get a device_node of the `node2' node you may call this:
* of_parse_phandle_with_args(node3, "list", "#list-cells", 1, &args);
*/
static int __of_parse_phandle_with_args(const struct device_node *np,
                    const char *list_name,
                    const char *cells_name, int index,
                    struct of_phandle_args *out_args)
{
    const __be32 *list, *list_end;
    int rc = 0, size, cur_index = 0;
    uint32_t count = 0;
    struct device_node *node = NULL;
    phandle phandle;

参数 np 指向当前节点;list_name 指向节点中 phandle 列表的属性名;cells_ name 参数指明 phandle 指向的节点所含的 cells 个数;index 表示 phandle 列表的索 引,0 代表第一个 phandle,1 代表第二个 phandle;out_args 参数用于存储 phandle 中的参数。

    /* Retrieve the phandle list property */
    list = of_get_property(np, list_name, &size);
    if (!list)
        return -ENOENT;
    list_end = list + size / sizeof(*list);

调用函数 of_get_property() 函数获得当前节点的 phandle list 属性的值,存储到 list 变量,然后计算 phandle list 属性结束的值。

    /* Loop over the phandles until all the requested entry is found */
    while (list < list_end) {
        rc = -EINVAL;
        count = 0;

        /*
         * If phandle is 0, then it is an empty entry with no
         * arguments.  Skip forward to the next entry.
         */
        phandle = be32_to_cpup(list++);
        if (phandle) {
            /*
             * Find the provider node and parse the #*-cells
             * property to determine the argument length
             */
            node = of_find_node_by_phandle(phandle);
            if (!node) {
                pr_err("%s: could not find phandle\n",
                     np->full_name);
                goto err;
            }
            if (of_property_read_u32(node, cells_name, &count)) {
                pr_err("%s: could not get %s for %s\n",
                     np->full_name, cells_name,
                     node->full_name);
                goto err;
            }

            /*
             * Make sure that the arguments actually fit in the
             * remaining property data length
             */
            if (list + count > list_end) {
                pr_err("%s: arguments longer than property\n",
                     np->full_name);
                goto err;
            }
        }

然后遍历节点 phandle list 里面的 cells,每遍历依次,只要 phandle 有效,就调用 of_find_node_by_phandle() 函数获得 phandle 对应的节点,然后读取该节点中 cells_name 名字对应的属性值,存储在 count 变量中。如果 list + count 的值越界, 那么判定位越界。

        /*
         * All of the error cases above bail out of the loop, so at
         * this point, the parsing is successful. If the requested
         * index matches, then fill the out_args structure and return,
         * or return -ENOENT for an empty entry.
         */
        rc = -ENOENT;
        if (cur_index == index) {
            if (!phandle)
                goto err;

            if (out_args) {
                int i;
                if (WARN_ON(count > MAX_PHANDLE_ARGS))
                    count = MAX_PHANDLE_ARGS;
                out_args->np = node;
                out_args->args_count = count;
                for (i = 0; i < count; i++)
                    out_args->args[i] = be32_to_cpup(list++);
            } else {
                of_node_put(node);
            }

            /* Found it! return success */
            return 0;
        }

        of_node_put(node);
        node = NULL;
        list += count;
        cur_index++;
    }

cur_index 和 index 的比较确保了正在读取指定的 phandle。如果 out_args 存在,那 么函数将 phandle 对应的参数都存储在 out_args 的 args 数组里,然后返回;否则调 用 of_node_put() 函数,释放节点的使用权;如果不是需要找的 phandle,那么继续遍 历下一个。

    /*
     * Unlock node before returning result; will be one of:
     * -ENOENT : index is for empty phandle
     * -EINVAL : parsing error on data
     * [1..n]  : Number of phandle (count mode; when index = -1)
     */
    rc = index < 0 ? cur_index : -ENOENT;
err:
    if (node)
        of_node_put(node);
    return rc;
}

发生错误时,函数跳转到这里处理。

of_find_node_by_phandle

/**
* of_find_node_by_phandle - Find a node given a phandle
* @handle:    phandle of the node to find
*
* Returns a node pointer with refcount incremented, use
* of_node_put() on it when done.
*/
struct device_node *of_find_node_by_phandle(phandle handle)
{
    struct device_node *np;
    unsigned long flags;

    raw_spin_lock_irqsave(&devtree_lock, flags);
    for (np = of_allnodes; np; np = np->allnext)
        if (np->phandle == handle)
            break;
    of_node_get(np);
    raw_spin_unlock_irqrestore(&devtree_lock, flags);
    return np;
}
EXPORT_SYMBOL(of_find_node_by_phandle);

参数 handle 指向节点中 phandle 的属性值。

函数首先调用 raw_spin_lock_irqsave() 函数加锁。由于 DTB 将所有节点都存放在 of_allnodes 为表头的单链表里,然后调用 for 循环遍历所有节点。每次遍历一个节点, 如果节点 device_node 的 phandle 成员和遍历到的节点一致,那么就找到 phandle 对 应的节点。接着停止 for 循环,调用 of_node_get() 函数添加节点引用数。最后返回 device_node 之前调用 raw_spin_unlock_irqrestore() 函数释放锁。

of_property_read_u32_array

/**
* of_property_read_u32_array - Find and read an array of 32 bit integers
* from a property.
*
* @np:        device node from which the property value is to be read.
* @propname:    name of the property to be searched.
* @out_value:    pointer to return value, modified only if return value is 0.
* @sz:        number of array elements to read
*
* Search for a property in a device node and read 32-bit value(s) from
* it. Returns 0 on success, -EINVAL if the property does not exist,
* -ENODATA if property does not have a value, and -EOVERFLOW if the
* property data isn't large enough.
*
* The out_value is modified only if a valid u32 value can be decoded.
*/
int of_property_read_u32_array(const struct device_node *np,
                   const char *propname, u32 *out_values,
                   size_t sz)
{
    const __be32 *val = of_find_property_value_of_size(np, propname,
                        (sz * sizeof(*out_values)));

    if (IS_ERR(val))
        return PTR_ERR(val);

    while (sz--)
        *out_values++ = be32_to_cpup(val++);
    return 0;
}
EXPORT_SYMBOL_GPL(of_property_read_u32_array);

参数 np 指向当前节点;propbane 参数指向属性的名字;out_values 参数用于存储整 形数组的指针;sz 参数用于执行需要读取 32 位整形的数量。

函数首先调用 of_find_property_value_of_size() 函数获得属性对应的属性值。然后进 行判断之后,将每个属性值存储到 out_values 中。

of_find_property_value_of_size

/**
* of_find_property_value_of_size
*
* @np:        device node from which the property value is to be read.
* @propname:    name of the property to be searched.
* @len:    requested length of property value
*
* Search for a property in a device node and valid the requested size.
* Returns the property value on success, -EINVAL if the property does not
*  exist, -ENODATA if property does not have a value, and -EOVERFLOW if the
* property data isn't large enough.
*
*/
static void *of_find_property_value_of_size(const struct device_node *np,
            const char *propname, u32 len)
{
    struct property *prop = of_find_property(np, propname, NULL);

    if (!prop)
        return ERR_PTR(-EINVAL);
    if (!prop->value)
        return ERR_PTR(-ENODATA);
    if (len > prop->length)
        return ERR_PTR(-EOVERFLOW);

    return prop->value;
}

参数 np 指向当前节点;propname 指向属性的名字,len 参数表示属性的长度。

函数首先调用 of_find_property() 函数获得属性,然后分别判断属性,属性的值,以及 属性的长度是否都符合要求。当条件都满足之后,返回属性的值。

of_find_property

struct property *of_find_property(const struct device_node *np,
                  const char *name,
                  int *lenp)
{
    struct property *pp;
    unsigned long flags;

    raw_spin_lock_irqsave(&devtree_lock, flags);
    pp = __of_find_property(np, name, lenp);
    raw_spin_unlock_irqrestore(&devtree_lock, flags);

    return pp;
}
EXPORT_SYMBOL(of_find_property);

参数 np 指向一个 device_node;name 参数表示需要查看属性的名字;lenp 用于存储属 性长度。

函数通过调用 raw_spin_lock_irqsave() 函数加锁之后,就直接调用 __of_find_property() 函数。__of_find_property() 函数用于查找并返回属性名字与 name 一致的属性。调用完 __of_find_property() 函数之后,调用 raw_spin_unlock_irqrestore() 函数解锁。最后返回 property 结构。

__of_find_property

static struct property *__of_find_property(const struct device_node *np,
                       const char *name, int *lenp)
{
    struct property *pp;

    if (!np)
        return NULL;

    for (pp = np->properties; pp; pp = pp->next) {
        if (of_prop_cmp(pp->name, name) == 0) {
            if (lenp)
                *lenp = pp->length;
            break;
        }
    }

    return pp;
}

由于每个 device_node 包含一个名为 properties 的成员,properties 是一个单链表 的表头,这个单链表包含了该节点的所有属性,函数通过使用 for 循环遍历这个链表, 以此遍历节点所包含的所有属性。每遍历一个属性就会调用 of_prop_cmp() 函数, of_prop_cmp() 函数用于对比两个字符串是否相等,如果相等返回 0. 因此,当遍历到 的属性的名字与参数 name 一致,那么定义为找到了指定的属性。此时,如果 lenp 不 为 NULL,那么会将属性的长度即 length 存储到 lenp 指向的地址。

实践

实践目的是在 DTS 文件中构建三个私有节点,第一个私有节点通过 phandle 的方式引用 了第二个和第三个节点,节点二和节点三都存储在第一个节点的 phandle list 属性中, 然后通过 of_parse_phandle_with_args() 函数分贝读取两个节点,函数定义如下:

int of_parse_phandle_with_args(const struct device_node *np, const char *list_name,
                const char *cells_name, int index,
                struct of_phandle_args *out_args)

这个函数经常用用于从节点的 phandle list 中读取 phandle 对应的节点

本文实践基于 Linux 4.20.8 arm32 平台,开发者可以参考如下文章快速搭建一个 调试环境:

Establish Linux 4.20.8 on ARM32

DTS 文件

由于使用的平台是 ARM32,所以在源码 /arch/arm/boot/dts 目录下创建一个 DTSI 文件,在 root 节点之下创建一个名为 DTS_demo 的子节点。节点默认打开。再创建两个节点,节点的 cells 分别是 3 和 2,具体内容如下:

/*
 * DTS Demo Code
 *
 * (C) 2019.01.06 <buddy.zhang@aliyun.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
/ {
    DTS_demo {
        compatible = "DTS_demo, BiscuitOS";
        status = "okay";
        phy-handle = <&phy0 1 2 3 &phy1 4 5>;
    };

    phy0: phy@0 {
        #phy-cells = <3>;
        compatible = "PHY0, BiscuitOS";
    };

    phy1: phy@1 {
        #phy-cells = <2>;
        compatible = "PHY1, BiscuitOS";
    };
};

创建完毕之后,将其保存并命名为 DTS_demo.dtsi。然后开发者在 Linux 4.20.8 的源 码中,找到 arch/arm/boot/dts/vexpress-v2p-ca9.dts 文件,然后在文件开始地方添 加如下内容:

#include "DTS_demo.dtsi"

编写对应驱动

准备好 DTSI 文件之后,开发者编写一个简单的驱动,这个驱动作为 DTS_demo 的设备驱 动,在 DTS 机制遍历时会调用匹配成功的驱动,最终运行驱动里面的代码。在驱动的 probe 函数中,首先获得驱动所对应的节点,通过 platform_device 的 of_node 成员传 递。获得驱动对应的节点之后,通过调用 of_parse_phandle_with_args() 函数获得指定 的节点。驱动编写如下:

/*
 * DTS: of_parse_phandle_with_args
 *
 * int of_parse_phandle_with_args(const struct device_node *np,
 *                    const char *list_name, const char *cells_name,
 *                    int index, struct of_phandle_args *out_args)
 *
 * int of_device_is_available(const struct device_node *device)
 *
 * (C) 2019.01.11 BuddyZhang1 <buddy.zhang@aliyun.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

/*
 * Private DTS file: DTS_demo.dtsi
 *
 * / {
 *        DTS_demo {
 *                compatible = "DTS_demo, BiscuitOS";
 *                status = "okay";
 *                phy-handle = <&phy0 1 2 3 &phy1 4 5>;
 *        };
 *
 *        phy0: phy@0 {
 *                #phy-cells = <3>;
 *                compatible = "PHY0, BiscuitOS";
 *        };
 *
 *        phy1: phy@1 {
 *                #phy-cells = <2>;
 *                compatible = "PHY1, BiscuitOS";
 *        };
 * };
 *
 * On Core dtsi:
 *
 * include "DTS_demo.dtsi"
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of_platform.h>

/* define name for device and driver */
#define DEV_NAME "DTS_demo"

/* probe platform driver */
static int DTS_demo_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct of_phandle_args args;
    int rc, index = 0;
    const u32 *comp;

    printk("DTS demo probe entence.\n");

    /* Read first phandle argument */
    rc = of_parse_phandle_with_args(np, "phy-handle", "#phy-cells",
                                        0, &args);
    if (rc < 0) {
        printk("Unable to parse phandle.\n");
        return -1;
    }
    
    comp = of_get_property(args.np, "compatible", NULL);
    if (comp)
        printk("%s compatible: %s\n", args.np->name, comp);

    for (index = 0; index < args.args_count; index++)
        printk("Args %d: %#x\n", index, args.args[index]);

    /* Read second phandle argument */
    rc = of_parse_phandle_with_args(np, "phy-handle", "#phy-cells",
                                        1, &args);
    if (rc < 0) {
        printk("Unable to parse phandle.\n");
        return -1;
    }
    
    comp = of_get_property(args.np, "compatible", NULL);
    if (comp)
        printk("%s compatible: %s\n", args.np->name, comp);

    for (index = 0; index < args.args_count; index++)
        printk("Args %d: %#x\n", index, args.args[index]);

    return 0;
}

/* remove platform driver */
static int DTS_demo_remove(struct platform_device *pdev)
{
    return 0;
}

static const struct of_device_id DTS_demo_of_match[] = {
    { .compatible = "DTS_demo, BiscuitOS", },
    { },
};
MODULE_DEVICE_TABLE(of, DTS_demo_of_match);

/* platform driver information */
static struct platform_driver DTS_demo_driver = {
    .probe  = DTS_demo_probe,
    .remove = DTS_demo_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = DEV_NAME, /* Same as device name */
        .of_match_table = DTS_demo_of_match,
    },
};
module_platform_driver(DTS_demo_driver);

编写好驱动之后,将其编译进内核。通过makefile设置编译架构与编译工具链指令,如下命令:

make ARCH=arm BiscuitOS/output/linux-4.20.8/arm-linux-gnueabi/arm-linux-gnueabi/bin/arm-linux-gnueabi- j8
make ARCH=arm BiscuitOS/output/linux-4.20.8/arm-linux-gnueabi/arm-linux-gnueabi/bin/arm-linux-gnueabi- dtbs

启动内核,在启动阶段就会运行驱动的 probe 函数,并打印如下信息:

[    3.534323] DTS demo probe entence.
[    3.534359] phy compatible: PHY0, BiscuitOS
[    3.534364] Args 0: 0x1
[    3.534369] Args 1: 0x2
[    3.534372] Args 2: 0x3
[    3.534379] phy compatible: PHY1, BiscuitOS
[    3.534383] Args 0: 0x4
[    3.534387] Args 1: 0x5

值得注意的是,调用 of_parse_phandle_with_args() 函数读取第一个节点时,index 参 数为 0;读取第二个节点时,index 参数为 1.phandle list 中,参数的个数必须和对应 节点 cells 一致。驱动中也增加了对 gpio 的读取。

转载:of_parse_phandle_with_args(BiscuitOS)

  • 6
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值