一、UEFI与设备树概述

1.1 UEFI规范简介

UEFI,全称是统一可扩展固件接口。说白了,它就是操作系统和硬件之间的一个「翻译官」。我刚开始接触UEFI时,总觉得它就是个花哨的BIOS替代品。后来踩过几次坑才明白——这玩意儿可比传统BIOS强大太多了。

UEFI规范定义了一套标准的接口。这套接口让操作系统在启动时,能知道硬件长什么样、该怎么用。举个例子,你插了块网卡,UEFI会告诉系统:「嘿,这里有个网卡,它的PCIe地址是xx,中断号是yy。」

我个人习惯把UEFI理解成一个微型操作系统。它有自己的驱动模型、内存管理、甚至网络协议栈。嗯,这里要注意——UEFI和BIOS最大的区别在于:BIOS是16位实模式,而UEFI是32位或64位保护模式。这意味着UEFI能直接访问大内存,跑复杂的驱动。

UEFI规范里几个核心概念,我列一下:

  • Boot Services:启动阶段的服务,比如内存分配、事件处理。系统启动后这些服务就没了。
  • Runtime Services:运行时服务,操作系统启动后还能调用。比如读写UEFI变量。
  • Protocols:协议接口,UEFI驱动通过协议暴露功能。比如Block IO协议用来读写磁盘。
  • Handles:句柄,代表一个设备或一个协议实例。

为什么UEFI这么重要?因为现在几乎所有x86服务器、ARM开发板、RISC-V设备都在用。你想想看,没有UEFI,操作系统就得自己写一堆硬件探测代码,那得多痛苦。

1.2 设备树的起源与作用

设备树(Device Tree),这名字听起来挺玄乎。其实它就是一份描述硬件信息的「清单」。我最早接触设备树是在做ARM Linux移植的时候,那时候被它折腾得够呛。

设备树的起源很有意思。早期Linux内核里,每个ARM板子都有一堆硬编码的C文件。比如mach-xxx.c、board-xxx.c,里面全是寄存器地址、中断号、GPIO映射。每次换个板子,就得改内核代码。这谁受得了?

于是就有了设备树。它把硬件描述从内核代码里剥离出来,变成一份独立的数据结构。内核启动时解析这个结构,就知道硬件长什么样了。说白了,设备树就是硬件的「身份证」。

设备树的核心文件是.dts(Device Tree Source)和.dtb(Device Tree Blob)。.dts是人可读的文本文件,.dtb是编译后的二进制文件。UEFI或bootloader把.dtb传给内核,内核照着它初始化硬件。

设备树里几个关键概念:

  • 节点(Node):每个硬件设备对应一个节点。比如CPU、内存、I2C控制器。
  • 属性(Property):节点的具体参数。比如reg属性描述寄存器地址范围,interrupts属性描述中断号。
  • 兼容性(Compatible):告诉内核这个设备用哪个驱动。比如"ti,am335x-uart"表示TI的AM335x UART。
  • 地址映射:通过ranges属性描述总线地址到父总线地址的映射关系。

举个例子,一个简单的UART设备节点长这样:

uart0: serial@44e09000 {
    compatible = "ti,am335x-uart", "ns16550";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
    clock-frequency = <48000000>;
    status = "okay";
};

这段代码告诉内核:在地址0x44e09000有个UART,寄存器范围0x2000字节,中断号72,时钟频率48MHz。内核看到这个节点,就知道怎么初始化这个串口了。

我曾经在一个项目里遇到过设备树写错地址的情况。板子死活起不来,串口没输出。查了两天才发现是reg属性里的地址少了个0。从那以后,我每次写设备树都会反复核对datasheet。

1.3 UEFI下设备树的意义

好,问题来了——UEFI已经有了ACPI(高级配置与电源管理接口),为什么还要用设备树?

这得从平台类型说起。x86平台用ACPI,ARM平台用设备树,这是历史原因。但UEFI规范本身是平台无关的。它既支持ACPI,也支持设备树。我见过不少ARM服务器,UEFI启动时传递的就是设备树。

UEFI下设备树的意义,说白了就是「标准化硬件描述」。UEFI负责探测硬件、生成设备树,然后传给操作系统。这样操作系统就不用自己探测了,省事。

具体来说,UEFI做这几件事:

  • 硬件枚举:UEFI驱动扫描PCIe总线、I2C总线、SPI总线,发现所有设备。
  • 设备树生成:根据枚举结果,动态生成或修改设备树节点。比如给每个PCIe设备添加一个节点。
  • 内存映射:把物理内存信息写入设备树的memory节点。
  • 时钟和电源管理:设置时钟频率、电源状态,写入设备树。

这里有个关键点:UEFI生成的设备树,和传统bootloader(比如U-Boot)生成的设备树,本质上是一样的。区别在于UEFI有更丰富的驱动库,能处理更复杂的硬件拓扑。

我举个例子。假设你有一块ARM开发板,上面有个PCIe插槽。传统做法是bootloader固定写死设备树,PCIe设备的信息写不进去。但UEFI可以在启动时扫描PCIe总线,发现插了什么卡,然后动态添加设备树节点。这样操作系统启动后,直接就能用这个PCIe设备。

核心要点:UEFI下的设备树,是UEFI固件和操作系统之间的「硬件契约」。UEFI负责描述硬件,操作系统负责使用硬件。两者通过设备树这个标准接口解耦。

嗯,这里要注意一个坑。UEFI生成的设备树,和内核里预置的设备树,可能会冲突。我曾经遇到过一个情况:UEFI给某个I2C设备生成了一个节点,但内核里也有一个同名节点。结果内核加载了两次驱动,设备直接挂了。解决方案是让UEFI在生成设备树时,先检查内核里有没有同名节点,有的话就跳过。

个人经验:做UEFI设备树适配时,建议先在UEFI Shell里用devicetree dump命令查看生成的设备树。确认节点、属性都正确后,再启动操作系统。这一步能省下大量调试时间。

最后,我用一张图总结UEFI与设备树的关系:

UEFI固件 硬件探测 驱动加载 生成 设备树 节点、属性 内存映射 中断、时钟 传递 操作系统 解析设备树 硬件平台 CPU 内存 PCIe设备 I2C/SPI外设 探测 描述

这张图展示了UEFI、设备树、操作系统和硬件之间的关系。UEFI固件探测硬件,生成设备树,然后传递给操作系统。操作系统解析设备树,就知道怎么驱动硬件了。整个过程环环相扣,缺一不可。

注意事项:设备树不是万能的。它只描述静态硬件信息,不描述动态行为。比如热插拔设备,设备树就搞不定。这种情况需要ACPI或UEFI的运行时协议来配合。

好了,第一章就讲到这里。设备树这东西,初看觉得简单,深入才发现水很深。后面我们会一步步拆解UEFI下设备树的适配细节,包括怎么生成、怎么调试、怎么和操作系统对接。到时候我会分享更多实战中踩过的坑和总结的经验。

可可精品资料库