Linux下led子系统 --- 分析篇

前言:

什么叫做驱动框架?
内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,并把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程师来实现,这就叫驱动框架。即标准化的驱动实现,统一管理系统资源,维护系统稳定。

概述:

led子系统驱动框架:

所有led共性:
 有和用户通信的设备节点
 亮和灭

不同点:
 有的led可能是接在gpio管脚上,不同的led有不同的gpio来控制
 有的led可能由其他的芯片来控制(节约cpu的pin,或者为了控制led的电流等)
 可以设置亮度
 可以闪烁
所以Linux中led子系统驱动框架把把所有led的共性给实现了,把不同的地方留给驱动工程师去做.
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-class.c(led子系统框架的入口) 和led-core.c


  1. 会在/sys/class/目录下创建leds类.
  2. 提供不同led设备往led子系统注册的接口,并在/sys/class/下创建对应的设备节点,并在节点下面创建对应的属性文件,最后提供给应用统一的访问接口(read/write).
    当make menuconfig选中LED Class Support这一项(在选择这个之前需要先选择device driver中的LED Support),就会调用led-class.c中的下面的入口:

subsys_initcall(leds_init);
leds_init模块入口函数负责在/sys/class/目录下面创建一个leds类目录,并为基于leds这个类的每个设备(device)创建对应的属性文件同时将led-class中的suspend的指针以及resume的指针初始化了,一般 来说是当系统休眠的时候系统上层会层层通知各个设备进入睡眠状态,那么负责这个设备的驱动则实际执行睡眠,例如手机的休眠键位,唤醒时调用的是 resume,恢复设备的运行状态,这也是为了省电。即电源管理。

static int __init leds_init(void)
{
    leds_class = class_create(THIS_MODULE, "leds");//会生成/sys/class/leds/目录
    leds_class->pm = &leds_class_dev_pm_ops;       //suspend的指针以及resume的指针初始化
    leds_class->dev_groups = led_groups;           //创建为基于这个class的所有设备创建属性
    return 0;
}
brightness max_brightness等属性的创建
static const struct attribute_group *led_groups[] = {
    &led_group,
#ifdef CONFIG_LEDS_TRIGGERS                   //只有打开这个宏,才会创建对应的trigger属性(trigger后面分析)
    &led_trigger_group,
#endif
    NULL,
};
static const struct attribute_group led_group = {
    .attrs = led_class_attrs,
};
static const struct attribute_group led_trigger_group = {
    .attrs = led_trigger_attrs,
};

其中DEVICE_ATTR属性的原型是(Documentation/driver-model/Device.txt中有对DEVICE_ATTR的详细介绍)
#define DEVICE_ATTR(_name, _mode, _show, _store) \

  struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

_name表示属性的名字,即在sys中呈现的文件.

_mode表示这个属性的读写权限,如0666, 分别表示user/group/other的权限都是可读可写

_show表示的是对此属性的读函数,当cat这个属性的时候被调用,_stroe表示的是对此属性的写函数,当echo内容到这个属性的时候被调用。

当然_ATTR还有一系列的如__ATTR_RO宏只有读方法,__ATTR_RW等等

static DEVICE_ATTR(trigger, 0666, led_trigger_show, led_trigger_store);
static DEVICE_ATTR_RO(max_brightness);
static DEVICE_ATTR_RW(brightness);
static struct attribute *led_class_attrs[] = {
    &dev_attr_brightness.attr,            //&dev_attr_xxx中xxx必须和DEVICE_ATTR中的name一致
    &dev_attr_max_brightness.attr,
    NULL,
};
static struct attribute *led_trigger_attrs[] = {
    &dev_attr_trigger.attr,
    NULL,
};
提供register接口

led-class.c还提供了一个让驱动开发者在/sys/class/leds这个类下创建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)
{
    char name[64];
    int ret;
    //获取你要创建的设备的名字
    ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
    //基于leds这个类创建对应的设备
    led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
                led_cdev, led_cdev->groups, "%s", name);          
    //将这个注册的设备添加到链表中
    list_add_tail(&led_cdev->node, &leds_list);
    //如果设备驱动在注册时没有设置max_brightness,则将max_brightness设置为满即255,在leds.h中定义
    if (!led_cdev->max_brightness)
        led_cdev->max_brightness = LED_FULL;

    led_cdev->flags |= SET_BRIGHTNESS_ASYNC;
    //如果在注册led_cdev时,设置了get_brightness方法,则读出当前的brightness并更新
    led_update_brightness(led_cdev);
    //设置一个定时器,
    led_init_core(led_cdev);

    //如果打开了trigger宏,则设置默认的trigger
#ifdef CONFIG_LEDS_TRIGGERS
    led_trigger_set_default(led_cdev);
#endif

    return 0;
}
EXPORT_SYMBOL_GPL(led_classdev_register);

当驱动调用led_classdev_register注册了一个设备,那么就会在/sys/class/leds目录下创建xxx设备,并这个xxx目录下创建一系列attr属性文件,如brightness max_brightness trigger等

cat 或者echo 属性文件时

当用户在文件系统下读写这些属性文件时,就会调用这些属性文件的show和store方法.
如,当用户cat /sys/class/leds/xxx/brightness时会调用led-class.c中的brightness_show函数

static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    //当你操作哪个设备下的属性文件,就会根据这个设备对应的device结构体获取leds_classdev结构,里面有这个设备的所有信息
    struct led_classdev *led_cdev = dev_get_drvdata(dev);

    /* no lock needed for this */
    led_update_brightness(led_cdev);
    //如果设备驱动注册时传入了brightness_get函数,则去调用他驱动实时的brightness,否则将设备初始化的brightness值返回给用户
    return sprintf(buf, "%u\n", led_cdev->brightness);
}
int led_update_brightness(struct led_classdev *led_cdev)
{
    int ret = 0;
    //调用驱动注册时传入的brightness_get函数,获取brightness值
    if (led_cdev->brightness_get) {
        ret = led_cdev->brightness_get(led_cdev);
    }

    return ret;
}

当用户echo 100 > /sys/class/leds/xxx/brightness时会调用led-class.c中的brightness_store函数

static ssize_t brightness_store(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t size)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);
    //读取用户传入的brightness值,并存入state中
    ret = kstrtoul(buf, 10, &state);
    if (ret)
        goto unlock;
    //如果用户写入的brightness是0,即关闭led,则把对应的trigger移除
    if (state == LED_OFF)
        led_trigger_remove(led_cdev);
    //否则调用设备驱动传入的设置亮度函数,设置相应的亮度
    led_set_brightness(led_cdev, state);

    ret = size;
}


void led_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness)
{
    int ret = 0;

    /* delay brightness if soft-blink is active */
    if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) {
        led_cdev->delayed_set_value = brightness;
        if (brightness == LED_OFF)
            schedule_work(&led_cdev->set_brightness_work);
        return;
    }

    if (led_cdev->flags & SET_BRIGHTNESS_ASYNC) {
        led_set_brightness_async(led_cdev, brightness);
        return;
    } else if (led_cdev->flags & SET_BRIGHTNESS_SYNC)
        ret = led_set_brightness_sync(led_cdev, brightness);
}
static inline void led_set_brightness_async(struct led_classdev *led_cdev,  enum led_brightness value)
{
    value = min(value, led_cdev->max_brightness);
    led_cdev->brightness = value;

    if (!(led_cdev->flags & LED_SUSPENDED))
        led_cdev->brightness_set(led_cdev, value);   //调用具体的led驱动的brighntness_set函数,操作具体的led的亮度
}
trigger操作

当用户cat /sys/class/leds/xxx/trigger

ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);
    struct led_trigger *trig;
    int len = 0;

    //如果没有默认的trigger,则将none这个list加上"[]",当我们cat trigger时会发现,当前的trigger是哪一项,那么这一项会用"[]"标示
    if (!led_cdev->trigger)
        len += sprintf(buf+len, "[none] ");
    else
        len += sprintf(buf+len, "none ");
    //列出trigger_list中的所有trigger
    list_for_each_entry(trig, &trigger_list, next_trig) {
        if (led_cdev->trigger && !strcmp(led_cdev->trigger->name,
                            trig->name))
            len += sprintf(buf+len, "[%s] ", trig->name);
        else
            len += sprintf(buf+len, "%s ", trig->name);
    }

    len += sprintf(len+buf, "\n");
    return len;
}

当用户echo timer > /sys/class/leds/xxx/trigger

ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);
    char trigger_name[TRIG_NAME_MAX];
    struct led_trigger *trig;

    trigger_name[sizeof(trigger_name) - 1] = '\0';
    strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
    len = strlen(trigger_name);

    if (len && trigger_name[len - 1] == '\n')
        trigger_name[len - 1] = '\0';

    if (!strcmp(trigger_name, "none")) {
        led_trigger_remove(led_cdev);
        goto unlock;
    }

    list_for_each_entry(trig, &trigger_list, next_trig) {
        if (!strcmp(trigger_name, trig->name)) {
            led_trigger_set(led_cdev, trig);         //这个地方会调用对应trigger的activated函数,然后激活此trigger
            goto unlock;
        }
    }

unlock:
    return ret;
}
触发器 trigger概述

当make menuconfig将Trigger support打开,并将对应的timer等trigger打开,那么就会调用对应的ledtri-xxx.c,在/sys/class/leds/xxx/目录下生成一个trigger文件,cat trigger可以列出你打开的这些trigger,例如timer one-short等.

  [*]   LED Trigger support  --->                           //会编译driver/leds/led-triggers.c
      [*]   LED Timer Trigger                                                        
      [*]   LED One-shot Trigger              
      [*]   LED Heartbeat Trigger          
      [ ]   LED backlight Trigger            
      [ ]   LED CPU Trigger                   
      [ ]   LED GPIO Trigger              
      [ ]   LED Default ON Trigger         
      .....

下面介绍一下常用的trigger - timer
对应ledtri-timer.c,称之为闪烁定时触发器,某个led_classdev与之连接后(即echo timer > trigger,或者在驱动代码中将timer设置为默认trigger),这个触发器会在/sys/class/leds//下创建两个属性文件delay_on/delay_off,顾名思义,这两个文件分别存储led灯亮的时间和灭的时间,led会按照这两个时间来进行闪烁,单位是ms,如果led_classdev注册了硬件闪烁的接口led_cdev->blink_set就是用硬件控制闪烁,否则用软件定时器(ledtri-timer.c,即led子系统中的定时器)来控制闪烁。
我第一次在使用led子系统实现触发器(trigger)的时候,一直在一个很低级的错误点纠结:
用户可以通过echo xxx > delay_on或者off来控制灯闪烁,然而我自己的驱动代码中根本没有让灯闪烁的代码,他是如何闪烁的呢,后来分析了ledtri-timer.c代码才发现,ledtri-timer.c中的blink函数会调用对应驱动注册的set_brightness函数,从而来控制对应led的闪烁,所以无论你是什么平台,不管你是哪个led,我的timer触发器都可以通过调用你的set_brightness函数从而来控制闪烁.
下面以ledtri-timer.c为例分析各种trigger
当在make menucong中把这个宏打开 [*] LED Timer Trigger就会加载module_init
module_init(timer_trig_init);

给timer这个trigger的activate等赋值,在上面介绍过,当用户echo timer > trigger的时候led_trigger_store会调用led_trigger_set(led_cdev, trig); 继而调用对应trigger的activate函数.

static int __init timer_trig_init(void)
{
    return led_trigger_register(&timer_led_trigger);
}
static struct led_trigger timer_led_trigger = {
    .name     = "timer",
    .activate = timer_trig_activate,
    .deactivate = timer_trig_deactivate,
};
/创建delay_on和delay_off这两个属性文件
static void timer_trig_activate(struct led_classdev *led_cdev)
{
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);

    led_blink_set(led_cdev, &led_cdev->blink_delay_on, &led_cdev->blink_delay_off);
    led_cdev->activated = true;
}
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);
}
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);
}
static void led_set_software_blink(struct led_classdev *led_cdev,
                   unsigned long delay_on,
                   unsigned long delay_off)
{
    led_cdev->blink_delay_on = delay_on;
    led_cdev->blink_delay_off = delay_off;

    /* never on - just set to off */
    if (!delay_on) {
        led_set_brightness_async(led_cdev, LED_OFF);
        return;
    }

    /* never off - just set to brightness */
    if (!delay_off) {
        led_set_brightness_async(led_cdev, led_cdev->blink_brightness);
        return;
    }
    //开启led_init_core函数初始化的定时器,开始执行led闪烁功能
    mod_timer(&led_cdev->blink_timer, jiffies + 1);
}

在led_classdev_register时,调用了led_init_core函数去初始化了一个timer定时器

void led_init_core(struct led_classdev *led_cdev)
{
    setup_timer(&led_cdev->blink_timer, led_timer_function,
            (unsigned long)led_cdev);
}
static void led_timer_function(unsigned long data)
{
    struct led_classdev *led_cdev = (void *)data;
    unsigned long brightness;
    unsigned long delay;
     //如果delay_on和delay_off没有设置的话,说明没有设置闪烁功能,则直接退出
    if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
        led_set_brightness_async(led_cdev, LED_OFF);
        return;
    }

    if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) {
        led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
        return;
    }

    //如果程序执行到这个位置,说明已经设置了闪烁功能,则执行闪烁
    brightness = led_get_brightness(led_cdev);
    if (!brightness) {        //如果led在处于关闭状态,就将brightness设置blink_brightness,最后打开led
        /* Time to switch the LED on. */
        if (led_cdev->delayed_set_value) {
            led_cdev->blink_brightness =
                    led_cdev->delayed_set_value;
            led_cdev->delayed_set_value = 0;
        }
        brightness = led_cdev->blink_brightness;
        delay = led_cdev->blink_delay_on;     //将delay设置为delay_on的时间,定时器
    } else {      //如果led在处于打开状态,就将brightness设置0,最后关闭led
        /* Store the current brightness value to be able
         * to restore it when the delay_off period is over.
         */
        led_cdev->blink_brightness = brightness;
        brightness = LED_OFF;
        delay = led_cdev->blink_delay_off;

led_set_brightness_async(led_cdev, brightness);

    /* Return in next iteration if led is in one-shot mode and we are in
     * the final blink state so that the led is toggled each delay_on +
     * delay_off milliseconds in worst case.
     */
    if (led_cdev->flags & LED_BLINK_ONESHOT) {
        if (led_cdev->flags & LED_BLINK_INVERT) {
            if (brightness)
                led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
        } else {
            if (!brightness)
                led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
        }
    }
    //实现循环定时器的功能,根据设置的on和off时间进行LED_ON和LED_OFF
    mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
}

到此为止, 整个led子系统框架分析完毕,当驱动工程师需要写一个led驱动的时刻,可以按照这个标准,在led子系统中注册对应的led设备,创建对应的属性,选择对应的触发方式(trigger).
应用实例请参考下篇 ——- Linux下led子系统 — 实例篇

你可能感兴趣的:(Linux驱动)