文章目录
- 1、前言
- 2、触发器设置相关函数
- 2.1 struct led_trigger
- 2.2 led_trigger_show
- 2.3 led_trigger_store
- 2.4 led_trigger_set
- 2.5 led_trigger_remove
- 3、触发器注册注销函数
- 3.1 led_trigger_register
- 3.2 led\_trigger\_unregister
- 3.3 devm_led_trigger_register
- 3.4 led_trigger_register_simple
- 3.5 led_trigger_unregister_simple
- 4、闪烁功能相关函数
- 4.1 led_trigger_blink
- 4.2 led_trigger_blink_oneshot
- 4.3 led_trigger_blink_setup
- 4.4 led_blink_set_oneshot和led_blink_set
- 4.5 led_blink_setup
- 4.6 led_set_software_blink
- 4.7 调用流程
- 5、总结回顾
1、前言
上篇文章我们了解了子系统的核心层
led-core.c
,下面我们来分析驱动框架中核心层的led-triggers.c
实现以及作用。
我们在了解led-class.c
文件的时候,就看到有定义好的触发控制相关的文件节点:
#ifdef CONFIG_LEDS_TRIGGERS
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
static struct attribute *led_trigger_attrs[] = {
&dev_attr_trigger.attr,
NULL,
};
DEVICE_ATTR
定义的led_trigger_show
和led_trigger_store
就是在led-triggers.c
文件中定义的!
2、触发器设置相关函数
2.1 struct led_trigger
struct led_trigger {
/* Trigger Properties */
const char *name;
int (*activate)(struct led_classdev *led_cdev);
void (*deactivate)(struct led_classdev *led_cdev);
/* LEDs under control by this trigger (for simple triggers) */
rwlock_t leddev_list_lock;
struct list_head led_cdevs;
/* Link to next registered trigger */
struct list_head next_trig;
const struct attribute_group **groups;
};
结构体名称:led_trigger
文件位置:include/linux/leds.h
主要作用:用于表示 Linux
内核中的 LED
触发器。LED
触发器是一种控制 LED
设备行为的机制,例如打开或关闭、闪烁或改变亮度等。
name
:标识触发器的字符串。activate
:当触发器被激活时调用的函数指针。deactivate
:当触发器被停用时调用的函数指针。leddev_list_lock
:保护触发器控制的LED
设备列表的读写锁。led_cdevs
:由触发器控制的LED
设备列表。next_trig
:指向下一个已注册触发器的指针。groups
:与触发器关联的属性组的数组。
2.2 led_trigger_show
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;
down_read(&triggers_list_lock);
down_read(&led_cdev->trigger_lock);
if (!led_cdev->trigger)
len += scnprintf(buf+len, PAGE_SIZE - len, "[none] ");
else
len += scnprintf(buf+len, PAGE_SIZE - len, "none ");
list_for_each_entry(trig, &trigger_list, next_trig) {
if (led_cdev->trigger && !strcmp(led_cdev->trigger->name,
trig->name))
len += scnprintf(buf+len, PAGE_SIZE - len, "[%s] ",
trig->name);
else
len += scnprintf(buf+len, PAGE_SIZE - len, "%s ",
trig->name);
}
up_read(&led_cdev->trigger_lock);
up_read(&triggers_list_lock);
len += scnprintf(len+buf, PAGE_SIZE - len, "\n");
return len;
}
函数介绍:led_trigger_show
该函数主要用于显示给定 LED
设备的当前触发方式的函数,还记得第一章我们讲的,触发方式包括以下几种:
触发方式 | 说明 |
---|---|
none | 无触发方式 |
disk-activity | 硬盘活动 |
nand-disk | nand flash活动 |
mtd | mtd设备活动 |
timer | 定时器 |
heartbeat | 系统心跳 |
实现方式:
- 先通过
dev_get_drvdata
接口,获取LED
数据 down_read
与up_read
是读写锁的函数,用于获取和释放读锁。具体用法如下:
当一个线程调用
down_read
时,如果没有其他线程持有写锁,则该线程将获得读锁并继续执行。如果有其他线程持有写锁,则该线程将被阻塞,直到所有写锁都被释放。当一个线程完成对共享资源的读取时,它应该调用
up_read
来释放读锁,以便其他线程可以获取读锁或写锁。总之,
down_read
和up_read
是用于获取和释放读锁的函数,用于确保多个线程可以同时读取共享资源,而不会发生竞争条件。
- 对触发器进行判断,如果没有,则返回
[none]
,[]
是选中的意思,表示触发器的当前触发方式;如果存在,则将触发器名称与当前触发器匹配,则将[trigger_name>]
添加到缓冲区。
2.3 led_trigger_store
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);
struct led_trigger *trig;
int ret = count;
mutex_lock(&led_cdev->led_access);
if (led_sysfs_is_disabled(led_cdev)) {
ret = -EBUSY;
goto unlock;
}
if (sysfs_streq(buf, "none")) {
led_trigger_remove(led_cdev);
goto unlock;
}
down_read(&triggers_list_lock);
list_for_each_entry(trig, &trigger_list, next_trig) {
if (sysfs_streq(buf, trig->name)) {
down_write(&led_cdev->trigger_lock);
led_trigger_set(led_cdev, trig);
up_write(&led_cdev->trigger_lock);
up_read(&triggers_list_lock);
goto unlock;
}
}
/* we come here only if buf matches no trigger */
ret = -EINVAL;
up_read(&triggers_list_lock);
unlock:
mutex_unlock(&led_cdev->led_access);
return ret;
}
EXPORT_SYMBOL_GPL(led_trigger_store);
函数介绍:led_trigger_store
函数用于设置给定LED设备的触发器。
相关实现:
- 先通过
dev_get_drvdata
接口,获取LED
数据。 - 调用
led_sysfs_is_disabled
接口,判断文件系统接口是否使能 - 如果使能,则函数检查输入缓冲区
buf
是否等于none
。如果是,则函数调用led_trigger_remove
从LED
设备中删除触发器并返回。 - 如果输入缓冲区
buf
不等于none
,则遍历触发器列表,并依次比较,如果匹配,则调用led_trigger_set
设置触发器
led_trigger_store
其内部根据不同情况,将设置led_trigger_set
或者移除led_trigger_remove
相关的LED
触发器。下面我们分析这两个函数。
2.4 led_trigger_set
int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
unsigned long flags;
char *event = NULL;
char *envp[2];
const char *name;
int ret;
if (!led_cdev->trigger && !trig)
return 0;
name = trig ? trig->name : "none";
event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name);
/* 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);
device_remove_groups(led_cdev->dev, led_cdev->trigger->groups);
led_cdev->trigger = NULL;
led_cdev->trigger_data = NULL;
led_cdev->activated = false;
led_set_brightness(led_cdev, LED_OFF);
}
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)
ret = trig->activate(led_cdev);
else
ret = 0;
if (ret)
goto err_activate;
ret = device_add_groups(led_cdev->dev, trig->groups);
if (ret) {
dev_err(led_cdev->dev, "Failed to add trigger attributes\n");
goto err_add_groups;
}
}
if (event) {
envp[0] = event;
envp[1] = NULL;
if (kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp))
dev_err(led_cdev->dev,
"%s: Error sending uevent\n", __func__);
kfree(event);
}
return 0;
err_add_groups:
if (trig->deactivate)
trig->deactivate(led_cdev);
err_activate:
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);
led_cdev->trigger = NULL;
led_cdev->trigger_data = NULL;
led_set_brightness(led_cdev, LED_OFF);
kfree(event);
return ret;
}
EXPORT_SYMBOL_GPL(led_trigger_set);
函数介绍:该函数主要用于清空所有触发器,并且设置给定的LED
触发器
实现方式:
- 调用
name = trig ? trig->name : "none";
获取给定的触发器名称 - 调用
kasprintf
格式化字符串,用于生成内核事件。 - 如果现有的
LED
触发器不为空,则删除所有的触发器,取消led-core.c
内部初始化的工作队列,调用led_stop_software_blink
停止所有LED
闪烁,并调用led_set_brightness
将LED
熄灭 - 如果给定的
LED
触发器不为空,调用list_add_tail
将给定的触发器添加到触发器列表,并调用trig->activate
激活该触发器。 - 最后调用
kobject_uevent_env
,出发*KOBJ*CHANGE
事件
2.5 led_trigger_remove
void led_trigger_remove(struct led_classdev *led_cdev)
{
down_write(&led_cdev->trigger_lock);
led_trigger_set(led_cdev, N ULL);
up_write(&led_cdev->trigger_lock);
}
函数介绍:该函数主要用于清空所有触发器
实现方式:调用led_trigger_set
,将给定触发器的参数设置为NULL
,即可做到清空所有触发器。
3、触发器注册注销函数
在
led-triggers.c
中,除了触发器设置相关的函数外,还有一些关于触发器注册注销的函数,如:led_trigger_register
、led_trigger_unregister
、devm_led_trigger_register
、devm_led_trigger_release
等
3.1 led_trigger_register
int led_trigger_register(struct led_trigger *trig)
{
struct led_classdev *led_cdev;
struct led_trigger *_trig;
rwlock_init(&trig->leddev_list_lock);
INIT_LIST_HEAD(&trig->led_cdevs);
down_write(&triggers_list_lock);
/* 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);
/* 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);
函数介绍:该函数主要用于注册一个LED
触发器
实现方式:作为触发器的入口函数,必须先进行初始化,才能去设置触发器的相关属性,执行相应动作。
- 调用
rwlock_init
接口,初始化读写锁;调用INIT_LIST_HEAD
初始化触发器链表 - 调用
list_for_each_entry
来遍历所有的触发器,与添加的触发器进行比较,调用led_trigger_set
设置触发器 - 调用
list_add_tail
接口,将该触发器加入到链表中 - 最后再次调用
list_for_each_entry
接口,将该触发器注册到任何具有该触发器作为默认触发器的LED
设备上。
3.2 led_trigger_unregister
void led_trigger_unregister(struct led_trigger *trig)
{
struct led_classdev *led_cdev;
if (list_empty_careful(&trig->next_trig))
return;
/* Remove from the list of led triggers */
down_write(&triggers_list_lock);
list_del_init(&trig->next_trig);
up_write(&triggers_list_lock);
/* Remove anyone actively using this 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 == trig)
led_trigger_set(led_cdev, NULL);
up_write(&led_cdev->trigger_lock);
}
up_read(&leds_list_lock);
}
函数介绍:该函数主要用于注销一个LED
触发器
实现方式:与注册相反的操作
- 调用
list_del_init
,反初始化链表 - 调用
list_for_each_entry
遍历触发器,匹配给定的触发器,然后调用led_trigger_set
,传入NULL
参数,来清空该触发器。
3.3 devm_led_trigger_register
static void devm_led_trigger_release(struct device *dev, void *res)
{
led_trigger_unregister(*(struct led_trigger **)res);
}
int devm_led_trigger_register(struct device *dev,
struct led_trigger *trig)
{
struct led_trigger **dr;
int rc;
dr = devres_alloc(devm_led_trigger_release, sizeof(*dr),
GFP_KERNEL);
if (!dr)
return -ENOMEM;
*dr = trig;
rc = led_trigger_register(trig);
if (rc)
devres_free(dr);
else
devres_add(dev, dr);
return rc;
}
EXPORT_SYMBOL_GPL(devm_led_trigger_register);
函数介绍:上面两个函数,为led_trigger_unregister
和led_trigger_register
的资源管理版本,这里不详细介绍。
3.4 led_trigger_register_simple
void led_trigger_register_simple(const char *name, struct led_trigger **tp)
{
struct led_trigger *trig;
int err;
trig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);
if (trig) {
trig->name = name;
err = led_trigger_register(trig);
if (err < 0) {
kfree(trig);
trig = NULL;
pr_warn("LED trigger %s failed to register (%d)\n",
name, err);
}
} else {
pr_warn("LED trigger %s failed to register (no memory)\n",
name);
}
*tp = trig;
}
EXPORT_SYMBOL_GPL(led_trigger_register_simple);
函数介绍:led_trigger_register_simple
函数是一个简单的LED
触发器注册函数,用于注册一个LED
触发器。
它的实现与
led_trigger_register
函数类似,但是它不涉及资源管理。调用者只需要传入触发器的名称以及触发器的指针即可创建成功。
3.5 led_trigger_unregister_simple
void led_trigger_unregister_simple(struct led_trigger *trig)
{
if (trig)
led_trigger_unregister(trig);
kfree(trig);
}
函数介绍:直接调用led_trigger_unregister
来注销一个已存在的触发器,并释放空间。
4、闪烁功能相关函数
在
led-triggers.c
中,除了上面提到的函数外,还包括最重要的闪烁功能控制接口。如果说上面的是一些基本配置,那么下面的就是核心实现。
4.1 led_trigger_blink
void led_trigger_blink(struct led_trigger *trig,
unsigned long *delay_on,
unsigned long *delay_off)
{
led_trigger_blink_setup(trig, delay_on, delay_off, 0, 0);
}
EXPORT_SYMBOL_GPL(led_trigger_blink);
4.2 led_trigger_blink_oneshot
void led_trigger_blink_oneshot(struct led_trigger *trig,
unsigned long *delay_on,
unsigned long *delay_off,
int invert)
{
led_trigger_blink_setup(trig, delay_on, delay_off, 1, invert);
}
EXPORT_SYMBOL_GPL(led_trigger_blink_oneshot);
上面两个函数,通过不同的参数,均对
led_trigger_blink_setup
调用来实现,下面我们来分析一下这个函数
4.3 led_trigger_blink_setup
static void led_trigger_blink_setup(struct led_trigger *trig,
unsigned long *delay_on,
unsigned long *delay_off,
int oneshot,
int invert)
{
struct led_classdev *led_cdev;
if (!trig)
return;
read_lock(&trig->leddev_list_lock);
list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) {
if (oneshot)
led_blink_set_oneshot(led_cdev, delay_on, delay_off,
invert);
else
led_blink_set(led_cdev, delay_on, delay_off);
}
read_unlock(&trig->leddev_list_lock);
}
函数介绍:该函数是触发器实现闪烁功能的接口,用于控制LED
亮灭的时间,以及闪烁一次,反转的标志位设定。
下面的函数属于
led-core.c
4.4 led_blink_set_oneshot和led_blink_set
void led_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
del_timer_sync(&led_cdev->blink_timer);
clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
led_blink_setup(led_cdev, delay_on, delay_off);
}
EXPORT_SYMBOL_GPL(led_blink_set);
void led_blink_set_oneshot(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off,
int invert)
{
if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
timer_pending(&led_cdev->blink_timer))
return;
set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
if (invert)
set_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
else
clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
led_blink_setup(led_cdev, delay_on, delay_off);
}
EXPORT_SYMBOL_GPL(led_blink_set_oneshot);
函数介绍:这两个函数均用于LED
闪烁功能的设置,其主要差别在于是否为闪烁一次的功能。
实现方式:两者的差别,是通过test_bit
、set_bit
、clear_bit
等接口,来控制LED
标志位的变化,从而设置特定的功能。
4.5 led_blink_setup
static void led_blink_setup(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
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);
}
函数介绍:当LED
亮灭时间均设置为0时,默认为1Hz
,最后调用led_set_software_blink
来控制LED
闪烁
4.6 led_set_software_blink
static void led_set_software_blink(struct led_classdev *led_cdev,
unsigned long delay_on,
unsigned long delay_off)
{
int current_brightness;
current_brightness = led_get_brightness(led_cdev);
if (current_brightness)
led_cdev->blink_brightness = current_brightness;
if (!led_cdev->blink_brightness)
led_cdev->blink_brightness = led_cdev->max_brightness;
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_nosleep(led_cdev, LED_OFF);
return;
}
/* never off - just set to brightness */
if (!delay_off) {
led_set_brightness_nosleep(led_cdev,
led_cdev->blink_brightness);
return;
}
set_bit(LED_BLINK_SW, &led_cdev->work_flags);
mod_timer(&led_cdev->blink_timer, jiffies + 1);
}
函数介绍:设置LED
软件闪烁功能
实现思路:
- 调用
led_get_brightness
接口,获取当前亮度 - 将
delay_on
和delay_off
赋值LED
亮和灭的时间间隔 - 特殊情况处理,如果
delay_on
为0,则将LED设置为关闭状态。如果delay_off
为0,则将LED设置为最大亮度。 - 设置
LED_BLINK_SW
闪烁标志位,并且调用mod_timer
启动一个1ms
定时器,该定时器用于调用led_timer_function
函数
4.7 调用流程
上面介绍的函数为闪烁功能的核心函数,其调用流程总结如下:
// LED触发层实现逻辑
led_trigger_blink(drivers/leds/led-triggers.c)
led_trigger_blink_oneshot(drivers/leds/led-triggers.c)
|--> led_trigger_blink_setup(drivers/leds/led-triggers.c)
|--> led_blink_set_oneshot // 闪烁一次
|--> led_blink_setup // 闪烁设置
|--> led_set_software_blink // 设置LED打开和关闭
|--> led_set_brightness_nosleep
|--> led_set_brightness_nopm
|--> __led_set_brightness
|--> led_blink_set // 闪烁
|--> led_blink_setup
由上面调用流程可知,led-triggers.c
的闪烁功能对外提供接口led_trigger_blink
和led_trigger_blink_oneshot
来设置闪烁。
5、总结回顾
我们这一章,了解了核心层的led-triggers.c
实现,其主要包括了:
- 触发器配置函数:
led_trigger_set
,led_trigger_remove
等 - 触发器注册注销函数:
led_trigger_register
,devm_led_trigger_register
等 - 触发器闪烁设置:
led_trigger_blink_oneshot
、led_trigger_blink
等
点赞+关注,永远不迷路