正点原子2026开发板教程——从0开始配置Linux内核(5)——设备树在内核中的使用
正点原子2026开发板教程——从0开始配置Linux内核5——设备树在内核中的使用教程已经在Github上开源https://github.com/Awesome-Embedded-Learning-Studio/imx-forge欢迎尝试和围观为什么要谈内核中的设备树上一章我们讲了内核模块解决了代码如何进内核的问题。但还有一个更根本的问题内核怎么知道你的板子上有哪些硬件这个问题在设备树出现之前是通过硬编码解决的——每块板子都要写专门的板级初始化代码把硬件信息写死在 C 代码里。那种做法的痛苦程度我们在 U-Boot 教程里已经吐槽过了。有了设备树之后硬件描述从代码中分离出来内核只需要读懂设备树就知道该初始化哪些硬件、怎么初始化。但这个过程对驱动开发者来说是隐式的——内核默默完成了设备树的解析、设备创建、驱动匹配你只需要写好驱动代码在compatible字段里填上正确的值一切就能工作。但隐式不代表不需要理解。实际开发中你一定会遇到这些问题我的驱动为什么不加载设备树配置对了吗怎么调试设备树相关问题compatible 字段到底怎么匹配所以这一章我们深入内核的设备树处理机制。你会看到 DTB 是怎么被解析的、设备和驱动是怎么匹配的、常见的调试方法有哪些。理解了这些你写驱动的时候就能有的放矢而不是像以前那样——改改设备树编译烧录启动看运气。设备树在内核中的角色内核启动流程中的设备树Linux 内核启动时设备树的解析是一个关键环节。来看一下简化版的启动流程1. BootROM 运行 2. U-Boot 加载并运行 3. U-Boot 加载 DTB 和内核镜像到内存 4. U-Boot 跳转到内核入口传递 DTB 地址通过 r2 寄存器 5. 内核解压如果是压缩的 zImage 6. 内核初始化早期setup_arch() └── unflatten_device_tree()解析 DTB构建设备树数据结构 7. 内核初始化中期arch_init_sync() └── of_platform_populate()根据设备树创建 platform 设备 8. 内核初始化后期device_initcall └── 驱动初始化尝试匹配已注册的设备和驱动 9. 内核启动完成用户空间启动init 进程第 6 步的unflatten_device_tree()是关键。DTBDevice Tree Blob是一种紧凑的二进制格式不适合内核运行时频繁访问。所以内核会在启动早期把它展开unflatten转换成更容易访问的链表/树结构。第 7 步的of_platform_populate()负责创建设备。对于设备树中的每个节点如果它有compatible属性且不是特殊节点如chosen、aliases内核会创建对应的struct device并注册到相应的总线通常是 platform 总线。第 8 步是驱动匹配。当驱动模块加载时或静态编入内核初始化时驱动的probe函数会被调用如果匹配成功。设备树相关的内核数据结构内核用几个关键数据结构来表示设备树struct device_node表示设备树节点structdevice_node{constchar*name;// 节点名称 之前的部分constchar*type;// 设备类型device_type 属性phandle phandle;// 节点的 phandle用于引用constchar*full_name;// 完整路径名structfwnode_handlefwnode;// 固件节点接口structproperty*properties;// 属性链表structproperty*deadprops;// 已删除的属性structdevice_node*parent;// 父节点structdevice_node*child;// 子节点structdevice_node*sibling;// 兄弟节点structkobjectkobj;// sysfs 表示// ...};struct property表示设备树属性structproperty{char*name;// 属性名intlength;// 属性值长度void*value;// 属性值structproperty*next;// 下一个属性// ...};这些结构在内核运行时可以通过/sys/firmware/devicetree/base看到# 查看根节点ls-la/sys/firmware/devicetree/base/# 查看某个节点的属性ls-la/sys/firmware/devicetree/base/soc/aips-bus02000000/spba-bus02000000/uart02020000/# 查看某个属性的内容cat/sys/firmware/devicetree/base/soc/aips-bus02000000/spba-bus02000000/uart02020000/compatible输出类似fsl,imx6ull-uart\x00fsl,imx6q-uart注意\x00是 null 分隔符表示compatible属性有多个值。DTB 解析过程DTB 传递给内核U-Boot 启动内核时需要把 DTB 地址告诉内核。对于 ARM这是通过寄存器传递的r0CPU IDr1机器 ID设备树时代已经不用设为 0r2DTB 物理地址或 ATAG 列表地址在 U-Boot 中这是bootm命令自动处理的。如果你手动启动可以用bootz ${kernel_addr_r} - ${fdt_addr_r}-表示没有 initramfs${fdt_addr_r}是 DTB 地址。unflatten_device_tree()从 DTB 到 device_node内核的unflatten_device_tree()函数在drivers/of/fdt.c负责把 DTB 转换成device_node树验证 DTB 格式检查魔数0xd00dfeed、版本、大小扫描 DTB遍历所有节点和属性创建 device_node为每个节点创建struct device_node创建 property为每个属性创建struct property建立关系设置 parent/child/sibling 指针完成后你可以通过of_allnodes全局变量访问根节点。设备树调试接口内核提供了几个设备树调试接口/sys/firmware/devicetree/sysfs 接口只读# 浏览设备树cd/sys/firmware/devicetree/base/find.-namecompatible|head-20# 查看某个属性cat/proc/device-tree/modelcat/proc/device-tree/compatible/proc/device-tree是/sys/firmware/devicetree/base的符号链接。/sys/kernel/debug/debugfs调试接口# 挂载 debugfs如果没挂载sudomount-tdebugfs none /sys/kernel/debug/# 查看设备树ls/sys/kernel/debug/device-tree/# 查看某个节点的属性ls/sys/kernel/debug/device-tree/soc//lib/firmware/设备树文件存放有些系统会把编译好的 DTB 文件放在这里。设备和驱动匹配机制platform 总线和驱动匹配设备树中的大部分设备都注册为platform_device对应的驱动是platform_driver。匹配过程由内核的 driver core 负责。匹配条件优先级从高到低设备树的compatible属性vs 驱动的of_match_table设备名称vs 驱动名称ACPI 匹配x86 系统嵌入式不常见对于设备树关键是compatible属性uart1: serial02020000 { compatible fsl,imx6ull-uart, fsl,imx6q-uart; reg 0x02020000 0x4000; interrupts 26 2 0; // ... };对应的驱动代码staticconststructof_device_idimx_uart_dt_ids[]{{.compatiblefsl,imx6ull-uart,.dataimx6ull_uart_data},{.compatiblefsl,imx6q-uart,.dataimx6q_uart_data},{/* sentinel */}};MODULE_DEVICE_TABLE(of,imx_uart_dt_ids);staticstructplatform_driverimx_uart_driver{.driver{.nameimx-uart,.of_match_tableimx_uart_dt_ids,},.probeimx_uart_probe,.removeimx_uart_remove,};MODULE_DEVICE_TABLE 宏这个宏很重要它做什么在模块中创建一个特殊的 section.modinfomodinfo可以读取这个信息模块加载时udev 可以根据这个信息自动加载模块# 查看模块的设备表modinfo imx_uart# 输出类似# alias: of:N*T*Cfsl,imx6ull-uart*# alias: of:N*T*Cfsl,imx6q-uart*这些 alias 规则告诉 udev当设备树中出现compatible fsl,imx6ull-uart的设备时自动加载这个模块。踩坑提醒忘记MODULE_DEVICE_TABLE是新手常见的错误。结果是手动insmod模块能工作但系统启动时模块不会自动加载。probe 函数调用时机驱动匹配成功后驱动的probe函数会被调用。这是驱动的初始化函数在这里进行硬件初始化、资源申请、注册子设备等。staticintimx_uart_probe(structplatform_device*pdev){structdevice_node*nppdev-dev.of_node;structresource*res;void__iomem*base;intirq;// 获取设备树中的 reg 属性寄存器地址resplatform_get_resource(pdev,IORESOURCE_MEM,0);basedevm_ioremap_resource(pdev-dev,res);// 获取中断号irqplatform_get_irq(pdev,0);// 获取时钟ipg_clkdevm_clk_get(pdev-dev,ipg);// 获取其他可选属性of_property_read_u32(np,fsl,uart-has-rtscts,has_rtscts);// 初始化硬件...return0;}常用设备树属性在内核中的使用reg 属性物理地址映射reg属性描述设备的寄存器地址范围。内核通过platform_get_resource()获取uart1: serial02020000 { reg 0x02020000 0x4000; };structresource*res;void__iomem*base;// 获取 MEM 资源resplatform_get_resource(pdev,IORESOURCE_MEM,0);if(!res){dev_err(pdev-dev,No MEM resource\n);return-ENODEV;}// 映射到虚拟地址basedevm_ioremap_resource(pdev-dev,res);if(IS_ERR(base)){returnPTR_ERR(base);}// 现在可以通过 base 访问寄存器writel(0x1234,baseOFFSET);interrupts 属性中断获取uart1: serial02020000 { interrupts 26 2 0; };intirq;// 获取中断号irqplatform_get_irq(pdev,0);if(irq0){dev_err(pdev-dev,No IRQ resource\n);returnirq;}// 申请中断retdevm_request_irq(pdev-dev,irq,uart_isr,IRQF_SHARED,imx-uart,port);clocks 属性时钟获取uart1 { clocks clks IMX6UL_CLK_UART1_IPG, clks IMX6UL_CLK_UART1_SERIAL; clock-names ipg, per; };structclk*ipg_clk,*per_clk;ipg_clkdevm_clk_get(pdev-dev,ipg);if(IS_ERR(ipg_clk))returnPTR_ERR(ipg_clk);per_clkdevm_clk_get(pdev-dev,per);if(IS_ERR(per_clk))returnPTR_ERR(per_clk);// 使能时钟clk_prepare_enable(ipg_clk);clk_prepare_enable(per_clk);GPIO 属性GPIO 获取usdhc1 { pinctrl-names default; pinctrl-0 pinctrl_usdhc1; cd-gpios gpio1 19 GPIO_ACTIVE_LOW; wp-gpios gpio1 20 GPIO_ACTIVE_HIGH; };structcd_gpio;cd_gpiodevm_gpiod_get(pdev-dev,cd,GPIOD_IN);if(IS_ERR(cd_gpio))returnPTR_ERR(cd_gpio);// 读取 GPIO 状态if(gpiod_get_value(cd_gpio))pr_info(Card present\n);自定义属性of_property_read 系列设备树中可以定义任意自定义属性驱动通过of_property_read_*系列函数读取mydevice { vendor,id 0x1234; vendor,name my-awesome-device; vendor,config 0x01 0x02 0x03 0x04; vendor,flag; };structdevice_node*nppdev-dev.of_node;u32 id;constchar*name;u32 config[4];bool flag;// 读取 u32of_property_read_u32(np,vendor,id,id);// 读取字符串of_property_read_string(np,vendor,name,name);// 读取数组of_property_read_u32_array(np,vendor,config,config,4);// 检查布尔属性是否存在flagof_property_read_bool(np,vendor,flag);设备树调试方法方法 1检查 DTB 是否正确首先确保设备树被正确编译和加载# 1. 反编译 DTB检查内容dtc-Idtb-Odts /boot/imx6ull-14x14-evk.dtb/tmp/my.dtsless/tmp/my.dts# 2. 确认内核使用的 DTBcat/sys/firmware/devicetree/base/model# 3. 检查 compatiblecat/sys/firmware/devicetree/base/compatible方法 2检查设备是否创建设备树解析后对应的设备应该被创建# 查看 platform 设备ls-la/sys/devices/platform/# 查看特定设备ls-la/sys/devices/platform/*.uart/# 或根据实际名称# 查看设备的 uevent 文件包含设备信息cat/sys/devices/platform/xxx/uevent方法 3检查驱动是否加载# 查看已加载的模块lsmod|grepuart# 查看 platform 驱动ls-la/sys/bus/platform/drivers/# 查看特定驱动cat/sys/bus/platform/drivers/imx-uart/module方法 4检查绑定情况# 查看哪些设备绑定到哪个驱动ls-la/sys/bus/platform/devices/*/driver# 查看驱动绑定了哪些设备ls-la/sys/bus/platform/drivers/imx-uart/方法 5启用内核调试选项在内核配置中启用设备树调试Device Drivers --- -*- Device Tree and Open Firmware support --- [*] Runtime debugging of the device tree然后# 查看设备树解析日志dmesg|grepof_resolve# 查看设备创建日志dmesg|grepof_platform常见问题排查问题 1驱动没加载现象设备树有节点但设备没工作lsmod看不到驱动。排查# 1. 检查设备节点是否存在ls/sys/firmware/devicetree/base/soc/.../uart02020000/# 2. 检查 compatible 属性cat/sys/firmware/devicetree/base/soc/.../uart02020000/compatible# 3. 查找对应的驱动find/sys/module-nameuevent-execgrep-lcompatible{}\;# 4. 检查驱动模块是否在文件系统中modinfo-nname-of-module常见原因驱动模块没安装MODULE_DEVICE_TABLE缺失compatible 字符串拼写错误问题 2设备存在但驱动没绑定现象/sys/devices/platform/下有设备但driver符号链接指向空。排查# 检查设备的 modaliascat/sys/devices/platform/xxx/modalias# 手动加载驱动sudomodprobe driver-name# 查看内核日志dmesg|grep-iprobe常见原因驱动没加载of_match_table为空compatible 字符串不匹配问题 3probe 失败现象驱动已加载但dmesg显示 probe 失败。排查# 查看 probe 失败原因dmesg|grep-A5imx-uart# 检查资源获取dmesg|grepresource# 检查时钟dmesg|grepclock常见原因资源获取失败reg、irq、clock内存分配失败硬件初始化失败实战分析 i.MX6ULL 设备树让我们看看 i.MX6ULL 开发板的一个实际设备树节点。UART1 设备树节点# 查看设备树中的 UART1cat/sys/firmware/devicetree/base/soc/aips-bus02100000/spba-bus02200000/serial02020000/compatible输出fsl,imx6ull-uart\x00fsl,imx6q-uart对应的驱动代码在内核源码中UART 驱动位于drivers/tty/serial/imx.cstaticconststructof_device_idimx_uart_dt_ids[]{{.compatiblefsl,imx6ull-uart,},{.compatiblefsl,imx6q-uart,},{/* sentinel */}};MODULE_DEVICE_TABLE(of,imx_uart_dt_ids);验证绑定关系# 查找 UART 设备find/sys/devices-name*uart*|head-10# 查看设备信息ls-la/sys/devices/platform/serial02020000/# 查看绑定的驱动cat/sys/devices/platform/serial02020000/driver/modalias设备树叠加Overlay设备树叠加Device Tree Overlay是一种在运行时修改设备树的技术。常见用途加载可插拔设备的配置如 Cape、HAT调试时临时修改配置不重新编译 DTB 的情况下添加设备叠加文件格式/dts-v1/; /plugin/; uart1 { status okay; pinctrl-names default; pinctrl-0 pinctrl_uart1; };加载叠加# 编译叠加dtc --Idts-Odtb-o/tmp/uart1.dtbo uart1-overlay.dts# 加载叠加echo/tmp/uart1.dtbo/sys/kernel/config/device-tree/overlays/uart1/dtbo# 卸载叠加rmdir/sys/kernel/config/device-tree/overlays/uart1写在最后设备树在内核中的使用是一个从硬件描述到驱动匹配的完整链条。理解这个链条你就能知道问题出在哪一环节DTB 没正确加载内核启动就失败节点解析有问题设备创建不出来compatible 不匹配驱动绑不上probe 失败硬件初始化有问题每一环节都有对应的调试方法我们都在这一章里讲了。剩下的就是多实践——改改设备树看看效果用我们讲的调试方法验证一下。到这里你应该理解了设备树是如何被内核解析和使用的。但要让内核真正在板子上跑起来还需要解决一个实际问题如何快速迭代开发和测试下一章我们进入一个非常实战的话题WSL2 TFTP 网络启动。你会看到如何用 WSL2 搭建网络开发环境如何配置 TFTP 服务器如何解决 Windows 防火墙带来的各种坑。那是一个从理论到实践的完整过程而且踩坑记录非常详细。准备好了吗让我们继续。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2443514.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!