Linux 驱动:LED子系统

Linux 驱动:LED子系统

背景

在调试aw9523的时候,为了实现客户要的一个效果。需要修改驱动,但是大概看了一下驱动,但是因为不太熟悉LED子系统,所以有点云里雾里。

参考:

前言

学习驱动就必须学习硬件知识,第一关通常都是点亮LED灯。对于刚刚从学校或者培训机构出来的学生,一般在工作的一开始都会安排一些比较简单的工作。例如LED DRIVER。Led灯从硬件方面来说非常简单,就是对一个IO管脚的拉高拉低的问题。

硬件这么简单,没有很多的硬件协议,那么学习什么。当然是linux的软件框架的构建了,对于驱动小白来说非常适合以此切入对内核的学习。

LED子系统 框架

img

在linux 系统中针对每一类设备都会有一套framework 层,提供这一类设备的驱动程序开发框架。其中的好处有:

1、标准,尽可能的向上抽象出操作这类设备的接口函数,比如led就应该有具备开关灯的统一接口

2、屏蔽细节,framework层只提供抽象接口,给底层drvier base 预留callback回调函数。

3、方便代码维护,驱动工程师只需要根据不同的平台完成:

  • leds-xxxx.c(控制灯亮度的驱动程序,将驱动函数注册到framework的回调函数上)
  • ledtrig-xxx.c(自定义led灯的闪烁方式,填充回调函数)。

Led子系统核心代码在内核中表现为三个源文件。

从 drivers/leds/Makefile 中可以看出来,分别对应着当前Makefile所在目录下的 led-core.cled-class.c led-triggers.c

其中led-triggers又分为了timer、ide-disk、heartbeat、backlight、gpio、default-on等算法。

# LED Core

## 管理所有led灯的内核对象struct led_classdev
obj-$(CONFIG_NEW_LEDS) += led-core.o
## 对接设备驱动模型接口,生成/sys目录下属性文件
obj-$(CONFIG_LEDS_CLASS) += led-class.o
## 管理所有的触发器内核对象 struct led_trigger
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
 
# LED PlatformDrivers
obj-$(CONFIG_LEDS_GPIO)                        += leds-gpio.o
 
# LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER) +=ledtrig-timer.o
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK)      +=ledtrig-ide-disk.o
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) +=ledtrig-heartbeat.o
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) +=ledtrig-backlight.o
obj-$(CONFIG_LEDS_TRIGGER_GPIO)              +=ledtrig-gpio.o
obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON)        += ledtrig-default-on.o

实际上,led子系统核心文件是下列这些:

driver/leds/led-class.c
driver/leds/led-core.c
driver/leds/led-triggers.c
include/linux/leds.h

以及一些辅助文件(也就是说可以根据需求来决定这部分代码是否需要)

driver/leds/led-triggers.c
driver/leds/trigger/led-triggers.c
driver/leds/trigger/ledtrig-oneshot.c
driver/leds/trigger/ledtrig-timer.c
driver/leds/trigger/ledtrig-heartbeat.c

关键函数与结构体

led-core.c

核心层主要提供了全局变量以及对外提供标准的操作led 的函数接口。

leds_list_lock

// 保证一次只能允许一次操作
DECLARE_RWSEM(leds_list_lock);
EXPORT_SYMBOL_GPL(leds_list_lock);

leds_list

// 保存所有的led节点
LIST_HEAD(leds_list);
EXPORT_SYMBOL_GPL(leds_list);
static void led_blink_setup(struct led_classdev *led_cdev,
             unsigned long *delay_on,
             unsigned long *delay_off)
{
    if (!(led_cdev->flags & LED_BLINK_ONESHOT) &&
        led_cdev->blink_set &&
        !led_cdev->blink_set(led_cdev, delay_on, delay_off))
        return;

    /* blink with 1 Hz as default if nothing specified */
    if (!*delay_on && !*delay_off)
        *delay_on = *delay_off = 500;

    led_set_software_blink(led_cdev, *delay_on, *delay_off);
}

void led_blink_set(struct led_classdev *led_cdev,
           unsigned long *delay_on,
           unsigned long *delay_off)
{
    del_timer_sync(&led_cdev->blink_timer);

    led_cdev->flags &= ~LED_BLINK_ONESHOT;
    led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;

    led_blink_setup(led_cdev, delay_on, delay_off);
}
// 函数功能: 控制led灯的闪烁 (不能在中断上下文中使用)
// 参数led_cdev, 用于表示led灯的设备对象// 参数delay_on, 亮多长时间(单位为 ms)
// 参数 delay_off, 灭多长时间(单位为 ms)
// 使用到了内核定时器 led_cdev->blink_timer,来完成定时亮灭的功能
static void led_blink_setup(struct led_classdev *led_cdev,
             unsigned long *delay_on,
             unsigned long *delay_off)
{
    if (!(led_cdev->flags & LED_BLINK_ONESHOT) &&
        led_cdev->blink_set &&
        !led_cdev->blink_set(led_cdev, delay_on, delay_off))
        return;

    /* blink with 1 Hz as default if nothing specified */
    if (!*delay_on && !*delay_off)
        *delay_on = *delay_off = 500;

    led_set_software_blink(led_cdev, *delay_on, *delay_off);
}

void led_blink_set_oneshot(struct led_classdev *led_cdev,
               unsigned long *delay_on,
               unsigned long *delay_off,
               int invert)
{
    if ((led_cdev->flags & LED_BLINK_ONESHOT) &&
         timer_pending(&led_cdev->blink_timer))
        return;

    led_cdev->flags |= LED_BLINK_ONESHOT;
    led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;

    if (invert)
        led_cdev->flags |= LED_BLINK_INVERT;
    else
        led_cdev->flags &= ~LED_BLINK_INVERT;

    led_blink_setup(led_cdev, delay_on, delay_off);
}
EXPORT_SYMBOL(led_blink_set_oneshot);
// 函数功能: 控制led 闪烁一次。
// 参数led_cdev, 用于表示led灯的设备对象
// 参数delay_on, 亮多长时间(单位为 ms)
// 参数 delay_off, 灭多长时间(单位为 ms)
// 参数 invert, 如果invert为假led灯亮delay_on毫秒->灭delay_off毫秒
// 如果invert为假led灯灭delay_off毫秒->亮delay_on毫秒
void led_stop_software_blink(struct led_classdev *led_cdev)
{
    del_timer_sync(&led_cdev->blink_timer);
    led_cdev->blink_delay_on = 0;
    led_cdev->blink_delay_off = 0;
}
EXPORT_SYMBOL_GPL(led_stop_software_blink);
// 函数功能: 控制led 停止闪烁 (不能在中断上下文中使用)
// 参数led_cdev, 用于表示led灯的设备对象

led_set_brightness

// drivers/leds/leds.h
static inline void __led_set_brightness(struct led_classdev *led_cdev,
                    enum led_brightness value)
{
    if (value > led_cdev->max_brightness)
        value = led_cdev->max_brightness;
    led_cdev->brightness = value;
    if (!(led_cdev->flags & LED_SUSPENDED))
        led_cdev->brightness_set(led_cdev, value);
}

void led_set_brightness(struct led_classdev *led_cdev,
            enum led_brightness brightness)
{
    /* delay brightness setting if need to stop soft-blink timer */
    if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) {
        led_cdev->delayed_set_value = brightness;
        schedule_work(&led_cdev->set_brightness_work);
        return;
    }

    __led_set_brightness(led_cdev, brightness);
}
// 函数功能: 控制led 灯亮度
// 参数led_cdev, 用于表示led灯的设备对象
// 参数 brightness,LED_OFF:关灯 LED_HALF:半亮度 LED_FULL:全亮度

1、如果有设置led灯定时闪烁,那么调度work关掉定时器,在led-class章节讲解

2、调用回调函数brightness_set,来设置led的亮度。 对回调函数 brightness_set成员的的赋值通常是在drivers/leds/led-xxxx.c(具体的led等驱动代码)中进行。

led-class.c

LED驱动框架中内核开发者实现的部分主要是led-class.c

1、创建struct class 生成 /sys/class/leds目录

2、提供注册函数,生成控制led灯的属性文件

3、实现电源管理相关接口

leds_init

主要是创建leds_class,赋值suspend和resume以及dev_attrs。

在内核驱动过程中会调用初始化函数,注册led的 class对象生成 /sys/class/leds 目录,为device对象和生成属性文件做准备

所有的led灯对象注册进入系统时,都会为其生成通用的属性文件

static int __init leds_init(void)
{
    leds_class = class_create(THIS_MODULE, "leds");
    if (IS_ERR(leds_class))
        return PTR_ERR(leds_class);
    leds_class->suspend = led_suspend;
    leds_class->resume = led_resume;
    leds_class->dev_attrs = led_class_attrs;
    return 0;
}
subsys_initcall(leds_init);
led_class_attrs

什么是attribute?对应/sys/class/leds/目录里的内容,一般是文件和文件夹。这些文件其实就是sysfs开放给应用层的一些操作接口(非常类似于/dev/目录下的那些设备文件)

attribute有什么用?作用就是让应用程序可以通过/sys/class/leds/目录下面的属性文件来操作驱动进而操作硬件设备。

attribute其实是另一条驱动实现的路线。有区别于之前讲的file_operations那条线。相当于用户空间与内核空间交互的另外一种方式。

static struct device_attribute led_class_attrs[] = {
    __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
    __ATTR(max_brightness, 0644, led_max_brightness_show,
            led_max_brightness_store),
#ifdef CONFIG_LEDS_TRIGGERS
    __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
    __ATTR_NULL,
};

如果定义了CONFIG_LEDS_TRIGGERS,那么在/sys/class/leds/ 目录下所有的led灯目录中都会有trigger属性文件。

当有来自用户空间对/sys/class/leds/xxxx/下的节点(属性)进行操作时,下面的接口就会被调用。

这些接口来自drivers/leds/led-triggers.c

void led_trigger_blink(struct led_trigger *trig, unsigned long *delay_on,
                       unsigned long *delay_off);//统一该触发器中所有led灯的闪烁类型。

ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count);

ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,char *buf);

例如:

1、在linux命令行中输入 cat /sys/class/leds/xxxx/trigger 列出内核中支持哪些触发器

其中,[]来表示当前led绑定的触发器。

cat 是读文件的命令, 最终会调用到led_trigger_show函数

2、输入echo heartbeat > /sys/class/leds/ xxxx /trigger 让xxxx这个led灯以心跳的方式闪烁(前提必须支持心跳触发)。

echo 在这是往属性文件中写,会调用led_trigger_store函数。

[root@farsight my_led2]# cat /sys/class/leds/my_led2/trigger
none mmc0 mmc1 timer oneshot heartbeat backlight gpio [transient] flash torch

[root@farsight my_led2]# echo heartbeat > /sys/class/leds/my_led2/trigger
[root@farsight my_led2]#

led_classdev_register

完成5件事情

1、在/sys/class/leds/目录下生成 led_cdev->name目录的链接。

2、把led灯设备对象添加到全局的链表中去(leds_list)。

3、初始化工作set_brightness_work,用于支撑核心功能函数led_set_brightness。

4、初始化内核定时器,用于支撑核心功能函数 led_blink_set、led_blink_set_oneshot、led_stop_software_blink的定时闪烁功能。

5、尝试给led灯设置默认的触发方式。触发方式章节详细描述。

led_classdev_register注册成功以后,一个新的led设备就注册好了,就可以使用了。

/**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
    // 1、创建classdev设备
    led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
                      "%s", led_cdev->name);
    if (IS_ERR(led_cdev->dev))
        return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
    init_rwsem(&led_cdev->trigger_lock);
#endif
    /* add to the list of leds */
    down_write(&leds_list_lock);
    // 2、加到leds_list链表中
    list_add_tail(&led_cdev->node, &leds_list);
    up_write(&leds_list_lock);

    if (!led_cdev->max_brightness)
        led_cdev->max_brightness = LED_FULL;

    led_update_brightness(led_cdev);
    // 3、初始化 工作队列
    INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);

    // 4、初始化blinktimer,指定blink_timer的function和data
    init_timer(&led_cdev->blink_timer);
    led_cdev->blink_timer.function = led_timer_function;
    led_cdev->blink_timer.data = (unsigned long)led_cdev;

#ifdef CONFIG_LEDS_TRIGGERS
    // 5、设置trigger
    led_trigger_set_default(led_cdev);
#endif

    dev_dbg(parent, "Registered led device: %s
",
            led_cdev->name);

    return 0;
}
EXPORT_SYMBOL_GPL(led_classdev_register);
led_classdev
// linux/leds.h
struct led_classdev {
    const char      *name;
    // 亮度
    int          brightness;
    // 最大亮度
    int          max_brightness;
    int          flags;

    /* Lower 16 bits reflect status */
#define LED_SUSPENDED       (1 << 0)
    /* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME  (1 << 16)
#define LED_BLINK_ONESHOT   (1 << 17)
#define LED_BLINK_ONESHOT_STOP  (1 << 18)
#define LED_BLINK_INVERT    (1 << 19)

    /* Set LED brightness level */
    /* Must not sleep, use a workqueue if needed */
    // 亮度设置接口(不允许阻塞调用)
    void        (*brightness_set)(struct led_classdev *led_cdev,
                      enum led_brightness brightness);
    /* Get LED brightness level */
    // 获取亮度接口
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

    /*
     * Activate hardware accelerated blink, delays are in milliseconds
     * and if both are zero then a sensible default should be chosen.
     * The call should adjust the timings in that case and if it can't
     * match the values specified exactly.
     * Deactivate blinking again when the brightness is set to a fixed
     * value via the brightness_set() callback.
     */
    // 闪烁时点亮和熄灭的时间设置
    int     (*blink_set)(struct led_classdev *led_cdev,
                     unsigned long *delay_on,
                     unsigned long *delay_off);

    struct device       *dev;
    struct list_head     node;          /* LED Device list */
    
    //默认trigger的名字
    const char      *default_trigger;   /* Trigger to use */ // 
    // 闪烁的开关时间
    unsigned long        blink_delay_on, blink_delay_off;
    // 闪烁的定时器链表
    struct timer_list    blink_timer;
    // //闪烁的亮度
    int          blink_brightness;

    struct work_struct  set_brightness_work;
    int         delayed_set_value;

#ifdef CONFIG_LEDS_TRIGGERS
    /* Protects the trigger data below */
    // 用于 trigger 的同步锁
    struct rw_semaphore  trigger_lock;
    // led的trigger
    struct led_trigger  *trigger;
    // trigger的链表
    struct list_head     trig_list;
    // trigger的数据
    void            *trigger_data;
    /* true if activated - deactivate routine uses it to do cleanup */
    bool            activated;
#endif
};
创建设备
    led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
                      "%s", led_cdev->name);

创建classdev设备,也即Leds_class类中实例化一个对象,类似于c++的new一个对象,leds有很多种,而这里是注册一个特定的led,内核中的面向对象思想也极其丰富。

在/sys/class/leds/目录下生成 led_cdev->name目录的链接。

注册进来的每一个led设备对象struct led_classdev ,都会生成各自的链接目录;目录中就就已经自动产生了上面提到的属性文件,包括触发器的和设置led灯亮度的。

添加led设备对象到全局链表中
    list_add_tail(&led_cdev->node, &leds_list);

把led灯设备对象添加到全局的链表中去(leds_list)。

在实际使用中,内部也是通过遍历这个链表来获取led灯设备对象,并通过第一章讲到的标准接口来操作灯的亮灭。

准备brightness_work
    INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);

初始化工作set_brightness_work,用于支撑核心功能函数led_set_brightness

对工作函数set_brightness_delayed做解释:

  • 为了让对外的接口函数led_set_brightness可以在中断上下文中使用,特意加上了set_brightness_work这个work。
  • 如果led灯已经在使用定时器blink_timer进行闪烁,那么就必须先使用函数 del_timer_sync关掉定时器,因为此函数不能在中断上下文中使用,能使用work的方式把到挪到中断下半部来处理了。
准备blinktimer
    init_timer(&led_cdev->blink_timer);
    led_cdev->blink_timer.function = led_timer_function;
    led_cdev->blink_timer.data = (unsigned long)led_cdev;

初始化内核定时器,用于支撑核心功能函数 led_blink_setled_blink_set_oneshotled_stop_software_blink的定时闪烁功能。

设置trigger
    led_trigger_set_default(led_cdev);

尝试给led灯设置默认的触发方式。触发方式章节详细描述。

set_brightness_delayed

static void set_brightness_delayed(struct work_struct *ws)
{
    struct led_classdev *led_cdev =
        container_of(ws, struct led_classdev, set_brightness_work);

    led_stop_software_blink(led_cdev);

    __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
}

led-triggers.c

要求,内核配置了CONFIG_LEDS_TRIGGERS

triggers主要负责:

1、统一led触发器 framework层代码

2、提供注册和注销一个触发器的函数

从效果上,主要用于设置led有规律的闪烁。内核已经提供的的led触发器有ledtrig-heartbeat.c(心跳),ledtrig-timer.c(时钟),ledtrig-oneshot.c(一次闪烁),ledtrig-transient.c(瞬间)等。

led_trigger_register

主要完成三件事情,

1、初始化:初始读写保护锁,初始化链表led_cdevs。

2、尝试添加trigger到全局链表中:在挂入之前确保没有重名的触发器存在,因为触发器的唯一标识就是name。

3、遍历led灯全局链表leds_list,查找是否有那个led灯的默认触发器default_trigger就是此触发器,如果是就调用led_trigger_set函数讲led_cdev(led灯对象)和trig(触发器对象)进行绑定。(如果led_classdev中有默认的trigger,那么就设置这个默认的)

扫描trigger链表中是否有同名的trigger,接着把当前trigger加入到链表中,。

int led_trigger_register(struct led_trigger *trig)
{
    struct led_classdev *led_cdev;
    struct led_trigger *_trig;

    // 1、初始化
    rwlock_init(&trig->leddev_list_lock);
    INIT_LIST_HEAD(&trig->led_cdevs);

    down_write(&triggers_list_lock);
    // 2、尝试添加trigger到全局链表中:在挂入之前确保没有重名的触发器存在,因为触发器的唯一标识就是name。
    /* Make sure the trigger's name isn't already in use */
    list_for_each_entry(_trig, &trigger_list, next_trig) {
        if (!strcmp(_trig->name, trig->name)) {
            up_write(&triggers_list_lock);
            return -EEXIST;
        }
    }
    /* Add to the list of led triggers */
    list_add_tail(&trig->next_trig, &trigger_list);
    up_write(&triggers_list_lock);

    // 3、绑定trigger与led灯
    /* Register with any LEDs that have this as a default trigger */
    down_read(&leds_list_lock);
    list_for_each_entry(led_cdev, &leds_list, node) {
        down_write(&led_cdev->trigger_lock);
        if (!led_cdev->trigger && led_cdev->default_trigger &&
                !strcmp(led_cdev->default_trigger, trig->name))
            led_trigger_set(led_cdev, trig);
        up_write(&led_cdev->trigger_lock);
    }
    up_read(&leds_list_lock);

    return 0;
}
EXPORT_SYMBOL_GPL(led_trigger_register);

void led_trigger_unregister(struct led_trigger *trig)
EXPORT_SYMBOL_GPL(led_trigger_unregister);
// 触发器的对外接口:注销函数。注册函数反向函数,不再具体描述。
初始化
    rwlock_init(&trig->leddev_list_lock);
    INIT_LIST_HEAD(&trig->led_cdevs);

初始读写保护锁,初始化链表led_cdevs。

尝试添加trigger

尝试添加trigger到全局链表中:在挂入之前确保没有重名的触发器存在,因为触发器的唯一标识就是name。

    // 扫描trigger链表中是否有同名的trigger,没有的话把当前trigger加入到链表中
    /* Make sure the trigger's name isn't already in use */
    list_for_each_entry(_trig, &trigger_list, next_trig) {
        if (!strcmp(_trig->name, trig->name)) {
            up_write(&triggers_list_lock);
            return -EEXIST;
        }
    }
    /* Add to the list of led triggers */
    list_add_tail(&trig->next_trig, &trigger_list);

注意到这里的trigger_list,用于保存所有的led灯触发器。

trigger_list
static LIST_HEAD(trigger_list);

原型为:

struct led_trigger {
    /* Trigger Properties */
    const char   *name;
    // 激活trigger
    void        (*activate)(struct led_classdev *led_cdev);
    // 激活trigger
    void        (*deactivate)(struct led_classdev *led_cdev);

    /* LEDs under control by this trigger (for simple triggers) */
    rwlock_t      leddev_list_lock;
    // led设备的链表,挂接该触发器的所有led灯,一个触发器可以挂接多个led灯。
    struct list_head  led_cdevs;

    /* Link to next registered trigger */
    // 挂接该触发器到全局链表trigger_list中
    struct list_head  next_trig;
};

上述的属性由触发器具体实现者(implementor)来填写。

比如:ledtrig-transient.c中的transient_trig_activatetransient_trig_deactivate两个函数就是implementor。

绑定
    list_for_each_entry(led_cdev, &leds_list, node) {
        down_write(&led_cdev->trigger_lock);
        if (!led_cdev->trigger && led_cdev->default_trigger &&
                !strcmp(led_cdev->default_trigger, trig->name))
            led_trigger_set(led_cdev, trig);
        up_write(&led_cdev->trigger_lock);
    }

如果led_classdev中有默认的trigger,那么就设置这个默认的。

具体的做法:

  • 遍历led灯全局链表leds_list,查找是否有某个led灯的默认触发器default_trigger就是此触发器。
  • 如果是就调用led_trigger_set函数将led_cdev(led灯对象)和trig(触发器对象)进行绑定。

led_trigger_set

为led灯对象绑定上触发器对象,需要完成4件事情:

1、配置事件,用于向用户空间发送消息。KOBJ_CHANGE

2、移除现有的触发器:led对象只能存在一个触发器

3、将led_cdev 和 trig 进行绑定,一个触发器对应多个led灯,一个led灯只能有一个触发器。 为此led灯激活该触发器。

4、向用户空间发送KOBJ_CHANGE 事件,设备驱动模型相关,用户空间监听进程可以接收到event 字符串信息。

/* Caller must ensure led_cdev->trigger_lock held */
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
    unsigned long flags;
    char *event = NULL;
    char *envp[2];
    const char *name;

    // 1、配置事件,用于向用户空间发送消息。
    name = trig ? trig->name : "none";
    event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name);

    // 2、移除现有的触发器,led对象只能存在一个触发器。
    /* Remove any existing trigger */
    if (led_cdev->trigger) {
        write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
        list_del(&led_cdev->trig_list);
        write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
            flags);
        cancel_work_sync(&led_cdev->set_brightness_work);
        led_stop_software_blink(led_cdev);
        if (led_cdev->trigger->deactivate)
            led_cdev->trigger->deactivate(led_cdev);
        led_cdev->trigger = NULL;
        led_set_brightness(led_cdev, LED_OFF);
    }
    // 3、将led_cdev 和 trig 进行绑定,一个触发器对应多个led灯,一个led灯只能有一个触发器。 为此led灯激活该触发器。
    if (trig) {
        write_lock_irqsave(&trig->leddev_list_lock, flags);
        list_add_tail(&led_cdev->trig_list, &trig->led_cdevs);
        write_unlock_irqrestore(&trig->leddev_list_lock, flags);
        led_cdev->trigger = trig;
        if (trig->activate)
            trig->activate(led_cdev);
    }
    
    // 4、向用户空间发送KOBJ_CHANGE 事件
    if (event) {
        envp[0] = event;
        envp[1] = NULL;
        kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp);
        kfree(event);
    }
}
EXPORT_SYMBOL_GPL(led_trigger_set);

led_trigger_event

设置触发器上所有的led灯统一为某一亮度。

这里就体现了接口统一的好处了,只要设置led灯的亮度就可以调用标准接口函数led_set_brightness, 而具体的实现者是led-xxxx.c 注册的回调函数。

这里是不是就有一些面向对象中的抽象了。

void led_trigger_event(struct led_trigger *trig,
            enum led_brightness brightness)
{
    struct list_head *entry;

    if (!trig)
        return;

    read_lock(&trig->leddev_list_lock);
    list_for_each(entry, &trig->led_cdevs) {
        struct led_classdev *led_cdev;

        led_cdev = list_entry(entry, struct led_classdev, trig_list);
        led_set_brightness(led_cdev, brightness);
    }
    read_unlock(&trig->leddev_list_lock);
}
EXPORT_SYMBOL_GPL(led_trigger_event);

各种trigger

对于led子系统中,比较多的trigger,下面就来简单了解下常见的trigger效果是如何实现的。

路径:kernel/drivers/leds/trigger/ledtrig-*.c

default-on

Default-on主要是设置led为最大亮度。

static void defon_trig_activate(struct led_classdev *led_cdev)
{
    __led_set_brightness(led_cdev, led_cdev->max_brightness);
}

static struct led_trigger defon_led_trigger = {
    .name     = "default-on",
    .activate = defon_trig_activate,
};
backlight
struct bl_trig_notifier {
         struct led_classdev *led;       //led子系统设备
         int brightness;               //亮度
         int old_status;
         struct notifier_block notifier;    //内核通知链
         unsigned invert;
};

static struct led_trigger bl_led_trigger = {
    .name       = "backlight",
    .activate   = bl_trig_activate,
    .deactivate = bl_trig_deactivate
};

static void bl_trig_deactivate(struct led_classdev *led)
{
    struct bl_trig_notifier *n =
        (struct bl_trig_notifier *) led->trigger_data;

    if (led->activated) {
        device_remove_file(led->dev, &dev_attr_inverted);
        fb_unregister_client(&n->notifier);
        kfree(n);
        led->activated = false;
    }
}

static struct led_trigger bl_led_trigger = {
    .name       = "backlight",
    .activate   = bl_trig_activate,
    .deactivate = bl_trig_deactivate
};

其中fb_register_client注册到了framebuffer中的fb_notifier_list中,一旦framebuffer驱动中有事件,就会调用内核通知链中注册好的函数fb_notifier_callback。

关于内核通知链,这里就插播一曲来自网络的摘抄了:

大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。

通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。

通知链技术可以概括为:事件的被通知者将事件发生时应该执行的操作通过函数指针方式保存在链表(通知链)中,然后当事件发生时通知者依次执行链表中每一个元素的回调函数完成通知。

static int fb_notifier_callback(struct notifier_block *p,
                unsigned long event, void *data)
{
    struct bl_trig_notifier *n = container_of(p,
                    struct bl_trig_notifier, notifier);
    struct led_classdev *led = n->led;
    struct fb_event *fb_event = data;
    int *blank = fb_event->data;
    int new_status = *blank ? BLANK : UNBLANK;

    switch (event) {
    case FB_EVENT_BLANK:
        if (new_status == n->old_status)
            break;

        if ((n->old_status == UNBLANK) ^ n->invert) {
            n->brightness = led->brightness;
            __led_set_brightness(led, LED_OFF);
        } else {
            __led_set_brightness(led, n->brightness);
        }

        n->old_status = new_status;

        break;
    }

    return 0;
}

如果触发了FB_EVENT_BLANK,那么就执行相应的操作。

timer
static struct led_trigger timer_led_trigger = {
    .name     = "timer",
    .activate = timer_trig_activate,
    .deactivate = timer_trig_deactivate,
};

static void timer_trig_activate(struct led_classdev *led_cdev)
{
    int rc;

    led_cdev->trigger_data = NULL;

    rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
    if (rc)
        return;
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
    if (rc)
        goto err_out_delayon;

    led_blink_set(led_cdev, &led_cdev->blink_delay_on,
              &led_cdev->blink_delay_off);
    led_cdev->activated = true;

    return;

err_out_delayon:
    device_remove_file(led_cdev->dev, &dev_attr_delay_on);
}

当某个led_classdev与之连接后,这个触发器会在/sys/class/leds/<device>/下创建两个文件delay_ondelay_off。用户空间往这两个文件中写入数据后,相应的led会按照设置的高低电平的时间(ms)来闪烁。

如果led_classdev注册了硬件闪烁的接口led_cdev->blink_set,则用硬件控制闪烁,否则用软件定时器来控制闪烁。

heatbeat
struct heartbeat_trig_data {
    unsigned int phase;
    unsigned int period;
    struct timer_list timer;
};

static void heartbeat_trig_activate(struct led_classdev *led_cdev)
{
    struct heartbeat_trig_data *heartbeat_data;

    heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
    if (!heartbeat_data)
        return;

    led_cdev->trigger_data = heartbeat_data;
    setup_timer(&heartbeat_data->timer,
            led_heartbeat_function, (unsigned long) led_cdev);
    heartbeat_data->phase = 0;
    led_heartbeat_function(heartbeat_data->timer.data);
    led_cdev->activated = true;
}


static struct led_trigger heartbeat_led_trigger = {
    .name     = "heartbeat",
    .activate = heartbeat_trig_activate,
    .deactivate = heartbeat_trig_deactivate,
};

设置了heartbeat_data->phase,然后调用led_heartbeat_function。

static void led_heartbeat_function(unsigned long data)
{
    struct led_classdev *led_cdev = (struct led_classdev *) data;
    struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
    unsigned long brightness = LED_OFF;
    unsigned long delay = 0;

    if (unlikely(panic_heartbeats)) {
        led_set_brightness(led_cdev, LED_OFF);
        return;
    }

    /* acts like an actual heart beat -- ie thump-thump-pause... */
    switch (heartbeat_data->phase) {
    case 0:
        /*
         * The hyperbolic function below modifies the
         * heartbeat period length in dependency of the
         * current (1min) load. It goes through the points
         * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
         */
        heartbeat_data->period = 300 +
            (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
        heartbeat_data->period =
            msecs_to_jiffies(heartbeat_data->period);
        delay = msecs_to_jiffies(70);
        heartbeat_data->phase++;
        brightness = led_cdev->max_brightness;
        break;
    case 1:
        delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
        heartbeat_data->phase++;
        break;
    case 2:
        delay = msecs_to_jiffies(70);
        heartbeat_data->phase++;
        brightness = led_cdev->max_brightness;
        break;
    default:
        delay = heartbeat_data->period - heartbeat_data->period / 4 -
            msecs_to_jiffies(70);
        heartbeat_data->phase = 0;
        break;
    }

    __led_set_brightness(led_cdev, brightness);
    mod_timer(&heartbeat_data->timer, jiffies + delay);
}

通过定时来实现类似于心跳的led灯。

ide-disk
void ledtrig_ide_activity(void)
{
    led_trigger_blink_oneshot(ledtrig_ide,
                  &ide_blink_delay, &ide_blink_delay, 0);
}
EXPORT_SYMBOL(ledtrig_ide_activity);

static int __init ledtrig_ide_init(void)
{
    led_trigger_register_simple("ide-disk", &ledtrig_ide);
    return 0;
}

通过定时器实现类似于硬盘灯的指示。

如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
博客地址:https://www.cnblogs.com/schips/
原文地址:https://www.cnblogs.com/schips/p/linux_kernel_led_subsystem.html