永久免费国外ip代理/宁波网站推广优化哪家正规
一、什么是设备树
- 1、新版本的 linux 中,ARM 相关的驱动全部采用了设备树,最新出的 CPU 的驱动开发基本都是基于设备树的
- 2、uboot 启动内核用到
zImage
、imx6ull-alientek-emmc.dtb
。bootz 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 { //处理同memorystdout-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...}; // socmodel = "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_START
和MACHINE_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 0backlight {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 属性// 1comppro = 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