设备树是一种用来描述硬件的数据结构,在Linux驱动程序中用来替代Platform_device等结构体用来描述设备的板级信息,Linux设备驱动程序通过特定的API接口从设备树中获取设备信息来对设备进行初始化和操作.
设备树可以用来描述CPU、描述时钟、描述中断控制器、描述IO控制器、描述SPI总线控制器、描述I2C总线控制器、描述存储设备等任何设备信息。
设备树是一个树状结构,由节点、属性构成,属性是成对出现的键值对,每个节点除了属性外还会包含其他的子节点。每个设备树有且只有一个根节点,每个节点中包含节点的属性和其他的子节点。
设备树有三种设备文件,DTS:供开发人员阅读编写的设备树源码,DTC:设备树源码的编译工具,需要手动安装,DTB:编译的设备树二进制文件,由uboot传递给内核,供内核使用。
设备树可以应用其他的设备树文件,一般会把公共的设备文件写到后缀名位.dtsi的文件中,其他的设备树文件使用include "xxx.dtsi"进行引用。
设备树节点格式:
node-name@unit-address{
属性 1 = …
属性 2 = …
属性 13= …
子节点…
}
node-name:设备树节点名
@unit-address:设备树单元地址,与reg属性一一对应,如果节点中不包含reg,则可以省略不写。
属性:节点的设备信息是由进行描述的,驱动通过特定API函数去获取节点中的属性值。
设备树基本概念:
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0>;
}
设备标签: cpu0:cpu@0中的cpu0是节点标签,用于向节点中追加数据。
&cpu0 {
dc-supply = <®_gpio_dvfs>;
clock-frequency = <800000000>
};
向节点中追加数据使用&+节点标签的方式进行属性的追加。
设备树别名:属性aliases用于为设备节点取别名。
aliases {
can0 = &flexcan1;
can1 = &flexcan2;
ethernet0 = &fec1;
ethernet1 = &fec2;
};
在实际应用中,设备别名很少用到,一般用于在设备树目录中快速查找设备树节点,设备标签经常使用。
chosen属性用于向内核传递参数,一般用于uboot传递启动参数到内核。
chosen {
stdout-path = &uart1;
};
节点属性介绍:
compatible属性用于设备信息和驱动程序的匹配。
intc: interrupt-controller@a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xa01000 0x1000>,
<0xa02000 0x100>;
};
compatible 属性:用于指定设备的制造商和型号
model = "Seeed i.MX6 ULL NPi Board";
status属性:用于指定设备的操作状态。
sound: sound {
status = "disabled";
};
#address-cells属性:用于指定子节点 reg 属性“地址字段”所占的长度(单元格数量)。
# size-cells属性:用于指定子节 点 reg 属性“大小字段”所占的长度(单元格数量)。
reg 属性:用于描述设备资源在其父总线定义的地址空间内的地址。通常情况下用于表示一块寄存器的起始地址 (偏移地址)和长度。
reg属性用法 :属性值类型:地址、长度数据对。
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
ocrams: sram@900000 {
compatible = "fsl,lpm-sram";
reg = <0x900000 0x4000>;
};
};
ranges属性:用于子节点地址空间和父地址空间的转换,常见格式是 ranges = (字地址, 父地址, 转 换长度)。
ranges属性用法:属性值类型:任意数量的 < 子地址、父地址、地址长度 > 编码
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges; //无需转换
busfreq {
};
}
驱动获取设备树节点属性:
内核提供了一组函数用于从设备节点获取资源(设备节点中定义的属性)的函数,这些函数以 of_ 开头,称
为 OF 操作函数。
常用的 OF 函数介绍如下:
1.通过节点路径查找节点API:
struct device_node *of_find_node_by_path(const char *path)
参数“path”,代表节点在设备树中的路径。
如果查找失败则返回 NULL,否则返回 device_node 类型的结构体指 针,它保存着设备节点的信息。
struct device_node {
const char *name; //节点名
const char *type; //设备类型
phandle phandle;
const char *full_name; //完整名字
struct fwnode_handle fwnode;
struct property *properties; //属性
struct property *deadprops;
/* removed properties */
struct device_node *parent; //父节点
struct device_node *child; //子节点
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
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
};
得到 device_node 结构体之后我们就可以使用其他 of 函数获取节点的详细信息。
2. 根据节点名查找节点API:
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
参数 from,指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为 NULL
表示从根节点开始查找。
参数 name,要寻找的节点名。
返回值:device_node 类型的结构体指针,保存获取得到的节点。同样,如果失败返回 NULL。
3. 根据节点类型查找节点API
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)
类似 of_find_node_by_name 函数。
参数 from,指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为 NULL
表示从根节点开始查找。
参数 type,要查找节点的类型,这个类型就是 device_node-> type。
返回值:device_node 类型的结构体指针,保存获取得到的节点。同样,如果失败返回 NULL。
4. 根据节点类型和 compatible 属性寻找节点API
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)
相比 of_find_node_by_name 函数增加了一个 compatible 属性作为筛选条件。
参数 from,指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为 NULL
表示从根节点开始查找。
参数 type,要查找节点的类型,这个类型就是 device_node-> type。
参数 compatible,要查找节点的 compatible 属性。
返回值:device_node 类型的结构体指针,保存获取得到的节点。同样,如果失败返回 NULL。
5. 根据匹配表寻找节点函数
static inline struct device_node *of_find_matching_node_and_match( struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)
参数 from,指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为 NULL
表示从根节点开始查找。
参数 matches,源匹配表,查找与该匹配表想匹配的设备节点。
结构体 struct of_device_id 如下所示。
可以看到,该结构体包含了更多的匹配参数,也就是说相比前三个寻找节点函数,这个函数匹配的参数更多,
对节点的筛选更细。
参数 match,查找得到的结果。
返回值:device_node 类型的结构体指针,保存获取得到的节点。同样,如果失败返回 NULL。
6. 寻找父节点API
struct device_node *of_get_parent(const struct device_node *node)
参数 node,指定谁(节点)要查找父节点。
返回值:device_node 类型的结构体指针,保存获取得到的节点。同样,如果失败返回 NULL。
7. 寻找子节点API
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
参数 node,指定谁(节点)要查找它的子节点。
参数 prev,前一个子节点,寻找的是 prev 节点之后的节点。这是一个迭代寻找过程,例如寻找第二个子节
点,这里就要填第一个子节点。参数为 NULL 表示寻找第一个子节点。
返回值:device_node 类型的结构体指针,保存获取得到的节点。同样,如果失败返回 NULL。
8 .查找节点属性API
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
参数 np,指定要获取那个设备节点的属性信息。
参数 name,属性名。
参数 lenp,获取得到的属性值的大小,这个指针作为输出参数,这个参数“带回”的值是实际获取得到的属
性大小。
返回值:获取得到的属性。这是一个结构体,我们把它称为节点属性结构体,如下所示。失败返回 NULL。
struct property {
char *name; //属性名
int length; //属性长度
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
};
9. 读取属性API
//8 位整数读取函数
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
//16 位整数读取函数
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
//32 位整数读取函数
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
//64 位整数读取函数
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
//8 位整数读取函数
int of_property_read_u8 (const struct device_node *np, const char *propname, u8 *out_values)
//16 位整数读取函数
int of_property_read_u16 (const struct device_node *np, const char *propname, u16 *out_values)
//32 位整数读取函数
int of_property_read_u32 (const struct device_node *np, const char *propname, u32 *out_values)
//64 位整数读取函数
int of_property_read_u64 (const struct device_node *np, const char *propname, u64 *out_values
参数 np,指定要读取那个设备节点结构体,也就是说读取那个设备节点的数据。
参数 propname,指定要获取设备节点的哪个属性。
参数 out_values,这是一个输出参数,是函数的“返回值”,保存读取得到的数据。
参数 sz,这是一个输入参数,它用于设置读取的长度。
返回值,成功返回 0,错误返回错误状态码(非零值),-EINVAL(属性不存在),-ENODATA(没有要读取
的数据),-EOVERFLOW(属性值列表太小)。
10 .读取字符串属性API
int of_property_read_string_index(const struct device_node *np,const char *propname,
int index,const char **out_string)
第一个函数,介绍如下:
参数 np,指定要获取那个设备节点的属性信息。
参数 propname,属性名。
了参数 index,它用于指定读取属性值中第几个字符串,index 从零开始计数。
参数 out_string,获取得到字符串指针。
返回值:成功返回 0,失败返回错误状态码。
11. BOOL型属性读取API
static inline bool of_property_read_bool(const struct device_node *np, const char *propname):
参数 np,指定要获取那个设备节点的属性信息。
参数 propname,属性名。
返回值:true , 属性存在。false, 其他。
12. 物理地址与虚拟地址转换API
void __iomem *of_iomap(struct device_node *np, int index)
参数 np,指定要获取那个设备节点的属性信息。
参数 index,通常情况下 reg 属性包含多段,index 用于指定映射那一段,标号从 0 开始。;
返回值:成功,得到转换得到的地址。失败返回 NULL。
13. 获取设备树中地址API
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
参数 np,指定要获取那个设备节点的属性信息。
参数 index,通常情况下 reg 属性包含多段,index 用于指定映射那一段,标号从 0 开始。
参数 r,这是一个 resource 结构体,是“输出参数”用于返回得到的地址信息。
返回值,成功返回 0,失败返回错误状态码。
struct resource {
resource_size_t start; //起始地址
resource_size_t end;
//结束地址
const char *name;
//属性名字
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};