十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

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_treeinitial_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_aliasesof_find_node_by_path(/aliases);of_chosenof_find_node_by_path(/chosen);if(of_chosenNULL)of_chosenof_find_node_by_path(/chosen0);if(of_chosen){/* linux,stdout-path and /aliases/stdout are for legacy compatibility */constchar*nameNULL;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_stdoutof_find_node_opts_by_path(name,of_stdout_options);}if(!of_aliases)return;//核心循环for_each_property_of_node(of_aliases,pp){constchar*startpp-name;constchar*endstartstrlen(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;npof_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))endstart)end--;lenend-start;if(kstrtoint(end,10,id)0)continue;/* Allocate an alias_prop with enough space for the stem */apdt_alloc(sizeof(*ap)len1,__alignof__(*ap));if(!ap)continue;memset(ap,0,sizeof(*ap)len1);ap-aliasstart;of_alias_add(ap,np,id,start,len);//注册}}举例说明假设设备树中有dts aliases{serial0uart0;serial1uart1;i2c0i2c1;};of_alias_scan 执行后全局链表 aliases_lookup 包含三个条目{ stemserial, id0, npuart0 } { stemserial, id1, npuart1 } { stemi2c, id0, npi2c1 }调用 of_alias_get_id(uart0, “serial”) 将返回 ‌0‌调用 of_alias_get_id(node, “i2c”) 在链表中查找将返回 ‌0用户空间看到的设备节点如 /dev/i2c-0始终保持一致实现了硬件描述与驱动逻辑的解耦。函数 of_find_node_by_pathLinux 内核设备树Device Tree子系统中用于‌根据绝对路径查找节点‌的核心 API。它允许驱动程序或内核子系统通过字符串形式的路径直接定位到设备树中的特定节点structdevice_node*of_find_node_by_path(constchar*path)参数与返回值 ‌path‌指向必须以/开头的绝对路径字符串。 例如/soc/i2c12340000或/chosen。 路径必须与设备树中的节点层级完全匹配。 ‌返回值‌ ‌成功‌返回指向对应structdevice_node的指针。 ‌失败‌如果路径不存在或格式错误返回NULL。设备树与驱动交互机制驱动中访问设备树API#includelinux/of.h#includelinux/of_device.h// 1. 获取设备节点structdevice_node*nppdev-dev.of_node;// 2. 读取字符串属性constchar*nameNULL;of_property_read_string(np,device-name,name);// 3. 读取整数属性u32 reg_value;of_property_read_u32(np,reg,reg_value);// 4. 读取数组属性intarray[10];intcountof_property_read_variable_u32_array(np,values,array,0,10);// 5. 获取GPIO描述符structgpio_desc*gpiogpiod_get(pdev-dev,enable,GPIOD_OUT_LOW);// 6. 获取中断号intirqplatform_get_irq(pdev,0);// 7. 获取寄存器资源structresource*resplatform_get_resource(pdev,IORESOURCE_MEM,0);void__iomem*basedevm_ioremap_resource(pdev-dev,res);复杂设备访问实例// 复杂设备驱动中的设备树使用 static int my_driver_probe(struct platform_device *pdev){struct device *devpdev-dev;struct device_node *npdev-of_node;struct my_private_data *priv;int ret;// 分配私有数据结构 privdevm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);if(!priv)return-ENOMEM;// 获取寄存器基地址 priv-basedevm_platform_ioremap_resource(pdev,0);if(IS_ERR(priv-base))returnPTR_ERR(priv-base);// 获取时钟 priv-clkdevm_clk_get(dev,core);if(IS_ERR(priv-clk)){dev_err(dev,failed to get core clock\n);returnPTR_ERR(priv-clk);}// 获取复位控制 priv-resetdevm_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-irqplatform_get_irq(pdev,0);if(priv-irq0){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);// 注册中断处理函数 retdevm_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/;/{compatiblemy-company,my-board;fragment0{targetgpio;__overlay__{my_gpio_pins:my_gpio_pins{pinsPA10,PA11;functiongpio_out;};};};fragment1{target-path/;__overlay__{my_gpio_device{compatiblemy-company,my-gpio-device;statusokay;pinctrl-namesdefault;pinctrl-0my_gpio_pins;led-gpiospio010GPIO_ACTIVE_HIGH,/* PA10 */pio011GPIO_ACTIVE_HIGH;/* PA11 */button-gpiospio012GPIO_ACTIVE_LOW;device-namemy-custom-device;clock-frequency100000;};};};};对应驱动程序// my-gpio-driver.c#includelinux/module.h#includelinux/platform_device.h#includelinux/gpio/consumer.h#includelinux/interrupt.h#includelinux/of.hstructmy_gpio_data{structgpio_desc*leds[2];structgpio_desc*button;intirq;structdevice*dev;};staticirqreturn_tbutton_irq_handler(intirq,void*dev_id){structmy_gpio_data*privdev_id;// 读取按钮状态并控制LEDintstategpiod_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*devpdev-dev;structdevice_node*npdev-of_node;structmy_gpio_data*priv;intret,i;u32 clock_freq;constchar*device_name;privdevm_kzalloc(dev,sizeof(*priv),GFP_KERNEL);if(!priv)return-ENOMEM;priv-devdev;// 获取LED GPIOsfor(i0;i2;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-buttondevm_gpiod_get(dev,button,GPIOD_IN);if(IS_ERR(priv-button)){dev_err(dev,failed to get button GPIO\n);returnPTR_ERR(priv-button);}// 获取设备树属性retof_property_read_string(np,device-name,device_name);if(ret)device_namedefault;retof_property_read_u32(np,clock-frequency,clock_freq);if(ret)clock_freq100000;// 默认值dev_info(dev,Device %s probed, clock frequency: %d Hz\n,device_name,clock_freq);// 设置中断priv-irqgpiod_to_irq(priv-button);retdevm_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*privplatform_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[]{{.compatiblemy-company,my-gpio-device},{}};MODULE_DEVICE_TABLE(of,my_gpio_of_match);staticstructplatform_drivermy_gpio_driver{.probemy_gpio_probe,.removemy_gpio_remove,.driver{.namemy-gpio-device,.of_match_tablemy_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.dtbfdtdump显示DTB内容fdtdump input.dtbdtc -O dtb编译DTS为DTBdtc -O dtb -o output.dtb input.dtsof_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/device1000/compatible dmesg|grep-idevice tree#5.调试驱动匹配 echo-nmy-company,my-device/sys/bus/platform/drivers/my-driver/new_id内核调试配置// 在驱动中添加调试输出#defineDEBUGstaticintmy_driver_probe(structplatform_device*pdev){structdevice_node*nppdev-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;}
返回列表