多彩编程 多彩编程MZPH · CODE BLOG
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-xxx和arch/arm/mach-xxx文件夹下。这些文件内通过手动定义不同的硬件设备步骤非常繁琐导致Linux内核代码中充斥着大量的垃圾代码。设备树的引入就是为了解决这个问题通过引入设备树可以直接传递给Linux而不再需要内核中大量的垃圾代码。使用设备树的目的解决传统开发模式的问题硬件配置变更导致寄存器修改维护成本高实现设备资源与驱动逻辑分离提高驱动复用性将易变的硬件配置部分独立管理驱动定义驱动本质是对硬件设备的操作控制版本支持Linux内核从3.x版本开始引入设备树机制1.3 设备树的组成① 整个设备树包含DTCdevice tree compilerDTSdevice tree source和DTBdevice 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-namesof_property_read_u32读取属性中的 32 位无符号整型数值如reg属性中的地址of_property_read_u32_array读取属性中的 32 位无符号整型数组如reg属性中的地址列表of_property_read_bool判断属性是否存在布尔型属性如status okay返回trueof_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_register3根据设备树的路径节点找到设备。 of_find_node_by_path(“/puteleds“);这个节点就在根目录的下面。4测试下设备树 of_property_read_string这个函数可以获得这个节点下的属性通过字符串查找。看看是否可以读到compatible。5从设备树节点中读取32位无符号整数数组reg。of_property_read_u32_array6完成物理地址到虚拟地址的映射使用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 ARCHarm CROSS_COMPILEarm-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);
返回列表