Appearance
我看我列张设备清单就挺好,怎么到你Linux还得长成一棵树?
一张设备清单为什么不够
你要是拿表格盘过设备的家底,这个念头我太懂了。一行一个设备,名字、地址、时钟都写在行里,多直白。咱们上一节刚说完,硬件描述要独立成数据,那最省事的想象可不就是一张清单?
行不行不靠感觉,咱们当场拿一个带连接的例子试。板上有颗温度传感器,挂在本板的 I²C 总线上,I²C 控制器做在 SoC 里。咱们按清单的写法来:传感器一行,控制器一行,SoC 一行。三行写完,传感器挂在那条 I²C 上,清单没处放这句话,你得自己记着。设备树把这层关系直接放进结构:传感器节点写在 I²C 控制器节点里面,你顺着往上一层,就知道它挂的是哪条总线。
咱们用一小段教学片段看这个形状。设备和名字都是虚构的,你只看层次,别究语法:
dts
/* 教学片段:虚构设备,只看层次 */
/ {
soc {
serial@1000 { /* 串口控制器 */ };
i2c-controller {
temp-sensor { /* 挂在这条总线上的传感器 */ };
};
};
chosen { /* 不是设备,下面细说 */ };
};这棵树的规则咱们分三层说清。根节点 / 是整棵树的入口,所有节点从它展开。节点表达一个设备、一个硬件功能,或者一份相关描述。属性是节点上“名字 = 值”的记录,给节点存信息。父子关系经常对应总线或组织关系,清单没处放的那层连接,树直接画在结构里。
树上不全是设备
你数一数上面的片段:四个节点里,只有一个半像“设备”。咱们把两类特殊的挑出来讲。
soc 这类节点是容器,把芯片里的控制器归拢在一起,它自己不是设备。chosen 更是个好反例:规范明确说它不代表系统里的真实设备,只记录固件在启动时选择或指定的参数,比如内核命令行。所以“每个节点都是一颗芯片”这话,咱们不能说满。多数节点描述设备或功能,少数是容器和信息节点。
还有一条边界:父子关系表达的连线,并不覆盖全部硬件关系。时钟、电源这类跨节点的依赖,设备树用节点之间的引用来表达,意思是“这个设备用谁的时钟”。引用的写法留给后续章节,你现在记住“树上有父子以外的连接表达”就够了。
四方怎么接上:硬件、设备树、binding、驱动
描述有了,约定谁定、活谁干?咱们把四位角色摆到一张桌上。
| 角色 | 交付什么 | 一句话定位 |
|---|---|---|
| 实际硬件 | 真实能力 | 焊在板上的事实 |
| 设备树 | 事实的描述 | 一份独立数据 |
| binding | 描述的约定 | 规定这类硬件该怎么写 |
| 驱动 | 操作的实现 | 内核里的代码 |
binding 最容易被误会,咱们多说两句。它是“某类硬件应该怎样被描述”的约定,写成结构化文档,还配了校验工具,属性不合规会被查出来。它不是另一份驱动,也不是哪个驱动的私有配置。顺带分清一个英语撞名:设备树这边说的 binding 指描述约定,内核文档里另有一个“driver binding”,指设备和驱动建立关联那件事,两码事。
四方里最忙的接头是 compatible。规范对它的定义是:一个或多个字符串,刻画这个设备的具体编程模型。换句话说,它声明“我跟哪种硬件操作方式兼容”。内核拿声明去匹配支持它的驱动,匹配上,驱动再来查资源、做初始化。咱们把三件事分开说:它不是节点的名字,serial@1000 才是,它也不是驱动源码的文件名,更不能靠乱填一个现成值把不兼容的硬件变成兼容的。兼容是硬件的事实,声明只是把它说出来。
咱们看一个普通设备从描述到跑起来的概念顺序:内核读到描述,按平台和总线规则建立相应的设备,匹配支持它的驱动,驱动检查资源并初始化。这是常见路径,不是处处如此。内存、CPU、早期控制台各有各的路,匹配成功也只代表“认了亲”,初始化成不成功还要看资源对不对。
描述换来了什么,换不来什么
咱们回到 0.1 的板 A 和板 B,盘点独立描述换来的收益:在内核已有支持的前提下,换一块板常常只是换一份描述,不必重编内核。SoC 级的公共信息可以做成共享片段,各板的描述只补差异。共享片段叫 .dtsi,0.3 末尾跟你正式介绍。硬件事实集中放,查起来改起来都清爽。
三句“不行”咱们也要讲明白,每句都回到四方的职责。硬件在、描述也对,内核就是没有这个驱动:描述变不出缺失的实现,数据填不了代码的缺口。驱动在,描述把资源写错了:初始化可能失败,或者带病工作。节点写了 status = "okay",设备压根没焊:数据改不了电路,纸面存在变不成物理存在。
所以准确的说法是:在内核已支持的前提下,不少硬件描述的调整不必重编内核,但改完描述要重新生成 DTB,并让启动流程用上新产物。描述产物会经过启动阶段的软件再到 Linux,U-Boot 这类引导器可能读取、修改、传递它,细节是 0.4 的主场,咱们在这里只留一条概念线:描述产物交给启动阶段的软件,再由它交给 Linux。咱们也不把 U-Boot 说成所有板子的必经之路。
描述的形状和生态咱们都过了一遍。接下来 0.3,咱们去亲眼看描述的源码长什么样,亲手把它编译成产物。
本节自检
合上文章,你拿四个小问题验一验自己。
- 温度传感器挂在一棵 I²C 控制器下,设备树用怎样的结构表达这层连接?为什么平铺清单做不到?
/chosen为什么是“每个节点都是设备”的反例?- 设备树、binding、驱动、硬件各交付什么?
compatible在谁和谁之间当接头? - 给一块没焊接该设备的板写
status = "okay"的节点,能让它工作吗?内核缺少驱动时,改compatible能补救吗?
参考答案(想好再展开)
- 传感器写成 I²C 控制器的子节点,父子关系承载“挂在哪条总线”。清单丢连接,树带着结构。
- 它不代表真实设备,只记录固件在启动时选择或指定的参数。
- 硬件提供能力,设备树描述事实,binding 约定描述的写法,驱动实现操作。compatible 连着硬件事实与驱动匹配。
- 不能。数据改变不了电路。改 compatible 也没用:兼容是硬件事实,乱填值不会变出不存在的实现。
延伸阅读
咱们引的规范条款出处如下,真实串口的例子也给你备好:
- Devicetree Specification v0.4:
/chosen的说法在 §3.6,compatible的编程模型定义在 §2.3.1,本仓有缓存副本。 - 写 binding 的守则:内核官方讲描述约定应该以硬件为依据。
- 8250 串口 binding(v6.12):一份真实串口的描述约定,看属性约束长什么样。
- 8250_of.c 驱动(v6.12):真的会从设备树读资源并初始化串口的驱动源码,眼见为实。