Linux内核与驱动:12.设备树实例分析

张开发
2026/4/15 4:24:46 15 分钟阅读

分享文章

Linux内核与驱动:12.设备树实例分析
本篇文章介绍一些基础的设备树实例分析包括GPIO中断pinmuxcpu时钟等内容逐一拆建这些设备常见设备树形式做到读懂会改。一、GPIO 相关GPIO 是最基础的一块因为 LED、按键、使能脚、复位脚、方向控制脚这些全都离不开 GPIO。但你要先建立一个正确认识GPIO 在设备树里不是只有“一个属性”而是分成“GPIO 控制器”和“GPIO 使用者”两类内容。1. GPIO 控制器节点是什么设备树里GPIO 控制器节点表示谁来提供 GPIO 资源。比如gpio0: gpiofdd60000 { compatible rockchip,gpio-bank; reg 0x0 0xfdd60000 0x0 0x100; gpio-controller; #gpio-cells 2; };你要把这段看成gpio0这个节点的标签后面别人会用 gpio0 来引用它gpiofdd60000这是一个 GPIO 控制器节点它的寄存器基地址和这个地址有关compatible说明它是什么类型的 GPIO 控制器reg说明它的寄存器地址范围gpio-controller说明这个节点是gpio控制器能给别人提供 GPIO#gpio-cells 2说明别的设备引用它时要跟两个参数2.#gpio-cells到底是什么意思你看到#gpio-cells 2;不要只记“等于 2”而要理解成以后谁来引用这个 GPIO 控制器就必须提供两个参数。例如led-gpios gpio0 5 0;这里gpio0 是被引用的 GPIO 控制器后面 5 0 这两个参数就对应 #gpio-cells 2一般可粗略理解为第一个参数bank 内的引脚号第二个参数标志位比如高低电平有效性、开漏等3. 使用 GPIO 的设备节点是什么这类节点才是你平时最常改的。例如 LED 节点myled { compatible myvendor,myled; led-gpios gpio0 5 0; status okay; };你要会这样理解myled 是一个设备节点它不是 GPIO 控制器它只是“使用 GPIO 的设备”led-gpios 表示这个设备要用一个 GPIOgpio0 表示这个 GPIO 来自 gpio0 控制器5 0 是这个控制器所需的参数所以你以后看到 xxx-gpios第一反应应该是这个设备在向某个 GPIO 控制器申请引脚资源。4. GPIO 在驱动里是怎么用起来的设备树里写了led-gpios gpio0 5 0;驱动在 probe() 里会去读这个属性比如通过of_get_named_gpio()或更推荐的 devm_gpiod_get()然后拿到一个 GPIO 资源句柄接着设置输入/输出方向输出高低电平读取输入电平所以你要理解设备树只是在“声明这个设备要用 GPIO”真正拿到并控制 GPIO 的动作是在驱动里完成的。5. GPIO 最容易混淆的地方最容易混淆的是误区 1以为 GPIO 只是一个引脚号不是。GPIO 在设备树里至少牵涉一个 GPIO 控制器节点一个引用它的设备节点若干参数误区 2以为引用 GPIO 就能直接用也不一定因为这个引脚还可能需要 pinmux 配置正确。这就引出了下一部分。二、pinmux 相关pinmux 是你必须和 GPIO 一起学的。如果只学 GPIO不学 pinmux后面你会经常遇到这种情况驱动里明明拿到 GPIO 了输出高低电平也没报错结果 LED 不亮、按键不响应很可能不是 GPIO 错了而是引脚压根没有被切到 GPIO 功能。1. pinmux 本质是什么pinmux 的本质是同一个物理引脚的功能选择。一个脚可能可以复用成GPIOUART_TX / UART_RXSPII2CPWMSDIO所以你不能只说“这个脚是 GPIO”更准确地说应该是这个脚当前被 pinmux 配成 GPIO 功能。2. 设备树里 pinmux 最常见长什么样设备树里pinmux 最常见会出现在设备节点里myled { compatible myvendor,myled; pinctrl-names default; pinctrl-0 led_pin; led-gpios gpio0 5 0; status okay; };你要把这两行理解透pinctrl-names default;表示这个设备有一个 pinctrl 状态名字叫 defaultpinctrl-0 led_pin;表示编号 0 的这个状态使用 led_pin 这组引脚配置也就是说设备在默认工作状态下会套用 led_pin 这组 pin 配置。3. 为什么有 GPIO 还要有 pinctrl这是最核心的问题。因为led-gpios gpio0 5 0 只说明“逻辑上使用这个 GPIO”pinctrl-0 led_pin 才说明“物理引脚要切到正确功能”你可以这样记GPIO告诉驱动我要用这个引脚做输入/输出pinmux告诉硬件请把这个物理脚切成 GPIO 模式而不是 UART/SPI/I2C 模式所以GPIO 决定“怎么用”pinmux 决定“能不能按这个方式用”。4. pinctrl 节点你要看到什么程度你现在不需要一开始就研究特别复杂的 pinctrl 大节点但你至少要知道设备节点里会通过 pinctrl-0 引用某个 pin 配置那个被引用的 pin 配置通常定义在 pinctrl 控制器下面它的作用就是把某些引脚设成某种复用功能比如 LED 可能是“切成 GPIO 输出”UART 可能是“切成 TX/RX 功能”。5. pinmux 这一块你必须掌握到什么程度你至少要做到理解 pinmux 不是控制电平而是选择引脚功能看懂 pinctrl-names看懂 pinctrl-0知道 default 是一种常见的默认引脚状态知道为什么很多设备节点既有 xxx-gpios 又有 pinctrl-06. pinmux 最容易混淆的地方误区 1把 pinmux 和 GPIO 当成一回事不是。GPIO使用引脚做输入输出pinmux决定引脚当前能不能做 GPIO误区 2以为所有引脚默认就是 GPIO很多引脚默认根本不是 GPIO而是别的外设功能。这也是为什么 pinmux 这一块一定要学。三、中断相关你必须真正掌握什么中断这一块建议你一定要结合“按键驱动”去理解。因为中断不是只背几个 API而是要看懂整条链路。1. 中断在设备树里是什么角色设备树里中断相关本质上是在回答两个问题问题一谁能提供中断这就是中断控制器节点。问题二谁要使用中断这就是设备节点里的interrupts、interrupt-parent。2. 中断控制器节点要怎么看你在某些节点里会看到interrupt-controller; #interrupt-cells 2;这表示这个节点本身可以作为中断控制器别人如果把它当作中断来源来引用就要跟两个参数比如 GPIO bank 很典型它既是 GPIO 控制器也可以作为中断控制器因为按键接到 GPIO 上时GPIO 控制器可以检测边沿/电平变化并上报中断。所以一个节点里同时出现gpio-controller; interrupt-controller;是很正常的。3. 使用中断的设备节点怎么看例如mykey { compatible myvendor,mykey; interrupt-parent gpio0; interrupts 5 IRQ_TYPE_EDGE_FALLING; status okay; };你要会看懂interrupt-parent gpio0;表示这个设备的中断来源是 gpio0interrupts 5 IRQ_TYPE_EDGE_FALLING;表示中断参数比如第 5 号引脚/中断源下降沿触发所以这整段的意思是这个按键设备通过 GPIO0 提供中断能力按某个触发方式触发中断。4. 中断和 GPIO 的关系要怎么理解按键这种场景里GPIO 和中断经常是绑在一起的。比如按键接在一个 GPIO 输入脚上按键按下后引脚电平变化GPIO 控制器检测到变化GPIO 控制器向中断控制器上报CPU 收到中断驱动的中断处理函数执行所以你要把中断链路理解成按键不是直接通知 CPU而是先通过 GPIO 控制器再进入中断链路。5. 中断在驱动里怎么对应设备树里写了interrupt-parentinterrupts驱动在 probe() 里通常会去拿 IRQ 号比如platform_get_irq()或其他 OF/IRQ 相关接口拿到 IRQ 后再request_irq()写中断处理函数也就是说设备树声明“这个设备有中断资源”驱动负责“把这个中断资源申请并用起来”。6. 中断这一块你必须掌握到什么程度你至少要做到看懂谁是中断控制器看懂谁在使用中断看懂 interrupt-parent看懂 interrupts理解为什么 GPIO 节点可能同时是 gpio-controller 和 interrupt-controller能把“按键按下 - GPIO 电平变化 - 中断触发 - 驱动处理函数执行”讲清楚7. 中断最容易混淆的地方误区 1以为interrupts就是中断号本身不总是。它的具体格式依赖 interrupt-parent 对应的中断控制器。误区 2以为设备直接和 CPU 相连很多时候不是。中断路径中间往往还隔着 GPIO 控制器和中断控制器。四、时钟相关1. 先建立一个正确认识时钟不是只有 CPU 才有。你要记住很多外设控制器本身也依赖时钟。比如GPIO 控制器UART 控制器PWMSPII2C定时器这些模块内部有寄存器接口逻辑状态机采样逻辑去抖逻辑波特率/时序逻辑这些很多都要靠时钟工作。2. 设备树里时钟最常见的属性你最需要看懂的是clocksclock-names例如gpio0: gpiofdd60000 { compatible rockchip,gpio-bank; reg 0x0 0xfdd60000 0x0 0x100; clocks pmucru PCLK_GPIO0, pmucru DBCLK_GPIO0; };你要这样理解pmucru表示时钟提供者PCLK_GPIO0表示 GPIO0 模块的 APB/总线接口时钟DBCLK_GPIO0表示 GPIO0 模块的 debounce 或功能参考时钟所以这不是“GPIO 对外输出时钟”而是GPIO 控制器自己需要这些时钟资源。3.clock-names又是干什么的有时候设备树里不仅写clocks ..., ...;还会写clock-names pclk, dbclk;作用就是给多个时钟资源起名字方便驱动按名字去获取。这样驱动里就不会只是“拿第 0 个、第 1 个时钟”而是“拿 pclk、dbclk”。4. 为什么驱动里要先开时钟很多外设控制器如果时钟没开可能会出现寄存器访问不正常模块不响应功能根本不起作用所以驱动在probe()里常常要先获取时钟使能时钟然后再映射寄存器初始化硬件注册中断或字符设备所以你要建立一个意识设备树里的clocks是“设备模块工作资源”的一部分。5. 时钟这一块你必须掌握到什么程度你至少要做到看懂 clocks看懂 clock-names知道谁是时钟提供者知道谁是时钟消费者知道外设为什么也要时钟知道驱动里通常要先使能时钟6. 时钟最容易混淆的地方误区 1以为时钟就是 CPU 主频不是。CPU 时钟只是整个 SoC 时钟树的一部分。误区 2以为 GPIO 节点写时钟很奇怪不奇怪。因为 GPIO 不是一根线而是一个片上控制器模块。五、CPU 相关你现在到底该掌握什么CPU 这部分确实也在设备树里但和前四部分比起来当前优先级没那么高。1. 设备树里 CPU 节点是什么样通常会看到cpus { #address-cells 1; #size-cells 0; cpu0 { device_type cpu; compatible arm,cortex-a55; reg 0; }; };你要知道cpus 是 CPU 节点的容器cpu0 表示第 0 个 CPU 核compatible 表示 CPU 类型reg 通常表示 CPU 的逻辑编号2. CPU 节点在设备树里为什么存在设备树里描述 CPU不是为了你写 LED 驱动用而更多是为了启动阶段识别 CPU多核系统描述CPU 频率、电压、功耗管理SMP 拓扑中断和调度相关配置也就是说CPU 设备树更多偏系统级而不是单个外设驱动级。3. 你当前对 CPU 最该掌握到什么程度你现在只需要做到知道设备树里有 cpus 节点知道 CPU 也有 compatible知道 CPU 也会和时钟、电源、频率等资源关联知道 CPU 是最终执行驱动代码、访问寄存器、响应中断的主体你现在暂时不用深入OPPDVFScpu-map拓扑结构cache / MMU 相关设备树细节这些更偏系统启动和电源管理。4. CPU 这块最容易混淆的地方误区以为 CPU 设备树和外设设备树是一个层级的重要性对内核整体来说都重要。但对你当前的驱动学习来说CPU 设备树只是“知道有这个东西”外设相关才是主战场。

更多文章