设备树是什么
- device tree 设备树
- dts device tree source 设备树源文件
- dtc device tree compiler 设备树编译/反编译/调试工具
- dtb device tree blob 设备树二进制文件
uboot只会加载一个设备树文件,即dtb文件
设备树语法
节点
根节点 子节点

在节点的{}里面是描述该节点的属性(property),即设备的特性。它的值是多样化的:
1.它可以是字符串string,如①;也可能是字符串数组string-list,如②
2.它也可以是32 bit unsigned integers,如cell⑧,整形用<>表示
3.它也可以是binary data,如③,十六进制用[]表示
4.它也可能是空,如⑦
典型节点
chosen node
chosen { bootargs = "tegraid=40.0.0.00.00 vmalloc=256M video=tegrafb console=ttyS0,115200n8 earlyprintk"; };chosen node 主要用来描述由系统指定的runtime parameter,它并没有描述任何硬件设备节点信息。原先通过tag list传递的一些linux kernel运行的参数,可以通过chosen节点来传递。如command line可以通过bootargs这个property来传递。如果存在chosen node,它的parent节点必须为“/”根节点。(本示例都以imx6ull的硬件环境下讲解)
aliases node
aliases { i2c6 = &pca9546_i2c0; i2c7 = &pca9546_i2c1; i2c8 = &pca9546_i2c2; i2c9 = &pca9546_i2c3; };aliases node用来定义别名,类似C++中引用。上面是一个在.dtsi中的典型应用,当使用i2c6时,也即使用pca9546_i2c0,使得引用节点变得简单方便。例:当.dts include 该.dtsi时,将i2c6的status属性赋值为okay,则表明该主板上的pca9546_i2c0处于enable状态;反之,status赋值为disabled,则表明该主板上的pca9546_i2c0处于disenable状态。
memory node
memory { device_type = "memory"; reg = <0x00000000 0x20000000>; /* 512 MB */ };对于memory node,device_type必须为memory,由之前的描述可以知道该memory node是以0x00000000为起始地址,以0x20000000为结束地址的512MB的空间。
一般而言,在.dts中不对memory进行描述,而是通过bootargs中类似521M@0x00000000的方式传递给内核。
设备树中的引用
示例
/ { uart1: serial@02020000 { compatible = "fsl,imx6ul-uart"; reg = <0x02020000 0x4000>; status = "disabled"; }; }; &uart1 { status = "okay"; };标签格式:
标签: 节点名@地址 { // 节点内容 };编译后,uart1节点最终会包含status = "okay",但标签uart1:和引用&uart1都会消失。
在板子的/proc/device-tree/下,你会看到目录/serial@02020000/(或者/soc/serial@02020000/,取决于具体路径),里面有一个status文件,内容为"okay"。
没有任何名为&uart1或uart1的文件或目录存在。
&xxxx是设备树源文件(.dts)中的语法,它用于引用一个预先定义的标签(label),目的是将后续的属性或子节点合并到那个标签所代表的节点中。这相当于一种“粘合剂”,方便在多个地方修改同一个节点。
如何使用?
使用步骤
- 在你的内核环境下编译dts为dtb文件
- 在你的开发板(或其他环境)下替换原来的dtb文件
- 重启内核,查看设备节点是否存在,在板子上,
/proc/device-tree目录是内核提供的设备树运行时视图,它的一级目录和文件直接对应设备树根节点(/)下的子节点和属性。 - 编写驱动代码和应用代码
驱动中如何使用设备树?
获取设备节点
dtsled.nd = of_find_node_by_path("/alphaled");
eg:
/* 获取设备树中的属性数据 */ /* 1、获取设备节点:alphaled */ dtsled.nd = of_find_node_by_path("/alphaled"); if(dtsled.nd == NULL) { printk("alphaled node can not found!\r\n"); return -EINVAL; } else { printk("alphaled node has been found!\r\n"); }驱动与设备树节点绑定的“信物”是compatible属性。
获取资源——核心 OF 操作函数
一旦进入probe函数,驱动就可以通过设备节点指针(通常是struct device_node *np = pdev->dev.of_node;)来提取所需的硬件信息了。以下是一些最常用的操作函数:
| 资源类型 | 常用函数 | 作用 |
|---|---|---|
| 寄存器地址 | of_iomap(np, 0) | 将reg属性中第0段地址直接映射为虚拟地址,省去了ioremap的步骤。 |
of_address_to_resource(np, 0, &res) | 获取reg属性,并填充到一个struct resource结构体中,之后可以用devm_ioremap_resource进行映射。 | |
| 中断号 | irq_of_parse_and_map(np, 0) | 解析interrupts属性第0个中断并映射为Linux中断号。 |
of_irq_get(np, 0) | 功能类似,推荐使用。 | |
| GPIO | of_get_named_gpio(np, "enable-gpios", 0) | 获取名为"enable-gpios"的属性中定义的GPIO编号,之后可以用gpio_request等函数操作。 |
| 整型属性 | of_property_read_u32(np, "clock-frequency", &val) | 读取一个32位整型属性值。 |
| 属性长度 | of_property_count_elems_of_size(np, "reg", sizeof(u32)) | 获取reg属性中包含了多少个地址/长度对。 |
| 字符串 | of_property_read_string(np, "status", &str) | 读取字符串属性。 |
设备树如何工作(进阶)
编译阶段
dtc :用于编译dts文件为dtb文件
dtb :用于kernel去使用设备树
.dts(源文件)↓ dtc编译 .dtb(二进制Blob)↓ 嵌入内核或单独加载 启动时加载到内存引导阶段
目的:加载到内存
Bootloader(如U-Boot)加载设备树 //1. 从存储设备读取dtb //2. 修正地址(relocation) //3. 传递给内核 //4. 跳转到内核入口内核初始化阶段
加载到内核完后,需要将设备树进行解析
// arch/arm/kernel/setup.cvoid__initsetup_arch(char**cmdline_p){// 1. 早期设备树扫描early_init_dt_scan_nodes();// 2. 解析内存信息early_init_dt_scan_memory();// 3. 解析chosen节点(bootargs等)of_scan_flat_dt(early_init_dt_scan_chosen,boot_command_line);}展开设备树结构
// drivers/of/fdt.cvoid__initunflatten_device_tree(void){// 将平面设备树转换为树形结构__unflatten_device_tree(initial_boot_params,&of_root,&of_allnodes);// 最终生成的结构:// of_root -> 根节点// of_allnodes -> 所有节点的链表}这样就得到了设备树数据,后续就需要驱动去匹配了。
驱动匹配设备树(进阶)
这里需要介绍几个核心的结构体:
相关数据结构
设备树相关结构
// 设备树节点内核表示structdevice_node{constchar*name;// 节点名称constchar*type;// 设备类型phandle phandle;// 句柄constchar*full_name;// 完整路径名structproperty*properties;// 属性链表structdevice_node*parent;// 父节点structdevice_node*child;// 子节点structdevice_node*sibling;// 兄弟节点constvoid*data;// 设备特定数据};// 设备树属性structproperty{char*name;// 属性名intlength;// 值长度void*value;// 属性值structproperty*next;// 下一个属性unsignedlong_flags;// 内部标志};平台设备结构
// 由设备树节点创建的platform_devicestructplatform_device{constchar*name;// 设备名称intid;// 设备IDstructdevicedev;// 基础设备结构u32 num_resources;// 资源数量structresource*resource;// 资源数组conststructplatform_device_id*id_entry;// 设备树相关structdevice_node*of_node;// 对应的设备树节点structirq_domain*irq_domain;};驱动匹配结构
// 设备树匹配表structof_device_id{charname[32];// 设备名称chartype[32];// 设备类型charcompatible[128];// 兼容字符串constvoid*data;// 私有数据};// 平台驱动结构structplatform_driver{int(*probe)(structplatform_device*);int(*remove)(structplatform_device*);void(*shutdown)(structplatform_device*);structdevice_driverdriver;// 基础驱动结构conststructplatform_device_id*id_table;// 设备树匹配表conststructof_device_id*of_match_table;};驱动匹配过程
阶段一:设备树节点转换为设备
// drivers/of/platform.c/** * of_platform_populate() - 从设备树创建设备 */intof_platform_populate(structdevice_node*root,conststructof_device_id*matches,structdevice*parent){structdevice_node*child;// 遍历设备树节点for_each_child_of_node(root,child){// 检查节点是否可用if(!of_device_is_available(child))continue;// 创建设备of_platform_device_create_pdata(child,matches,parent,NULL);}return0;}/** * of_platform_device_create_pdata() - 创建platform_device */staticstructplatform_device*of_platform_device_create_pdata(structdevice_node*np,constchar*bus_id,void*platform_data,structdevice*parent){structplatform_device*pdev;// 1. 分配platform_devicepdev=platform_device_alloc("",PLATFORM_DEVID_NONE);if(!pdev)returnNULL;// 2. 设置设备树节点pdev->dev.of_node=of_node_get(np);pdev->dev.parent=parent;// 3. 设置设备名称if(bus_id)pdev->name=bus_id;elsepdev->name=np->name;// 4. 解析设备资源(内存、IRQ等)of_device_add_resource(pdev,np);// 5. 添加到系统if(platform_device_add(pdev)!=0){platform_device_put(pdev);returnNULL;}returnpdev;}阶段二:驱动注册
// 驱动注册示例staticstructplatform_drivermy_driver={.driver={.name="my-device",.owner=THIS_MODULE,.of_match_table=my_of_match,// 关键:设备树匹配表},.probe=my_probe,.remove=my_remove,};// 设备树匹配表staticconststructof_device_idmy_of_match[]={{.compatible="vendor,device-v1.0"},{.compatible="vendor,device-v2.0"},{.compatible="vendor,generic-device"},{}// 结束标记};MODULE_DEVICE_TABLE(of,my_of_match);// 驱动注册module_platform_driver(my_driver);// 展开后的注册函数staticint__initmy_driver_init(void){returnplatform_driver_register(&my_driver);}module_init(my_driver_init);阶段三:匹配执行过程
此处是内核在做的事情,具体不展开分析了。
后续持续更新!感谢观看!