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/10/14 17:12:34 阅读更多 →
Patroni配置生成与验证实战指南

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

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

2026/10/14 17:12:34 阅读更多 →
C语言高级语法:内存管理与数据结构实战

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

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

2026/9/23 20:59:53 阅读更多 →

最新新闻

Zotero MCP 语义搜索实现原理:从智能文本分块到 Int8 量化向量检索

Zotero MCP 语义搜索实现原理:从智能文本分块到 Int8 量化向量检索

【免费下载链接】zotero-mcp Its a plugin extension in Zotero. Zotero MCP Plugin enables integration between AI assistants and Zotero through MCP. Zotero MCP Plugin 是一个 Zotero 插件,通过 MCP协议实现 AI 助手与 Zotero深度集成。插件支持文献检索、元…

2026/10/14 17:12:37 阅读更多 →
Velune 构建与发布指南:从源码编译到 F-Droid、Obtainium、IzzyOnDroid 分发

Velune 构建与发布指南:从源码编译到 F-Droid、Obtainium、IzzyOnDroid 分发

【免费下载链接】Velune A premium ad-free YouTube Music client for Android. Built with Kotlin & Jetpack Compose. 项目地址: https://gitcode.com/gh_mirrors/ve/Velune 点击查看 免费下载 Velune 是一款用 Kotlin 与 Jetpack Compose 打造的高端无广告 …

2026/10/14 17:12:37 阅读更多 →
具身智能落地真相:实时音视频与VLA工程实践

具身智能落地真相:实时音视频与VLA工程实践

具身智能这两年从学术圈一路烧到产业圈,几乎每隔几周就有新的demo刷屏:机器人叠衣服、冲咖啡、分拣零件、在工厂里来回穿梭。看多了这些视频,很容易产生一种错觉——好像只要把人形机器人的硬件做得足够像人,再塞一个大模型进去&a…

2026/10/14 17:12:37 阅读更多 →
视频预训练中丢掉95% token反而更准:原理、实现与调参实录

视频预训练中丢掉95% token反而更准:原理、实现与调参实录

1. 视频预训练里那个反直觉的发现第一次看到“丢掉 95% 的 token,视频预训练反而更准”这个结论时,我的反应和大多数人一样:这不科学。视频理解任务里,帧与帧之间的时序信息、空间上的细节纹理,不都是靠密密麻麻的 tok…

2026/10/14 17:12:37 阅读更多 →
AI 深度伪造治理加码:短视频平台上线人脸声音仿冒一键举报功能

AI 深度伪造治理加码:短视频平台上线人脸声音仿冒一键举报功能

现在 AI 深度合成工具操作起来简单很多,AI 换脸、盗用别人声音造成侵权的事情也越来越多。不少做内容的创作者还有普通网友,都会碰到自己照片或者声音被别人拿去合成视频的情况。以前想要维权的时候,准备证据的整个流程比较麻烦,很…

2026/10/14 17:12:37 阅读更多 →
如何安全的提出新方案

如何安全的提出新方案

2026/10/14 17:11:36 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →