本文共 14712 字,大约阅读时间需要 49 分钟。
受身无间者永远不死,寿长乃无间地狱中之大劫。 ----电影《无间道》经典台词
上一节在介绍总线设备驱动模型时,提到platform平台修改引脚时,设备端的代码需要重新编译生成platform_device结构体,同时过多的设备对应的.c文件也会造成过多的冗余代码,致使以优雅冠称的Linux臃肿不堪,Linus祖师爷自然要发火,于是就有了设备树。
设备树和总线驱动模型Platform是类似的,都是构造platform_device,不涉及驱动程序核心。而设备树是如何做到优雅的呢?看下图左侧部分,使用配置文件让系统生成Platform_device!
需要牢记的一点是:设备树不可能用来写驱动。
设备树只是用来给内核里的驱动程序, 指定硬件的信息。比如 LED 驱动,在内核的驱动程序里去操作寄存器,但是操作哪一个引脚?这由设备树指定。 一个单板启动时, u-boot 先运行,它的作用是启动内核。 U-boot 会把内核和设备树文件都读入内存,然后启动内核。在启动内核时会把设备树在内存中的地址告诉内核。既然称之为设备树,得名原因自然像一个树的形状咯,如下图。
描述设备树的文件叫做 DTS(Device Tree Source),它需要编译为 dtb(device tree blob)文件供内核使用。所以这里我们要谈的设备树语法,当然就是DTS语法了!还需要知道的是.dtsi
文件,它是芯片的公共设备树文件(i是include),我们写的.dts
文件会追加到了.dtsi
文件中。 我们先来直观感受一个设备树实例。
/dts-v1/; // 表示版本[memory reservations] // 格式为: /memreserve/;/ { [property definitions] [child nodes]};
[label:] node-name[@unit-address] { [properties definitions] [child nodes]};
举例:
/dts-v1/;/ { uart0: uart@fe001000 { compatible="ns16550"; reg=<0xfe001000 0x100>; };};
// ①、在根节点之外使用 label 引用 node:&uart0 { status = “disabled”;};// ②、在根节点之外使用全路径:&{ /uart@fe001000} { status = “disabled”;};
dts 中可以包含.h 头文件,也可以包含 dtsi 文件,在.h 头文件中可以定义一些宏。dts 文件之所以支持“#include”语法,是因为 arm-linux-gnueabihf-gcc 帮忙。如果只用 dtc(类比gcc) 工具,它是不支持”#include”语法的,只支持“/include”语法。
/dts-v1/;#include#include "imx6ull.dtsi"/ { ……};
compatible
属性 compatible
属性 led { compatible = “A”, “B”, “C”;};
model
status
示例
//address-cells 为 1,所以 reg 中用 1 个数来表示地址,即用 0x80000000 来表示地址//size-cells 为 1,所以 reg 中用 1 个数来表示大小,即用 0x20000000 表示大小/ { #address-cells = <1>; #size-cells = <1>; memory { reg = <0x80000000 0x20000000>; };};
#address-cells、 #size-cells
reg
示例:
/dts-v1/;/ { #address-cells = <1>; #size-cells = <1>; memory { reg = <0x80000000 0x20000000>; };};
对于 ARM 系统,寄存器和内存是统一编址的,即访问寄存器时用某块地址,访问内存时用某块地址,在访问方法上没有区别。
name(已过时)
device_type(已过时)
“name=value”
,其中 value
有多种取值方式 arrays of cells
,1个或多个cell【32 位】,用尖括号< >
表示。 interrupts = <17 0xc>;
、clock-frequency = <0x00000001 0x00000000>;
string(字符串)
,用" "
表示。 compatible = "simple-bus";
A bytestring(字节【8位】序列)
,用中括号[ ]
表示。 local-mac-address = [00 00 12 34 56 78]; // 每个 byte 使用 2 个 16 进制数来表示
,
分隔开每种表示方法。 example = <0xf00f0000 19>, "a strange property format";
根节点 /
CPU节点
memory 节点
chosen 节点
chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";};
如何查看设备树的节点信息
ls /proc/device-tree
从源代码文件 dts 文件开始,设备树的处理过程为:
根节点被保存在全局变量 of_root 中, 从 of_root 开始可以访问到任意节点。
示例讲解:
/ { mytest { compatile = "mytest", "simple-bus"; mytest@0 { compatile = "mytest_0"; }; }; i2c { compatile = "samsung,i2c"; at24c02 { compatile = "at24c02"; }; }; spi { compatile = "samsung,spi"; flash@0 { compatible = "winbond,w25q32dw"; spi-max-frequency = <25000000>; reg = <0>; }; }; };
1、/mytest
会被转换为 platform_device(依据情形A)。它的子节点/mytest/mytest@0
也会被转换为 platform_device(依据情形B)。
/i2c
节点一般表示 i2c 控制器, 它会被转换为 platform_device, 在内核中有对应的platform_driver。/i2c/at24c02
节点不会被转换为 platform_device, 它被如何处理完全由父节点的platform_driver 决定, 一般是被创建为一个 i2c_client。 3、/spi
节点一般也是用来表示 SPI 控制器, 它会被转换为 platform_device,在内核中有对应的 platform_driver。/spi/flash@0
节点不会被转换为 platform_device, 它被如何处理完全由父节点的platform_driver 决定, 一般是被创建为一个 spi_device。 结论如下:
从设备树转换得来的 platform_device 会被注册进内核里,以后当我们每注册一个platform_driver 时, 它们就会两两确定能否配对,如果能配对成功就调用 platform_driver 的probe 函数。套路是一样的,我们需要将上一节谈到的“匹配规则”再完善一下。源码如下:
一个图概括所有的配对过程
任意驱动程序里,都可以直接访问设备树。可以使用of相关函数找到节点,读出里面的值。具体内容参考4.3。
内核源码中 include/linux/目录下有很多 of 开头的头文件, of 表示“open firmware”即开
放固件。设备树的处理过程是: dtb -> device_node -> platform_deviceof_fdt.h // dtb 文件的相关操作函数, 我们一般用不到, // 因为 dtb 文件在内核中已经被转换为 device_node 树(它更易于使用)
of.h // 提供设备树的一般处理函数, // 比如 of_property_read_u32(读取某个属性的 u32 值), // of_get_child_count(获取某个 device_node 的子节点数)of_address.h // 地址相关的函数, // 比如 of_get_address(获得 reg 属性中的 addr, size 值) // of_match_device (从 matches 数组中取出与当前设备最匹配的一项)of_dma.h // 设备树中 DMA 相关属性的函数of_gpio.h // GPIO 相关的函数of_graph.h // GPU 相关驱动中用到的函数, 从设备树中获得 GPU 信息of_iommu.h // 很少用到of_irq.h // 中断相关的函数of_mdio.h // MDIO (Ethernet PHY) APIof_net.h // OF helpers for network devices.of_pci.h // PCI 相关函数of_pdt.h // 很少用到of_reserved_mem.h // reserved_mem 的相关函数
of_platform.h // 把 device_node 转换为 platform_device 时用到的函数, // 比如 of_device_alloc(根据 device_node 分配设置 platform_device), // of_find_device_by_node (根据 device_node 查找到 platform_device), // of_platform_bus_probe (处理 device_node 及它的子节点)of_device.h // 设备相关的函数, 比如 of_match_device
of_platform.h 中声明了很多函数,但是作为驱动开发者,我们只使用其中的 1、 2 个。其他的都是给内核自己使用的,内核使用它们来处理设备树,转换得到 platform_device。
函数原型为:
extern struct platform_device *of_find_device_by_node(struct device_node *np);
设备树中的每一个节点,在内核里都有一个 device_node;你可以使用 device_node 去找到对应的 platform_device
这个函数跟设备树没什么关系, 但是设备树中的节点被转换为 platform_device 后, 设备树中的 reg 属性、 interrupts 属性也会被转换为“resource”(呼应前面的:怎么转换为 platform_device?)。这时,你可以使用这个函数取出这些资源。
数原型为:/*** platform_get_resource - get a resource for a device* @dev: platform device* @type: resource type // 取哪类资源? IORESOURCE_MEM、 IORESOURCE_REG* // IORESOURCE_IRQ 等* @num: resource index // 这类资源中的哪一个?*/struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
对于设备树节点中的 reg 属性,它属性 IORESOURCE_MEM 类型的资源;
对于设备树节点中的 interrupts 属性,它属性 IORESOURCE_IRQ 类型的资源。内核会把 dtb 文件解析出一系列的 device_node 结构体,我们可以直接访问这些device_node。内核源码 incldue/linux/of.h 中声明了 device_node 和属性 property 的操作函数,device_node 和 property 的结构体定义如下:
of_find_node_by_path
static inline struct device_node *of_find_node_by_path(const char *path);
of_find_node_by_name
extern struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
of_find_node_by_type
extern struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
of_find_compatible_node
extern struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compat);
of_find_node_by_phandle
extern struct device_node *of_find_node_by_phandle(phandle handle);
of_get_parent
extern struct device_node *of_get_parent(const struct device_node *node);
of_get_next_parent
extern struct device_node *of_get_next_parent(struct device_node *node);
of_get_next_child
extern struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);
of_get_next_available_child
struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *prev);
of_get_child_by_name
根据名字取出子节点.
extern struct device_node *of_get_child_by_name(const struct device_node *node, const char *name);
内核源码 incldue/linux/of.h 中声明了 device_node 的操作函数,当然也包括属性的操作函数。
extern struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
举个例子,在设备树中,节点大概是这样:
xxx_node { xxx_pp_name = “hello”;};
上述节点中,“xxx_pp_name”就是属性的名字,值的长度是 6。
of_get_property
const void *of_get_property(const struct device_node *np, const char *name, int *lenp)
of_property_count_elems_of_size
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
return prop->length / elem_size;
举个例子,在设备树中,节点大概是这样:
xxx_node { xxx_pp_name = <0x50000000 1024> <0x60000000 2048>;};
调用 of_property_count_elems_of_size(np, “xxx_pp_name”, 8)时,返回值是 2 - ?(疑问)
调用 of_property_count_elems_of_size(np, “xxx_pp_name”, 4)时,返回值是 4 - ?(疑问)static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
extern int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value);
举个例子,在设备树中,节点大概是这样
xxx_node { name1 = <0x50000000>; name2 = <0x50000000 0x60000000>;};
调用 of_property_read_u32 (np, “name1”, &val)时, val 将得到值 0x50000000;
调用 of_property_read_u64 (np, “name2”, &val)时, val 将得到值0x0x6000000050000000。extern int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
举个例子,在设备树中,节点大概是这样:
xxx_node { name2 = <0x50000000 0x60000000>;};
调用 of_property_read_u32 (np, “name2”, 1, &val)时, val 将得到值 0x0x60000000。?(疑问)
int of_property_read_variable_u8_array( const struct device_node *np, const char *propname, u8 *out_values, size_t sz_min, size_t sz_max);int of_property_read_variable_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz_min, size_t sz_max);int of_property_read_variable_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz_min, size_t sz_max);int of_property_read_variable_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz_min, size_t sz_max);
举个例子,在设备树中,节点大概是这样
xxx_node { name2 = <0x50000012 0x60000034>;};
上述例子中属性 name2 的值,长度为 8。
调用 of_property_read_variable_u8_array (np, “name2”, out_values, 1, 10)时, out_values 中将会保存这 8 个字节: 0x12,0x00,0x00,0x50,0x34,0x00,0x00,0x60。调用 of_property_read_variable_u16_array (np, “name2”, out_values, 1, 10)时, out_values中将会保存这 4 个 16 位数值: 0x0012, 0x5000,0x0034,0x6000。
总之,这些函数要么能取到全部的数值,要么一个数值都取不到;如果值的长度在 sz_min 和 sz_max 之间,就返回全部的数值;否则一个数值都不返回。
int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
一个写得好的驱动程序, 它会尽量确定所用资源。只把不能确定的资源留给设备树, 让设备树来指定。根据原理图确定"驱动程序无法确定的硬件资源", 再在设备树文件中填写对应内容。那么, 所填写内容的格式是什么?
有些芯片,厂家提供了对应的设备树生成工具,可以选择某个引脚用于某些功能,就可以自动生成设备树节点。你再把这些节点复制到内核的设备树文件里即可。
内核文档 Documentation/devicetree/bindings/
做得好的厂家也会提供设备树的说明文档本节,我们初步介绍了设备树的引入,设备树的作用,设备树的基本语法以及设备树常用的of函数。关于of函数当然有很多,我们不可能每个都要记住,还是那个原则,Learn by doing,做着学着,做着记着!下一节我们将通过设备树实际操作LED,来吧,一起感受下设备树的魅力吧!
转载地址:http://rfnaf.baihongyu.com/