Linux设备树相关操作以及驱动开发流程
简介
最近在学习设备树以及设备基于设备树的驱动开发,其实我是一直在纠结要不要写这篇博客的,因为自己了解的仅仅是设备树的一些定义,一些操作,还没有去看源码,这样写出来的博客并不是太有深度的,但是还是想记录一下自己的学习历程,对自己这一周多的学习大概做一些输出,所以还是写了这篇博客,讲解一下Linux设备树的一些相关操作以及基于设备树的驱动开发流程。
设备树的引入主要是为了减少重用率低的代码,随着Linux内核以及ARM的发展,arch/arm文件夹的规模是日益庞大,同时里面的代码大部分是芯片厂商对自己的芯片描述和板卡厂商对自己产品的板级描述,一大堆xx-mach文件,这样的文件是难以复用的,它们各自实现了IO操作以及板级的控制,为了改变这样的局面引入了设备树机制,设备树机制最先是PowerPC引入了,设备树用一个树状结构来描述了支持体系,一款芯片可以看做是一个树干,基于它所开发的板卡可以看做是树枝,他们有共同的部分,也有板级的区别,同时也引入了pinctrl子系统改进了GPIO子系统,全部基于设备树,这样内核的代码复用率大大提高,同时也大大降低了驱动的移植难度,很多的驱动仅仅只需要修改设备树即可完成驱动的移植,所以设备树的加入虽然略微提高了学习成本但是带来的回报也是丰厚的。
相关文件组成和介绍
dts和bingings
文件分为dts和bingings
bindings包含设备树用到的所有宏定义,都放到bindings目录下
dts分为dts和dtsi文件,dts是板级文件,dtsi是“平台文件”,另外还有使用文档在Documentation/devicetree
.dts描述板级信息(有哪些IIC设备、SPI设备等)
.dtsi描述SOC级信息
DTS是设备树源码文件
DTB是将DTS编译后得到的二进制文件
将.dts编译为.dtb需要DTC文件 工具源码在scripts/dtc目录下
在源码文件夹中执行make dtbs就可以进行设备树的编译
4412开发板的设备树文件:arch/arm/boot/dts/exynos4412-itop-elite.dts
这里的平台文件是指支持的不止一块板子而是一类板子
设备树文件之间的关系
dts文件包含的头文件
#include <dt-bindings/sound/samsung-i2s.h>
#include <dt-bindings/pwm/pwm.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include "exynos4412-itop-scp-core.dtsi"
dtsi文件
----dt-bindings/clock/exynos4.h
----dt-bindings/clock/exynos-audss-clk.h
----dt-bindings/interrupt-controller/arm-gic.h
----dt-bindings/interrupt-controller/irq.h
----exynos-syscon-restart.dtsi
--exynos4412-pinctrl.dtsi
----dt-bindings/pinctrl/samsung.h
--exynos4-cpu-thermal.dtsi
----include <dt-bindings/thermal/thermal.h>
官方文档
设备树的文档资料十分详尽,基本上看着文档就可以进行配置,设备树文档对每一个需要配置的地方都有详细的解释以及示例
比如下面两个文档
Documentation/devicetree/usage-model.txt
Documentation/devicetree/bindings/gpio/gpio-samsung.txt
设备树dts的基本构造
节点和根节点
{}框起来的,称为节点
/{}在dts的最开头,称为根节点
节点的标准结构是xxx@yyy{ … }
xxx是节点的名字,yyy则不是必须的,其值为节点的地址(寄存器地址或其他地址)
label:node-name@unit-address
引入label的目的是为了方便访问节点,可以直接通过&label来访问这个节点
支持几种数据形式:
- 字符串
- 32位无符号整数
- 字符串列表
节点可以包含属性和子节点
属性
设备树学习的主要部分:设备树文件中的属性的配置,驱动文件中调用设备树中的属性
- compatible
类似设备名称,兼容性属性,字符串列表,用于将设备和驱动绑定起来
格式:“manufacturer,model”
其中manufacturer表示厂商,model一般是模块对应的驱动名字
一般的驱动程序文件都会有一个OF匹配表,此OF匹配表保存着一些compatible值,如果设备节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动 - model
字符串
描述设备模块信息,比如名字 - status
字符串,设备的状态
okey:可操作
disable:当前不可操作,但是在未来可以变为可操作,如热插拔设备插入后
fail:不可操作,检测到了一系列错误,也不大可能变得可操作
fail-sss:同fail,后面的sss部分是检测到的错误内容 - #address-cells和#size-cells
都是无符号32位整型,可以用在任何拥有子节点的设备中
用于描述子节点的地址信息
#address-cells决定子节点reg属性地址信息所占用的字长(32位)
#size-cells决定了子节点reg属性中长度信息所占用的字长(32位)
一般这两个都是1
这两个属性表明了子节点应该如何编写reg属性值。一般reg属性都是和地址相关的内容
reg=<address1 length1 address2 length2 address3 length3……> - reg
用于描述设备地址空间资源信息
一般都是某个外设的寄存器地址范围信息 - ranges
可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵
是一个地址映射/转移表,每个项目由字地址、父地址和地址空间长度三部分组成
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells确定所占用字长
parent-bus-address:父总线空间的物理地址,同样由父节点的#address-cells确定所占用字长
length:子地址空间的长度,由父节点的#size-cells确定所占用字长 - name
字符串,记录节点名字,已被启用不推荐 - device_type
字符串,IEEE 1275会用到此属性,用于描述设备的FCode,设备树没有FCode,被弃用
只能用于cpu节点或者memory节点 - label—标签
- gpios—IO
- pwms—PWM
在设备树上添加节点
在.dts文件中添加
设备树在系统中的体现
/proc/device-tree/ 目录下是根据节点名字创建的不同文件夹
就是将设备树分级存储
每个文件夹就是一个节点,里面包含这个节点的属性以及它所包含的子节点
特殊子节点
- aliases子节点
别名 &label - chosen子节点
不是一个真正的设备,主要是为了uboot向linux内核传递数据
重点是bootargs
stdout-path 设置debug串口
uboot会将bootargs填入chosen节点
内核如何识别设备树
匹配方式
使用设备树之前:
uboot传递machine id,MACHINE_START、MACHINE_END
使用设备树之后:
machine_desc
DT_MACHINE_START、DT_MACHINE_END
machine_desc结构体的.dt_compat成员变量保存着本设备兼容属性
Linux内核调用start_kernel函数来启动内核
会调用setup_arch函数来匹配machine_desc
setup_arch中调用setup_machine_fdt来获取machine_desc,其参数是uboot传递给linux内核的dtb文件首地址,返回值是最匹配的machine_desc
内核解析DTB文件
Linux内核会在启动的时候解析DTB文件,然后在/proc/device-tree目录下生成相应的设备树节点文件
设备树常用OF操作函数
of是open firmware的缩写,是定义计算机固件系统接口的标准,以前由电气和电子工程师协会认可。它起源于Sun,已被Sun,Apple,IBM,ARM和大多数其他非x86 PCI芯片组供应商使用。
ARM的设备树操作就遵守open firmware标准
OF操作函数是编写驱动的时获取设备树信息调用的函数
定义在include/linux/of.h文件中
查找节点的OF函数
linux内核使用device_node结构体来描述一个节点
struct device_node {
const char *name;
const char *type;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
}
- 1、of_find_node_by_name
通过子节点名字查找子节点 - 2、of_find_node_by_type
通过子节点类型查找子节点,device_type - 3、of_find_compatible_node
根据device_type和compatible查找子节点,device_type可以设置为NULL - 4、of_find_matching_node_and_match
通过of_device_id匹配表来查找指定的节点 - 5、of_find_node_by_path
通过路径来查找指定节点
查找父/子节点的OF函数
- 1、of_get_parent
父节点 - 2、of_get_next_child
迭代的查找子节点
提取属性值
节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要, Linux内核中使用结构体 property表示属性,此结构体同样定义在文件 include/linux/of.h中,内容如下:
struct property { char *name; /* 属性名字 */
int length; /* 属性长度 */
void *value; /* 属性值 */
struct property *next; /* 下一个属性 */
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
- 1 、of_find_property
用于查找指定的属性,函数原型如下:
struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
- 2、of_property_count_elems_of_size
函数用于获取属性中元素的数量,比如 reg属性值是一个数组,那么使用此函数可以获取到这个数组的大小,此函数原型如下:
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size);
- 3、of_property_read_u32_index
函数用于从属性中获取指定标号的 u32类型数据值 (无符号 32位 ),比如某个属性有多个 u32类型的值,那么就可以使用此函数来获取指定标号的数据值,原型如下:
int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index, u32 *out_value);
- 4、读取数组数据的函数
of_property_read_u8_array
of_property_read_u16_array
of_property_read_u32_array
of_property_read_u64_array
这 4个函数分别是读取属性中 u8、 u16、 u32和 u64类型的数组数据,比如大多数的 reg属性都是数组数据,可以使用这 4个函数一次读取出 reg属性中的所有数据。 - 5 、读取整形值属性的函数
of_property_read_u8
of_property_read_u16
of_property_read_u32
of_property_read_u64 - 6、of_property_read_string
用于读取属性中字符串值,函数原型如下:
int of_property_read_string(const struct device_node *np,
const char *propname,
const char **out_string);
- 7、of_n_addr_cells
函数用于获取 #address-cells属性值,函数原型如下:
int of_n_addr_cells(struct device_node *np);
- 8、 of_n_size_cells
函数用于获取 #size-cells属性值,函数原型如下:
int of_n_size_cells(struct device_node *np) 函数
其他常用的OF函数
- 1、of_device_is_compatible
函数用于查看节点的 compatible属性是否有包含 compat指定的字符串,也就是检查设备节点的兼容性,函数原型如下:
int of_device_is_compatible(const struct device_node *device,
const char *);
- 2、of_get_address
函数用于获取地址相关属性,主要是“ reg”或者 assigned-addresses”属性值,函数属性如下:
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags);
- 3、of_translate_address
函数负责将从设备树读取到的地址转换为物理地址,函数原型如下:
u64 of_translate_address(struct device_node *dev,const __be32 *in_addr)
- 4、of_address_to_resource
IIC、 SPI、 GPIO等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间, Linux内核使用 resource结构体来描述一段内存空间,“ resource”翻译出来就是“资源”,因此用 resource结构体描述的都是设备资源信息, resource结构体定义在文件 include/linux/ioport.h中,定义如下:
struct resource {resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
对于 32位的 SOC来说, resource_size_t是 u32类型的。其中 start表示开始地址, end表示结束地址, name是这个资源的名字, flags是资源标志位,一般表示资源类型,可选的资源标志定义在文件 include/linux/ioport.h中,如下所示:
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
#define IORESOURCE_READONLY 0x00004000
#define IORESOURCE_CACHEABLE 0x00008000
#define IORESOURCE_RANGELENGTH 0x00010000
#define IORESOURCE_SHADOWABLE 0x00020000
#define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */
#define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */
#define IORESOURCE_MEM_64 0x00100000
#define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */
#define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */
#define IORESOURCE_EXT_TYPE_BITS 0x01000000 /* Resource extended types */
#define IORESOURCE_SYSRAM 0x01000000 /* System RAM (modifier) */
#define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */
#define IORESOURCE_DISABLED 0x10000000
#define IORESOURCE_UNSET 0x20000000 /* No address assigned yet */
#define IORESOURCE_AUTO 0x40000000
#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */
一般最常见的资源标志是IORESOURCE_MEM、IORESOURCE_REG和IORESOURCE_IRQ等
of_address_to_resource函数是从设备树中提取资源值,本质上就是取reg属性值然后将其转换为resource结构体类型,原型如下:
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
- 5、of_iomap
函数用于直接内存映射,以前我们会通过 ioremap函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap函数来获取内存地址所对应的虚拟地址,不需要使用 ioremap函数了。
当然ioremap函数也是可以使用的,只是在采用了设备树后,大部分的驱动都使用了of_iomap函数
函数原型如下:
void __iomem *of_iomap(struct device_node *np, int index)
总结
这个博客讲解的都是设备树很表面的东西,它的由来、语法以及驱动中如何进行调用,后面如果自己了解深入了也会对设备树关键部分的代码进行分析。
我们可以总结设备树的一些套路
三个主要目标:
1、platform identification,平台描述
2、runtime configuration,运行环境配置
3、device population.描述所有设备
- platform identification
compatible = “ti,omap3-beagleboard”, “ti,omap3450”, “ti,omap3”;
compatible = “ti,omap3-beagleboard-xm”, “ti,omap3450”, “ti,omap3”;
可以知道这是TI omap3平台,omap3450芯片,omap3-beagleboard开发板
compatible = “topeet,itop4412-elite”, “samsung,exynos4412”, “samsung,exynos4”; - runtime configuration
大多通过chosen节点
例如:
chosen { bootargs = “console=ttyS0,115200 loglevel=8”;
initrd-start = <0xc8000000>;
initrd-end = <0xc8200000>; };
4412例子
chosen {
bootargs = “root=/dev/mmcblk0p2 rw rootfstype=ext4 rootdelay=1 rootwait”;
stdout-path = “serial2:115200n8”;
}; - device population
这个就很常见了,设备树其他的就是做这个的
理解它的主要目标以及使用方法我们先熟练的使用然后再去研究它的核心。