嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(1)
前言:当设备树遇见驱动代码
前面我们聊了设备树的语法和编译原理,知道了.dts文件是如何被编译成.dtb然后被内核解析的。但说实话,这些只是"准备工作"。对于驱动开发者来说,真正的问题在于:我的驱动代码怎么去用这些设备树信息?
设备树里写着reg = <0x020C406C 0x04>,但这只是个文本描述。驱动程序在运行时需要知道这个地址,需要把它映射成虚拟地址,然后才能去读写寄存器。中间缺了一个环节 —— 需要有人在运行时去解析设备树,把那些< >里的数字提取出来,塞给C代码。
Linux内核提供了这个环节,那就是一系列以of_为前缀的API函数。你可以把它们理解为设备树和驱动代码之间的"翻译官"。
但这里有个历史遗留问题可能会困扰你:为什么叫"OF"而不是"DT"?Device 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.h
GPIO相关函数。
include/linux/of_irq.h
中断相关函数。
这些文件里定义的所有函数,都是我们接下来要讲的内容。
