Linux(3)Device Tree概念1(下)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Linux(3)Device Tree概念1

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调试:


相关文章
|
5月前
|
Linux Windows
Linux01---目录结构,Linux系统下只有一个最顶级的树/,Windows系统有盘符概念,而Linux系统没有盘符概念,整个系统都在/根目录下,Linux 系统写法 /user/local
Linux01---目录结构,Linux系统下只有一个最顶级的树/,Windows系统有盘符概念,而Linux系统没有盘符概念,整个系统都在/根目录下,Linux 系统写法 /user/local
|
2月前
|
Ubuntu Java Linux
Linux操作系统——概念扫盲I
Linux操作系统——概念扫盲I
44 4
|
4月前
|
存储 缓存 Linux
在Linux中,文件系统概念是什么?
在Linux中,文件系统概念是什么?
|
4月前
|
Linux Shell 调度
【在Linux世界中追寻伟大的One Piece】Linux进程概念
【在Linux世界中追寻伟大的One Piece】Linux进程概念
42 1
|
4月前
|
存储 安全 Linux
在Linux中,用户和组的概念是什么?
在Linux中,用户和组的概念是什么?
|
4月前
|
Linux 持续交付 虚拟化
在Linux中,Docker和容器虚拟概念是什么?
在Linux中,Docker和容器虚拟概念是什么?
|
4月前
|
安全 Linux 调度
在Linux中, 用户和组的概念是什么?作用分别是什么?
在Linux中, 用户和组的概念是什么?作用分别是什么?
|
6月前
|
存储 Shell Linux
Linux进程概念(下)
本文详细的介绍了环境变量和进程空间的概念及其相关的知识。
39 0
Linux进程概念(下)
|
5月前
|
缓存 Linux 编译器
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
71 0