43 设备树

一、什么是设备树

  • 1、新版本的 linux 中,ARM 相关的驱动全部采用了设备树,最新出的 CPU 的驱动开发基本都是基于设备树的
  • 2、uboot 启动内核用到 zImageimx6ull-alientek-emmc.dtbbootz 80800000 - 83000000
  • 3、设备树 就是 设备,描述设备树的文件叫做 DTS,DTS 采用树形结构描述板级设备信息。
    在这里插入图片描述
    主干是系统总线,主干的分支是控制器,分支的分支上接的是具体的设备
  • 4、在单片机驱动 里面,spi flash芯片:w25q64 的速度属性都是在 .c 文件中写死的。
    若板级信息都写到 .c 里面会产生大量的 .c 文件。把不同的板子信息以这样的形式都写到内核里显然不现实。
    因此将板子信息做成独立的格式,文件后缀为 .dts。一个平台会者机器对应一个 .dts

二、DTS、DTB 和 DTC 的关系

  • .dts :相当于 .c 文件,将板级信息从linux内核中分离出来,用此种格式来描述,就是设备树源码文件
  • .dtsi :相当于 .h 文件,一款SOC可以做出很多种板子,这些不同的板子必然有共同的信息,将这些信息提取出来做为有一个通用的文件,其它的 .dts文件直接引用这个 .dtsi文件即可(一般 .dts 描述板级信息( 也就是开发板上有哪些 IIC 设备、 SPI 设备等 ), .dtsi 描述 SOC 级信息(比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、 IIC 等等。比如 imx6ull.dtsi 就是描述 I.MX6ULL 这颗 SOC 内部外设情况信息的 )
  • DTC 工具就相当于gcc编译器,将 .dts 编译成 .dtb 文件
  • .dtb 文件相当于 bin文件 或者 可执行文件,可由 DTC 工具将 .dts 编译成 .dtb 文件, DTC 源码在 Linux 内核的 scripts/dtc 目录下
  • 描述板级硬件信息的内容都从 Linux 内中分离开来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树,文件扩展名为.dts。 一个 SOC 可以作出很多不同的板子,这些不同的板子肯定是有共同的信息, 将这些共同的信息提取出来作为一个通用的文件,其他的.dts 文件直接引用这个通用文件即可,这个通用文件就是.dtsi 文件,类似于 C 语言中的头文件。一般.dts 描述板级信息(也就是开发板上有哪些 IIC 设备、 SPI 设备等), .dtsi 描述 SOC 级信息(也就是 SOC 有几个 CPU、主频是多少、各个外设控制器信息等)
  • 通过 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs 编译所有的 dts文件,若要编译指定的 .dts

三、DTS基本语法

  • 1、设备树文件也就是 dts文件 也有头文件,扩展名为 .dtsi。可以将一款SOC的其它所有设备 / 平台的共有的信息提出来,作为一个通用的 .dtsi 文件,.dtsi 文件一般是用来描述cpu内部外设的属性的。(.dts 还可以包含 c 的头文件)
  • 2、DTS也是以 / 开始。
    dts文件的注释方法同c。
/dts-v1/;

#include <dt-bindings/input/input.h> 			// 可以包含c的头文件
#include "imx6ull.dtsi"		// 设备树头文件 
	
/ { 					// 斜杠后面这个括号表示根结点

	// skeleton.dtsi文件(3者的)的根结点合并到一起
	#address-cells = <1>;
	#size-cells = <1>;
	chosen {  //处理同memory
		stdout-path = &uart1;
	};
	aliases { 
		// skeleton.dtsi文件中有此一级子节点但是空的,imx6ull.dtsi也有此节点,将节点内的内容移动到此处,如下
		can0 = &flexcan1;
		...
	};
	memory { 
		device_type = "memory"; 
		//reg = <0 0>; 下面也有个memory一级子节点,此种情况下的处理方法是合并同类项,同样的reg,保留后面的。所以欲修改某个属性值可以在后面再赋值一次即可
		 reg = <0x80000000 0x20000000>;  //(起始地址和长度)
	};
	cpus {

	};
	intc: interrupt-controller@00a01000 {

	};
	// 描述 6ull 芯片内部内存映射,内部外设信息
	clocks {

	};
	soc {
		...
		aips2: aips-bus@02100000 {
			compatible = "fsl,aips-bus", "simple-bus";
			#address-cells = <1>;
			#size-cells = <1>;
			reg = <0x02100000 0x100000>;
			ranges;
			usbotg1: usb@02184000 {

			};
			usbotg2: usb@02184200 {

			};
			usbmisc: usbmisc@02184800 {

			};
			fec1: ethernet@02188000 {

			};
			...
			i2c1: i2c@021a0000 {
				// imx6ull.dtsi 里的 i2c1 属性信息只有这么多,通用信息或属性,半导体厂商做好的
				#address-cells = <1>;
				#size-cells = <0>;
				compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
				reg = <0x021a0000 0x4000>;
				interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_I2C1>;
				// status = "disabled"; 默认是 disable,在下面改为 okay,此处屏蔽
				// imx6ull-alientek-emmc.dts 追加的部分如下
				clock-frequency = <100000>;
				pinctrl-names = "default";
				pinctrl-0 = <&pinctrl_i2c1>;
				status = "okay";
				// 具体的 i2c 设备,磁力计,0e表示设备地址
				mag3110@0e {
					compatible = "fsl,mag3110";
					reg = <0x0e>;
					position = <2>;
				};
				// 具体的 i2c 设备,6轴传感器
				fxls8471@1e {
					compatible = "fsl,fxls8471";
					reg = <0x1e>;
					position = <0>;
					interrupt-parent = <&gpio5>;
					interrupts = <0 8>;
				};
			}; // i2c1
			...
		}; // aips2
		...
	}; // soc
	model = "string"; 		// (属性名 = 属性值)
	compatible = "string"
	chosen { 				 //(一级子节点,他自己没有子节点了,若有其就是二级子节点)
		stdout-path = &uart1;
	};
	memory {				 //(一级子节点)
		reg = <0x80000000 0x20000000>;  //(起始地址和长度)
	};
	reserved-memory {

	};
	backlight {

	};
	pxp_v4l2 {

	};
	regulators {

	};
	sound {

	};
	spi4 {

	};
};
  • 3、从根节点开始描述设备信息
  • 4、在 根节点外有一些取址符号如 &cpu0 这样的语句是追加
  • 5、节点名字的完整要求:node_name@unit_address
    有时也常常会遇到这种格式 label: node-name@unit-address,label 是节点标签,后面的才是节点名字,引入 label 的目的就是为了方便访问节点,可以直接通过 &label 来访问这个节点,比如通过 &cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。.dts 文件中有使用此方法来访问 .dtsi 文件中定义的节点的 &lcdif ,注意只有标签后面才能接 冒号: ,节点名称后面直接 空格,大括号。而且追加访问只能在根结点同一级处里面追加,不能在一级子节点、二级子节点中追加访问
    在这里插入图片描述
    unit_address 一般都是外设寄存器的起始地址(不绝对),有时候是 I2C 的设备地址或者其它含义,具体节点具体分析
    例如:
i2c4: i2c@021f8000 { // 021f8000是单元起始地址
				#address-cells = <1>;
				#size-cells = <0>;
				compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
				reg = <0x021f8000 0x4000>;
				interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_I2C4>;
				status = "disabled";
			};
uart6: serial@021fc000 {
				compatible = "fsl,imx6ul-uart",
					     "fsl,imx6q-uart", "fsl,imx21-uart";
				reg = <0x021fc000 0x4000>;
				interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_UART6_IPG>,
					 <&clks IMX6UL_CLK_UART6_SERIAL>;
				clock-names = "ipg", "per";
				dmas = <&sdma 0 4 0>, <&sdma 47 4 0>;
				dma-names = "rx", "tx";
				status = "disabled";
			};

五、设备树在系统中的体现

  • 使用指令 ls /proc/device-tree
  • 设备启动以后可以在根文件系统中看到设备树的节点信息。
    /proc/device-tree 目录下存放的是设备树中 根节点的各个一级子节点,这些 一级子节点 是以 目录 的形式给出的
    分别进入这些代表着 一级子节点的目录,里面的文件表示这些一级子节点的各个属性
/sys/firmware/devicetree/base # ls
#address-cells                 memory
#size-cells                    model
aliases                        name
backlight                      pxp_v4l2
chosen                         regulators
clocks                         reserved-memory
compatible                     soc
cpus                           sound
interrupt-controller@00a01000  spi4
/sys/firmware/devicetree/base # 
/sys/firmware/devicetree/base # 
/sys/firmware/devicetree/base # 
/sys/firmware/devicetree/base # 
  • 内核启动的时候会解析设备树,然后在 /procdevice-tree 目录下呈现出来。解析过程本次不做介绍。

六、特殊节点

  • 1、aliases

  • 2、chosen:主要是了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数作为命令行参数。
    uboot里面的 bootargs 值为 bootargs=console=ttymxc0,115200 rw root=/dev/nfs nfsroot=192.168.1.77:/home/jl/linux/nfs/rootfs ip=192.168.1.66:192.168.1.77:192.168.1.1:255.255.255.0::eth0:off
    linux kernel cmdline 为 Kernel command line: console=ttymxc0,115200 rw root=/dev/nfs nfsroot=192.168.1.77:/home/jl/linux/nfs/rootfs ip=192.168.1.66:192.168.1.77:192.168.1.1:255.255.255.0::eth0:off
    一般 .dts 文件中 chosen 节点通常为空或者内容很少

  • uboot 是如何向 kernel 传递 bootargs 的?
    cd /proc/device-tree
    该目录下有三个文件:bootargs name stdout-path
    bootargs 属性值和 uboot 里面的环境变量 一样
    但是在设备树文件 imx6ull-alientek-emmc.dts 文件中,chosen 节点内只有 stdout-path 这一个
    uboot 接触过 dtb,最终通过 bootz 80800000 - 83000000 来启动内核的,经过分析判断, uboot 拥有 bootargs 环境变量和 dtb 文件,可能是 uboot 修改了 dtb,最终发现在 fdt_support.c 中的 fdt_chosen 函数中添加了这个属性值。
    在这里插入图片描述
    在这里插入图片描述

七、特殊属性

  • 脱离一个具体的器件来看设备树中结点的属性是没有意义的,此处只介绍一些约定俗成的属性(page 1082)
  • 1、compatible
    兼容性属性
    值是一个字符串列表
    用于将设备和驱动绑定起来
pxp_v4l2 {
		compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
		status = "okay";
	};
...
sound {
		compatible = "fsl,imx6ul-evk-wm8960",
			   "fsl,imx-audio-wm8960";  // 支持这两个设备
		model = "wm8960-audio";
		cpu-dai = <&sai2>;
		...
}
// 内核源码 imx-wm8960.c
static const struct of_device_id imx_wm8960_dt_ids[] = {
	{ .compatible = "fsl,imx-audio-wm8960", },
	{ /* sentinel */ }
};
  • 2、model
    model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的
  • 3、status
    是和设备状态有关的, status 属性值也是字符串,字符串是设备的状态信息,上两个值常用
    在这里插入图片描述
  • 4、#address-cells 和#size-cells
    #address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息
    设备树中#address-cells和#size-cells作用
// 父节点的决定子节点的
i2c1: i2c@021a0000 {
				#address-cells = <1>;
				#size-cells = <0>;
				compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
				reg = <0x021a0000 0x4000>;
				interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_I2C1>;
				status = "disabled";
			};

  • 根结点的compatible属性,值是字符串

  • 根结点下的用于内核查找,判断是否支持这个平台
    内核启动时会检查是否支持此平台或者机器(教程 1086)

  • 不使用设备树时,通过 machine id 来判断内核是否支持
    Linux内核都用 MACHINE_STARTMACHINE_END 来定义一个 machine_desc 结构体来描述这个设备

// arch.h
/* 
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};

展开示例见教程 1087,1088

  • 使用设备树的话,就不需要使用机器ID,而是使用根节点的 compatible 属性值 (教程1088 末尾开始)

八、linux内核的 OF 操作函数

  • 1、驱动如何获取到设备书中节点信息,在驱动中使用 OF函数 来获取设备树属性内容(教程 1106)
  • 2、驱动要想获取到设备树节点内容,首先要找到节点

九、源码

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/of.h>
#include<linux/of_address.h>
#include<linux/of_irq.h>

#if 0
	backlight {
		compatible = "pwm-backlight";
		pwms = <&pwm1 0 5000000>;
		brightness-levels = <0 4 8 16 32 64 128 255>;
		default-brightness-level = <6>;
		status = "okay";
	};
#endif

// 驱动入口函数
static int __init dtsof_init(void)
{
    // 在调用 modprobe 时完成获取设备树
    int ret = 0;
    struct device_node *bl_nd;
    struct property *comppro;
    const char *str;
    u32 def_value;
    u32 *brival;
    u8 cnt = 0;
    u8 i = 0;
    
    // 1. 寻找节点,node:backlight
    // 参数为 节点的路径
    bl_nd = of_find_node_by_path("/backlight");
    if(bl_nd == NULL) // 寻找失败
    {
        printk("Fail\r\n");
        ret = -1;
        goto fail_findnd;
    }
    // 获取 backlight 结点下的 compatible 属性
    // 1
    comppro = of_find_property(bl_nd, "compatible", NULL);
    if(comppro == NULL)
    {
        printk("Fail\r\n");
        ret = -1;
        goto failfindpro;
    }
    else
    {
        printk("compatilble = \"%s\"\r\n", (char *)comppro->value);
    }
    // 2
    // 获取 backlight 结点下的 status 属性
    ret = of_property_read_string(bl_nd, "status", &str);
    if(ret)
    {
        goto fail_getstatus;
    }
    else
    {
        printk("status = \"%s\"\r\n", str);
    }
    //3
    // 获取 backlight 结点下的 default-brightness-level 属性
    ret = of_property_read_u32(bl_nd, "default-brightness-level", &def_value);
    if(ret < 0)
    {
        goto fail_readu32;
    }
    else
    {
        printk("default-brightness-level = %u\r\n", def_value);
    }
    // 4
    // 获取 backlight 结点下的 brightness-levels 属性
    ret = of_property_count_elems_of_size(bl_nd, "brightness-levels", sizeof(u32));
    if(ret < 0)
    {
        goto fail_readele;
    }
    else
    {
        cnt = ret;
        printk("ele_size = %d\r\n", cnt);
    }
    brival = kmalloc(ret*sizeof(u32), GFP_KERNEL);
    if(!brival)
    {
        ret = -1;
        goto fail_kmalloc;
    }
    ret = of_property_read_u32_array(bl_nd, "brightness-levels", brival,cnt);
    if(ret < 0)
    {
        goto fail_readarray;
    }
    else
    {
        printk("\"brightness-levels\" = < ");
        for(i=0; i<cnt; i++)
        {
            printk("%d ", brival[i]);
        }
        printk(" >\r\n");
        kfree(brival);
    }

    return 0;

fail_readarray:
    kfree(brival);
fail_kmalloc:
fail_readele:
fail_readu32:
fail_getstatus:
failfindpro:
fail_findnd:
    return ret;
}

// 出口函数
static void __exit dtsof_exit(void)
{
    ;
}
// register module entrance and exit
module_init(dtsof_init);
module_exit(dtsof_exit);
MODULE_LICENSE("GPL");


十、测试

  • 1、第一次加载 dtsof.ko 这个驱动,先试用指令 depmod
  • 2、modprobe dtsof.ko
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值