嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(1)

嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(1) 前言当设备树遇见驱动代码前面我们聊了设备树的语法和编译原理知道了.dts文件是如何被编译成.dtb然后被内核解析的。但说实话这些只是准备工作。对于驱动开发者来说真正的问题在于我的驱动代码怎么去用这些设备树信息设备树里写着reg 0x020C406C 0x04但这只是个文本描述。驱动程序在运行时需要知道这个地址需要把它映射成虚拟地址然后才能去读写寄存器。中间缺了一个环节 —— 需要有人在运行时去解析设备树把那些 里的数字提取出来塞给C代码。Linux内核提供了这个环节那就是一系列以of_为前缀的API函数。你可以把它们理解为设备树和驱动代码之间的翻译官。但这里有个历史遗留问题可能会困扰你为什么叫OF而不是DTDevice Tree的缩写不是DT 吗这个问题的答案藏在设备树的历史里我们稍后再说。现在先记住一点当你看到of_xxx()这样的函数时它们就是在操作设备树。这一章我们会系统地介绍这些 API看看它们是如何在实际驱动中使用的。我们还会拿LED驱动的代码做例子看看那些在设备树里写的属性是怎么一步步变成驱动里的寄存器地址的。快速回顾设备树的前世今生在深入API之前我们先快速过一遍设备树是怎么走到今天的。这段历史能帮你理解为什么内核里操作设备树的函数都叫of_xxx()以及设备树为什么被设计成现在这个样子。从PowerPC到ARM一场被逼出来的变革设备树最早不是ARM的发明。上世纪90年代IBM和苹果在PowerPC架构上制定了一个叫Open Firmware的固件标准核心思想是让固件向操作系统提供一份完整的硬件描述这样操作系统就不用为每块板子写专门的初始化代码了。设备树就是这个标准里定义的数据结构 —— 用树状层次描述所有设备每个节点包含寄存器地址、中断号、时钟频率等属性。到了2000年代中后期ARM芯片爆发式增长但ARM Linux处理硬件描述的方式极其原始直接硬编码在C代码里。内核源码树里塞满了arch/arm/mach-xxx和arch/arm/plat-xxx目录每个对应一种板子重复率高达90%以上。维护成本高得离谱代码膨胀到arch/arm的代码量比其它所有架构加起来还多。2011 年Linus Torvalds终于爆发了This whole ARM thing is a f*cking pain in the ass.他明确拒绝继续合并这些垃圾代码。ARM社区被迫改革引入了PowerPC上已经成熟的设备树机制经历了一个从可选到强制的演进过程。到了2013年左右新的ARM板级代码几乎都使用了设备树ARM64更是从设计之初就强制要求设备树不支持传统的板级C代码。如今设备树已成为Linux嵌入式领域描述硬件的通用机制覆盖ARM、ARM64、RISC-V、PowerPC、MIPS等多个架构。OF命名的由来因为设备树起源于Open Firmware标准内核里操作设备树的函数就都叫of_xxx()。后来ARM社区引入设备树时为了复用已有的基础设施也沿用了这个命名前缀。所以今天我们在ARM Linux 里看到的设备树API依然叫OF API而不是DT API。你可以把它理解为一种历史遗产——就像 C语言的printf而不是print。那么OF和设备树是什么关系呢设备树是数据结构OF API是操作这个数据结构的一套函数。就像C语言里有struct和操作struct的函数一样设备树是“数据”OF API是“操作数据的工具”。在Linux内核的源码里你会看到这样的头文件include/linux/of.h核心OF API定义。include/linux/of_address.h地址映射相关函数。include/linux/of_gpio.hGPIO相关函数。include/linux/of_irq.h中断相关函数。这些文件里定义的所有函数都是我们接下来要讲的内容。