news 2026/8/19 14:34:33

Linux -设备树(Device Tree)解析流程(五)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux -设备树(Device Tree)解析流程(五)

内核解析代码流程

// 设备树解析核心函数调用链start_kernel()-->setup_arch(&command_line)-->unflatten_device_tree()-->__unflatten_device_tree()-->unflatten_dt_nodes()// 驱动匹配流程driver_register()-->bus_add_driver()-->driver_attach()-->__driver_attach()-->driver_match_device()-->of_driver_match_device()// 设备创建流程of_platform_populate()-->of_platform_bus_create()-->of_platform_device_create_pdata()

setup_arch() -> unflatten_device_tree() -> of_alias_scan() -> of_find_node_by_path() -> of_property_read_string() -> for_each_property_of_node() -> of_alias_add() ->

unflatten_device_tree‌

主要职责是将固件(如 U-Boot)传递的‌扁平化设备树二进制块(Flat Device Tree Blob, FDT/DTB)‌解析并展开为内核可操作的‌层级化 struct device_node 链表树‌。

函数原型

void__initunflatten_device_tree(void){__unflatten_device_tree(initial_boot_params,NULL,&of_root,early_init_dt_alloc_memory_arch,false);/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */of_alias_scan(early_init_dt_alloc_memory_arch);unittest_unflatten_overlay_base();}

函数 __unflatten_device_tree

  • initial_boot_params‌:指向 Bootloader 传递的扁平设备树(DTB)在内存中的起始地址。
  • NULL‌:通常用于指定特定的节点过滤或父节点,此处为 NULL 表示从根节点开始完整展开。
  • &of_root‌:‌关键输出‌。展开后生成的根节点 struct device_node 指针将被赋值给全局变量 of_root。此后,内核中所有基于设备树的操作(如 of_find_node_by_name)都以此为基础。
  • early_init_dt_alloc_memory_arch‌:内存分配回调函数。由于此时内核常规内存管理器(如 Slab)尚未就绪,必须使用架构特定的早期内存分配器(通常基于 Memblock 或 Bootmem)。
  • false‌:标志位,通常指示是否仅进行大小计算而不实际分配内存(第一遍扫描常设为 true 以计算所需空间,第二遍设为 false 进行实际分配)。

of_alias_scan‌

主要任务是扫描设备树根节点下的 ‌/aliases‌ 子节点,解析其中的属性,建立“简短别名”到“完整设备路径”的映射表

函数原型

voidof_alias_scan(void*(*dt_alloc)(u64 size,u64 align)){structproperty*pp;of_aliases=of_find_node_by_path("/aliases");of_chosen=of_find_node_by_path("/chosen");if(of_chosen==NULL)of_chosen=of_find_node_by_path("/chosen@0");if(of_chosen){/* linux,stdout-path and /aliases/stdout are for legacy compatibility */constchar*name=NULL;if(of_property_read_string(of_chosen,"stdout-path",&name))of_property_read_string(of_chosen,"linux,stdout-path",&name);if(IS_ENABLED(CONFIG_PPC)&&!name)of_property_read_string(of_aliases,"stdout",&name);if(name)of_stdout=of_find_node_opts_by_path(name,&of_stdout_options);}if(!of_aliases)return;//核心循环for_each_property_of_node(of_aliases,pp){constchar*start=pp->name;constchar*end=start+strlen(start);structdevice_node*np;structalias_prop*ap;intid,len;/* Skip those we do not want to proceed */if(!strcmp(pp->name,"name")||!strcmp(pp->name,"phandle")||!strcmp(pp->name,"linux,phandle"))continue;np=of_find_node_by_path(pp->value);if(!np)continue;/* walk the alias backwards to extract the id and work out * the 'stem' string */while(isdigit(*(end-1))&&end>start)end--;len=end-start;if(kstrtoint(end,10,&id)<0)continue;/* Allocate an alias_prop with enough space for the stem */ap=dt_alloc(sizeof(*ap)+len+1,__alignof__(*ap));if(!ap)continue;memset(ap,0,sizeof(*ap)+len+1);ap->alias=start;of_alias_add(ap,np,id,start,len);//注册}}

举例说明
假设设备树中有:

dts aliases{serial0=&uart0;serial1=&uart1;i2c0=&i2c1;};

of_alias_scan 执行后:

全局链表 aliases_lookup 包含三个条目:

{ stem="serial", id=0, np=uart0 } { stem="serial", id=1, np=uart1 } { stem="i2c", id=0, np=i2c1 }
  • 调用 of_alias_get_id(uart0, “serial”) 将返回 ‌0‌,
  • 调用 of_alias_get_id(node, “i2c”) 在链表中查找,将返回 ‌0,
  • 用户空间看到的设备节点(如 /dev/i2c-0)始终保持一致,实现了硬件描述与驱动逻辑的解耦。

函数 of_find_node_by_path

Linux 内核设备树(Device Tree)子系统中用于‌根据绝对路径查找节点‌的核心 API。它允许驱动程序或内核子系统通过字符串形式的路径,直接定位到设备树中的特定节点

structdevice_node*of_find_node_by_path(constchar*path)参数与返回值 ‌path‌:指向必须以/开头的绝对路径字符串。 例如:"/soc/i2c@12340000""/chosen"。 路径必须与设备树中的节点层级完全匹配。 ‌返回值‌: ‌成功‌:返回指向对应structdevice_node的指针。 ‌失败‌:如果路径不存在或格式错误,返回NULL

设备树与驱动交互机制

驱动中访问设备树API

#include<linux/of.h>#include<linux/of_device.h>// 1. 获取设备节点structdevice_node*np=pdev->dev.of_node;// 2. 读取字符串属性constchar*name=NULL;of_property_read_string(np,"device-name",&name);// 3. 读取整数属性u32 reg_value;of_property_read_u32(np,"reg",&reg_value);// 4. 读取数组属性intarray[10];intcount=of_property_read_variable_u32_array(np,"values",array,0,10);// 5. 获取GPIO描述符structgpio_desc*gpio=gpiod_get(&pdev->dev,"enable",GPIOD_OUT_LOW);// 6. 获取中断号intirq=platform_get_irq(pdev,0);// 7. 获取寄存器资源structresource*res=platform_get_resource(pdev,IORESOURCE_MEM,0);void__iomem*base=devm_ioremap_resource(&pdev->dev,res);

复杂设备访问实例

// 复杂设备驱动中的设备树使用 static int my_driver_probe(struct platform_device *pdev){struct device *dev=&pdev->dev;struct device_node *np=dev->of_node;struct my_private_data *priv;int ret;// 分配私有数据结构 priv=devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);if(!priv)return-ENOMEM;// 获取寄存器基地址 priv->base=devm_platform_ioremap_resource(pdev,0);if(IS_ERR(priv->base))returnPTR_ERR(priv->base);// 获取时钟 priv->clk=devm_clk_get(dev,"core");if(IS_ERR(priv->clk)){dev_err(dev,"failed to get core clock\n");returnPTR_ERR(priv->clk);}// 获取复位控制 priv->reset=devm_reset_control_get_optional(dev, NULL);if(IS_ERR(priv->reset)){dev_err(dev,"failed to get reset control\n");returnPTR_ERR(priv->reset);}// 获取中断 priv->irq=platform_get_irq(pdev,0);if(priv->irq<0){dev_err(dev,"failed to get IRQ\n");returnpriv->irq;}// 解析自定义属性if(of_property_read_bool(np,"big-endian"))priv->flags|=BIG_ENDIAN_FLAG;u32 dma_mask;if(!of_property_read_u32(np,"dma-mask",&dma_mask))dma_set_mask(dev, dma_mask);// 注册中断处理函数 ret=devm_request_irq(dev, priv->irq, my_irq_handler, IRQF_SHARED, dev_name(dev), priv);if(ret){dev_err(dev,"failed to request IRQ %d: %d\n", priv->irq, ret);returnret;}platform_set_drvdata(pdev, priv);return0;}

实战:简单GPIO设备实现

设备树定义

// my-gpio-device.dts/dts-v1/;/plugin/;/{compatible="my-company,my-board";fragment@0{target=<&gpio>;__overlay__{my_gpio_pins:my_gpio_pins{pins="PA10","PA11";function="gpio_out";};};};fragment@1{target-path="/";__overlay__{my_gpio_device{compatible="my-company,my-gpio-device";status="okay";pinctrl-names="default";pinctrl-0=<&my_gpio_pins>;led-gpios=<&pio010GPIO_ACTIVE_HIGH>,/* PA10 */<&pio011GPIO_ACTIVE_HIGH>;/* PA11 */button-gpios=<&pio012GPIO_ACTIVE_LOW>;device-name="my-custom-device";clock-frequency=<100000>;};};};};

对应驱动程序

// my-gpio-driver.c#include<linux/module.h>#include<linux/platform_device.h>#include<linux/gpio/consumer.h>#include<linux/interrupt.h>#include<linux/of.h>structmy_gpio_data{structgpio_desc*leds[2];structgpio_desc*button;intirq;structdevice*dev;};staticirqreturn_tbutton_irq_handler(intirq,void*dev_id){structmy_gpio_data*priv=dev_id;// 读取按钮状态并控制LEDintstate=gpiod_get_value(priv->button);gpiod_set_value(priv->leds[0],state);gpiod_set_value(priv->leds[1],!state);dev_info(priv->dev,"Button state: %d\n",state);returnIRQ_HANDLED;}staticintmy_gpio_probe(structplatform_device*pdev){structdevice*dev=&pdev->dev;structdevice_node*np=dev->of_node;structmy_gpio_data*priv;intret,i;u32 clock_freq;constchar*device_name;priv=devm_kzalloc(dev,sizeof(*priv),GFP_KERNEL);if(!priv)return-ENOMEM;priv->dev=dev;// 获取LED GPIOsfor(i=0;i<2;i++){priv->leds[i]=devm_gpiod_get_index(dev,"led",i,GPIOD_OUT_LOW);if(IS_ERR(priv->leds[i])){dev_err(dev,"failed to get LED GPIO %d\n",i);returnPTR_ERR(priv->leds[i]);}}// 获取按钮GPIOpriv->button=devm_gpiod_get(dev,"button",GPIOD_IN);if(IS_ERR(priv->button)){dev_err(dev,"failed to get button GPIO\n");returnPTR_ERR(priv->button);}// 获取设备树属性ret=of_property_read_string(np,"device-name",&device_name);if(ret)device_name="default";ret=of_property_read_u32(np,"clock-frequency",&clock_freq);if(ret)clock_freq=100000;// 默认值dev_info(dev,"Device %s probed, clock frequency: %d Hz\n",device_name,clock_freq);// 设置中断priv->irq=gpiod_to_irq(priv->button);ret=devm_request_irq(dev,priv->irq,button_irq_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,"my-gpio-button",priv);if(ret){dev_err(dev,"failed to request IRQ: %d\n",ret);returnret;}platform_set_drvdata(pdev,priv);return0;}staticintmy_gpio_remove(structplatform_device*pdev){structmy_gpio_data*priv=platform_get_drvdata(pdev);// 关闭LEDsgpiod_set_value(priv->leds[0],0);gpiod_set_value(priv->leds[1],0);dev_info(&pdev->dev,"Device removed\n");return0;}staticconststructof_device_idmy_gpio_of_match[]={{.compatible="my-company,my-gpio-device"},{}};MODULE_DEVICE_TABLE(of,my_gpio_of_match);staticstructplatform_drivermy_gpio_driver={.probe=my_gpio_probe,.remove=my_gpio_remove,.driver={.name="my-gpio-device",.of_match_table=my_gpio_of_match,},};module_platform_driver(my_gpio_driver);MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("Simple GPIO device driver with Device Tree support");

设备树调试与诊断

常用工具命令

工具命令功能描述使用示例
dtc设备树编译器dtc -I dtb -O dts -o output.dts input.dtb
fdtdump显示DTB内容fdtdump input.dtb
dtc -O dtb编译DTS为DTBdtc -O dtb -o output.dtb input.dts
of_find_node_by_name内核调试函数在驱动中查找节点
cat /proc/device-tree查看已加载设备树find /proc/device-tree -type f

调试技巧

#1.检查设备树语法 dtc-I dts-O dtb-o/dev/null my-device.dts #2.反编译现有DTB dtc-I dtb-O dts-o extracted.dts/boot/device_tree.dtb #3.查看内核解析的设备树 ls/proc/device-tree/cat/proc/device-tree/compatible #4.检查设备是否成功匹配 cat/sys/firmware/devicetree/base/device@1000/compatible dmesg|grep-i"device tree"#5.调试驱动匹配 echo-n"my-company,my-device">/sys/bus/platform/drivers/my-driver/new_id

内核调试配置

// 在驱动中添加调试输出#defineDEBUGstaticintmy_driver_probe(structplatform_device*pdev){structdevice_node*np=pdev->dev.of_node;// 打印设备树信息dev_dbg(&pdev->dev,"Device tree node: %s\n",np->full_name);// 遍历属性structproperty*prop;for_each_property_of_node(np,prop){dev_dbg(&pdev->dev,"Property: %s\n",prop->name);}return0;}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/19 14:33:11

追更的小说一夜下架?这个免费开源小说下载器 3 分钟备份整个书单

追更的小说一夜下架&#xff1f;这个免费开源小说下载器 3 分钟备份整个书单 【免费下载链接】novel-downloader 一个可扩展的通用型小说下载器。 项目地址: https://gitcode.com/gh_mirrors/no/novel-downloader 你是否有过这种经历&#xff1a;晚上刷到一本正上头的连…

作者头像 李华
网站建设 2026/8/19 14:33:11

Hopsum:利用IPv4 TTL过期包实现网络内分布式计算

最近在排查网络问题时&#xff0c;发现一个有趣的现象&#xff1a;一些本该被路由器丢弃的“过期”数据包&#xff0c;竟然在特定条件下还能被利用起来。这让我联想到一个名为 Hopsum 的实验性概念。它并非一个现成的工具&#xff0c;而是一种颠覆性的网络协议设计思路—— …

作者头像 李华
网站建设 2026/8/19 14:30:10

AI智能体如何重塑主动安全防御:从索尼AI诱饵账号看攻防新范式

上周&#xff0c;我偶然在技术社区里看到一个讨论&#xff0c;说索尼 PlayStation 申请了一项新专利&#xff0c;核心是利用 AI 模拟未成年人账号&#xff0c;来主动识别平台上的潜在恶意用户。乍一看&#xff0c;这似乎只是个游戏平台的安全功能&#xff0c;但如果你仔细琢磨一…

作者头像 李华
网站建设 2026/8/19 14:25:49

集群服务变更的兼容与回退方案

集群服务变更的兼容与回退方案 发布前先设计回退 灰度的目的&#xff0c;是在可控范围内确认行为&#xff0c;不是把风险延后到更多用户。版本、分组方式和停止条件要先写好。 明确版本识别、流量切分位置和停止条件。协议或数据改动先兼容读取&#xff0c;再逐步切换写入&…

作者头像 李华
网站建设 2026/8/19 14:17:04

第110篇 DDS通信原理——ROS2底层是怎么传数据的

面试的时候被问过&#xff1a;"ROS2的topic底层是怎么传输的&#xff1f;"我说"用DDS"。面试官说&#xff1a;"DDS具体怎么工作的&#xff1f;数据从发布到接收经过了哪些步骤&#xff1f;" 这个问题确实有深度。很多人用ROS2写了很多节点&…

作者头像 李华
网站建设 2026/8/19 14:16:32

第118篇 ROS2动态重配置——运行时改变系统行为

面试被问&#xff1a;"ROS2里怎么做动态重配置&#xff1f;就是运行时改参数让节点立即生效那种。" 我说"用参数回调就行"。面试官说&#xff1a;"ROS1有dynamic_reconfigure包&#xff0c;ROS2里对应的方案是什么&#xff1f;有没有图形化界面&…

作者头像 李华