Linux系统LED驱动程序源码分析
Linux系统LED驱动程序源码分析
一、概述
本文将对Linux系统LED驱动程序ledsdrv.c和设备树文件进行详细分析,介绍驱动的实现原理、设备树配置以及使用方法。
二、设备树配置分析
2.1 设备树结构
/dts-v1/;
/{
leds {
compatible = "gpio-leds";
status = "okay";
led@0 {
label = "heartbeat";
gpios = <&gpio PG 1 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
led@1 {
label = "alarm";
gpios = <&gpio PG 2 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "alarm";
};
};
};
2.2 关键节点解析
-
leds节点:
compatible = "gpio-leds":指定设备兼容性,与驱动中的匹配表对应status = "okay":表示设备处于启用状态
-
led@0节点:
label = "heartbeat":LED名称标签gpios = <&pio PG 1 GPIO_ACTIVE_HIGH>:指定GPIO引脚,使用PG1引脚,高电平有效linux,default-trigger = "heartbeat":默认触发方式为心跳模式
-
led@1节点:
label = "alarm":LED名称标签gpios = <&pio PG 2 GPIO_ACTIVE_HIGH>:指定GPIO引脚,使用PG2引脚,高电平有效linux,default-trigger = "alarm":默认触发方式为报警模式
三、驱动程序分析(ledsdrv.c)
3.1 头文件包含
#include // 内核模块基础头文件
#include // 平台设备驱动框架头文件
#include // 设备树操作头文件
#include // 设备树GPIO操作头文件
#include // sysfs文件系统操作头文件
#include // kobject对象操作头文件
#include // GPIO操作头文件
#include // 字符串操作头文件
3.2 结构体定义
3.2.1 LED设备信息结构体
// LED设备信息结构体
struct led_info {
const char *label; // LED名称标签
int gpio; // LED对应的GPIO编号
int active_low; // 是否低电平有效
struct kobject *kobj; // 对应sysfs节点的kobject对象
struct kobj_attribute attr; // sysfs属性结构体
};
3.2.2 驱动私有数据结构体
// LED驱动私有数据结构体
struct ledsdrv_data {
int num_leds; // LED数量
struct led_info *leds; // LED设备信息数组
};
3.3 核心函数实现
3.3.1 state_show函数
state_show是sysfs节点的state属性读操作函数,当用户读取/sys/…/state文件时调用此函数,返回LED的当前状态(0表示熄灭,1表示点亮)。
// sysfs节点的state属性读操作函数
// 当用户读取/sys/.../state文件时调用此函数
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
// 通过container_of宏从attr指针反向获取led_info结构体指针
struct led_info *led = container_of(attr, struct led_info, attr);
int value;
// 读取GPIO当前电平值
value = gpio_get_value(led->gpio);
// 如果是低电平有效,则反转值(0变1,1变0)
if (led->active_low)
value = !value;
// 将状态值写入buf,返回写入的字节数
return sprintf(buf, "%d
", value);
}
3.3.1.1 关键技术点解析
1. container_of宏
container_of是Linux内核中常用的宏,用于从结构体成员指针反向获取结构体指针- 语法:
container_of(ptr, type, member) - 作用:根据
attr指针(结构体成员)获取led_info结构体指针
2. GPIO状态读取
gpio_get_value:读取GPIO的当前电平值(0或1)- 考虑
active_low属性:如果LED是低电平有效,则反转读取到的值
3. sysfs接口
sprintf:将LED状态格式化为字符串写入buf- 返回值:写入的字节数,内核会根据此值更新文件指针
3.3.2 state_store函数
state_store是sysfs节点的state属性写操作函数,当用户向/sys/…/state文件写入数据时调用此函数,控制LED的亮灭。
// sysfs节点的state属性写操作函数
// 当用户向/sys/.../state文件写入数据时调用此函数
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
// 通过container_of宏从attr指针反向获取led_info结构体指针
struct led_info *led = container_of(attr, struct led_info, attr);
unsigned int value;
int ret;
// 将用户输入的字符串转换为无符号整数
ret = kstrtouint(buf, 10, &value);
if (ret < 0)
return ret; // 转换失败,返回错误码
// 检查输入值是否为0或1
if (value != 0 && value != 1)
return -EINVAL; // 输入无效,返回无效参数错误
// 如果是低电平有效,则反转值(0变1,1变0)
if (led->active_low)
value = !value;
// 设置GPIO电平值,控制LED亮灭
gpio_set_value(led->gpio, value);
// 返回写入的字节数,表示成功
return count;
}
3.3.2.1 关键技术点解析
1. 字符串转换
kstrtouint:将用户输入的字符串转换为无符号整数,支持错误检查- 比
atoi更安全,因为它会检查输入是否有效
2. 输入验证
- 检查输入值是否为0或1,确保用户只能输入有效的LED状态
- 无效输入返回
-EINVAL错误码
3. GPIO状态设置
gpio_set_value:设置GPIO的电平值,控制LED的亮灭- 考虑
active_low属性:如果LED是低电平有效,则反转输入值
4. 返回值处理
- 返回用户写入的字节数,表示成功
- 内核会根据返回值更新文件指针
3.3.3 ledsdrv_probe函数
ledsdrv_probe是平台设备驱动的核心初始化函数,当设备树中的compatible属性与驱动匹配时,内核会调用此函数完成LED设备的初始化。该函数的主要职责包括:
- 内存分配:为驱动私有数据结构体和LED设备信息数组分配内存
- 设备树解析:从设备树中读取LED的配置信息
- GPIO资源请求:请求并初始化LED对应的GPIO引脚
- sysfs节点创建:为每个LED创建sysfs节点,提供用户空间控制接口
// 平台设备驱动的probe函数
// 当设备与驱动匹配成功时调用此函数,初始化LED设备
static int ledsdrv_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev; // 获取设备结构体指针
struct device_node *np = dev->of_node; // 获取设备树节点指针
struct ledsdrv_data *drv_data; // 驱动私有数据结构体指针
int i, ret;
// 分配驱动私有内存,使用devm_kzalloc自动管理内存
drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL);
if (!drv_data)
return -ENOMEM; // 内存分配失败,返回内存不足错误
// 获取设备树节点的子节点数量(即LED数量)
drv_data->num_leds = of_get_child_count(np);
if (drv_data->num_leds == 0) {
dev_err(dev, "No LED nodes found in device tree
");
return -ENODEV; // 未找到LED节点,返回设备不存在错误
}
// 分配LED设备信息数组内存
drv_data->leds = devm_kcalloc(dev, drv_data->num_leds,
sizeof(struct led_info), GFP_KERNEL);
if (!drv_data->leds)
return -ENOMEM; // 内存分配失败
i = 0;
// 遍历所有LED子节点
for_each_child_of_node(np, child) {
struct led_info *led = &drv_data->leds[i];
const char *label;
// 读取LED节点的label属性
ret = of_property_read_string(child, "label", &label);
if (ret < 0) {
dev_err(dev, "LED node missing label property
");
of_node_put(child);
return ret; // 读取失败,返回错误码
}
// 复制label字符串到内核内存
led->label = devm_kstrdup(dev, label, GFP_KERNEL);
if (!led->label) {
of_node_put(child);
return -ENOMEM; // 内存分配失败
}
// 读取LED节点的gpios属性,获取GPIO编号
led->gpio = of_get_named_gpio(child, "gpios", 0);
if (!gpio_is_valid(led->gpio)) {
dev_err(dev, "Invalid GPIO for LED %s
", led->label);
of_node_put(child);
return -EINVAL; // GPIO无效,返回无效参数错误
}
// 读取LED节点的active-low属性,判断是否低电平有效
led->active_low = of_property_read_bool(child, "active-low");
// 请求GPIO资源,初始化为低电平
ret = devm_gpio_request_one(dev, led->gpio, GPIOF_OUT_INIT_LOW,
led->label);
if (ret < 0) {
dev_err(dev, "Failed to request GPIO %d for LED %s
",
led->gpio, led->label);
of_node_put(child);
return ret; // 请求GPIO失败
}
// 创建sysfs节点,名称为LED的label
led->kobj = kobject_create_and_add(led->label, NULL);
if (!led->kobj) {
dev_err(dev, "Failed to create kobject for LED %s
", led->label);
of_node_put(child);
return -ENOMEM; // 创建kobject失败
}
// 初始化sysfs属性结构体
memset(&led->attr, 0, sizeof(led->attr));
led->attr.attr.name = "state";
led->attr.attr.mode = 0644;
led->attr.show = state_show;
led->attr.store = state_store;
// 在sysfs节点中创建state属性文件
ret = sysfs_create_file(led->kobj, &led->attr.attr);
if (ret < 0) {
dev_err(dev, "Failed to create sysfs file for LED %s
", led->label);
kobject_put(led->kobj); // 释放kobject资源
of_node_put(child);
return ret; // 创建sysfs文件失败
}
i++;
}
// 将驱动私有数据保存到平台设备中
platform_set_drvdata(pdev, drv_data);
// 打印驱动初始化成功信息
dev_info(dev, "Custom LED driver probed successfully with %d LEDs
",
drv_data->num_leds);
return 0; // 初始化成功
}
3.3.3.1 关键技术点解析
1. 内存管理
- 使用
devm_kzalloc和devm_kcalloc进行内存分配,这些函数会自动管理内存,当设备被移除时自动释放内存,避免内存泄漏 GFP_KERNEL表示使用内核内存分配器,允许睡眠等待内存
2. 设备树解析
of_get_child_count:获取设备树节点的子节点数量,即LED的数量of_property_read_string:读取设备树节点的字符串属性,如LED的labelof_get_named_gpio:读取设备树节点的GPIO属性,获取LED对应的GPIO编号of_property_read_bool:读取设备树节点的布尔属性,判断LED是否低电平有效
3. GPIO资源管理
devm_gpio_request_one:请求GPIO资源,并初始化GPIO为输出模式,初始电平为低gpio_is_valid:验证GPIO编号是否有效
4. sysfs节点创建
kobject_create_and_add:创建kobject对象,作为sysfs节点的基础sysfs_create_file:在kobject节点下创建sysfs文件,用于用户空间与内核空间的交互memset:初始化sysfs属性结构体,设置属性名称、权限和操作函数
5. 错误处理
- 每个操作都有错误检查,一旦发生错误立即返回错误码
- 错误发生时会释放已分配的资源,避免资源泄漏
- 使用
dev_err打印错误信息,便于调试
3.3.3.2 执行流程
┌─────────────────────────────────────────────────────────┐
│ ledsdrv_probe │
├─────────────────────────────────────────────────────────┤
│ 1. 分配驱动私有内存 │
│ 2. 获取LED数量 │
│ 3. 分配LED设备信息数组内存 │
│ 4. 遍历每个LED子节点 │
│ a. 读取LED label属性 │
│ b. 读取LED GPIO属性 │
│ c. 读取LED active-low属性 │
│ d. 请求GPIO资源并初始化 │
│ e. 创建sysfs节点 │
│ f. 初始化sysfs属性结构体 │
│ g. 创建sysfs文件 │
│ 5. 保存驱动私有数据 │
│ 6. 打印初始化成功信息 │
│ 7. 返回0表示成功 │
└─────────────────────────────────────────────────────────┘
3.3.4 ledsdrv_remove函数
ledsdrv_remove是平台设备驱动的清理函数,当设备被移除或驱动卸载时调用此函数,释放已分配的资源。
// 平台设备驱动的remove函数
// 当设备被移除或驱动卸载时调用此函数,释放资源
static int ledsdrv_remove(struct platform_device *pdev)
{
// 获取驱动私有数据
struct ledsdrv_data *drv_data = platform_get_drvdata(pdev);
int i;
// 遍历所有LED设备,释放kobject资源
for (i = 0; i < drv_data->num_leds; i++) {
kobject_put(drv_data->leds[i].kobj);
}
return 0; // 释放成功
}
3.3.4.1 关键技术点解析
1. 驱动私有数据获取
platform_get_drvdata:获取之前通过platform_set_drvdata保存的驱动私有数据- 驱动私有数据包含了LED设备的所有信息
2. kobject资源释放
kobject_put:释放kobject资源,减少引用计数- 当引用计数为0时,kobject会被销毁,对应的sysfs节点也会被删除
3. 内存管理
- 使用
devm_系列函数分配的内存会自动释放,不需要在remove函数中手动释放 - 只有kobject资源需要手动释放,因为它们是通过
kobject_create_and_add创建的
3.4 驱动注册
// 设备树匹配表
// 当设备树中的compatible属性与"ledsdrv,gpio-leds"匹配时,驱动会被加载
static const struct of_device_id ledsdrv_of_match[] = {
{ .compatible = "ledsdrv,gpio-leds" },
{} // 空条目,表示匹配表结束
};
MODULE_DEVICE_TABLE(of, ledsdrv_of_match); // 注册设备树匹配表
// 平台驱动结构体定义
static struct platform_driver ledsdrv_driver = {
.probe = ledsdrv_probe, // 设备匹配成功时调用的初始化函数
.remove = ledsdrv_remove, // 设备移除或驱动卸载时调用的清理函数
.driver = {
.name = "ledsdrv", // 驱动名称
.of_match_table = ledsdrv_of_match, // 设备树匹配表
.owner = THIS_MODULE, // 模块所有者
},
};
// 注册平台驱动
module_platform_driver(ledsdrv_driver);
MODULE_AUTHOR("Custom LED Driver Author"); // 驱动作者
MODULE_DESCRIPTION("Custom LED Driver for Development Board"); // 驱动描述
MODULE_LICENSE("GPL"); // 驱动许可证
MODULE_ALIAS("platform:ledsdrv"); // 驱动别名,用于模块匹配
四、驱动使用方法
4.1 加载驱动
insmod ledsdrv.ko
4.2 控制LED
驱动加载成功后,会在/sys目录下创建每个LED对应的节点,例如:
/sys/heartbeat/state
/sys/alarm/state
可以通过读写这些文件来控制LED的亮灭:
# 点亮heartbeat LED
echo 1 > /sys/heartbeat/state
# 熄灭heartbeat LED
echo 0 > /sys/heartbeat/state
# 查看heartbeat LED状态
cat /sys/heartbeat/state
六、完整源码
6.1 ledsdrv.c完整源码
// SPDX-License-Identifier: GPL-2.0-only
/*
* Custom LED driver for development board
* Creates sysfs nodes for LED control
*/
#include // 内核模块基础头文件
#include // 平台设备驱动框架头文件
#include // 设备树操作头文件
#include // 设备树GPIO操作头文件
#include // sysfs文件系统操作头文件
#include // kobject对象操作头文件
#include // GPIO操作头文件
#include // 字符串操作头文件
// LED设备信息结构体
struct led_info {
const char *label; // LED名称标签
int gpio; // LED对应的GPIO编号
int active_low; // 是否低电平有效
struct kobject *kobj; // 对应sysfs节点的kobject对象
struct kobj_attribute attr; // sysfs属性结构体
};
// LED驱动私有数据结构体
struct ledsdrv_data {
int num_leds; // LED数量
struct led_info *leds; // LED设备信息数组
};
// sysfs节点的state属性读操作函数
// 当用户读取/sys/.../state文件时调用此函数
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
// 通过container_of宏从attr指针反向获取led_info结构体指针
struct led_info *led = container_of(attr, struct led_info, attr);
int value;
// 读取GPIO当前电平值
value = gpio_get_value(led->gpio);
// 如果是低电平有效,则反转值(0变1,1变0)
if (led->active_low)
value = !value;
// 将状态值写入buf,返回写入的字节数
return sprintf(buf, "%d
", value);
}
// sysfs节点的state属性写操作函数
// 当用户向/sys/.../state文件写入数据时调用此函数
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
// 通过container_of宏从attr指针反向获取led_info结构体指针
struct led_info *led = container_of(attr, struct led_info, attr);
unsigned int value;
int ret;
// 将用户输入的字符串转换为无符号整数
ret = kstrtouint(buf, 10, &value);
if (ret < 0)
return ret; // 转换失败,返回错误码
// 检查输入值是否为0或1
if (value != 0 && value != 1)
return -EINVAL; // 输入无效,返回无效参数错误
// 如果是低电平有效,则反转值(0变1,1变0)
if (led->active_low)
value = !value;
// 设置GPIO电平值,控制LED亮灭
gpio_set_value(led->gpio, value);
// 返回写入的字节数,表示成功
return count;
}
// 平台设备驱动的probe函数
// 当设备与驱动匹配成功时调用此函数,初始化LED设备
static int ledsdrv_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev; // 获取设备结构体指针
struct device_node *np = dev->of_node; // 获取设备树节点指针
struct device_node *child; // 设备树子节点指针(用于遍历LED节点)
struct ledsdrv_data *drv_data; // 驱动私有数据结构体指针
int i, ret;
// 分配驱动私有内存,使用devm_kzalloc自动管理内存
drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL);
if (!drv_data)
return -ENOMEM; // 内存分配失败,返回内存不足错误
// 获取设备树节点的子节点数量(即LED数量)
drv_data->num_leds = of_get_child_count(np);
if (drv_data->num_leds == 0) {
dev_err(dev, "No LED nodes found in device tree
");
return -ENODEV; // 未找到LED节点,返回设备不存在错误
}
// 分配LED设备信息数组内存
drv_data->leds = devm_kcalloc(dev, drv_data->num_leds,
sizeof(struct led_info), GFP_KERNEL);
if (!drv_data->leds)
return -ENOMEM; // 内存分配失败
i = 0;
// 遍历所有LED子节点
for_each_child_of_node(np, child) {
struct led_info *led = &drv_data->leds[i];
const char *label;
// 读取LED节点的label属性
ret = of_property_read_string(child, "label", &label);
if (ret < 0) {
dev_err(dev, "LED node missing label property
");
of_node_put(child);
return ret; // 读取失败,返回错误码
}
// 复制label字符串到内核内存
led->label = devm_kstrdup(dev, label, GFP_KERNEL);
if (!led->label) {
of_node_put(child);
return -ENOMEM; // 内存分配失败
}
// 读取LED节点的gpios属性,获取GPIO编号
led->gpio = of_get_named_gpio(child, "gpios", 0);
if (!gpio_is_valid(led->gpio)) {
dev_err(dev, "Invalid GPIO for LED %s
", led->label);
of_node_put(child);
return -EINVAL; // GPIO无效,返回无效参数错误
}
// 读取LED节点的active-low属性,判断是否低电平有效
led->active_low = of_property_read_bool(child, "active-low");
// 请求GPIO资源,初始化为低电平
ret = devm_gpio_request_one(dev, led->gpio, GPIOF_OUT_INIT_LOW,
led->label);
if (ret < 0) {
dev_err(dev, "Failed to request GPIO %d for LED %s
",
led->gpio, led->label);
of_node_put(child);
return ret; // 请求GPIO失败
}
led->kobj = kobject_create_and_add(led->label, NULL);
if (!led->kobj) {
dev_err(dev, "Failed to create kobject for LED %s
", led->label);
of_node_put(child);
return -ENOMEM; // 创建kobject失败
}
// 初始化sysfs属性结构体
memset(&led->attr, 0, sizeof(led->attr));
led->attr.attr.name = "state";
led->attr.attr.mode = 0644;
led->attr.show = state_show;
led->attr.store = state_store;
// 在sysfs节点中创建state属性文件
ret = sysfs_create_file(led->kobj, &led->attr.attr);
if (ret < 0) {
dev_err(dev, "Failed to create sysfs file for LED %s
", led->label);
kobject_put(led->kobj); // 释放kobject资源
of_node_put(child);
return ret; // 创建sysfs文件失败
}
i++;
}
// 将驱动私有数据保存到平台设备中
platform_set_drvdata(pdev, drv_data);
// 打印驱动初始化成功信息
dev_info(dev, "Custom LED driver probed successfully with %d LEDs
",
drv_data->num_leds);
return 0; // 初始化成功
}
// 平台设备驱动的remove函数
// 当设备被移除或驱动卸载时调用此函数,释放资源
static int ledsdrv_remove(struct platform_device *pdev)
{
// 获取驱动私有数据
struct ledsdrv_data *drv_data = platform_get_drvdata(pdev);
int i;
// 遍历所有LED设备,释放kobject资源
for (i = 0; i < drv_data->num_leds; i++) {
kobject_put(drv_data->leds[i].kobj);
}
return 0; // 释放成功
}
// 设备树匹配表
// 当设备树中的compatible属性与"ledsdrv,gpio-leds"匹配时,驱动会被加载
static const struct of_device_id ledsdrv_of_match[] = {
{ .compatible = "ledsdrv,gpio-leds" },
{} // 空条目,表示匹配表结束
};
MODULE_DEVICE_TABLE(of, ledsdrv_of_match); // 注册设备树匹配表
// 平台驱动结构体定义
static struct platform_driver ledsdrv_driver = {
.probe = ledsdrv_probe, // 设备匹配成功时调用的初始化函数
.remove = ledsdrv_remove, // 设备移除或驱动卸载时调用的清理函数
.driver = {
.name = "ledsdrv", // 驱动名称
.of_match_table = ledsdrv_of_match, // 设备树匹配表
.owner = THIS_MODULE, // 模块所有者
},
};
// 注册平台驱动
module_platform_driver(ledsdrv_driver);
MODULE_AUTHOR("Custom LED Driver Author"); // 驱动作者
MODULE_DESCRIPTION("Custom LED Driver for Development Board"); // 驱动描述
MODULE_LICENSE("GPL"); // 驱动许可证
MODULE_ALIAS("platform:ledsdrv"); // 驱动别名,用于模块匹配
七、总结
本文对LED驱动程序和设备树文件进行了详细分析,介绍了驱动的实现原理、设备树配置以及使用方法。该驱动采用平台设备驱动框架,支持设备树配置,通过sysfs文件系统提供用户空间接口,实现了对LED的灵活控制。






