Linux(3)Device Tree概念1(上)+https://developer.aliyun.com/article/1488393
3. 设备节点属性值
KEY
在设备树中,键值对是描述属性的方式,比如,Linux驱动中可以通过设备节点中的"compatible"这个属性查找设备节点。
Linux设备树语法中定义了一些具有规范意义的属性,包括:compatible, address, interrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有 "mac_addr","gpio","clock","power"。"regulator" 等等。
compatible
compatible 属性也叫做 “兼容性” 属性,这是非常重要的一个属性! compatible 属性的值是一个字符串列表, compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序。compatible 属性值的推荐格式: “manufacturer , model ”① manufacturer : 表示厂商;② model : 一般是模块对应的驱动名字。
例如:
compatible = "mediatek,mt6890-eth";
上面的compatible有一个属性“mediatek,mt6890-eth”; 厂商是 mediatek;设备首先使用这个属性值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,直到查到到对应的驱动程序 或者 查找完整个 Linux 内核也没有对应的驱动程序为止。
设备节点中对应的节点信息已经被内核构造成struct platform_device。驱动可以通过相应的函数从中提取信息。compatible属性是用来查找节点的方法之一,另外还可以通过节点名或节点路径查找指定节点。dm9000驱动中就是使用下面这个函数通过设备节点中的"compatible"属性提取相应的信息,所以二者的字符串需要严格匹配。
在下面的这个dm9000的例子中,我们在相应的板级dts中找到了这样的代码块:
然后我们取内核源码中找到dm9000的网卡驱动,从中可以发现这个驱动是使用的设备树描述的设备信息(这不废话么,显然用设备树好处多多)。我们可以找到它用来描述设备信息的结构体,可以看出,驱动中用于匹配的结构使用的compatible和设备树中一模一样,否则就可能无法匹配,这里另外的一点是struct of_device_id数组的最后一个成员一定是空,因为相关的操作API会读取这个数组直到遇到一个空。
status
status 属性看名字就知道是和设备状态有关的, status 属性值也是字符串,字符串是设备的状态信息,可选的状态如下表所示:
address-cells、size-cells和reg
reg 属性的值一般是 (address, length) 对,reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。
#address-cells 和 #size-cells的值都是无符号 32 位整型,可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。
#address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位), #size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。
#address-cells 和 #size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度
reg <address1 length1 address2 length2 address3 length3.....>
(几乎)所有的设备都需要与CPU的IO口相连,所以其IO端口信息就需要在设备节点节点中说明。常用的属性有
- #address-cells,用来描述子节点"reg"属性的地址表中用来描述首地址的cell的数量,
- #size-cells,用来描述子节点"reg"属性的地址表中用来描述地址长度的cell的数量。
有了这两个属性,子节点中的"reg"就可以描述一块连续的地址区域。下例中,父节点中指定了#address-cells = <2>;#size-cells = <1>,则子节点dev-bootscs0中的reg中的前两个数表示一个地址,即MBUS_ID(0xf0, 0x01)和0x1045C,最后一个数的表示地址跨度,即是0x4。
ranges
ranges属性值可以为空或者按照 (child-bus-address,parent-bus-address,length) 格式编写的数字矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:child-bus-address: 子总线地址空间的物理地址,由父节点的 #address-cells 确定此物理地址所占用的字长。parent-bus-address: 父总线地址空间的物理地址,同样由父节点的 #address-cells 确定此物理地址所占用的字长。length: 子地址空间的长度,由父节点的 #size-cells 确定此地址长度所占用的字长。
interrupts
一个计算机系统中大量设备都是通过中断请求CPU服务的,所以设备节点中就需要在指定中断号。常用的属性有
- interrupt-controller 一个空属性用来声明这个node接收中断信号,即这个node是一个中断控制器。
- #interrupt-cells,是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,用来描述子节点中"interrupts"属性使用了父节点中的interrupts属性的具体的哪个值。一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是2,则是<中断 触发方式>
- interrupt-parent,标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的
- interrupts,一个中断标识符列表,表示每一个中断输出信号
设备树中中断的部分涉及的部分比较多,interrupt-controller表示这个节点是一个中断控制器,需要注意的是,一个SoC中可能有不止一个中断控制器,这就会涉及到设备树中断组织的很多概念,下面是在文件"arch/arm/boot/dts/exynos4.dtsi"中对exynos4412的中断控制器(GIC)节点描述:
要说interrupt-parent,就得首先讲讲Linux设备管理中对中断的设计思路演变。随着linux kernel的发展,在内核中将interrupt controller抽象成irqchip这个概念越来越流行,甚至GPIO controller也可以被看出一个interrupt controller chip,这样,系统中至少有两个中断控制器了,另外,在硬件上,随着系统复杂度加大,外设中断数据增加,实际上系统可以需要多个中断控制器进行级联,形成事实上的硬件中断处理结构:
在这种趋势下,内核中原本的中断源直接到中断号的方式已经很难继续发展了,为了解决这些问题,linux kernel的大牛们就创造了irq domain(中断域)这个概念。domain在内核中有很多,除了irqdomain,还有power domain,clock domain等等,所谓domain,就是领域,范围的意思,也就是说,任何的定义出了这个范围就没有意义了。如上所述,系统中所有的interrupt controller会形成树状结构,对于每个interrupt controller都可以连接若干个外设的中断请求(interrupt source,中断源),interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。有了irq domain这个概念之后,这个编号仅仅限制在本interrupt controller范围内,有了这样的设计,CPU(Linux 内核)就可以根据级联的规则一级一级的找到想要访问的中断。当然,通常我们关心的只是内核中的中断号,具体这个中断号是怎么找到相应的中断源的,我们作为程序员往往不需要关心,除了在写设备树的时候,设备树就是要描述嵌入式软件开发中涉及的所有硬件信息,所以,设备树就需要准确的描述硬件上处理中断的这种树状结构,如此,就有了我们的interrupt-parant这样的概念:用来连接这样的树状结构的上下级,用于表示这个中断归属于哪个interrupt controller,比如,一个接在GPIO上的按键,它的组织形式就是:
中断源--interrupt parent-->GPIO--interrupt parent-->GIC1--interrupt parent-->GIC2--...-->CPU
有了parant,我们就可以使用一级一级的偏移量来最终获得当前中断的绝对编号,这里,可以看出,在我板子上的dm9000的的设备节点中,它的"interrupt-parent"引用了"exynos4x12-pinctrl.dtsi"(被板级设备树的exynos4412.dtsi包含)中的gpx0节点:
而在gpx0节点中,指定了"#interrupt-cells = <2>;",所以在dm9000中的属性"interrupts = <6 4>;"表示dm9000的的中断在作为irq parant的gpx0中的中断偏移量,即gpx0中的属性"interrupts"中的"<0 22 0>",通过查阅exynos4412的手册知道,对应的中断号是EINT[6]。
gpio
gpio也是最常见的IO口,常用的属性有
- "gpio-controller",用来说明该节点描述的是一个gpio控制器
- "#gpio-cells",用来描述gpio使用节点的属性一个cell的内容,即 `属性 = <&引用GPIO节点别名 GPIO标号 工作模式>
GPIO的设置同样采用了上述偏移量的思想,比如下面的这个led的设备书,表示使用GPX2组的第7个引脚:
驱动自定义key
针对具体的设备,有部分属性很难做到通用,需要驱动自己定义好,通过内核的属性提取解析函数进行值的获取,比如dm9000节点中的下面这句就是自定义的节点属性,用以表示配置EEPROM不可用。
VALUE
dts描述一个键的值有多种方式,当然,一个键也可以没有值
字符串信息
32bit无符号整型数组信息
二进制数数组
字符串哈希表
混合形式
上述几种的混合形式
设备树代码解析
device_node结构体
Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体, 最终一般会被挂接到具体的struct device结构体。struct device_node结构体描述如下:
//include/of.h struct device_node { const char *name; //节点名 name属性的值,没有为<NULL> const char *type; //设备类型 device._type属性的值,没有为<NULL> phandle phandle; const char *full_name; //全路径节点名,可用于其他节点引用的标记 struct property *properties; //指向该设备节点下的第一个属性,其他属性与该属性链表相接 struct property *deadprops; /* removed properties */ struct device_node *parent; //父节点指针 struct device_node *child; //子节点指针 struct device_node *sibling; //设备兄弟节点 struct device_node *next; /* next device of same type */ struct device_node *allnext; /* next in list of all nodes */ struct proc_dir_entry *pde; /* this node's proc directory */ struct kref kref; unsigned long _flags; void *data; #if defined(CONFIG_SPARC) const char *path_component_name; unsigned int unique_id; struct of_irq_controller *irq_trans; #endif };
查找节点API
/** * of_find_compatible_node - 通过compatible属性查找指定节点 * @from - 指向开始路径的节点,如果为NULL,则从根节点开始 * @type - device_type设备类型,可以为NULL * @compat - 指向节点的compatible属性的值(字符串)的首地址 * 成功:得到节点的首地址;失败:NULL */ struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compat); /** * of_find_matching_node - 通过compatible属性查找指定节点 * @from - 指向开始路径的节点,如果为NULL,则从根节点开始 * @matches - 指向设备ID表,注意ID表必须以NULL结束 * 范例: const struct of_device_id mydemo_of_match[] = { { .compatible = "fs4412,mydemo", }, {} }; * 成功:得到节点的首地址;失败:NULL */ struct device_node *of_find_matching_node(struct device_node *from,const struct of_device_id *matches); /** * of_find_node_by_path - 通过路径查找指定节点 * @path - 带全路径的节点名,也可以是节点的别名 * 成功:得到节点的首地址;失败:NULL */ struct device_node *of_find_node_by_path(const char *path); /** * of_find_node_by_name - 通过节点名查找指定节点 * @from - 开始查找节点,如果为NULL,则从根节点开始 * @name- 节点名 * 成功:得到节点的首地址;失败:NULL */ struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
提取通用属性API
/** * of_find_property - 提取指定属性的值 * @np - 设备节点指针 * @name - 属性名称 * @lenp - 属性值的字节数 * 成功:属性值的首地址;失败:NULL */ struct property *of_find_property(const struct device_node *np, const char *name, int *lenp); /** * of_property_count_elems_of_size - 得到属性值中数据的数量 * @np - 设备节点指针 * @propname - 属性名称 * @elem_size - 每个数据的单位(字节数) * 成功:属性值的数据个数;失败:负数,绝对值是错误码 */ int of_property_count_elems_of_size(const struct device_node *np,const char *propname, int elem_size); /** * of_property_read_u32_index - 得到属性值中指定标号的32位数据值 * @np - 设备节点指针 * @propname - 属性名称 * @index - 属性值中指定数据的标号 * @out_value - 输出参数,得到指定数据的值 * 成功:0;失败:负数,绝对值是错误码 */ int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value); /** * of_property_read_string - 提取字符串(属性值) * @np - 设备节点指针 * @propname - 属性名称 * @out_string - 输出参数,指向字符串(属性值) * 成功:0;失败:负数,绝对值是错误码 */ int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);
提取addr属性API
/** * of_n_addr_cells - 提取默认属性“#address-cells”的值 * @np - 设备节点指针 * 成功:地址的数量;失败:负数,绝对值是错误码 */ int of_n_addr_cells(struct device_node *np); /** * of_n_size_cells - 提取默认属性“#size-cells”的值 * @np - 设备节点指针 * 成功:地址长度的数量;失败:负数,绝对值是错误码 */ int of_n_size_cells(struct device_node *np); /** * of_get_address - 提取I/O口地址 * @np - 设备节点指针 * @index - 地址的标号 * @size - 输出参数,I/O口地址的长度 * @flags - 输出参数,类型(IORESOURCE_IO、IORESOURCE_MEM) * 成功:I/O口地址的首地址;失败:NULL */ __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags); /** * of_translate_address - 从设备树中提取I/O口地址转换成物理地址 * @np - 设备节点指针 * @in_addr - 设备树提取的I/O地址 * 成功:物理地址;失败:OF_BAD_ADDR */ u64 of_translate_address(struct device_node *dev, const __be32 *in_addr); /** * of_iomap - 提取I/O口地址并映射成虚拟地址 * @np - 设备节点指针 * @index - I/O地址的标号 * 成功:映射好虚拟地址;失败:NULL */ void __iomem *of_iomap(struct device_node *np, int index); /** * 功能:提取I/O口地址并申请I/O资源及映射成虚拟地址 * @np - 设备节点指针 * @index - I/O地址的标号 * @name - 设备名,申请I/O地址时使用 * 成功:映射好虚拟地址;失败:NULL */ void __iomem *of_io_request_and_map(struct device_node *np, int index, const char *name);
提取resource属性API
/** * of_address_to_resource - 从设备树中提取资源resource(I/O地址) * @np - 设备节点指针 * @index - I/O地址资源的标号 * @r - 输出参数,指向资源resource(I/O地址) * 成功:0;失败:负数,绝对值是错误码 */ int of_address_to_resource(struct device_node *dev, int index, struct resource *r);
提取gpio属性API
/** * include/of_gpio.h * of_get_named_gpio - 从设备树中提取gpio口 * @np - 设备节点指针 * @propname - 属性名 * @index - gpio口引脚标号 * 成功:得到GPIO口编号;失败:负数,绝对值是错误码 */ int of_get_named_gpio(struct device_node *np, const char *propname, int index);
提取irq属性API
/** * of_irq_count从设备树中提取中断的数量 * @np - 设备节点指针 * 成功:大于等于0,实际中断数量,0则表示没有中断 */ int of_irq_count(struct device_node *dev); /** * of_irq_get - 从设备树中提取中断号 * @np - 设备节点指针 * @index - 要提取的中断号的标号 * 成功:中断号;失败:负数,其绝对值是错误码 */ int of_irq_get(struct device_node *dev, int index);
提取其他属性API
/** * of_get_mac_address - 从设备树中提取MAC地址 * @np - 设备节点指针 * @成功:MAC(6字节)的首地址;失败:NULL */ void *of_get_mac_address(struct device_node *np);
代码解析dtb流程
kernel会为设备树root节点下所有带"" 属性的节点都分配并注册一个platform_device
dts->dtb->device_node->platform_device
常用解析API使用
设备树调试手段
在调试的过程中,没有达到预期时,需要先确定修改有没有编译到对应的dtbo.img中,就需要反编译dtbo.img
反编译工具
反编译工具代码中自带,只需要初始化一下环境变量就可以使用。初始化指令如下:
android_project$ . build/envsetup.sh
反编译dtb.img
dtc-I dtb -O dts dtb.img -o dtsi.txt
反编译dtbo.img
mkdtimgdump dtbo.img -b dtbo dtc -I dtb -O dts dtbo.00 -o dtsi.txt
设备树配置之Pinctrl子系统
Pinctrl概述:
Linux内核提供了pinctrl子系统,目的是为了统一各soc厂商的pin脚管理。
Linux Pinctrl子系统提供的功能:
(1)管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。 (2)管理这些pin的复用(Multiplexing)。对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,行程特定的功能。pin control subsystem要管理所有的pin group。
(3)配置这些pin的特性。例如使能或关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength。在高通、MTK平台上, pinmux管脚复用控制器,是TLMM(Top-Level Mode Multiplexer)顶级模式多路复用控制器。
Pinctrl使用:
默认名称
Client device会通过pinctrl来将pin设置为相应的功能及配置。
Client Device
Pin Controller
定制名称
Client device会通过pinctrl来将pin设置为相应的功能及配置。
Client Device
Pin Controller
定制名称解析
sgmii:mtk_probe:
pinctrl_select_state 就是根据我们在 设备树中对 state 的解析配置,相关 state 中的每一个子节点都被解析成一个 pinctrl_setting,这些 setting 被统一存放到链表当中,这里被逐个取出判断类型并设置。
Pinctrl调试: