1. 设备树的基本概念
1.1 什么是设备树
在linux3.x版本之前的内核源码,存在大量对板级细节信息的描述造成内核源码的冗余。为了解决这个问题引入了设备树
官方对设备树的描述是: 一个描述硬件的数据结构 设备树通过bootloader将硬件资源传给内核。使内核和硬件资源描述相对独立
1.2 设备树的组成
设备树的主要由三部分组成 DTC (device tree compiler) DTS (device tree source) 和 DTB (device tree blob)
DTS:dts 文件时对device Tree 的描述,放置在内核的/arch/arm/boot/dts目录中。一个***.dtb 文件对应一个ARM的machine。dts文件描述了一个板子的硬件资源
DTC:DTC为编译工具,它可以将.dts文件编译成.dtb文件
DTB:DTC编译***.dts 生成的二进制文件(.dtb) bootload 在引导内核时,先预读取.dtb到内存,进而由内核解析。
设备树中还有一个文件: dtsi文件 由于一个SOC可能有多个不同的电路板。而每个电路板都拥有一个.dts。 这些dts会存在很多共同的部分,为了提高代码的复用率。设备树使用dist文件保存了这些共同部分的代码。这样就可以提供给不同的dts使用。
所以1个dts文件+n个dtsi文件,它们编译而成的dtb文件就是真正的设备树,此外,dts/dtsi兼容c语言的一些语法,能使用宏定义,也能包含.h文件
2. 设备树的基本结构
下面是六轴主控制器的设备树文件的一部分 下面以它为例对设备树的基本结构进行分析
/include/ "am572x_clock.dtsi"
/ {
model = "TI_AM572X_IDK - Cortex-A15 (ARMV7A)";
compatible = "ti,sitara-ctxa15";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
...
i2c1@48070000
{
compatible = "ti,am38xx-i2c";
interrupts = <88 0 4>;
reg = <0x48070000 0xd6>;
clocks = <&l4per_i2c1_mod>;
clock-names = "l4per_i2c1_mod";
clock-frequency = <400000>;
#address-cells = <1>;
#size-cells = <0>;
pinmux-0 = <&i2c1_pads>;
pcf8564@0x51
{
compatible = "nxp,pcf8564";
reg = <0x51>;
data-scl-frequency = <400000>;
};
tps659037@0x58
{
compatible = "ti,tps659037-compatible";
reg = <0x58>;
data-scl-frequency = <400000>;
};
pinmux-0 = <&i2c1_pads>; 标号引用&i2c1_pads
...
}
2.1 设备树的基本构造
{}包围起来的结构称之为设备树节点,dts中最开头的/ {},称为根节点。节点的标准结构是xxx@yyy{…},xxx是节点的名字,yyy则不是必须的,其值为节点的地址(寄存器地址或其他地址).比如i2c1: i2c@48070000中的48070000就是一个i2c控制器的寄存器基地址,pcf8564@0x51中的0x51就是这个rtc设备的i2c地址
1.属性:地址
有关节点的地址,比如i2c1@48070000,虽然它在名字后面跟了地址。但正式的设置是在reg属性中设置的比如 reg = <0x48070000 0xd6>; reg的格式通常为<address(地址) lenth(长度)>;0x48070000是寄存器基地址,0xd6是长度。address和length的个数是可变的,由父节点的属性#address-cells 和#size-cells 决定,比如节点i2c1@48070000的父节点 #address-cells 和#size-cells均为1,所以下面的i2c节点的reg属性就有address 和length,而i2c节点本身#address-cells 和#size-cells 分别为1和0,所以其下的rtc: pcf8564@0x51 的reg属性就只有一个0x51(i2c地址)了
2.属性:兼容性
如果一个节点是设备节点,这个设备节点就会有一个compatible(属性),这是设备用于和驱动匹配的依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求。而根节点的compatible也是非常的重要,也是就是"ti,sitara-ctxa15"这个字符串,因为内核系统启动后,将根据根节点的compatible来判断cpu信息,并由此进行初始化。
3.属性设置的技巧
属性设置主要有二种技巧
(1).可以参考类似的dts
(2).查询内核中的文档
2.2 节点之间的联系
节点和节点之间的关联,由“标号引用” 和“包含”来实现
标号引用就是在节点名称前加上标号,这样设备树的其他位置就能够通过&符号来调用/访问该节点,比如上面代码所示i2c节点中的 pinmux-0属性,就引用了i2c1_pads标号处的节点
pinmux-0 = <&i2c1_pads>; 标号引用&i2c1_pads
i2c1_pads: i2c1pads
{
pin-set = <
0x1800 0x00060000 /* BALL:C21 CTRL_CORE_PAD_I2C1_SDA = PIN_INPUT_PULLUP | MUX_MODE0 */
0x1804 0x00060000 /* BALL:C20 CTRL_CORE_PAD_I2C1_SCL = PIN_INPUT_PULLUP | MUX_MODE0 */
>;
};
包含是最基本的方式,比如如上面的代码我们要在在i2c1接口添加一个i2c外设,那么就必须要在i2c1下面添加一个节点,比如上面代码中的pcf8564@0x51{}
2.3内核与节点匹配
内核需要知道dtb文件的地址,这样内核就可以通过一些API任意获取设备树的内部信息
驱动程序直接通过设备节点中的compatible(兼容性)来与设备节点直接进行配对的,
这里以GPMC驱动为例 只要驱动中的compatible值与设备节点中的compatible相匹配,
LOCAL const VXB_FDT_DEV_MATCH_ENTRY vxbGpmc_match[] = //match table
{
{
"zjc,gpmc", /* compatible */
(void *)NULL
},
{}
};
Probe 函数匹配如下图所示 vxbFdtDevMatch函数进行兼容性匹配 如果匹配成功就进入attach函数进行挂载设备
STATUS vxbGpmcProbe
(
struct vxbDev * pDev
)
{
VXB_FDT_DEV_MATCH_ENTRY * match;
STATUS ret;
/* Check if the pDev pointer is valid */
VXB_ASSERT (pDev != NULL, ERROR);
ret = vxbFdtDevMatch (pDev, vxbGpmc_match, &match);
if (ret == ERROR)
return ERROR;
printf(" GPMC Probe success \n");
return OK;
}
2.4 自定义属性的设置与获取
所谓的自定义属性,有点类似于老内核中的platform_data,我们在设备节点中可以随意添加自定义属性,比如下面GPMC节点里面的nand-addr属性
gpmc:gpmc@0x50000000
{
compatible = "zjc,gpmc";
reg = <0x50000000 0x37c>,
<0x08000000 0x01000000>;
nand-addr = <0x08000000>;
pinmux-0 = <&gpmc_pads>;
};
使用以下vxFdtPropGet函数进行获取nand-addr属性的
const void * vxFdtPropGet
(
int offset, /* node offset */
char * pPropName, /* contains property name */
int * pLen /* on return, contains length, in bytes, */
/* of property value */
)
vxFdtPropGet( pFdtDev->offset, "nand-addr", &len)
针对32位整形的属性可以利用下面这个API来获取属性值,第一个参数是节点,第二个参数是属性名字,第三个参数是返回属性值内容的字节大小
vxFdtPropGet( pFdtDev->offset, "nand-addr", &len)
针对32位整形的属性可以利用下面这个API来获取属性值,第一个参数是节点,第二个参数是属性名字,第三个参数是返回属性值内容的字节大小