Linux内核LED子系统概述

1. LED子系统架构

1.1 概述

Linux内核LED子系统是一个用于管理和控制LED设备的框架,提供了统一的用户空间接口和内核API,支持多种触发模式和硬件平台。该子系统的设计目标是简化LED设备的驱动开发,提供一致的用户体验,并支持复杂的LED控制逻辑。

LED子系统的主要特点包括:

  1. 统一接口:通过sysfs提供统一的LED控制接口,用户可以通过读写文件来控制LED的亮度和触发模式。
  2. 模块化设计:采用分层架构,将LED控制逻辑与硬件驱动分离,便于扩展和维护。
  3. 触发机制:支持多种内置触发模式(如heartbeat、timer、netdev等),并允许用户自定义触发模式。
  4. 硬件抽象:通过LED Class框架抽象不同硬件平台的LED设备,使驱动开发更加简单。
  5. 异步操作:支持异步亮度设置,提高系统响应性能。

LED子系统的典型应用场景包括:

  • 系统状态指示(如电源灯、硬盘活动灯)
  • 网络状态指示(如以太网活动灯)
  • 报警指示(如系统错误灯)
  • 用户自定义闪烁效果

1.2 核心组件

Linux内核LED子系统主要由以下组件构成:

+-------------------+     +-------------------+     +-------------------+
|  LED Class        |     |  LED Trigger      |     |  LED Driver       |
+-------------------+     +-------------------+     +-------------------+
          |                         |                         |
          v                         v                         v
+-------------------+     +-------------------+     +-------------------+
|  /sys/class/leds  |     |  Trigger Interface|     |  Hardware Control |
+-------------------+     +-------------------+     +-------------------+

1.3 核心数据结构

1.3.1 led_classdev
struct led_classdev {
    const char      *name;                // LED设备名称
    int             brightness;           // 当前亮度值
    int             max_brightness;       // 最大亮度值
    enum led_brightness (*brightness_set)(struct led_classdev *led_cdev,
                                       enum led_brightness brightness);
                                          // 异步亮度设置回调
    void            (*brightness_set_sync)(struct led_classdev *led_cdev,
                                           enum led_brightness brightness);
                                          // 同步亮度设置回调
    void            (*blink_set)(struct led_classdev *led_cdev,
                                 unsigned long *delay_on,
                                 unsigned long *delay_off);
                                          // 闪烁模式设置回调
    struct device   *dev;                 // 设备结构体指针
    struct list_head node;                // 链表节点,用于管理多个LED设备
    const char      *default_trigger;     // 默认触发模式
    unsigned        flags;                // 设备标志位
    struct timer_list blink_timer;        // 闪烁定时器
    int             blink_brightness;     // 闪烁时的亮度值
    unsigned long   delay_on;             // 点亮时间(ms)
    unsigned long   delay_off;            // 熄灭时间(ms)
    struct work_struct set_brightness_work; // 亮度设置工作队列
    enum led_brightness new_brightness;   // 新的亮度值(用于异步操作)
};
1.3.2 led_trigger
struct led_trigger {
    const char      *name;                // 触发模式名称
    struct list_head next_trig;           // 触发模式链表节点
    struct list_head led_list;            // 关联的LED设备链表
    rwlock_t        led_list_lock;        // LED链表读写锁
    void            (*activate)(struct led_classdev *led_cdev);
                                          // 激活触发模式时的回调
    void            (*deactivate)(struct led_classdev *led_cdev);
                                          // 取消触发模式时的回调
    struct module   *owner;               // 所属模块
    struct device   *dev;                 // 设备结构体指针
};

1.4 核心函数分析

1.4.1 led_classdev_register()
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev);
  • 注册LED设备到系统
  • 创建sysfs接口
  • 初始化默认属性
1.4.2 led_classdev_unregister()
void led_classdev_unregister(struct led_classdev *led_cdev);
  • 注销LED设备
  • 清理sysfs接口
1.4.3 led_trigger_register_simple()
int led_trigger_register_simple(const char *name, struct led_trigger **trigger);
  • 注册简单触发模式
  • 创建触发模式实例
1.4.4 led_trigger_event()
void led_trigger_event(struct led_trigger *trigger, enum led_brightness brightness);
  • 触发LED事件
  • 通知所有关联的LED设备

1.5 架构层次

  1. 用户空间接口:通过sysfs提供统一的LED控制接口
  2. LED Class:核心框架,管理LED设备和属性
  3. LED Trigger:事件触发机制,支持多种触发模式
  4. LED Driver:硬件抽象层,控制具体LED硬件

2. 驱动程序示例

2.1 基本LED驱动框架

#include <linux/module.h>
#include <linux/leds.h>
#include <linux/platform_device.h>

// LED设备私有数据结构
struct my_led_data {
    struct led_classdev cdev;  // LED类设备结构体
    unsigned int gpio;         // 控制LED的GPIO编号
};

// LED亮度设置回调函数
static void my_led_set(struct led_classdev *led_cdev, 
                       enum led_brightness brightness) {
    // 从led_classdev结构体获取私有数据
    struct my_led_data *led_data = container_of(led_cdev, 
                                                struct my_led_data, cdev);
    
    // 根据亮度值设置GPIO电平
    // brightness > 0 时点亮LED,否则熄灭
    gpio_set_value(led_data->gpio, brightness ? 1 : 0);
}

// 平台设备探测函数
static int my_led_probe(struct platform_device *pdev) {
    struct my_led_data *led_data;
    int ret;
    
    // 分配并初始化LED设备私有数据
    led_data = devm_kzalloc(&pdev->dev, sizeof(*led_data), GFP_KERNEL);
    if (!led_data)
        return -ENOMEM;
    
    // 从平台设备获取GPIO编号
    led_data->gpio = platform_get_irq(pdev, 0);
    if (led_data->gpio < 0)
        return led_data->gpio;
    
    // 初始化LED类设备结构体
    led_data->cdev.name = "my_led:red";  // LED设备名称
    led_data->cdev.brightness_set = my_led_set;  // 亮度设置回调
    led_data->cdev.brightness = LED_OFF;  // 初始状态为熄灭
    
    // 注册LED类设备
    ret = devm_led_classdev_register(&pdev->dev, &led_data->cdev);
    if (ret < 0)
        return ret;
    
    // 保存私有数据到平台设备
    platform_set_drvdata(pdev, led_data);
    return 0;
}

// 平台驱动结构体
static struct platform_driver my_led_driver = {
    .probe = my_led_probe,  // 探测函数
    .driver = {
        .name = "my_led",  // 驱动名称,需与设备树匹配
        .owner = THIS_MODULE,  // 模块所有者
    },
};

// 注册平台驱动
module_platform_driver(my_led_driver);

// 模块信息
MODULE_LICENSE("GPL");  // 许可证
MODULE_DESCRIPTION("Simple LED Driver");  // 模块描述

2.2 设备树配置

leds {
    compatible = "gpio-leds";
    
    red_led {
        label = "my_led:red";
        gpios = <&gpio 17 GPIO_ACTIVE_HIGH>;
        default-state = "off";
        linux,default-trigger = "heartbeat";
    };
};

3. 测试方法

3.1 sysfs接口测试

查看LED设备
ls /sys/class/leds/
控制LED亮度
echo 255 > /sys/class/leds/my_led:red/brightness
echo 0 > /sys/class/leds/my_led:red/brightness
设置触发模式
echo heartbeat > /sys/class/leds/my_led:red/trigger
echo none > /sys/class/leds/my_led:red/trigger
查看可用触发模式
cat /sys/class/leds/my_led:red/trigger

3.2 应用程序测试

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define LED_PATH "/sys/class/leds/my_led:red/brightness"

int main() {
    int fd = open(LED_PATH, O_WRONLY);
    if (fd < 0) {
        perror("Failed to open LED file");
        return 1;
    }
    
    write(fd, "255", 3);
    sleep(1);
    write(fd, "0", 1);
    
    close(fd);
    return 0;
}

3.3 内核API测试

#include <linux/leds.h>

struct led_classdev *led_cdev;

// 注册LED设备
led_cdev = devm_kzalloc(dev, sizeof(*led_cdev), GFP_KERNEL);
led_cdev->name = "my_led:red";
led_cdev->brightness_set = my_led_set;
devm_led_classdev_register(dev, led_cdev);

// 控制LED亮度
led_set_brightness(led_cdev, LED_FULL);
led_set_brightness(led_cdev, LED_OFF);

4. 常用触发模式

4.1 内置触发模式

触发模式描述
none无触发,手动控制
timer定时闪烁
heartbeat心跳模式
default-on默认开启
ide-diskIDE磁盘活动
mtdMTD存储活动
netdev网络设备活动
panic系统崩溃时闪烁
mmc0MMC卡活动

4.2 自定义触发模式

static struct led_trigger *my_trigger;

// 注册触发模式
led_trigger_register_simple("my_trigger", &my_trigger);

// 触发LED事件
led_trigger_event(my_trigger, LED_FULL);
led_trigger_event(my_trigger, LED_OFF);

// 注销触发模式
led_trigger_unregister_simple(my_trigger);

5. 常见问题与解决方案

5.1 LED不亮

  1. 检查GPIO配置是否正确
  2. 确认LED极性是否匹配
  3. 检查内核日志:dmesg | grep led

5.2 触发模式不工作

  1. 确认触发模式已编译进内核
  2. 检查设备树配置
  3. 查看触发模式是否可用

5.3 性能问题

  1. 减少sysfs操作频率
  2. 使用内核API直接控制
  3. 优化GPIO驱动

6. 总结

Linux内核LED子系统提供了统一的LED控制框架,支持多种触发模式和硬件平台。通过sysfs接口和内核API,用户可以方便地控制LED设备,实现各种闪烁效果和状态指示。

LED子系统的设计遵循了Linux内核的模块化原则,具有良好的可扩展性和兼容性,适用于各种嵌入式系统和工业应用场景。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐