设备树可以说是platform平台的升级版,经过前面的学习,我知道了platform分为两部分,平台设备和平台驱动。而设备树就是替代了平台设备。平台设备是以C文件的方式加载的,而设备树是以配置文件加载的(xxx.dtb,就够有点类似于json),在linux系统启动的时候就加载设备树文件。其实平时我们只需要会修改设备树就好了,不需要自己去写出全部内容,芯片厂家会提供相关设备树的,你只需要根据需要去添加外设就好了。
1. 设备树的语法
1.1 设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设
备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。
label: node-name@unit-address { // 各种属性(1.2讲解) // 子节点(节点是可以嵌套的) }
- lable: 你给这个节点的别名,设备树的其他要要用到的就可以使用**&label**来引用。
- node-name: 节点名字,一般命名应该与功能挂钩
- unit-address: 一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要
1.2 属性的数据类型
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流
1.2.1 字符串
compatible = "arm,cortex-a7" // 这种类型的字符串是内置的,在平台驱动加载到系通过,匹配时的name来源就是这里
1.2.1 32位无符号整数
这种类型一般用来表示寄存器的地址,以及字符串的偏移量。用<>包裹起来,可有多个整数,用空格隔开
reg = <0x2020>; reg = <0x2020 0x20200 0x12344>;
1.2.2 字符串列表
用逗号隔开
compatible = "fsl,imx6ull-fire-nand", "fsl,imx6ull-gpmi-nand";
1.3 标准属性
1.3.1 compatible属性
这个属性是很重要的,该属性是一个字符串类型的,用于将设备和驱动绑定起来,在驱动被加载到系统的时候会有一个匹配的过程,设备端的依据就是该属性,驱动就是那个of匹配表
// 驱动的of匹配表实例 static const struct of_device_id rgb_led[] = { {.compatible = "fire,my_rgb_led"}, }; static struct platform_driver rgb_led_platform_driver = { .probe = rgb_led_probe, .remove = rgb_led_remove, .driver = { .name = "rgb-leds-device-tree", .owner = THIS_MODULE, //.remove = rgb_led_remove, .of_match_table = rgb_led, // match匹配的时候会用到这个列表里面的compatible和设备树里面的compatible对比 } };
1.3.2 status属性
该属性与设备状态有关
值 | 描述 |
“okay” | 设备可操作 |
“disabled” | 设备不可操作 |
“fail” | 设备因为检测到错误而不可操作 |
“fail-sss” | 与"fail"相同,后面的sss部分是检测到的错误内容 |
1.3.3 reg属性
reg属性的值一般是(address, length)对。address表示该设备的起始地址,length表示该设备的地址长度
1.3.4 #address-cells和#size-cells
这两个属性都是unsigned int 类型的,用于描述子节点的地址信息。前者决定了reg里面的地址信息所占的字长(32位),后者决定了reg里面的长度所占的字长(32位)。
1.3.5 model属性
该属性值是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的
2. 设备树常用的OF操作函数
OF操作函数的目的就是为了从设备树中提取驱动需要的资源
2.1 查找节点的OF函数
2.1.1 of_find_node_by_name
通过节点名字查找节点
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
- from: 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
- name: 要查找的节点名字
2.1.2 of_find_compatible_node
通过节点的compatible查找属性(因为device_type已经不使用了,直接赋值为NULL)
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
- from: 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
- 要查找的device_type 属性值,直接给NULL,忽略掉它
- 要查找的节点所对应的 compatible 属性列表
2.1.3 of_find_node_by_path
通过全路径查找节点
inline struct device_node *of_find_node_by_path(const char *path)
- path: 从根节点开始的路径,类似于绝对路径
2.2 查找父/子节点的OF函数
2.2.1 of_get_parent
获得指定节点的父节点
struct device_node *of_get_parent(const struct device_node *node)
2.2.2 of_get_next_child
用迭代的方式查找子节点
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
- node: 父节点
- prev: 该节点为父节点下的一个节点,我们最终获取的就是该节点后面一个节点,如果该节点是父节点最后一个子节点,name就会返回NULL
2.3 提取属性的OF函数
2.3.1 of_find_property
用于查找指定的属性
property *of_find_property(const struct device_node *np, const char *name, int *lenp)
- np: 设备节点
- name: 属性名字
- lenp: 属性的字节数
2.3.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)
- np: 设备节点
- propname: 需要统计的属性名
- elem_size: 元素长度
- 返回值: 元素数量
2.3.3 of_property_read_u32_index
从属性中获取指定标号的u32类型的数据值
int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
- np: 设备节点
- propname: 要读取的属性名字
- index: 要读取的值标号(从0开始)
- out_value: 读取的数据存放地址
- 返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
2.3.4 批量读取
下面四个函数会以数组的方式一次性读取多个值
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_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
- np: 设备节点
- propname: 要读取的属性名
- out_values: 存放数据的数组的起始地址
- sz: 要读取的个数
- 返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
2.3.5 单个读取
当reg只有一个值的时候,可以使用下面的函数读取
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value) int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value) int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value) int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value)
2.3.6 of_property_read_string
用户读取属性中的字符串值
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
- np: 设备节点
- propname: 属性名
- out_string: 读取到的字符串值
2.3.7 of_n_addr_cells
获取#address-cells属性值
int of_n_addr_cells(struct device_node *np)
2.3.8 of_n_size_cells
获取#size-cells属性值
int of_n_size_cells(struct device_node *np)
2.3 其他OF函数
2.3.1 of_iomap
可用于直接内存映射(映射的就是reg的值),比ioremap更加方便,但我不知道我的板子用不了这个函数
void __iomem *of_iomap(struct device_node *np, int index)
- np: 设备节点
- index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0
- 返回值:经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败
3. 实例(野火I.MX6ULL pro开发板)
3.1 设备树
在野火官方提供的设备树的根节点下添加如下内容
/* RGB_LED设备节点 */ my_rgb_led { #address-cells = <1>; #size-cells = <1>; compatible = "fire,my_rgb_led"; // 驱动的of_device_id表要和这里的一直 led_red@0x20C406C { compatible = "fire,led_red"; reg = <0x020C406C 0x00000004 0x020E006C 0x00000004 0x020E02F8 0x00000004 0x0209C004 0x00000004 0x0209C000 0x00000004>; pin = <4>; /* 引脚*/ clock_offset = <26>; /* 时钟配置位的偏移量 */ status = "okay"; }; led_green@0x020C4074 { compatible = "fire,led_green"; reg = <0x020C4074 0x00000004 0x020E01E0 0x00000004 0x020E046C 0x00000004 0x020A8004 0x00000004 0x020A8000 0x00000004>; pin = <20>; clock_offset = <12>; status = "okay"; }; led_blue@0x020C4074 { compatible = "fire,led_blue"; reg = <0x020C4074 0x00000004 0x020E01DC 0x00000004 0x020E0468 0x00000004 0x020A8004 0x00000004 0x020A8000 0x00000004>; pin = <19>; clock_offset = <12>; status = "okay"; }; };
3.2 驱动文件
/* 编写要点 1. 完成平台驱动(platform_driver_register())的注册,probe, remove函数的实现 2. 完成驱动(driver_register())的注册,增加设备节点,实现file_operation结构体的相关函数 */ #include <linux/module.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/mutex.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/stat.h> #include <linux/init.h> #include <linux/device.h> #include <linux/tty.h> #include <linux/kmod.h> #include <linux/gfp.h> #include <linux/of.h> #include <linux/platform_device.h> #include <asm/mach/map.h> #include <asm/io.h> static int major = 0; // 主设备号 struct class *led_class; // 获取到的资源存在该结构体内 struct led_resource { //rgb_led_red 的设备树节点 struct device_node *device_node; // 存放物理地址转换后的虚拟地址 u32 *virtual_CCM_CCGR; u32 *virtual_IOMUXC_SW_MUX_CTL_PAD; u32 *virtual_IOMUXC_SW_PAD_CTL_PAD; u32 *virtual_GDIR; u32 *virtual_DR; int pin; int clock_offset; }; struct led_resource rgb_led_resource[3] = {0}; // 配置引脚 static int gpio_init(int which) { /* 初始化引脚 */ // 开时钟 *(rgb_led_resource[which].virtual_CCM_CCGR) |= (3<<(rgb_led_resource[which].clock_offset)); // 设置为GPIO模式 *(rgb_led_resource[which].virtual_IOMUXC_SW_MUX_CTL_PAD) &= ~0xf; *(rgb_led_resource[which].virtual_IOMUXC_SW_MUX_CTL_PAD) |= 5; // 设置引脚属性 //*(leds[which].va_iomuxc_pad_ctl) = 0x1F838; // 设置为输出 *(rgb_led_resource[which].virtual_GDIR) |= (1<<(rgb_led_resource[which].pin)); return 0; } // 获得资源, 并将物理地址转为虚拟地址 static void pyaddr_to_viaddr(void) { int i = 0; int ret = 0; u32 regdata[20]; if (rgb_led_resource[i].device_node == NULL) return; for (i=0;i<ARRAY_SIZE(rgb_led_resource);i++) { // 获取引脚的位置 , 时钟配置位在寄存器的偏移量 ret = of_property_read_u32(rgb_led_resource[i].device_node, "pin", &(rgb_led_resource[i].pin)); if (ret != 0) { printk("%s,%s,%d error: not find pin \n", __FILE__, __FUNCTION__, __LINE__); return; } ret = of_property_read_u32(rgb_led_resource[i].device_node, "clock_offset", &(rgb_led_resource[i].clock_offset)); if (ret != 0) { printk("%s,%s,%d error: not find clock_offset \n", __FILE__, __FUNCTION__, __LINE__); return; } ret = of_property_read_u32_array(rgb_led_resource[i].device_node, "reg", regdata, 10); if (ret != 0) { printk("%s,%s,%d error: not find regdata \n", __FILE__, __FUNCTION__, __LINE__); return; } rgb_led_resource[i].virtual_CCM_CCGR = ioremap(regdata[0], 4); rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD = ioremap(regdata[2], 4); rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD = ioremap(regdata[4], 4); rgb_led_resource[i].virtual_GDIR = ioremap(regdata[6], 4); rgb_led_resource[i].virtual_DR = ioremap(regdata[8], 4); //rgb_led_resource[i].virtual_CCM_CCGR = of_iomap(rgb_led_resource[i].device_node, 0); //rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(rgb_led_resource[i].device_node, 1); //rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(rgb_led_resource[i].device_node, 2); //rgb_led_resource[i].virtual_GDIR = of_iomap(rgb_led_resource[i].device_node, 3); //rgb_led_resource[i].virtual_DR = of_iomap(rgb_led_resource[i].device_node, 4); //printk("======debug ccgr addr: %x=========\n", rgb_led_resource[i].virtual_CCM_CCGR); //printk("======debug mux addr: %x=========\n", rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD); //printk("======debug pad addr: %x=========\n", rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD); //printk("======debug gdir addr: %x=========\n", rgb_led_resource[i].virtual_GDIR); //printk("======debug dr addr: %x=========\n", rgb_led_resource[i].virtual_DR); //printk("======debug pin: %d=========\n", rgb_led_resource[i].pin); //printk("======debug clock_offset: %d=========\n", rgb_led_resource[i].clock_offset); printk("%s\n", __FUNCTION__); } } static int led_drv_open(struct inode *node, struct file *file) { int minor = iminor(node); printk("=============%s\n============", __FUNCTION__); // 配置gpio模式 gpio_init(minor); printk("===============%s===============\n", __FUNCTION__); return 0; } static int led_drv_close(struct inode *node, struct file *file) { printk("%s\n", __FUNCTION__); return 0; } static ssize_t led_drv_read(struct file *file, char __user * buf, size_t size, loff_t *offset) { printk("%s\n", __FUNCTION__); return 0; } static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) { int minor; char status; struct inode *node = file_inode(file); minor = iminor(node); copy_from_user(&status, buf, 1); if (status) { // 开灯 *(rgb_led_resource[minor].virtual_DR) &= ~(1<<(rgb_led_resource[minor].pin)); } else { *(rgb_led_resource[minor].virtual_DR) |= 1<<(rgb_led_resource[minor].pin); } printk("%s\n", __FUNCTION__); return 0; } static struct file_operations led_drv = { .owner = THIS_MODULE, //gcc用法 .open = led_drv_open, .read = led_drv_read, .write = led_drv_write, .release = led_drv_close, }; static int rgb_led_probe(struct platform_device *pdev) { int err; // 获取资源 struct device_node *rgb_device_node = NULL; // led节点的父节点 rgb_device_node = of_find_node_by_path("/my_rgb_led"); if (rgb_device_node == NULL) { printk("%s,%s,%d error: not find /my_reg_led\n", __FILE__, __FUNCTION__, __LINE__); return -1; } // red_led资源 rgb_led_resource[0].device_node = of_find_node_by_name(rgb_device_node, "led_red"); if (rgb_led_resource[0].device_node == NULL) { printk("%s,%s,%d error: not find led_red\n", __FILE__, __FUNCTION__, __LINE__); return -1; } // red_green资源 rgb_led_resource[1].device_node = of_find_node_by_name(rgb_device_node, "led_green"); if (rgb_led_resource[1].device_node == NULL) { printk("%s,%s,%d error: not find led_green\n", __FILE__, __FUNCTION__, __LINE__); return -1; } // red_blue资源 rgb_led_resource[2].device_node = of_find_node_by_name(rgb_device_node, "led_blue"); if (rgb_led_resource[2].device_node == NULL) { printk("%s,%s,%d error: not find led_blue\n", __FILE__, __FUNCTION__, __LINE__); return -1; } pyaddr_to_viaddr(); // 地址转换 // 完成字符设备的注册 major = register_chrdev(0, "rgb_led", &led_drv); // 注册设备类 led_class = class_create(THIS_MODULE, "hxdled_class"); err = PTR_ERR(led_class); if (IS_ERR(led_class)) { unregister_chrdev(major, "rgb_led"); return -1; } // 注册设备节点,/dev/rgb_led% device_create(led_class, NULL, MKDEV(major, 0), NULL, "led_red"); device_create(led_class, NULL, MKDEV(major, 1), NULL, "led_green"); device_create(led_class, NULL, MKDEV(major, 2), NULL, "led_blue"); printk("=======================%s========================\n", __FUNCTION__); return 0; } static int rgb_led_remove(struct platform_device *dev) { int i =0; // 释放虚拟地址 for (i=0;i<ARRAY_SIZE(rgb_led_resource);i++) { iounmap(rgb_led_resource[i].virtual_CCM_CCGR); iounmap(rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD); iounmap(rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD); iounmap(rgb_led_resource[i].virtual_GDIR); iounmap(rgb_led_resource[i].virtual_DR); } // 释放设备节点 device_destroy(led_class, MKDEV(major, 0)); device_destroy(led_class, MKDEV(major, 1)); device_destroy(led_class, MKDEV(major, 2)); // 释放设备类 class_destroy(led_class); // 释放注册字符设备 unregister_chrdev(major, "rgb_led"); printk("%s\n", __FUNCTION__); return 0; } static const struct of_device_id rgb_led[] = { {.compatible = "fire,my_rgb_led"}, }; static struct platform_driver rgb_led_platform_driver = { .probe = rgb_led_probe, .remove = rgb_led_remove, .driver = { .name = "rgb-leds-device-tree", .owner = THIS_MODULE, //.remove = rgb_led_remove, .of_match_table = rgb_led, // match匹配的时候会用到这个列表里面的compatible和设备树里面的compatible对比 } }; // 平台驱动入口函数 static int __init platform_rgb_led_init(void) { int ret = 0; // 完成平台驱动的注册 ret = platform_driver_register(&rgb_led_platform_driver); printk("%s\n", __FUNCTION__); return 0; } // 平台驱动出口函数 static void __exit platform_rgb_led_exit(void) { // 完成平台驱动的移除 platform_driver_unregister(&rgb_led_platform_driver); printk("%s\n", __FUNCTION__); return; } module_init(platform_rgb_led_init); module_exit(platform_rgb_led_exit); MODULE_LICENSE("GPL");
3.3 应用层
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<fcntl.h> #include<sys/stat.h> #include<string.h> int main(int argc, char **argv) { int fd; char status; if (argc != 3) { printf("Usage %s <dev-path> <on/off>\n", argv[0]); return -1; } fd = open(argv[1], O_RDWR); if (fd < 0) { perror("open error"); return -1; } if (strcmp(argv[2], "on") == 0) { // 开灯 status = 1; write(fd, &status, 1); } else if (strcmp(argv[2], "off") == 0) { // 关灯 status = 0; write(fd, &status, 1); } else { printf("Usage %s <dev-path> <on/off>\n", argv[0]); return -1; } close(fd); return 0; }
3.4 Makefile
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- export ARCH CROSS_COMPILE KERN_DIR = /home/hxd/workdir/ebf_linux_kernel_6ull_depth1/build_image/build all: make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o led_test led_test.c clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order rm -f hello_drv_test obj-m += led_driver.o