三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Linux驱动04—设备树

Linux驱动04—设备树

一、设备树介绍

1.1 设备树概念

设备树(Device Tree)是一种用于描述硬件资源信息的数据结构,它在Linux内核中的作用是实现驱动代码与设备硬件信息的分离。设备树通过bootloader(如U-Boot)传递到内核,使得内核能够从设备树中获取对应的硬件信息。这种机制对于同一SOC的不同主板,只需更换设备树文件即可实现不同主板的无差异支持,而无需更换内核文件,从而实现了内核和不同板级硬件数据的拆分。

设备树的引入极大地简化了Linux内核对不同硬件平台的支持,使得内核维护变得更加高效和灵活。随着Linux的不断发展,基本上现在所有的驱动程序都是基于设备树的,而设备树的基本语法、属性等知识对于理解和使用Linux内核至关重要。

通过设备树机制,Linux内核实现了硬件配置与驱动逻辑的解耦,显著提升了驱动复用性和可维护性。

1.2 设备树的由来

在Linux内核v2.6版本以前,ARM架构用于描述不同硬件信息的文件都存放在arch/arm/plat-xxxarch/arm/mach-xxx文件夹下。这些文件内通过手动定义不同的硬件设备,步骤非常繁琐,导致Linux内核代码中充斥着大量的垃圾代码。设备树的引入就是为了解决这个问题,通过引入设备树,可以直接传递给Linux,而不再需要内核中大量的垃圾代码。

使用设备树的目的:

  • 解决传统开发模式的问题:

    • 硬件配置变更导致寄存器修改,维护成本高
    • 实现设备资源与驱动逻辑分离,提高驱动复用性
    • 将易变的硬件配置部分独立管理
  • 驱动定义:

    • 驱动本质是对硬件设备的操作控制
  • 版本支持:

    • Linux内核从3.x版本开始引入设备树机制

1.3 设备树的组成

① 整个设备树包含DTC(device tree compiler),DTS(device tree source)和DTB(device tree blob)。

  • DTS是ASCII文本格式的设备树描述,通常放在arch/arm/boot/dts/目录中。
  • DTC是将.dts编译为.dtb的工具,相当于gcc。
  • DTB文件是.dts被DTC编译后的二进制格式的设备树文件,由Linux内核解析,也可以被bootloader进行解析。

对于采用相同SOC的不同开发板,其共性信息可以提取为通用的.dtsi文件(类似于C语言的头文件),其他.dts文件只需引用该文件即可:

  • .dts文件主要描述板级信息,如开发板上的IIC、SPI等外设配置
  • .dtsi文件则包含SOC级信息,如CPU数量、主频参数及外设控制器等基础配置

③ imx6ull设备树的目录位置

/home/linux/imx6ull/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/boot/dts 描述设备相关的资源 imx6ull-alientek-emmc.dts 需要修改的文件 imx6ull.dsti 公共的信息

④ 我们使用的一般是该目录下的imx6ull-alientekemmc.dts作为源码,然后通过make dtbs编译生成imx6ull-alientek-emmc.dtb文件。

1.4 设备树的语法

① 设备树文件(DTS)的语法包括节点(Node)和属性(Property)。每个设备树文件都有一个根节点,每个设备都是一个节点。节点间可以嵌套,形成父子关系,每个设备的属性都用一组key-value对来描述。属性的描述用分号结束。例如,根节点(/)下可能有多个子节点,如cpusmemory等,每个节点通过属性如compatiblereg等来描述其硬件信息。

② 设备树节点介绍

格式:

节点名 { 属性1; 可以有值,也可以没有值 属性2=值1 ; 值的类型可以是int,字符串,数组 }

如:

  • 节点名是自定义的控制设备节点名称 /*myled*/
  • 属性#address-cells#size-cells的值设为 1,表示在reg属性中,起始地址和地址长度各占用一个字长(cell)。

  • compatible属性用于实现设备与驱动的匹配绑定。绑定设备与驱动程序的标识符

  • status属性表示设备状态,常见值包括"okay"和"disable"。"okay"显示启用该设备,若为"disabled"则内核会忽略此节。

  • reg属性指定驱动中需要使用的寄存器物理地址。每对值表示一个寄存器起始地址和长度。

/ 根节点 { led { #address-cells = <1>; #size-cells = <1>; compatible = "my,myled"; status = "okay"; reg = < 0x020E0068 0x04 /*MUX*/ 0x020E02F4 0x04 /*PAD*/ 0x0209C000 0x04 /*DAT*/ 0x0209C004 0x04 /*DIR*/ >; }; }

③ 设备树在系统中的体现

ls /sys/fireware/devicetree/base/

1.5 设备树的加载流程

Linux内核通过读取bootloader传递的设备树二进制文件(DTB)的地址,解析DTB文件,获取设备树中的平台信息(machine_desc),配置信息(如bootargs),并将DTB文件中的每个节点转换为内核中的device_node结构体。这些device_node构成一棵树,根节点为of_root。内核提供了API来直接获取设备树中对应的属性,如of_property_read_u32_index等。

1.6 设备树的调试

可以通过/sys/firmware/fdt/proc/device-tree来查看原始的DTB文件和设备树信息。系统中所有的platform_device,包括来自设备树的和在.c文件中注册的,都可以在/sys/devices/platform中查看。

二、设备树相关函数接口

#include <linux/of.h> //头文件
函数名功能描述
of_find_node_by_path通过设备树节点的完整路径(如/soc/usb)查找对应节点
of_find_property在指定设备树节点下查找特定名称的属性
of_property_count_elems_of_size计算属性值中元素的数量(需指定单个元素字节数)
of_property_read_string读取属性中的字符串值(如compatible属性)
of_property_read_string_array读取属性中的字符串数组(如pinctrl-names
of_property_read_u32读取属性中的 32 位无符号整型数值(如reg属性中的地址)
of_property_read_u32_array读取属性中的 32 位无符号整型数组(如reg属性中的地址列表)
of_property_read_bool判断属性是否存在(布尔型属性,如status = "okay"返回true
of_iomap将设备树中指定的物理地址区域映射到内核虚拟地址空间(替代ioremap)(index参数表示要映射的reg属性中的段索引,若reg属性仅包含一段,则index应设为 0)
struct device_node *pnode = NULL; int ret = 0; u32 reg_array[8] = {0}; int i = 0; const char *compa = NULL; const char *led_status = NULL; pnode = of_find_node_by_path("/puteled"); if (IS_ERR(pnode)) { ret = PTR_ERR(pnode); pr_err("of_find_node_by_path error, errno:%d\n", ret); return ret; } // compatible status ret = of_property_read_string(pnode, "compatible", &compa); if (ret) { pr_err("of_property_read_string error,errno %d\n", ret); return ret; } ret = of_property_read_string(pnode, "status", &led_status); if (ret) { pr_err("of_property_read_string error,errno %d\n", ret); return ret; } pr_info("compa:%s status:%s\n", compa, led_status); ret = of_property_read_u32_array(pnode, "reg", reg_array, 8); if (ret) { pr_err("of_property_read_u32_array error,errno %d\n", ret); return ret; }

三、用设备树实现LED灯驱动

实现步骤

(1)修改设备树文件,增加设备节点。
(2)注册杂项设备。
misc_register
(3)根据设备树的路径节点,找到设备。 of_find_node_by_path(“/puteleds“);这个节点就在根目录的下面。
(4)测试下设备树, of_property_read_string,这个函数可以获得这个节点下的属
性,通过字符串查找。看看是否可以读到compatible。

(5)从设备树节点中读取32位无符号整数数组reg。of_property_read_u32_array

(6)完成物理地址到虚拟地址的映射(使用of_iomap, 这个函数会在设备树节点中读取reg属性)。

(7)完成相关寄存器设置(IO引脚复用寄存器、IO引脚电气特性、GPIO方向、GPIO的数据寄存器)。时钟寄存器不需要设置

① 修改设备树文件

cd linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek vi linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/boot/dts/imx6ull-alientek-emmc.dts puteled { #address-cells = <1>; #size-cells = <1>; compatible = "PT, pute-led"; status = "okay"; reg = < 0x020E0068 0x04 // MUX 0x020E02F4 0x04 // PAD 0x0209C000 0x04 // DAT 0x0209C004 0x04>; // DIR }; make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb ~/tftpboot/

② 实现驱动

//drv.c #include "linux/err.h" #include "linux/export.h" #include "linux/of_address.h" #include "linux/printk.h" #include "linux/string.h" #include "linux/sysfs.h" #include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/device.h> #include <linux/miscdevice.h> #include <linux/of.h> #define DEV_NAME "myled0" static volatile void __iomem *pmux = NULL; static volatile void __iomem *ppad = NULL; static volatile void __iomem *pdir = NULL; static volatile void __iomem *pdata = NULL; struct device_node *pnode = NULL; static ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *off); static ssize_t mywrite(struct file *f, const char __user *userdata, size_t size, loff_t *off); static int myopen(struct inode *inode, struct file *f); static int myrelease(struct inode *inode, struct file *f); static ssize_t myshow(struct device *dev, struct device_attribute *attr, char *buf); static ssize_t mystore(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); static struct device_attribute attr = __ATTR(led_status, 0664, myshow, mystore); struct file_operations fops = { .owner = THIS_MODULE, .open = myopen, .release = myrelease, .read = myread, .write = mywrite, }; static struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, //自动获取次设备号 .name = DEV_NAME, // 设备节点名 /sys/class/misc/xxx .fops = &fops, //杂项设备 文件操作集合 }; typedef enum { LED_OFF = 0, LED_ON = 1 } LED_STATE; static LED_STATE status; /** * @brief 在命令行对设备的属性 进行读取的时候,会调用这个函数 * * @param dev 本设备 * @param attr 本设备的属性结构体,暂时不用 * @param buf 返回命令的行的字符串信息 * @return ssize_t 返回buf字符串的长度 */ static ssize_t myshow(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t len = 0; if (LED_ON == status) { strcpy(buf, "LED_ON\n"); } else { strcpy(buf, "LED_OFF\n"); } len = strlen(buf); return len; } /** * @brief 当命令向该属性写入指令的时候,会调用这个函数 * * @param dev * @param attr * @param buf 命令行写入该属性的 指令字符串 * @param count 命令行的长度 * @return ssize_t */ static ssize_t mystore(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { u32 v = 0; v = readl(pdata); if (0 == strcmp(buf, "1\n")) // 1 -> ledon 0->ledoff { v &= ~(1 << 3); // 1111 0111 writel(v, pdata); status = LED_ON; } else if (0 == strcmp(buf, "0\n")) { v |= (1 << 3); // 1111 0111 writel(v, pdata); status = LED_OFF; } return count; } ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *off) { unsigned long ret = 0; ret = copy_to_user(userdata, &status, sizeof(status)); if (ret) { pr_err(" copy_to_user error\n"); return ret; } return sizeof(status); } ssize_t mywrite(struct file *f, const char __user *userdata, size_t size, loff_t *off) { int sw = 0; u32 v = 0; int ret = 0; ret = copy_from_user(&sw, userdata, size); if (ret) { pr_err(" copy_from_user error\n"); return ret; } v = readl(pdata); if (1 == sw) { // led on v &= ~(1 << 3); // 1111 0111 writel(v, pdata); status = LED_ON; // } else if (0 == sw) { // led off v |= (1 << 3); // 1111 0111 writel(v, pdata); status = LED_OFF; } pr_info("write caller\n"); return size; } int myopen(struct inode *inode, struct file *f) { pr_info("open caller\n"); return 0; } int myrelease(struct inode *inode, struct file *f) { pr_info("close caller\n"); return 0; } static int __init mydrv_init(void) { int ret = 0; u32 v = 0; u32 reg_array[8] = {0}; int i = 0; const char *compa = NULL; const char *led_status = NULL; ret = misc_register(&misc_dev); if (ret) { pr_err("misc_register error,errno %d\n", ret); return ret; } pnode = of_find_node_by_path("/puteled"); if (IS_ERR(pnode)) { ret = PTR_ERR(pnode); pr_err("of_find_node_by_path error, errno:%d\n", ret); return ret; } // compatible status ret = of_property_read_string(pnode, "compatible", &compa); if (ret) { pr_err("of_property_read_string error,errno %d\n", ret); return ret; } ret = of_property_read_string(pnode, "status", &led_status); if (ret) { pr_err("of_property_read_string error,errno %d\n", ret); return ret; } pr_info("compa:%s status:%s\n", compa, led_status); ret = of_property_read_u32_array(pnode, "reg", reg_array, 8); if (ret) { pr_err("of_property_read_u32_array error,errno %d\n", ret); return ret; } for (i = 0; i < 4; i++) { printk("index:%d reg add:0x%x\n", i, reg_array[i * 2]); // 0 2 4 6 } //pmux = ioremap(reg_array[0], reg_array[1]); pmux = of_iomap(pnode, 0); if (NULL == pmux) { pr_err("ioremap ADDR_MUX error\n"); return -ENOMEM; } //ppad = ioremap(reg_array[2], reg_array[3]); ppad = of_iomap(pnode, 1); if (NULL == ppad) { pr_err("ioremap ADDR_PAD error\n"); return -ENOMEM; } //pdir = ioremap(reg_array[6], reg_array[7]); pdir = of_iomap(pnode, 3); if (NULL == pdir) { pr_err("ioremap LED_DIR error\n"); return -ENOMEM; } //pdata = ioremap(reg_array[4], reg_array[5]); pdata = of_iomap(pnode, 2); if (NULL == pdata) { pr_err("ioremap LED_DATA error\n"); return -ENOMEM; } // set to gpio writel(0x05, pmux); // set to eletric attr writel(0x10b0, ppad); // 设置输出 v = readl(pdir); v |= (1 << 3); writel(v, pdir); // data register led off v = readl(pdata); v = v | (1 << 3); writel(v, pdata); pr_info("major:%d , minor:%d\n", 10, misc_dev.minor); ret = device_create_file(misc_dev.this_device, &attr); if (ret) { pr_err("device_create_file error\n"); return ret; } pr_info("hello,aaa\n"); return 0; } static void __exit mydrv_exit(void) { device_remove_file(misc_dev.this_device, &attr); iounmap(pmux); iounmap(ppad); iounmap(pdata); iounmap(pdir); misc_deregister(&misc_dev); pr_info("hello,bbb\n"); return; } module_exit(mydrv_exit); module_init(mydrv_init); MODULE_LICENSE("GPL");
← 返回列表