前言
当前新版本的 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
】,这用于字符串列表的拆解有些设备树属性确实有多个属性值,有的是数值型的,有的是字符串列表型的,需要解析匹配并获取 匹配值的 索引值。