| 维度 | 主设备树 (Device Tree) | 设备树插件 (DT Overlay) |
|---|---|---|
| 类似 | 原始蓝图 | 插件查到蓝图里面 |
| 文件后缀 | .dts (源码) -> .dtb (二进制) |
.dts (源码) -> .dtbo (二进制) |
| 修改方式 | 直接修改内核源码。 | 新建一个独立的文件。即插即用。 |
| 编译成本 | 慢。通常需要重新编译所有关联的 DTB。 | 快。只编译这一个小文件。 |
| 生效逻辑 | 覆盖。必须替换启动分区的系统文件,风险大。 | 叠加。在原系统基础上"打补丁",风险小。 |
| 依赖关系 | 是基础。定义了 &gpioa 是什么。 |
是上层。引用基础,"要用到 &gpioa"。 |
设备树插件,顾名思义新建插入。我们不用改设备树源码只需要在内核源码的arch/arm/boot/dts/overlays/ 目录下新建一个.dts作为插件,编译后内核能够动态加载它。
设备树插件格式:
c
/* * 文件名: fire-stm32mp157-overlay.dts
* 描述: STM32MP157 设备树插件模板 (现代语法)
*/
/* 1. 头部声明 (必须有) */
/* 告诉编译器版本 */
/dts-v1/;
/* 告诉编译器这是一个"插件",不是完整设备树 */
/plugin/;
/* * 2. 场景一:在根节点下"无中生有"添加新设备
* 语法:&{/} 代表引用根节点
*/
&{/} {
/* 自定义的 RGB 灯节点 */
fire_rgb_led {
/* 匹配暗号,驱动程序靠这个找到我 */
compatible = "rgb-led";
/* 硬件资源描述 */
/* 注意: &gpioa 需要主设备树里有这个标签 */
/* 如果报错找不到 &gpioa,请往下看注意事项 */
red-gpios = <&gpioa 13 0>;
green-gpios = <&gpioa 14 0>;
status = "okay";
};
/* 另一个测试节点 */
my_test_node {
compatible = "test";
my-id = <100>;
};
};
/* * 3. 场景二:修改/开启板子上已有的设备
* 语法:直接引用标签 (例如 &uart4, &i2c1)
*/
&uart4 {
/* 原本可能是 disabled,我们把它打开 */
status = "okay";
/* 我们还可以修改它的属性,比如波特率 */
/* current-speed = <115200>; */
};
&i2c1 {
status = "okay";
/* 在 I2C 总线上挂载一个从设备 */
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
如果您编译插件时报错:Reference to non-existent node or label "gpioa"(找不到标签 gpioa)。要使用绝对路径引用法
c
&gpioa { ... };//修改为以下内容
/* 使用 fragment 语法 + 绝对路径 target-path */
fragment@0 {
/* 去查手册或源码,找到 gpioa 的绝对路径 */
target-path = "/soc/pin-controller/gpio@50002000";
__overlay__ {
/* 这里写内容 */
};
};
驱动设备树插件编译使用流程
概览流程
html
1.准备环境: 打开两个终端窗口。
窗口 A:进内核源码目录(编译插件用)。/arch/arm/boot/dts/overlays
窗口 B:进驱动代码目录(编译驱动用)。/linux_driver/dynamic_device_tree/overlay_rgb_led
2.搞定硬件(设备树插件):
在窗口 B 写好 .dts。
把它复制到窗口 A 的 arch/arm/boot/dts/overlays/,并修改Makfile文件-添加设备树插件文件。
在窗口 A 根运行 make dtbs。
一般情况乱下生成的.dtbo 位于内核根目录下的"/arch/arm/boot/dts/overlays"目录下。
我们为了不污染源吗我们设定O=... 参数,保持源码目录干净。
我们采用如下方式:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- O=build_image/build -j4 dtbs
把生成的 .dtbo 扔到板子上重启。
3.搞定软件(驱动):
在窗口 B 写好 .c。
在窗口 B 运行 make。
把生成的 .ko 扔到板子上加载。
设备树代码文件fire-rgb-led-overlay.dts
c
/*
* 野火 STM32MP1 RGB LED 设备树插件 (现代语法版)
*/
/dts-v1/;
/plugin/;
/* 引用必要的头文件 */
#include <dt-bindings/pinctrl/stm32-pinfunc.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/mfd/st,stpmic1.h>
#include <dt-bindings/gpio/gpio.h>
/* * 现代写法核心:
* 使用 &{/} 直接引用根节点,替代 fragment@0 + target-path + __overlay__
*/
&{/} {
/* 添加 led 父节点 */
rgb_led {//节点名
#address-cells = <1>;
#size-cells = <1>;
compatible = "rgb_led";
ranges; /* 允许子节点继承父节点的地址空间 */
/* -------------------------------------------------
* 红灯节点 (GPIOA 13)
* 包含 GPIOA 的 5 个寄存器 + RCC 时钟寄存器,顺序根据驱动中的顺序改变的
* ------------------------------------------------- */
rgb_led_red@0x50002000 {//节点名
compatible = "fire,rgb_led_red";//匹配名
reg = <
0x50002000 0x4 /* GPIOA_MODER */
0x50002004 0x4 /* GPIOA_OTYPER */
0x50002008 0x4 /* GPIOA_OSPEEDR */
0x5000200C 0x4 /* GPIOA_PUPDR */
0x50002018 0x4 /* GPIOA_BSRR */
0x50000A28 0x4 /* RCC_MP_AHB4ENSETR (时钟) */
>;
status = "okay";
};
/* -------------------------------------------------
* 绿灯节点 (GPIOG 2)
* 包含 GPIOG 的 5 个寄存器 + RCC 时钟寄存器
* ------------------------------------------------- */
rgb_led_green@0x50008000 {
compatible = "fire,rgb_led_green";
reg = <
0x50008000 0x4 /* GPIOG_MODER */
0x50008004 0x4 /* GPIOG_OTYPER */
0x50008008 0x4 /* GPIOG_OSPEEDR */
0x5000800C 0x4 /* GPIOG_PUPDR */
0x50008018 0x4 /* GPIOG_BSRR */
0x50000A28 0x4 /* RCC_MP_AHB4ENSETR */
>;
status = "okay";
};
/* -------------------------------------------------
* 蓝灯节点 (GPIOB 5)
* 包含 GPIOB 的 5 个寄存器 + RCC 时钟寄存器
* ------------------------------------------------- */
rgb_led_blue@0x50003000 {
compatible = "fire,rgb_led_blue";
reg = <
0x50003000 0x4 /* GPIOB_MODER */
0x50003004 0x4 /* GPIOB_OTYPER */
0x50003008 0x4 /* GPIOB_OSPEEDR */
0x5000300C 0x4 /* GPIOB_PUPDR */
0x50003018 0x4 /* GPIOB_BSRR */
0x50000A28 0x4 /* RCC_MP_AHB4ENSETR */
>;
status = "okay";
};
};
};
驱动代码文件rgb_led.c
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
/*------------------字符设备内容----------------------*/
#define DEV_NAME "rgb_led"
#define DEV_CNT (1)
/*定义 led 资源结构体,保存获取得到的节点信息以及转换后的虚拟寄存器地址*/
struct led_resource
{
struct device_node *device_node; //rgb_led_red的设备树节点
void __iomem *va_MODER;
void __iomem *va_OTYPER;
void __iomem *va_OSPEEDR;
void __iomem *va_PUPDR;
void __iomem *va_BSRR;
};
static void __iomem *va_clkaddr;
static dev_t led_devno; //定义字符设备的设备号
static struct cdev led_chr_dev; //定义字符设备结构体chr_dev
struct class *class_led; //保存创建的类
struct device *device; // 保存创建的设备
struct device_node *rgb_led_device_node; //rgb_led的设备树节点结构体
/*定义 R G B 三个灯的led_resource 结构体,保存获取得到的节点信息*/
struct led_resource led_red;
struct led_resource led_green;
struct led_resource led_blue;
/*字符设备操作函数集,open函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
printk("\n open form driver \n");
return 0;
}
/*字符设备操作函数集,write函数*/
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
unsigned int register_data = 0; //暂存读取得到的寄存器数据
unsigned char write_data; //用于保存接收到的数据
int error = copy_from_user(&write_data, buf, cnt);
if (error < 0)
{
return -1;
}
// 开启a、b、g的时钟
writel(0x43, va_clkaddr);
//write_data & 0x04 找到对应的GPIO引脚
/*设置 GPIOA13 输出电平*/
//高 16 位 写入 1 (低电平)电压差 ---> 导致 灯开。 低 16 位 写入 1 (高电平)两端高电平 ---> 导致 灯关。(对应pin脚)
if (write_data & 0x04)
{
register_data |= (0x01 << (13+16));
writel(register_data, led_red.va_BSRR); // GPIOA13引脚输出低电平,红灯亮
}
else
{
register_data |= (0x01 << (13));
writel(register_data, led_red.va_BSRR); // GPIOA13引脚输出高电平,红灯灭
}
/*设置 GPIOG2 输出电平*/
if (write_data & 0x02)
{
register_data |= (0x01 << (2+16));
writel(register_data, led_green.va_BSRR); // GPIOG2引脚输出低电平,绿灯亮
}
else
{
register_data |= (0x01 << (2));
writel(register_data, led_green.va_BSRR); // GPIOG2引脚输出高电平,绿灯灭
}
/*设置 GPIOB5 输出电平*/
if (write_data & 0x01)
{
register_data |= (0x01 << (5+16));
writel(register_data, led_blue.va_BSRR); //GPIOB5引脚输出低电平,蓝灯亮
}
else
{
register_data |= (0x01 << (5));
writel(register_data, led_blue.va_BSRR); //GPIOB5引脚输出高电平,蓝灯灭
}
return 0;
}
/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = led_chr_dev_open,
.write = led_chr_dev_write,
};
/*----------------平台驱动函数集-----------------*/
static int led_probe(struct platform_device *pdv)
{
int ret = -1; //保存错误状态码
unsigned int register_data = 0;
printk(KERN_EMERG "\t match successed \n");
//设备树编写部分 就是先设备树插件的rgb_led节点
//然后在rgb_led节点下编写rgb_led_red、rgb_led_green、rgb_led_blue子节点
//最后在每个子节点下编写reg属性以及时钟属性
//驱动程序部分 先获取rgb_led节点,然后分别获取rgb_led_red、rgb_led_green、rgb_led_blue子节点
//最后获取每个子节点下的reg属性并转换为虚拟地址
/*获取rgb_led的设备树节点*/
rgb_led_device_node = of_find_node_by_path("/rgb_led");
if (rgb_led_device_node == NULL)
{
printk(KERN_ERR "\t get rgb_led failed! \n");
return -1;
}
/*获取rgb_led节点的红灯子节点*/
led_red.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_red");
if (led_red.device_node == NULL)
{
printk(KERN_ERR "\n get rgb_led_red_device_node failed ! \n");
return -1;
}
/*获取 reg 属性并转化为虚拟地址*/
led_red.va_MODER = of_iomap(led_red.device_node, 0);
led_red.va_OTYPER = of_iomap(led_red.device_node, 1);
led_red.va_OSPEEDR = of_iomap(led_red.device_node, 2);
led_red.va_PUPDR = of_iomap(led_red.device_node, 3);
led_red.va_BSRR = of_iomap(led_red.device_node, 4);
va_clkaddr = of_iomap(led_red.device_node, 5);
register_data = readl(va_clkaddr);
// 开启a、b、g的时钟
register_data |= (0x43); // 开启a、b、g的时钟
writel(register_data, va_clkaddr);
// 设置模式寄存器:输出模式
register_data = readl(led_red.va_MODER);
register_data &= ~((unsigned int)0X3 << (2 * 13));
register_data |= ((unsigned int)0X1 << (2 * 13));
writel(register_data,led_red.va_MODER);
// 设置输出类型寄存器:推挽模式
register_data = readl(led_red.va_OTYPER);
register_data &= ~((unsigned int)0X1 << 13);
writel(register_data, led_red.va_OTYPER);
// 设置输出速度寄存器:高速
register_data = readl(led_red.va_OSPEEDR);
register_data &= ~((unsigned int)0X3 << (2 * 13));
register_data |= ((unsigned int)0x2 << (2 * 13));
writel(register_data, led_red.va_OSPEEDR);
// 设置上下拉寄存器:上拉
register_data = readl(led_red.va_PUPDR);
register_data &= ~((unsigned int)0X3 << (2*13));
register_data |= ((unsigned int)0x1 << (2*13));
writel(register_data,led_red.va_PUPDR);
// 设置置位寄存器:默认输出高电平
register_data = readl(led_red.va_BSRR);
register_data |= ((unsigned int)0x1 << (13));
writel(register_data, led_red.va_BSRR);
/*获取rgb_led节点的绿灯子节点*/
led_green.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_green");
if (led_green.device_node == NULL)
{
printk(KERN_ERR "\n get rgb_led_green_device_node failed ! \n");
return -1;
}
/*获取 reg 属性并转化为虚拟地址*/
led_green.va_MODER = of_iomap(led_green.device_node, 0);
led_green.va_OTYPER = of_iomap(led_green.device_node, 1);
led_green.va_OSPEEDR = of_iomap(led_green.device_node, 2);
led_green.va_PUPDR = of_iomap(led_green.device_node, 3);
led_green.va_BSRR = of_iomap(led_green.device_node, 4);
// 设置模式寄存器:输出模式
register_data = readl(led_green.va_MODER);
register_data &= ~((unsigned int)0X3 << (2 * 2));
register_data |= ((unsigned int)0X1 << (2 * 2));
writel(register_data,led_green.va_MODER);
// 设置输出类型寄存器:推挽模式
register_data = readl(led_green.va_OTYPER);
register_data &= ~((unsigned int)0X1 << 2);
writel(register_data, led_green.va_OTYPER);
// 设置输出速度寄存器:高速
register_data = readl(led_green.va_OSPEEDR);
register_data &= ~((unsigned int)0X3 << (2 * 2));
register_data |= ((unsigned int)0x2 << (2 * 2));
writel(register_data, led_green.va_OSPEEDR);
// 设置上下拉寄存器:上拉
register_data = readl(led_green.va_PUPDR);
register_data &= ~((unsigned int)0X3 << (2*2));
register_data |= ((unsigned int)0x1 << (2*2));
writel(register_data,led_green.va_PUPDR);
// 设置置位寄存器:默认输出高电平
register_data = readl(led_green.va_BSRR);
register_data |= ((unsigned int)0x1 << (2));
writel(register_data, led_green.va_BSRR);
/*获取rgb_led节点的蓝灯子节点*/
led_blue.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_blue");
if (led_blue.device_node == NULL)
{
printk(KERN_ERR "\n get rgb_led_blue_device_node failed ! \n");
return -1;
}
/*获取 reg 属性并转化为虚拟地址*/
led_blue.va_MODER = of_iomap(led_blue.device_node, 0);
led_blue.va_OTYPER = of_iomap(led_blue.device_node, 1);
led_blue.va_OSPEEDR = of_iomap(led_blue.device_node, 2);
led_blue.va_PUPDR = of_iomap(led_blue.device_node, 3);
led_blue.va_BSRR = of_iomap(led_blue.device_node, 4);
// 设置模式寄存器:输出模式
register_data = readl(led_blue.va_MODER);
register_data &= ~((unsigned int)0X3 << (2 * 5));
register_data |= ((unsigned int)0X1 << (2 * 5));
writel(register_data,led_blue.va_MODER);
// 设置输出类型寄存器:推挽模式
register_data = readl(led_blue.va_OTYPER);
register_data &= ~((unsigned int)0X1 << 5);
writel(register_data, led_blue.va_OTYPER);
// 设置输出速度寄存器:高速
register_data = readl(led_blue.va_OSPEEDR);
register_data &= ~((unsigned int)0X3 << (2 * 5));
register_data |= ((unsigned int)0x2 << (2 * 5));
writel(register_data, led_blue.va_OSPEEDR);
// 设置上下拉寄存器:上拉
register_data = readl(led_blue.va_PUPDR);
register_data &= ~((unsigned int)0X3 << (2*5));
register_data |= ((unsigned int)0x1 << (2*5));
writel(register_data,led_blue.va_PUPDR);
// 设置置位寄存器:默认输出高电平
register_data = readl(led_blue.va_BSRR);
register_data |= ((unsigned int)0x1 << (5));
writel(register_data, led_blue.va_BSRR);
/*---------------------注册 字符设备部分-----------------*/
//第一步
//采用动态分配的方式,获取设备编号,次设备号为0,
//设备名称为rgb-leds,可通过命令cat /proc/devices查看
//DEV_CNT为1,当前只申请一个设备编号
ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
if (ret < 0)
{
printk("fail to alloc led_devno\n");
goto alloc_err;
}
//第二步
//关联字符设备结构体cdev与文件操作结构体file_operations
led_chr_dev.owner = THIS_MODULE;
cdev_init(&led_chr_dev, &led_chr_dev_fops);
//第三步
//添加设备至cdev_map散列表中
ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
if (ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
//第四步
/*创建类 */
class_led = class_create(THIS_MODULE, DEV_NAME);
/*创建设备*/
device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
return 0;
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(led_devno, DEV_CNT);
printk("\n error! \n");
alloc_err:
return -1;
}
static const struct of_device_id rgb_led[] = {
{.compatible = "fire,rgb_led"},
{/* sentinel */}};
/*定义平台设备结构体*/
struct platform_driver led_platform_driver = {
.probe = led_probe,
.driver = {
.name = "rgb-leds-platform",
.owner = THIS_MODULE,
.of_match_table = rgb_led,
}};
/*
*驱动初始化函数
*/
static int __init led_platform_driver_init(void)
{
int DriverState;
DriverState = platform_driver_register(&led_platform_driver);
printk(KERN_EMERG "\tDriverState is %d\n", DriverState);
return 0;
}
/*
*驱动注销函数
*/
static void __exit led_platform_driver_exit(void)
{
/*取消物理地址映射到虚拟地址*/
iounmap(va_clkaddr);
iounmap(led_green.va_MODER);
iounmap(led_green.va_OTYPER);
iounmap(led_green.va_OSPEEDR);
iounmap(led_green.va_PUPDR);
iounmap(led_green.va_BSRR);
iounmap(led_red.va_MODER);
iounmap(led_red.va_OTYPER);
iounmap(led_red.va_OSPEEDR);
iounmap(led_red.va_PUPDR);
iounmap(led_red.va_BSRR);
iounmap(led_blue.va_MODER);
iounmap(led_blue.va_OTYPER);
iounmap(led_blue.va_OSPEEDR);
iounmap(led_blue.va_PUPDR);
iounmap(led_blue.va_BSRR);
/*删除设备*/
device_destroy(class_led, led_devno); //清除设备
class_destroy(class_led); //清除类
cdev_del(&led_chr_dev); //清除设备号
unregister_chrdev_region(led_devno, DEV_CNT); //取消注册字符设备
/*注销字符设备*/
platform_driver_unregister(&led_platform_driver);
printk(KERN_EMERG "HELLO WORLD exit!\n");
}
module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);
MODULE_LICENSE("GPL");
由于板子引脚可能被占用,因此引脚被占用后,设备树可能无法再加载或驱动中无法再申请对应的资源。到boot下的uEnv.txt中注销对应led插件加#,之后重启
编译好的文件同理放到板子/usr/lib/linux-image-4.19.94-stm-r1/overlays/目录下,然后uEnv.txt中进行注册
整体流程串联
html
设备树插件、驱动、总线工作流程
1.设备树插件加载 (Overlay Loaded):
把 .dtbo 加载到系统时,内核会解析它,发现里面描述了一个新硬件( fire,rgb-led)。
内核会自动在 平台总线 上创建一个新的 "平台设备" (Platform Device)。
compatible和具体硬件信息
2.驱动加载 (Driver Loaded):
当insmod rgb_led.ko 时,驱动程序向 平台总线 注册自己。
总线上多了一个驱动,里面有compatible信息
3.总线匹配 (Bus Match):
平台总线发现新来的设备和驱动的Compatible一致。
总线把它们俩"绑"在一起,并触发驱动的 probe 函数。