1. 程式人生 > 其它 >Linux 驅動:LED子系統

Linux 驅動:LED子系統

Linux 驅動:LED子系統

背景

在除錯aw9523的時候,為了實現客戶要的一個效果。需要修改驅動,但是大概看了一下驅動,但是因為不太熟悉LED子系統,所以有點雲裡霧裡。

參考:

前言

學習驅動就必須學習硬體知識,第一關通常都是點亮LED燈。對於剛剛從學校或者培訓機構出來的學生,一般在工作的一開始都會安排一些比較簡單的工作。例如LED DRIVER。Led燈從硬體方面來說非常簡單,就是對一個IO管腳的拉高拉低的問題。

硬體這麼簡單,沒有很多的硬體協議,那麼學習什麼。當然是linux的軟體框架的構建了,對於驅動小白來說非常適合以此切入對核心的學習。

LED子系統 框架

在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;
}

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