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);

相关新闻

微软商店网络故障排查与修复指南

微软商店网络故障排查与修复指南

1. 微软商店网络故障现象解析最近在Windows社区频繁看到用户反馈微软商店(Microsoft Store)出现各种网络连接问题。典型症状包括:点击应用图标后长时间白屏、显示"我们这边出了错"的报错提示、下载进度条卡在0%不动、或者直接提示"检查你的网络连接&…

2026/8/10 9:23:07 阅读更多 →
Patroni配置生成与验证实战指南

Patroni配置生成与验证实战指南

1. Patroni配置生成与验证命令完全指南 作为PostgreSQL高可用方案的事实标准,Patroni通过YAML配置文件驱动整个集群的运作。但很多DBA在初次接触时,往往会被其复杂的配置项和验证流程困扰。本文将分享我在生产环境中总结出的Patroni配置生成最佳实践和验…

2026/8/10 9:23:07 阅读更多 →
C语言高级语法:内存管理与数据结构实战

C语言高级语法:内存管理与数据结构实战

1. 为什么需要掌握C语言高级语法 在嵌入式开发、操作系统内核、高性能计算等底层领域,C语言依然是无可争议的王者。但很多初学者在掌握了基础语法后,面对实际项目中的内存管理、复杂数据结构时常常手足无措。我曾见过不少开发者因为堆内存使用不当导致的…

2026/8/10 9:22:07 阅读更多 →

最新新闻

从戏剧化设定到可信叙事:如何构建“篡改志愿”故事的人物与情节

从戏剧化设定到可信叙事:如何构建“篡改志愿”故事的人物与情节

1. 先搞清楚这个标题到底在讲什么:一个关于“志愿篡改”的叙事内核 看到这个标题,第一反应可能觉得这是个猎奇故事,或者是个技术教程。但仔细拆解,它的核心其实是一个 高度戏剧化的叙事设定 ,而非一个真实的技术操作…

2026/8/10 10:15:32 阅读更多 →
Unity Linux中文输入法兼容性解决方案:应用层内置输入法实现

Unity Linux中文输入法兼容性解决方案:应用层内置输入法实现

1. 项目概述:当Unity在Linux下“失语” 如果你是一名在Linux环境下使用Unity引擎进行开发的创作者,尤其是需要处理中文文本输入的游戏或应用开发者,那么你很可能已经踩过这个“坑”:在Unity编辑器或运行时构建的程序中&#xff0…

2026/8/10 10:15:32 阅读更多 →
基于SpringBoot+Vue的农业病虫害防治系统开发实践

基于SpringBoot+Vue的农业病虫害防治系统开发实践

1. 项目概述:作物病虫害防治科普系统的技术架构与价值 这个毕业设计选题瞄准了农业信息化领域的实际需求痛点——如何让病虫害防治知识更便捷地触达一线农户。系统采用前后端分离架构,后端基于SpringBoot构建RESTful API服务,前端使用Vue.js实…

2026/8/10 10:15:32 阅读更多 →
工业通讯基础:RS232、RS485、RJ45与Modbus协议核心区别与应用解析

工业通讯基础:RS232、RS485、RJ45与Modbus协议核心区别与应用解析

在工业自动化、智能楼宇、数据采集等项目中,我们经常会遇到 RS485、RS232、RJ45 和 Modbus 这几个名词。对于刚接触的新手来说,它们常常混在一起,分不清谁是谁,甚至误以为它们是同一种东西。比如,有人会问:…

2026/8/10 10:15:32 阅读更多 →
XXE 漏洞深度实战:XML 外部实体攻击与 OOB 带外

XXE 漏洞深度实战:XML 外部实体攻击与 OOB 带外

XML 如今不像十年前那样到处当主数据格式,可它从没真正离开:办公文档、SOAP、SAML、一大堆企业集成、支付回调、日志采集、老 RPC、安全产品自己的规则文件……只要服务端还在“把用户可控的 XML 喂给解析器”,XXE(XML External E…

2026/8/10 10:15:32 阅读更多 →
基于图神经网络(GCN)的环法自行车赛表现预测实战指南

基于图神经网络(GCN)的环法自行车赛表现预测实战指南

最近在分析体育赛事数据时,发现一个有趣的现象:环法自行车赛的顶尖车手,其表现数据(如功率、速度)与车队、赛段、装备等因素之间存在着复杂的、非线性的关联。传统的统计方法难以捕捉这种深层关系,而图神经…

2026/8/10 10:14:31 阅读更多 →

日新闻

GraphQL-CSS API全解析:useGqlCSS、GqlCSS组件与getStyles实用指南

GraphQL-CSS API全解析:useGqlCSS、GqlCSS组件与getStyles实用指南

GraphQL-CSS API全解析:useGqlCSS、GqlCSS组件与getStyles实用指南 【免费下载链接】graphql-css A blazing fast CSS-in-GQL™ library. 项目地址: https://gitcode.com/gh_mirrors/gr/graphql-css GraphQL-CSS是一个基于GraphQL的CSS-in-GQL™库&#xff0…

2026/8/10 0:00:02 阅读更多 →
告别语言障碍:KISS Translator 双语翻译插件终极指南

告别语言障碍:KISS Translator 双语翻译插件终极指南

告别语言障碍:KISS Translator 双语翻译插件终极指南 【免费下载链接】kiss-translator A simple, open source bilingual translation extension & Greasemonkey script (一个简约、开源的 双语对照翻译扩展 & 油猴脚本) 项目地址: https://gitcode.com/…

2026/8/10 0:00:02 阅读更多 →
BepInEx配置管理器:游戏插件配置的终极可视化解决方案

BepInEx配置管理器:游戏插件配置的终极可视化解决方案

BepInEx配置管理器:游戏插件配置的终极可视化解决方案 【免费下载链接】BepInEx.ConfigurationManager Plugin configuration manager for BepInEx 项目地址: https://gitcode.com/gh_mirrors/be/BepInEx.ConfigurationManager 你是否曾经因为游戏插件的复杂…

2026/8/10 0:00:02 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/10 1:05:29 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/10 1:05:29 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/10 1:05:29 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/9 17:05:02 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/10 1:05:29 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/9 17:05:02 阅读更多 →