Linux_设备树

1 设备树起源

Linux 3.x之前,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,很多代码只是在描述板级细节,并且有大量重复性的描述,而这些板级细节对内核来讲并没有什么用。

设备树是一种描述硬件的数据结构,它起源于OpenFirmware(OF)。在Linux 2.6中,ARM架构的板级硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx中,采用设备树后,许多硬件的细节可以直接通过它传递给Linux,而不需要在内核里进行大量的冗余编码。

设备树由一系列被命名的节点(node)和属性(property)组成,而节点本身可包含子节点,属性就是成对出现的名称和值。在设备树中,可描述的信息包括(原先此类信息大多被硬编码到内核中):

  • CPU的数量和类型;
  • 内存基地址和大小;
  • 总线和桥;
  • 外设连接;
  • 中断控制器和中断使用情况;
  • GPIO控制器和GPIO使用情况;
  • 时钟控制器和时钟使用情况;

2 设备树的目的

设备树是一个本质是一个描述硬件的数据结构,并不能解决所有的硬件配置问题。设备树只是给我们提供了一种语言,将板级细节从Linux内核源码中提取出来。设备树使得目标板和设备变成数据驱动的,它们必须基于传递给内核的数据进行初始化,而不是像以前一样采用硬编码的方式。理论上,这种方式可以带来较少的代码重复率,使单个内核镜像能够支持很多硬件平台。

Linux使用设备树主要有三个目的:

2.1 平台识别

Linux内核通过设备树中数据来识别特定的板卡。最完美的情况是内核与特定硬件平台无关,因为所有硬件平台的细节都由设备树来描述,然而硬件平台并不是完美的,所以内核需要在早期初始化阶段识别机器,这样内核才能有机会运行与特定机器相关的初始化序列。

内核和设备树通过compatible属性来进行匹配,当内核和设备树匹配成功后,内核会调用对应的probe函数;

内核中compatible:

static const struct of_device_id dw8250_of_match[] = {
    { .compatible = "snps,dw-apb-uart" },
    { .compatible = "cacium,octeon-3860-uart" },
    { }
};

设备树中的compatibe:

compatible = "snps,dw-apb-uart";

2.2 实时配置

NULL

2.3 设备植入

NULL

3 Linux设备树的组成和使用

3.1 基本数据格式

在Linux中,设备树文件的类型有 .dts 、.dtsi、.dtb,其中.dtsi是被包含的设备树源文件,类似C语言的头文件;.dts是设备树文件,可以包含其他.dtsi文件,然后由dtc编译生产.dtb文件,.dtb文件是二进制文件会在内核里被进一步转换为device_node,device_node又会转换成platform_device。

设备树是一个包含节点和属性的简单树状结构,属性就是键值对,节点可同时包含属性和子节点;

.dts结构:

/{
	node1{
		a-string-property = "A string";
		a-string-list-property = "first string", "second string";
		a-byte-data-property =[0x01 0x23 0x34 0x56];
		child-node1
			{
			first-child-property;
			second-child-property = < 1 >;
			a-string-property = "Hello, world";
			};
		child-node2
			{
			};
		};
	node2{
		an-empty-property;
		a-cell-property = < 1 2 3 4 >;	/* each number (cell) is a uint32 */
		child-node1
			{
			};
		};
	};

该设备树包含了下面的内容: 

  • 1个root节点”/”;
  • root节点下面含一系列子节点,“node1” and “node2”
  • 节点node1和下又含有一系列子节点,“child-node1” and “child-node2”
  • 一些分散在节点中的属性,属性是简单的键值对,它的值可以为空或者包含一个任意字节流:
  • 空:an-empty-property
  • 字符串:用双引号表示,如:a-string-property = "A string";
  • 字符串列表:使用逗号连在一起,如:a-string-list-property = "first string", "second string";
  • Cells:u32整数组成,用角括号限定,如:second-child-property = < 1 >;
  • 二进制数据:用方括号限定,如:a-byte-data-property =[0x01 0x23 0x34 0x56];
  • 混合表示:使用逗号连在一起;

3.2 DTS的组成

3.2.1 包含其他的".dtsi"文件:

#include "ks23xx.dtsi"

3.2.2 节点名称

一个节点的命名形式:

[label:] node-name[@unit-address] {

        [properties definitions]

        [child nodes]

}; 

方括号中的内容不是必须的,所以可以定义一个只有name的空节点,name是一个不超过31位的简单字符串 用于描述节点对应的设备类型;
   如果一个节点描述的设备有地址,则应该给出@unit-address。多个相同类型设备节点的name可以一样,只要unit-address不同即可。对于挂在内存空间的设备而言,@字符后跟的一般就是该设备在内存空间的基地址。
多个节点可使用一样的通用节点,如:

    serial@0a000000
    serial@0a010000

3.2.3 compatible属性

系统中的每个设备都会被表示为一个设备树节点,系统通过compatible属性来找到节点对应的设备驱动。

compatible属性的形式为一个字符串列表,其中第一个字符串指定了确切的设备,剩下的字符串表示兼容的设备,通常形式为:compatible = "<厂商>,<型号>";

compatible = "snps,dw-apb-uart";

3.2.4 地址属性

reg
#address-cells   // 子节点reg的address为几个32bit的整型数据
#size-cells     // 长度为几个32bit整型数据,如果为0,则没有lenth字段

reg的组织形式为reg=<addr1 len1[addr2 len2] [addr3len3] ...>;

其中的每一组address length表明了设备使用的一个地址范围。address为1个或多个32位的整型(即cell),而length的意义则意味着从address到address+length–1的地址范围都属于该节点。若#size-cells=0,则length字段为空。

address和length字段是可变长的,父节点的#address-cells和#size-cells分别决定了子节点reg属性的address和length字段的长度。

soc {
	#address-cells = <1>;
	#size-cells = <1>;
	serial {
		compatible = "ns16550";
		reg = <0x4600 0x100>;
		clock-frequency = <0>;
		interrupts = <0xA 0x8>;
		interrupt-parent = <&ipic>;
	};
};

3.2.4.1 Memory Mapped Devices

与在cpu节点中单独的address值不同,内存映射设备被分配了一系列将要响应的地址,因此不仅需要包含内存的基地址而且还需要映射地址的长度,因此需要使用#size-cells用来表示在每个子reg元组中长度字段的大小。在以下示例中,每个address值为1 cell(32 bits),每个长度值也是1 cell,这在32 bit系统是比较典型的。64 bit设备也许会为#address-cells和#size-cells使用数值2,在device tree中获取64 bit addressing。

/ {
    #address-cells = <1>;
    #size-cells = <1>;
    ...
    serial@101f0000 {
        compatible = "arm,pl011";
        reg = <0x101f0000 0x1000 >;
    };

    serial@101f2000 {
        compatible = "arm,pl011";
        reg = <0x101f2000 0x1000 >;
    };

    gpio@101f3000 {
        compatible = "arm,pl061";
        reg = <0x101f3000 0x1000
               0x101f4000 0x0010>;
    };

    interrupt-controller@10140000 {
        compatible = "arm,pl190";
        reg = <0x10140000 0x1000 >;
    };

    spi@10115000 {
        compatible = "arm,pl022";
        reg = <0x10115000 0x1000 >;
    };
    ...
};


3.2.4.2 Non Memory Mapped Devices

处理器总线的其它设备为非内存映射设备。他们有地址范围,但不能被CPU直接寻址。母设备的驱动程序将代替CPU进行间接访问。以i2c设备为例,每个设备都分配了一个地址,但没有长度或范围与之相匹配。这与CPU地址分配很相似。 

i2c@1,0 {
     compatible = "acme,a1234-i2c-bus";
     #address-cells = <1>;
     #size-cells = <0>;
     reg = <1 0 0x1000>;
     rtc@58 {
         compatible = "maxim,ds1338";
         reg = <58>;
     };
 };


3.2.4.2 Ranges (Address Translation)

我们已经讨论过如何向设备分配地址,但此时这些地址只是本地设备节点,还没有说明如何从那些地址里映射到cpu可以使用的地址。根节点经常描述地址空间的CPU视图。根节点的子节点已经使用了CPU的address domain,所以不需要任何明确的映射。例如,serial@101f0000设备被直接分配了地址0x101f0000。
根节点的非直接子节点是无法使用CPU的address domain的。为了在deivce tree获取内存映射地址必须指定如何从一个域名将地址转换到另一个。Ranges属性就用于此目的。以下是添加了ranges属性的device tree示例。

/ {
    compatible = "acme,coyotes-revenge";
    #address-cells = <1>;
    #size-cells = <1>;
    ...
    external-bus {
        #address-cells = <2>
        #size-cells = <1>;
        ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
                  1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
                  2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash

        ethernet@0,0 {
            compatible = "smc,smc91c111";
            reg = <0 0 0x1000>;
        };

        i2c@1,0 {
            compatible = "acme,a1234-i2c-bus";
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <1 0 0x1000>;
            rtc@58 {
                compatible = "maxim,ds1338";
                reg = <58>;
            };
        };

        flash@2,0 {
            compatible = "samsung,k8f1315ebm", "cfi-flash";
            reg = <2 0 0x4000000>;
        };
    };
};


Ranges是一个地址转换列表。每个输入ranges表格的是包含子地址的元组,母地址和子地址空间的范围大小。每个字段的大小都由获取的子地址的#address-cells值,母地址的#address-cell值和子地址的#size-cells值而定。以外部总线为例,子地址是2 cells,母地址是1 cell,大小也为1 cell。转换三个ranges:

Offset 0 from chip select 0 is mapped to address range 0x10100000…0x1010ffff
Offset 0 from chip select 1 is mapped to address range 0x10160000…0x1016ffff
Offset 0 from chip select 2 is mapped to address range 0x30000000…0x30ffffff

例如上面的总线是有片选的,就需要描述片选及片选的偏移量,在说明地址时,还需要说明地址映射范围。
 

3.2.5 中断属性

描述中断连接需要四个属性:

  1. interrupt-controller–这个属性为空,中断控制器应该加上此属性表明自己的身份;
  2. #interrupt-cells–与#address-cells和#size-cells相似,它表明连接此中断控制器的设备的中断属性的cell大小。#interrupt-cells属性设置为2表示每个中断描述符包含2个cells,一般第一个cells表示表示中断线号,第二个cells表示一个标记号,比如表示该中断是高电平触发、是低电平触发还是电平触发等等。对于所有给定的中断控制器,请参考控制器绑定文档以便获取对象中断编码。
  3. interrupt-parent–设备节点通过它来指定它所依附的中断控制器的phandle,当节点没有指定interrupt-parent时,则从父级节点继承。
  4. interrupts–用到了中断的设备节点,通过它指定中断号、触发方法等,这个属性具体含有多少个cell,由它依附的中断控制器节点的#interrupt-cells属性决定。而每个cell具体又是什么含义,一般由驱动的实现决定,而且也会在设备树的绑定文档中说明。
soc {
	compatible = "simple-bus";
	#address-cells = <1>;
	#size-cells = <1>;
	open-pic {
		clock-frequency = <0>;
		interrupt-controller;
		#address-cells = <0>;
		#interrupt-cells = <2>;
	};
	pci {
		#interrupt-cells = <1>;
		#size-cells = <2>;
		#address-cells = <3>;
		interrupt-map-mask = <0xf800 0 0 7>;
		interrupt-map = <
		/ * IDSEL 0x11 - PCI slot 1* /
		0x8800 0 0 1 &open-pic 2 1 / * INTA* /
		0x8800 0 0 2 &open-pic 3 1 / * INTB* /
		0x8800 0 0 3 &open-pic 4 1 / * INTC* /
		0x8800 0 0 4 &open-pic 1 1 / * INTD* /
		/ * IDSEL 0x12 - PCI slot 2* /
		0x9000 0 0 1 &open-pic 3 1 / * INTA* /
		0x9000 0 0 2 &open-pic 4 1 / * INTB* /
		0x9000 0 0 3 &open-pic 1 1 / * INTC* /
		0x9000 0 0 4 &open-pic 2 1 / * INTD* /
	>;
	};
};

 3.2.6 clock属性和reset属性

clocks = <&clk_sys FCK_AP_UART0_CLK>, <&clk_sys FCK_AP_UART0_PCLK>;

clock-names = "baudclk", "apb_pclk";

resets = <&reset_sys FRST_APPERI_UART0>, <&reset_sys FRST_APPERI_UART0_APB>;

reset-names = "reset", "apb_reset";

3.2.7 GPIO属性

对于GPIO控制器而言,其对应的设备节点需声明gpio-controller属性,并设置#gpio-cells的大小。譬如,对于兼容性为fsl,imx28-pinctrl的pinctrl驱动而言,其GPIO控制器的设备节点类似于:

pinctrl @ 80018000
{
	compatible			= "fsl,imx28-pinctrl", "simple-bus";
	reg 				= < 0x80018000 2000 >;
    gpio0:gpio @ 0
		{
		compatible			= "fsl,imx28-gpio";
		interrupts			= < 127 >;
		gpio - controller;
               #gpio -cells = <2>;
		interrupt - controller;
               #interrupt -cells = <2>;
		};
    gpio1:gpio @ 1
		{
		compatible			= "fsl,imx28-gpio";
		interrupts			= < 126 >;
		gpio - controller;
               #gpio - cells = <2>;
        interrupt - controller;
               #interrupt -cells = <2>;
		};
	...
};

其中,#gpio- cells为2,第1个cell为GPIO号,第2个为GPIO的极性。为0的时候是高电平有效,为1的时候则是低电平有效。
使用GPIO的设备则通过定义命名xxx-gpios属性来引用GPIO控制器的设备节点,如: 

sdhci@c8000400 {
    status = "okay";
    cd-gpios = <&gpio01 0>;
    wp-gpios = <&gpio02 0>;
    power-gpios = <&gpio03 0>;
    bus-width = <4>;
};

3.2.8 aliases节点

aliases节点为了解决节点路径名过长的问题,引入了节点别名的概念,可以引用到一个全路径的节点。如/external-bus/ethernet@0,0,但当用户想知道具体内容的时候显得太累赘,“哪个设备是eth0?”

aliases {
        ethernet0 = &eth0;
        serial0 = &serial0;
    };

3.2.9 chosen节点

chosen节点并不代表一个真正的设备,只是一个为固件和操作系统传递数据的地方,如引导参数。chosen节点里的数据也不代表硬件。

chosen {
		bootargs = "console=ttyS3,460800n8";
		stdout-path = &uart3;
	};

3.2.10 设备特定数据

用于定义特定于某个具体设备的一些属性,这些属性可以自由命名定义,但要注意避免和现有标准属性名相冲突。

3.3 常用of API

int of_device_is_compatible(const struct device_node *device,const char *compat);

此函数用于判断设备节点的兼容属性是否包含compat指定的字符串。这个API多用于一个驱动支持两个以上设备的时候。

读取属性:

  • int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz);
  • int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz);
  • int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);
  • int of_property_read_u64(const struct device_node *np, const char*propname, u64 *out_value);

读取设备节点np的属性名,为propname,属性类型为8、16、32、64位整型数组。对于32位处理器来讲,最常用的是of_property_read_u32_array()。

除了整型属性外,字符串属性也比较常用,其对应的API包括:

  • int of_property_read_string(struct device_node *np, const char*propname,const char **out_string);
  • int of_property_read_string_index(struct device_node *np,  const char*propname,int index, const char **output);

  除整型、字符串以外的最常用属性类型就是布尔型,其对应的API很简单:
   static inline bool of_property_read_bool(const struct device_node *np, const char *propname);
   如果设备节点np含有propname属性,则返回true,否则返回false。一般用于检查空属性是否存在。

内存映射:
   void __iomem *of_iomap(struct device_node *node, int index);
   上述API可以直接通过设备节点进行设备内存区间的ioremap(),index是内存段的索引。若设备节点的reg属性有多段,可通过index标示要ioremap()的是哪一段,在只有1段的情况,index为0。采用设备树后,一些设备驱动通过of_iomap()而不再通过传统的ioremap()进行映射,当然,传统的ioremap()的用户也不少。

   int of_address_to_resource(struct device_node *dev, int index,struct resource *r);
   上述API通过设备节点获取与它对应的内存资源的resource结构体。其本质是分析reg属性以获取内存基地址、大小等信息并填充到struct resource*r参数指向的结构体中。


解析中断:
   unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
   通过设备树获得设备的中断号,实际上是从.dts中的interrupts属性里解析出中断号。若设备使用了多个中断,index指定中断的索引号。


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值