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\n",
            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;
}

通過定時器實現類似於硬盤燈的指示。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM