马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
Linux设备树原理与应用详解
一、设备树概述
1.1 什么是设备树
设备树(Device Tree,简称DT)是一种形貌硬件资源的数据结构,它通过一种树状结构来形貌系统硬件设置,包罗CPU、内存、总线、外设等硬件信息。设备树最初在PowerPC架构中利用,厥后被ARM等架构广泛接纳,成为Linux内核中形貌非可发现硬件(non-discoverable hardware)的标准方式。
1.2 设备树的发展历史
设备树的概念并非Linux首创,它劈头于Open Firmware标准(IEEE 1275),重要用于PowerPC和SPARC架构。随着ARM架构在嵌入式范畴的普及,Linux社区面临着大量ARM芯片和板级的支持需求。传统的"board file"方式导致内核中充斥着大量板级特定代码,维护困难。
2006年,设备树被引入到ARM架构的Linux支持中,并逐渐成为ARM Linux的标准硬件形貌方式。设备树的接纳解决了以下问题:
- 减少了内核中板级特定代码的数量
- 提高了内核的通用性
- 使单个内核镜像能够支持多种硬件平台
- 简化了硬件形貌的维护工作
1.3 设备树的优势
与传统硬编码硬件信息的方式相比,设备树具有以下优势:
- 硬件形貌与内核分离:硬件设置信息不再硬编码在内核中,而是通过外部文件形貌
- 可移植性增强:同一内核可以支持不同硬件,只需加载不同的设备树文件
- 可维护性提高:硬件变动只需修改设备树文件,无需重新编译内核
- 可读性好:设备树源文件(dts)接纳文本格式,易于理解和修改
- 层次化结构:可以复用公共部分,减少冗余形貌
二、设备树原理
2.1 设备树的基本结构
设备树接纳树状结构形貌硬件,重要包罗以下组成部分:
- 节点(Node):设备树的基本构建块,表示一个设备或总线
- 属性(Property):节点的特征形貌,是键值对的形式
- 值(Value):属性的具体内容,可以是字符串、数字、数组或phandle等
一个简单的设备树示例:
- /dts-v1/;
- / {
- model = "My Board";
- compatible = "myvendor,myboard";
-
- cpus {
- #address-cells = <1>;
- #size-cells = <0>;
-
- cpu@0 {
- compatible = "arm,cortex-a9";
- reg = <0>;
- };
- };
-
- memory@80000000 {
- device_type = "memory";
- reg = <0x80000000 0x10000000>;
- };
-
- serial@101f0000 {
- compatible = "arm,pl011";
- reg = <0x101f0000 0x1000>;
- interrupts = <0 12 4>;
- };
- };
复制代码 2.2 设备树源文件格式
设备树源文件重要有两种格式:
- .dts (Device Tree Source):设备树源文件,人类可读的文本格式
- .dtsi (Device Tree Source Include):设备树包罗文件,类似于C语言的头文件
这些源文件必要通过设备树编译器(DTC)编译成二进制格式:
- .dtb (Device Tree Blob):编译后的设备树二进制文件,由Bootloader加载并传递给内核
2.3 设备树编译流程
设备树的编译流程如下:
- 编写.dts和.dtsi文件
- 利用DTC编译器将.dts文件编译为.dtb文件
- Bootloader将.dtb文件和内核镜像一起加载到内存
- 内核启动时解析设备树,初始化形貌的硬件
编译下令示例:
- dtc -I dts -O dtb -o myboard.dtb myboard.dts
复制代码 2.4 设备树绑定(Bindings)
设备树绑定是形貌特定设备节点应包罗哪些属性和值的规范文档。这些绑定文档通常位于内核源码的Documentation/devicetree/bindings目录下,为设备树编写者提供指导。
绑定文档通常包罗:
- 设备兼容性字符串(compatible)
- 必需的属性
- 可选的属性
- 子节点的要求
- 示例
三、设备树语法详解
3.1 设备树节点
设备树节点是形貌硬件的基本单位,语法如下:
- [label:] node-name[@unit-address] {
- [properties]
- [child-nodes]
- };
复制代码
- label:可选,节点的标签,用于在其他地方引用
- node-name:节点名称,通常表示设备类型
- unit-address:可选,设备的地址,通常与reg属性中的第一个地址相同
3.2 常用属性
- compatible:最重要的属性之一,用于匹配驱动程序
- compatible = "manufacturer,model", "generic-model";
复制代码 - reg:形貌设备寄存器或内存区域的地址和大小
- reg = <address1 length1 [address2 length2] ... >;
复制代码 - #address-cells和#size-cells:形貌子节点reg属性的地址和大小字段的单元格数量
- #address-cells = <1>; // 地址用1个32位数表示
- #size-cells = <1>; // 大小用1个32位数表示
复制代码 - interrupts:形貌设备的中断号
- interrupts = <IRQ_NUM TRIGGER_TYPE>;
复制代码 - status:形貌设备状态
- status = "okay"; // 或 "disabled", "fail", "fail-sss"
复制代码 3.3 特殊节点
- 根节点:设备树的起点,用/表示
- aliases节点:为节点提供符号链接
- aliases {
- serial0 = &uart0;
- };
复制代码 - chosen节点:形貌运行时参数,如bootargs
- chosen {
- bootargs = "console=ttyS0,115200";
- };
复制代码 3.4 设备树包罗机制
类似于C语言的#include,设备树利用/include/或#include指令包罗其他文件:
或者
四、设备树在Linux中的应用
4.1 内核如何解析设备树
Linux内核在启动过程中解析设备树的流程:
- Bootloader将设备树二进制(.dtb)加载到内存,并将指针传递给内核
- 内核初始化阶段,OF(Open Firmware)子系统开始解析设备树
- 内核将设备树转换为设备节点的链表结构
- 驱动程序通过匹配compatible属性与设备节点绑定
- 驱动程序从设备节点中获取硬件设置信息
4.2 设备树与驱动程序的匹配
驱动程序通过of_match_table声明支持的设备树兼容性字符串:
- static const struct of_device_id my_driver_ids[] = {
- { .compatible = "vendor,device" },
- { /* sentinel */ }
- };
- MODULE_DEVICE_TABLE(of, my_driver_ids);
- static struct platform_driver my_driver = {
- .probe = my_probe,
- .driver = {
- .name = "my-device",
- .of_match_table = my_driver_ids,
- },
- };
复制代码 当设备树节点的compatible属性与驱动程序的of_match_table中的条目匹配时,内核会调用驱动程序的probe函数。
4.3 从设备树获取硬件信息
驱动程序可以从设备树节点中获取各种硬件信息:
- 获取寄存器地址和大小:
- struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- void __iomem *base = devm_ioremap_resource(&pdev->dev, res);
复制代码 - 获取中断号:
- int irq = platform_get_irq(pdev, 0);
复制代码 - 获取属性值:
- u32 value;
- of_property_read_u32(np, "property-name", &value);
复制代码 - 获取GPIO:
- int gpio = of_get_named_gpio(np, "gpio-name", 0);
复制代码 4.4 设备树覆盖(Overlay)
设备树覆盖是一种动态修改设备树的技能,重要用于支持运行时硬件设置变化,常见于嵌入式Linux系统:
- 创建覆盖文件(.dtbo)
- 在运行时加载覆盖:
- echo overlay.dtbo > /sys/kernel/config/device-tree/overlays/0/path
复制代码 - 卸载覆盖:
- echo 0 > /sys/kernel/config/device-tree/overlays/0/status
复制代码 五、设备树调试本领
5.1 检察设备树
- 检察/proc/device-tree:
- 利用dtc反编译:
- dtc -I fs /proc/device-tree
复制代码 5.2 调试工具
- dtc:设备树编译器,可用于反编译和验证
- dtc -I dtb -O dts -o myboard.dts myboard.dtb
复制代码 - fdtdump:显示设备树二进制文件内容
- ofdump:内核工具,显示设备树信息
5.3 常见问题排查
- 设备未初始化:查抄status属性是否为"okay"
- 驱动未加载:查抄compatible属性是否匹配
- 资源辩说:查抄reg、interrupts等属性是否精确
- 语法错误:利用dtc验证设备树源文件
六、设备树实践示例
6.1 添加一个GPIO设备
设备树形貌:
- gpio_keys {
- compatible = "gpio-keys";
- button {
- label = "User Button";
- gpios = <&gpio0 23 GPIO_ACTIVE_LOW>;
- linux,code = <KEY_ENTER>;
- };
- };
复制代码 驱动程序:
- static int gpio_keys_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct gpio_keys_button *button;
-
- button = devm_kzalloc(dev, sizeof(*button), GFP_KERNEL);
- if (!button)
- return -ENOMEM;
-
- button->gpio = of_get_named_gpio(dev->of_node, "gpios", 0);
- button->code = KEY_ENTER;
-
- // 注册输入设备...
- return 0;
- }
复制代码 6.2 添加一个I2C设备
设备树形貌:
- &i2c1 {
- status = "okay";
- clock-frequency = <100000>;
-
- temperature-sensor@48 {
- compatible = "ti,tmp75";
- reg = <0x48>;
- };
- };
复制代码 驱动程序:
- static int tmp75_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
- {
- struct device *dev = &client->dev;
- // 初始化温度传感器...
- return 0;
- }
- static const struct of_device_id tmp75_of_match[] = {
- { .compatible = "ti,tmp75" },
- { }
- };
- MODULE_DEVICE_TABLE(of, tmp75_of_match);
复制代码 七、设备树最佳实践
- 尽量复用:将公共部分提取到.dtsi文件中
- 遵循绑定:严格按照内核文档中的绑定规范编写
- 合理命名:节点和属性命名要清晰、一致
- 充分注释:复杂部分添加详细注释
- 验证修改:每次修改后都要验证功能
- 版本控制:将设备树文件纳入版本控制系统
八、总结
设备树作为现代Linux系统中形貌硬件设置的标准方式,已经广泛应用于ARM、PowerPC等架构。它通过将硬件形貌与内核分离,提高了系统的可移植性和可维护性。把握设备树的原理和应用,对于嵌入式Linux开辟者和内核驱动开辟者来说是一项必备技能。
随着Linux内核和设备树标准的不断发展,设备树的功能也在不断增强,如动态设备树覆盖、设备树单元测试等新特性的加入,使得设备树能够更好地满意复杂嵌入式系统的需求。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |