前言:
- 什么叫做驱动框架?
内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,并把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程师来实现,这就叫驱动框架。
驱动框架,也就是标准化的驱动实现,统一管理系统资源,维护系统稳定。
概述:
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 会在 /sys/class/ 目录下创建 leds 类, 该类提供了不同 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_stor e函数:
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 定时器
//位于: drivers/leds/led-core.c
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);
//如果led在处于关闭状态,就将brightness设置blink_brightness,最后打开led
if (!brightness) {
/* 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设置为delay_on的时间,定时器
delay = led_cdev->blink_delay_on;
}
//如果 led 在处于打开状态,就将 brightness 设置 0 ,最后关闭 led
else
{
/* 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;
}
}
//实现循环定时器的功能,根据设置的 o n和 off 时间进行 LED_ON 和 LED_OFF
mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
}
到此为止, 整个led子系统框架分析完毕,当驱动工程师需要写一个led驱动的时刻,可以按照这个标准,在led子系统中注册对应的led设备,创建对应的属性,选择对应的触发方式(trigger).
应用实例请参考下篇
- Linux下led子系统之实例篇 https://www.softool.cn/blog-168.html
作者:hanp_linux
https://blog.csdn.net/hanp_linux/article/details/79037610