前言
-
当前新版本的 Linux 内核 设备驱动框架,与设备树(Device Tree)结合密切,整体 设备树的设备驱动框架,比较的庞大,但又非常的经典。
-
一个个的 设备树解析函数,都是前人【智慧】的结晶,了解 设备树的实现,了解设备树的解析,对Linux 设备驱动开发非常有利,并且可以大大提高开发编码能力
-
虽然Linux 内核庞大、开源,但是Linux 内核各个模块的实现都是经典,非常适合学习深造
设备树节点与设备树属性
-
通过
include\linux\of.h
可以获取 设备树节点struct device_node
与设备树节点属性struct property
的定义 -
这里需要特别了解 设备树节点属性:属性name = 属性value,属性value 可能是一个字符串的列表,比如
i2c1: i2c@fea90000 {compatible = "rockchip,rk3588-i2c", "rockchip,rk3399-i2c";reg = <0x0 0xfea90000 0x0 0x1000>;clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 318 IRQ_TYPE_LEVEL_HIGH>;pinctrl-names = "default";pinctrl-0 = <&i2c1m0_xfer>;resets = <&cru SRST_I2C1>, <&cru SRST_P_I2C1>;reset-names = "i2c", "apb";#address-cells = <1>;#size-cells = <0>;status = "disabled";};
-
这里
compatible
与clock-names
reset-names
都有两个 属性值,比如reset-names = "i2c", "apb";
-
这里需要确认
reset-names = "i2c", "apb";
这种 多个属性值的具体 内存储存方式,用于理解如何解析这样的设备树属性,比如分别获取其中的属性,也就是"i2c", "apb"
,分别得到"i2c"
与"apb"
property 的 dtb 存储方式
-
可能大家认为
reset-names = "i2c", "apb";
在 dtb 中就是 这样的字符串存储,其实 dtb 的生成,有 dtc 的一套复杂的规则决定,这里 属性 name 可能只是一个索引,作为公用的字符串,而属性值如果是 字符串列表(多个字符串),比如"i2c", "apb"
,引号不需要,并且 中间的分隔符是\0
,而不是 逗号,
。 -
这样拆解字符串列表,就不再是以 逗号分隔,而是 字符串结束符
\0
进行分隔,这就需要获取整个字符串的长度【字节数】,这个 设备树属性 的 长度是struct property
中的int length;
成员
struct property {char *name;int length; /* 属性 value 的长度(字节数) */void *value;struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)struct bin_attribute attr;
#endif
};
of_property_match_string 解析
-
明白了设备树 属性值(property -> value)字符串列表的储存方式后,解析起来就明白多了,这里有个字符串列表 【index】索引的概念,比如第一个,第二个字符串
-
Linux 内核
drivers\of\property.c
中of_property_match_string
的实现代码如下
/*** of_property_match_string() - Find string in a list and return index* @np: pointer to node containing string list property* @propname: string list property name* @string: pointer to string to search for in string list** This function searches a string list property and returns the index* of a specific string value.*/
int of_property_match_string(const struct device_node *np, const char *propname,const char *string)
{const struct property *prop = of_find_property(np, propname, NULL);size_t l;int i;const char *p, *end;if (!prop)return -EINVAL;if (!prop->value)return -ENODATA;p = prop->value;end = p + prop->length;for (i = 0; p < end; i++, p += l) {l = strnlen(p, end - p) + 1;if (p + l > end)return -EILSEQ;pr_debug("comparing %s with %s\n", string, p);if (strcmp(string, p) == 0)return i; /* Found it; return index */}return -ENODATA;
}
-
这里入参: 设备树节点,设备树属性 name,就可以查找这个设备树属性 property。
-
查找到设备树属性 property不是目的,目的就是 查找 设备树属性值 是否匹配,给定一个字符串,是否存在于 字符串列表中,返回字符串列表的索引 index
-
举个例子:
reset-names = "i2c", "apb";
,这里查找"apb"
是否存在,这里存在,就是返回了 index,比如是 1(索引以 0 作为起始) -
这里有个经典的字符串列表匹配的算法,就是 通过 总长度,获取整个字符串列表的结束地址,
-
prop->length
就是这个设备树属性 value,也就是 字符串列表的总长度(字节数)
p = prop->value;end = p + prop->length;
-
利用了 字符串操作函数都是以 NULL(
\0
) 作为结束,因此通过获取 单个字符串的 长度,利用指针的加减法(地址加减),就可以逐个判断分隔出来的 单个字符是否匹配 -
这里
strnlen
用的比较的经典,获取到的其实就是第一次 遇到 字符串结束符 NULL(\0
)之前的长度。
小结
-
这个
of_property_match_string
实现原理比较的经典,当然与 设备树节点属性值的存储有关系,也就是 属性值是字符串列表时,字符串列表的分隔符是 【NULL 或者\0
】,这用于字符串列表的拆解 -
有些设备树属性确实有多个属性值,有的是数值型的,有的是字符串列表型的,需要解析匹配并获取 匹配值的 索引值。