linux设备树
设备树概念:以前硬件信息写死在内核 C 代码里 → 乱、重复、难维护现在硬件信息写在设备树里内核只负责驱动逻辑结果一个内核镜像可以适配不同硬件只换设备树就行设备树DeviceTree是一种硬件描述机制用于描述硬件设 备的特性、连接关系和配置信息。它提供了一种与平台无关的方式来描述硬件。就是替代上节说的platform-device设备树文件存放路径:ARM体系结构下的设备树源文件通常存放在arch/arm/boot/dts/目录中ARM64 体系结构存放在arch/arm64/boot/dts/ 目录及其子目录中例如rk3588设备树目录为 arch/arm64/boot/dts/rockchip设备树的编译:dtc-I dts-O dtb-o output.dtb input.dts //eg: ///home/topeet/Linux/linux_sdk/kernel/scripts/dtc/dtc-I dts-O dtb-o test.dtb test.dts设备树的反编译dtc-I dtb-O dts-o output.dts input.dtb //eg ///home/topeet/Linux/linux_sdk/kernel/scripts/dtc/dtc-I dtb-O dts-o 1.dts test.dtb设备树语法:1reg:reg 属性用于在设备树中指定设备的寄存器地址和大小提供了与设备树中的物理设备之 间的寄存器映射关系reg address size my_device { compatible vendor,device; reg 0x1000 0x4; // 其他属性和子节点的定义 }2:address-cells 和 size-cells 属性:用于指定子节点的reg的地址长度和size长度node1 { #address-cells 1; #size-cells 1; node1-child { reg 0x02200000 0x4000; // 其他属性和子节点的定义 }; };3compatible用于和驱动匹配my_device { compatible vendor,device; };4aliases总的节点重命名aliases { mmc0sdmmc0; mmc1sdmmc1; mmc2sdhci; serial0 /simplefe000000/seria111c500; };5:chosen 节点:用于给uboot传递参数信息chosen { }; bootargs root/dev/nfs rw nfsroot192.168.1.1 consolettyS0,115200;6:device_type 节点的存在有助于操作系统或其他软件识别和处理设备。它提供了设备的基本分类信息使得驱动程序、设备树解析器或其他系统组件能够根据设备的类型执行相应的操作。核根据 device_type cpu 做专属操作不同的 device_type内核干的事完全不一样当内核看到device_type cpu它会专门执行 CPU 初始化代码启动 CPU初始化 CPU 调度初始化多核初始化时钟、电源管理把这个 CPU 加入系统管理device_type 节点 /* 常见的设备类型包括但不限于 1cpu表示中央处理器。 2memory表示内存设备。 3display表示显示设备如液晶显示屏。 4serial表示串行通信设备如串口 */ cpu0 { // 0 是地址 device_type cpu; // 身份我是 CPU };7:自定义属性自己随便取的test_device12340000 { compatible my,testdev; reg 0x12340000 0x1000; // 下面这两个就是 自定义属性 status ok; my_driver_param 100; my_name hello_sensor; };实例分析中断比如iTOP-RK3588开发板SDK源码中的ft5x06设备树gpio3: gpiofec40000 { compatible rockchip,gpio-bank; reg 0x0 0xfec40000 0x0 0x100; interrupts GIC_SPI 280 IRQ_TYPE_LEVEL_HIGH; clocks cru PCLK_GPIO3, cru DBCLK_GPIO3; gpio-controller; #gpio-cells 2; gpio-ranges pinctrl 0 96 32; interrupt-controller; #interrupt-cells 2; }; ft5x06: ft5x0638 { status disabled; compatible edt,edt-ft5406; reg 0x38; touch-gpio gpio3 RK_PC0 IRQ_TYPE_EDGE_RISING; interrupt-parent gpio3; interrupts RK_PC0 IRQ_TYPE_LEVEL_LOW; reset-gpios gpio3 RK_PC1 GPIO_ACTIVE_LOW; touchscreen-size-x 800; touchscreen-size-y 1280; };interrupts 属性用于指定设备的中断相关信息。它描述了中断控制器的类型、中断号以及 中断触发类型gpio-controller声明这是一个 GPIO 控制器#gpio-cells 2描述一个 GPIO 需要 2 个参数引脚编号 标志interrupt-controller声明这是一个中断控制器#interrupt-cells 2描述一个中断需要 2 个参数引脚 触发方式中断实例编写/{ model This is my devicetree!; ft5x0638 { compatible edt,edt-ft5206; interrupt-parent gpio3; interrupts RK_PA5 IRQ_TYPE_EDGE_RISING; //应为我引用了中断控制器gpio3 }; };GPIO相关属性比如下面一个gpio属性gpio3: gpiofec40000 { compatible rockchip,gpio-bank; reg 0x0 0xfec40000 0x0 0x100; interrupts GIC_SPI 280 IRQ_TYPE_LEVEL_HIGH; clocks cru PCLK_GPIO3, cru DBCLK_GPIO3; gpio-controller; #gpio-cells 2; gpio-ranges pinctrl 0 96 32; interrupt-controller; #interrupt-cells 2; }; ft5x06: ft5x0638 { status disabled; compatible edt,edt-ft5406; reg 0x38; touch-gpio gpio3 RK_PC0 IRQ_TYPE_EDGE_RISING; reset-gpios gpio3 RK_PC1 GPIO_ACTIVE_LOW; };gpio-controller声明本节点是一个GPIO 控制器可向外提供 GPIO 引脚#gpio-cells 2描述一个 GPIO 需要 2 个参数引脚编号标志位如gpio3 RK_PC0 GPIO_ACTIVE_LOWreset-gpios gpio3 RK_PC1 GPIO_ACTIVE_LOW复位引脚为 GPIO3_C1低电平有效拉低复位芯片gpio 引脚描述属性个数由#gpio-cells所决定因为gpio3节点中的#gpio-cells属性设置为了 2所以上面设备树gpio引脚描述属性个数也为2。pinctrl设置复用关系客户端Client大部分都一样node { pinctrl-names default; pinctrl-0 pinctrl_hog_1; }pinctrl-0 属性指定了第一个状态default对应的引脚配置。 是一个引脚描述符它引用了一个名为 pinctrl_hog_1的引脚控制器节 点。这表示在default状态下设备的引脚配置将使用pinctrl_hog_1节点中定义的配置。服务端Server在设备树的pinctrl节点下面添加表明引脚的复用关系rk_led{ rk_led_gpio:rk-led-gpio { rockchip,pins 2 RK_PC4 RK_FUNC_GPIO pcfg_pull_none; }; };//客户端 my_led: led { compatible topeet,led; gpios gpio2 RK_PC4 GPIO_ACTIVE_HIGH; pinctrl-names default; pinctrl-0 rk_led_gpio; };dtb展开成device_node实验设备树展开设备树展开是指将设备树二进制文件解析成内核中的设备节点 device_node的过程。内核会读取设备树二进制文件的内容并根据设备树的描述信息 构建设备树数据结构例如设备节点、中断控制器、寄存器、时钟等。这些设备树数据结构将 在内核运行时用于管理和配置硬件资源。结构体如下这样就可以构造一棵设备树struct device_node{ const char*name; //设备节点的名称 struct property* properties; //设备节点的属性列表 struct property* deadprops; //已删除的属性列表 struct device_node* parent; //父设备节点指针 struct device_node* child; //子设备节点指针 struct device_node* sibling; //兄弟设备节点指针 ... }dtb 解析过程源码分析:首先来到源码目录下的“/init/main.c”文件start_kernel函数start_kernel函 数是 Linux 内核启动的入口点它是Linux内核的核心函数之一负责完成内核的初始化和启动然后调用函数找到寄存器x0,x0是dtb二进制文件加载到内存的地址,解析流程图如图所示device_node转换成platform_device转换规格根据规则1根节点下包含 compatible 属性的子节点对于每个子节点创建 一个对应的 platform_device。根据规则2遍历包含 compatible 属性为 simple-bus、simple-mfd 或 isa 的节点以 及它们的子节点。如果子节点包含 compatible 属性值则会创建一个对应的platform_device。根据规则3检查节点的 compatible 属性是否包含 arm 或 primecell。如果是则不 将该节点转换为 platform_device而是将其识别为 AMBA 设备。转换流程源码分析:就是编译设备树节点按照刚才那种规则一个一个检查of操作函数实验获取设备树节点1of_find_node_by_name 是 通过节点名称查找设备树节点的函数// 头文件 #include linux/of.h // 函数功能通过节点名字查找设备树节点 // from : 从哪个节点开始查找填 NULL 表示从根节点开始 // name : 要查找的节点名字如 gpio, serial // return : 找到返回 struct device_node *失败返回 NULL struct device_node *of_find_node_by_name(struct device_node *from, const char *name);struct device_node* mydevice_node; //从根节点开始查找匹配的节点 mydevice_nodeof_find_node_by_name(NULL,myLed);2:of_find_node_by_path是通过节点路径查找设备树节点的函数//--------------------------------------------------- // of_find_node_by_path // 功能通过**绝对路径**查找设备树节点 //--------------------------------------------------- #include linux/of.h struct device_node *of_find_node_by_path(const char *path);mydevice_nodeof_find_node_by_path(/topeet/myLed); printk(mydevicenodeis%s\n,mydevice_node-name);3:用of_find_compatible_node函数,查找与指定兼容性字符串匹配的节 点。#include linux/of.h // 函数功能通过 compatible 字符串查找设备树节点 // from : 从哪个节点开始找NULL 表示从根节点开始 // type : 设备类型对应 device_type一般填 NULL // compatible: 要匹配的字符串必须和设备树里 compatible 一模一样 // return : 找到返回节点指针找不到返回 NULL struct device_node *of_find_compatible_node( struct device_node *from, const char *type, const char *compatible);compatible rockchip,gpio-bank; of_find_compatible_node(NULL, NULL, rockchip,gpio-bank);4of_findmatchingnode_and_match函数用于根据给定的of_device_id匹 配表在设备树中查找匹配的节点。#include linux/of.h // 函数功能根据 of_device_id 匹配表 查找设备树节点 // from : 从哪个节点开始找NULL 从根节点开始 // matches : 匹配表驱动里的 compatible 列表 // match : 输出参数 - 返回找到的那条匹配项 // return : 找到返回节点指针失败返回 NULL struct device_node *of_find_matching_node_and_match( struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match);static const struct of_device_id my_match_table[] { { .compatible edt,edt-ft5406 }, { .compatible rockchip,gpio-bank }, { /* 结束 */ } }; const struct of_device_id *match; struct device_node *node; node of_find_matching_node_and_match( NULL, my_match_table, match); 结果 内核会遍历设备树找到第一个 compatible 匹配的节点。of操作函数实验获取属性1of_find_property函数用于在设备树中查找节点下具有指定名称的属性#include linux/of.h // 函数功能在设备节点中查找指定名字的**属性**如 compatible、reg // np : 设备节点指针你要查哪个节点 // name : 属性名字字符串例如 compatible、reg、status // lenp : 输出参数 - 保存属性值的**长度**字节数 // return : 找到返回 property 结构体指针失败返回 NULL struct property *of_find_property( const struct device_node *np, const char *name, int *lenp);int len; struct property *prop; // 查找节点里的 reg 属性 prop of_find_property(node, reg, len); if (prop) { // 找到了属性值长度 len }2of_property_count_elems_of_size函数 该函数在设备树中的设备节点下查找指定名称的属性并获取该属性中元素的数量#include linux/of.h // 函数功能计算设备树属性里**有多少个数据元素** // np : 设备节点 // propname : 属性名字如 reg、interrupts // elem_size : 单个元素的大小32位用464位用8 // 返回值 : 成功返回元素个数失败返回负数 int of_property_count_elems_of_size( const struct device_node *np, const char *propname, int elem_size);int cnt; cnt of_property_count_elems_of_size(node, reg, 4); if (cnt 0) { // 成功cnt 就是元素数量 } 计算reg属性有多少4字节的数据3of_property_read_u32_index函数 该函数在设备树中的设备节点下查找指定名称的属性并获取该属性在给定索引位置处的 u32类型的数据值#include linux/of.h // 函数功能从设备树属性中**读取第 index 个 32位无符号整数** // np : 设备节点指针 // propname : 要读取的属性名如 reg、clocks // index : 元素下标从 0 开始数 // out_value : 输出参数读取到的数值存在这里 // 返回值 : 成功返回 0失败返回负数 int of_property_read_u32_index( const struct device_node *np, const char *propname, u32 index, u32 *out_value);reg 0x1000 0x2000 0x3000; // 3个u32数字 u32 val; // 读取 reg 属性里 **第 1 个** 数值index1 of_property_read_u32_index(node, reg, 1, val); val 0x20004f_property_read_variable_u32_array函数 该函数用于从设备树中读取指定属性名的变长数组。通过提供设备节点、属性名和输出数 组的指针可以将设备树中的数组数据读取到指定的内存区域中。同时还需要指定数组的最 小大小和最大大小以确保读取到的数组符合预期的大小范围#include linux/of.h // 函数功能一次性读取**整个 u32 数组**变长、自动判断长度 // np : 设备节点 // propname : 属性名如 reg、interrupts、clocks // out_values : 读取出来的数组存这里 // sz_min : 最少要读多少个元素下限 // sz_max : 最多能读多少个元素上限 // 返回值 : 成功返回读到的元素个数失败返回负数 int of_property_read_variable_u32_array( const struct device_node *np, const char *propname, u32 *out_values, size_t sz_min, size_t sz_max);u32 reg_data[10]; int cnt; cnt of_property_read_variable_u32_array( node, reg, reg_data, 2, 10); if (cnt 0) { // 读取成功cnt 真实元素个数 }5of_property_read_string函数 该函数在设备树中的设备节点下查找指定名称的属性并获取该属性的字符串值最后返 回读取到的字符串的指针#include linux/of.h // 函数功能从设备树节点中**读取字符串属性** // np : 设备节点指针 // propname : 要读取的属性名如compatible, status // out_string : 输出参数二级指针保存读取到的字符串地址 // 返回值 : 成功返回 0失败返回负数 static inline int of_property_read_string( const struct device_node *np, const char *propname, const char **out_string);compatible edt,edt-ft5406; const char *str; // 定义一个字符串指针 // 读取 compatible 属性 of_property_read_string(node, compatible, str); str edt,edt-ft5406获取中断资源1;irq_of_parse_and_map函数 该函数的主要功能是解析设备节点的interrupts属性并将对应的中断号映射到系统的中 断号#include linux/of_irq.h // 函数功能解析设备树中的 interrupts 属性获取 Linux 内核中断号 // dev : 设备节点指针 // index : 中断索引号从 0 开始取第几个中断 // 返回值 : 成功返回 内核虚拟中断号失败返回 0 unsigned int irq_of_parse_and_map(struct device_node *dev, int index);2:函数irq_get_irq_data的作用是根据中断号获取对应的中断数据结构irq_data。#include linux/irq.h // 函数功能根据 Linux 中断号获取对应的 irq_data 结构体指针 // irq : 内核虚拟中断号来自 irq_of_parse_and_map // 返回值 : 成功返回 irq_data* 失败返回 NULL struct irq_data *irq_get_irq_data(unsigned int irq);3:of_irq_get函数 该函数的主要功能是从给定的设备节点的interrupts属性中解析并获取对应的中断号。#include linux/of_irq.h // 函数功能从设备树 interrupts 属性中直接获取内核可用中断号 // dev : 设备节点指针 // index : 取第几个中断从 0 开始 // 返回值 : 成功返回中断号正数失败返回负数 int of_irq_get(struct device_node *dev, int index);4irq_of_parse_and_map函数的主要功能是根据给定的平台设备和索引号获取对应的中断 号#include linux/platform_device.h // 函数功能从平台设备 platform_device 中获取中断号 // dev : 平台设备结构体指针 // num : 中断索引号从 0 开始取第几个中断 // 返回值: 成功返回中断号正数失败返回负数 int platform_get_irq(struct platform_device *dev, unsigned int num);5pio_to_irq函数 该函数的主要功能是将给定的GPIO编号转换为对应的中断号#include linux/gpio.h // 函数功能将 GPIO 编号 转换为 内核中断号 // gpio : GPIO 硬件编号 // 返回值 : 成功返回中断号失败返回负数 int gpio_to_irq(unsigned int gpio);6irq_get_trigger_type函数 该函数的主要功能是从给定的中断数据结构中提取中断触发类型。中断触发类型描述了中 断信号的触发条件例如边沿触发edge-triggered或电平触发level-triggered#include linux/irq.h // 函数功能获取中断的触发方式上升沿/下降沿/高电平/低电平 // d : 中断数据结构指针 irq_data // 返回值 : 触发类型标志位u32 u32 irqd_get_trigger_type(struct irq_data *d);例子如下static int my_platform_probe(struct platform_device *pdev) { printk(KERN_INFO my_platform_probe: Probing platform device\n); // 查找设备节点 mydevice_node of_find_node_by_name(NULL, myirq); // 解析和映射中断 irq irq_of_parse_and_map(mydevice_node, 0); printk(irq is %d\n, irq); // 获取中断数据结构 my_irq_data irq_get_irq_data(irq); // 获取中断触发类型 trigger_type irqd_get_trigger_type(my_irq_data); printk(trigger type is 0x%x\n, trigger_type); // 将GPIO转换为中断号 irq gpio_to_irq(101); printk(irq is %d\n, irq); // 从设备节点获取中断号 irq of_irq_get(mydevice_node, 0); printk(irq is %d\n, irq); // 获取平台设备的中断号 irq platform_get_irq(pdev, 0); printk(irq is %d\n, irq); return 0; }