嵌入式Linux设备树驱动开发 - dtsof驱动
嵌入式Linux设备树驱动开发 - dtsof驱动
一、项目概述
本项目实现了一个基于设备树(Device Tree)的驱动程序,用于解析IMX6ULL开发板的设备树信息。该驱动程序主要演示了如何在Linux内核模块中访问和解析设备树中的属性信息。
二、驱动程序分析 (dtsof.c)
1. 头文件与宏定义
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h> // 设备树相关头文件
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h> // kmalloc/kfree相关// 模块初始化和退出函数声明
static int __init dtsof_init(void);
static void __exit dtsof_exit(void);
2. 设备树解析函数
模块初始化函数
static int __init dtsof_init(void)
{int ret = 0;struct device_node *bl_nd = NULL; // 设备节点指针struct property *compprop = NULL; // 属性指针const char *status; // 状态属性值u32 def_value = 0; // 默认亮度值u32 elemsize = 0; // 数组元素个数u32 *brival; // 亮度值数组指针/* 查找backlight节点 */bl_nd = of_find_node_by_path("/backlight");if (bl_nd == NULL){ret = -ENODEV;goto fail_findnode;}/* 查找compatible属性 */compprop = of_find_property(bl_nd, "compatible", NULL);if (compprop == NULL){ret = -ENOENT;goto fail_findprop;}else{printk(KERN_INFO "Compatible property found: %s\r\n", (char *)compprop->value);}/* 读取status属性 */ret = of_property_read_string(bl_nd, "status", &status);if (ret < 0) {goto fail_findprop;} else {printk(KERN_INFO "Status property value: %s\n", status);}/* 读取default-brightness-level属性 */ret = of_property_read_u32(bl_nd, "default-brightness-level", &def_value);if (ret < 0) {goto fail_findprop;} else {printk(KERN_INFO "defult-brightness-level property value: %u\n", def_value);}/* 获取brightness-levels属性元素个数 */elemsize = of_property_count_elems_of_size(bl_nd, "brightness-levels", sizeof(u32));if (elemsize < 0) {goto fail_findprop;} else {printk(KERN_INFO "Number of brightness-level elements: %d\n", elemsize);}/* 分配内存用于存储亮度值数组 */brival = kmalloc(elemsize * sizeof(u32), GFP_KERNEL);if (!brival) {ret = -ENOMEM;goto fail_kmalloc;}/* 读取brightness-levels属性数组 */ret = of_property_read_u32_array(bl_nd, "brightness-levels", brival, elemsize);if (ret < 0) {goto fail_findprop;} else {int i;printk(KERN_INFO "Brightness levels: ");for (i = 0; i < elemsize; i++) {printk(KERN_CONT "%u ", brival[i]);}printk(KERN_CONT "\n");}kfree(brival); // 释放内存return 0;fail_kmalloc:kfree(brival);fail_findprop:fail_findnode:return ret;
}
模块退出函数
static void __exit dtsof_exit(void)
{printk(KERN_INFO "dtsof module exited\n");
}
3. 模块注册
module_init(dtsof_init);
module_exit(dtsof_exit);
4. 模块信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("alientek");
三、设备树文件分析 (imx6ull-alientek-emmc.dts)
1. 设备树基本结构
/dts-v1/;#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"/ {model = "Freescale i.MX6 ULL 14x14 EVK Board";compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";/* 标准输出配置 */chosen {stdout-path = &uart1;};/* 内存配置 */memory {reg = <0x80000000 0x20000000>;};/* 保留内存区域 */reserved-memory {#address-cells = <1>;#size-cells = <1>;ranges;linux,cma {compatible = "shared-dma-pool";reusable;size = <0x14000000>;linux,cma-default;};};/* 背光配置节点 */backlight {compatible = "pwm-backlight";pwms = <&pwm1 0 5000000>;brightness-levels = <0 4 8 16 32 64 128 255>;default-brightness-level = <6>;status = "okay";};/* 其他外设配置... */
};
2. 重要配置节点
backlight节点
backlight {compatible = "pwm-backlight";pwms = <&pwm1 0 5000000>; // 使用PWM1,周期5000000nsbrightness-levels = <0 4 8 16 32 64 128 255>; // 亮度等级default-brightness-level = <6>; // 默认亮度等级status = "okay"; // 状态启用
};
pwm1配置
&pwm1 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_pwm1>;status = "okay";
};
pinctrl_pwm1配置
pinctrl_pwm1: pwm1grp {fsl,pins = <MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0>;
};
四、Makefile分析
KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := dtsof.o # 编译成内核模块build : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modules # 进入内核目录编译模块clean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean # 清理编译结果
五、设备树基础理论
1. 设备树基本概念
设备树(Device Tree)是一个描述硬件配置的数据结构,它将硬件信息从内核代码中分离出来,使得同一个内核可以支持多种硬件平台。
2. 设备树基本格式
- 节点: 用斜杠斜杠包裹的路径表示,如
/backlight
- 属性: 每个节点可以包含多个属性,属性值可以是字符串、32位整数或它们的数组
- 兼容性:
compatible
属性用于匹配驱动程序 - 状态:
status
属性表示设备状态,"okay"表示启用
3. 常用设备树属性
compatible
: 驱动匹配字符串reg
: 寄存器地址范围interrupts
: 中断配置clocks
: 时钟配置pwms
: PWM配置brightness-levels
: 亮度等级数组default-brightness-level
: 默认亮度等级gpios
: GPIO配置
六、设备树操作函数详解
1. of_find_node_by_path
struct device_node *of_find_node_by_path(const char *path);
功能:通过路径查找设备树节点
参数:path - 设备树节点路径
返回值:成功返回设备节点指针,失败返回NULL
2. of_find_property
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
功能:查找设备节点的属性
参数:
- np - 设备节点指针
- name - 属性名称
- lenp - 返回属性值长度
返回值:成功返回属性指针,失败返回NULL
3. of_property_read_string
int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
功能:读取字符串类型的属性值
参数:
- np - 设备节点指针
- propname - 属性名称
- out_string - 输出的字符串指针
返回值:成功返回0,失败返回错误码
4. of_property_read_u32
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
功能:读取u32类型的属性值
参数:
- np - 设备节点指针
- propname - 属性名称
- out_value - 输出的u32值
返回值:成功返回0,失败返回错误码
5. of_property_count_elems_of_size
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);
功能:计算属性值中元素的数量
参数:
- np - 设备节点指针
- propname - 属性名称
- elem_size - 元素大小
返回值:成功返回元素数量,失败返回错误码
6. of_property_read_u32_array
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t count);
功能:读取u32数组类型的属性值
参数:
- np - 设备节点指针
- propname - 属性名称
- out_values - 输出的u32数组
- count - 要读取的元素个数
返回值:成功返回0,失败返回错误码
七、驱动编译与测试流程
1. 驱动编译
make -C /path/to/kernel/source M=$(pwd) modules
2. 加载驱动
insmod dtsof.ko
3. 查看内核日志
dmesg | grep "dtsof"
4. 卸载驱动
rmmod dtsof.ko
八、设备树在嵌入式Linux中的作用
1. 硬件描述
设备树通过结构化的方式描述硬件平台的硬件配置,包括:
- 外设类型和型号
- 寄存器地址空间
- 中断配置
- 时钟配置
- 外设参数配置
2. 驱动匹配
通过compatible
属性,Linux内核可以将驱动程序与设备树中的设备节点进行匹配。
3. 资源分配
设备树提供了一种机制,让驱动程序可以查询硬件资源,如:
- 内存地址
- 中断号
- 时钟源
- GPIO引脚
4. 平台抽象
设备树使得Linux内核可以与硬件平台解耦,同一个内核可以支持多种不同的硬件平台。
九、设备树驱动开发要点
1. 错误处理
- 检查设备节点和属性是否为空
- 处理内存分配失败情况
- 返回合适的错误码
- 使用goto进行错误清理
2. 内存管理
- 使用kmalloc/kfree进行动态内存分配
- 确保所有分配的内存都被正确释放
- 使用合适的内存分配标志(GFP_KERNEL)
3. 日志输出
- 使用printk输出调试信息
- 使用合适的日志级别(KERN_INFO, KERN_CONT等)
- 提供详细的属性解析信息
4. 错误码使用
- 使用标准Linux错误码(-ENODEV, -ENOENT, -ENOMEM等)
- 确保错误码正确传递
- 在模块加载时返回合适的错误码
Giee 源码仓库