内核解析代码流程
// 设备树解析核心函数调用链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",®_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为DTB | dtc -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;}