文章目录
- 1、前言
- 2、led_init_core分析
- 2.1 相关数据结构
- 2.1.1 work_struct
- 2.1.2 timer_list
- 2.2 相关实现
- 2.2.1 INIT_WORK
- 2.2.2 timer_setup
- 3、led_timer_function分析
- 3.1.1 led_get_brightness
- 3.1.2 led_set_brightness_nosleep
- 3.1.3 led_set_brightness_nopm
- 4、set_brightness_delayed分析
- 4.1.1 __led_set_brightness
- 4.1.2 __led_set_brightness_blocking
- 5、总结
- 5.1 代码实现流程
1、前言
上篇文章我们了解了子系统的核心层
led-class.c
,下面我们来分析驱动框架中核心层的led-core.c
实现以及作用。
我们接着从led-core.c
文件开始分析
2、led_init_core分析
上一篇文章,我们知道在将
leds_classdev
注册进入子系统后,会调用led_init_core
函数,初始化核心层,下面我们以led_init_core
该函数为突破口分析。
2.1 相关数据结构
2.1.1 work_struct
struct work_struct {atomic_long_t data;struct list_head entry;work_func_t func;
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};
结构体名称:work_struct
文件位置:include/linux/workqueue.h.h
主要作用:定义一个工作队列,包括了工作项的状态和数据,以及处理工作项的函数指针,用于实现异步执行任务的功能。在工作队列中,每个工作项都是一个work_struct
结构体的实例,通过将工作项添加到工作队列中,可以实现后台执行任务的功能。
2.1.2 timer_list
struct timer_list {/** All fields that change during normal runtime grouped to the* same cacheline*/struct hlist_node entry;unsigned long expires;void (*function)(struct timer_list *);u32 flags;#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};
结构体名称:work_struct
文件位置:include/linux/timer.h
主要作用:表示一个定时器,其中包括定时器到期时间,处理函数,以及一些标志位信息。
entry
:一个hlist_node
结构体,用于将定时器添加到哈希表中。expires
:一个unsigned long
类型的值,表示定时器何时到期。function
:表示定时器的处理函数flags
:一个u32
类型的值,表示定时器的标志位lockdep_map
:如果定义了CONFIG_LOCKDEP
,则包含一个lockdep_map
结构体,用于跟踪定时器的锁定情况。
2.2 相关实现
void led_init_core(struct led_classdev *led_cdev)
{INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);timer_setup(&led_cdev->blink_timer, led_timer_function, 0);
}
函数介绍:这部分代码,用于初始化核心层,此函数通过设置延迟工作队列来设置 LED
亮度,并通过计时器来进行软件闪烁。
2.2.1 INIT_WORK
INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);#define INIT_WORK(_work, _func) \__INIT_WORK((_work), (_func), 0)// #define __INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed, 0)#define __INIT_WORK(_work, _func, _onstack) \do { \__init_work((_work), _onstack); \(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \INIT_LIST_HEAD(&(_work)->entry); \(_work)->func = (_func); \} while (0)/*
do {__init_work(&led_cdev->set_brightness_work, 0);(&led_cdev->set_brightness_work)->data = (atomic_long_t) WORK_DATA_INIT();INIT_LIST_HEAD(&(&led_cdev->set_brightness_work)->entry);(&led_cdev->set_brightness_work)->func = set_brightness_delayed;
} while (0)
*/
函数介绍:INIT_WORK
该宏定义,两个参数,一个是表示工作队列的指针&led_cdev->set_brightness_work
,一个是工作队列的处理函数set_brightness_delayed
实现思路:
- 调用
INIT_WORK
宏定义,将工作队列指针和处理函数作为参数传递 - 最终通过
do while
将工作队列初始化函数包括在内,实现工作队列的初始化
2.2.2 timer_setup
timer_setup(&led_cdev->blink_timer, led_timer_function, 0);
函数介绍:这里不展开介绍,同上,将定时器指针和处理函数作为参数,直接初始化。定时器的超时时间通过mod_timer
来设置。
由上文可知,我们
led-core.c
的核心是初始化了一个工作队列和一个定时器函数,通过延迟工作队列来设置LED
亮度,并通过计时器来进行软件闪烁。我们先来分析
led_timer_function
函数
3、led_timer_function分析
由上文可知,
led_timer_function
主要负责LED
的闪烁功能
static void led_timer_function(struct timer_list *t)
{struct led_classdev *led_cdev = from_timer(led_cdev, t, blink_timer); // 获取结构体指针unsigned long brightness;unsigned long delay;if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { // 闪烁延时判断led_set_brightness_nosleep(led_cdev, LED_OFF);clear_bit(LED_BLINK_SW, &led_cdev->work_flags);return;}if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP, // 测试是否为闪烁一次&led_cdev->work_flags)) {clear_bit(LED_BLINK_SW, &led_cdev->work_flags);return;}brightness = led_get_brightness(led_cdev); // 获取当前亮度值if (!brightness) {/* Time to switch the LED on. */if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE,&led_cdev->work_flags))brightness = led_cdev->new_blink_brightness; // 设置新的亮度值elsebrightness = led_cdev->blink_brightness; // 设置默认亮度值delay = led_cdev->blink_delay_on;} 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; // 更新亮度值为0delay = led_cdev->blink_delay_off;}led_set_brightness_nosleep(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 (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) { // 一次闪烁判断if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) {if (brightness)set_bit(LED_BLINK_ONESHOT_STOP,&led_cdev->work_flags);} else {if (!brightness)set_bit(LED_BLINK_ONESHOT_STOP,&led_cdev->work_flags);}}mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); // 更新定时器时间
}
函数介绍:该函数实现了定时触发,每次触发控制LED
的亮度情况,实现亮灭的效果。
实现思路:
- 首先通过
from_timer
接口,底层还是通过container_of
函数,获取led_classdev
结构体指针 - 判断
blink_delay_on
和blink_delay_off
延时是否为0
,如果为0
,默认为关闭LED
- 通过
led_get_brightness
获取亮度值,闪烁逻辑如下:- 如果亮度为
0
,则设置亮度值,更新延时为亮延时:delay = led_cdev->blink_delay_on
- 如果亮度不为0,则设置亮度为0,
brightness = LED_OFF;
然后设置延时为灭延时:delay = led_cdev->blink_delay_off
- 如果亮度为
- 调用
led_set_brightness_nosleep
设置LED
亮度 - 调用
mod_timer
更新定时器时间
3.1.1 led_get_brightness
static inline int led_get_brightness(struct led_classdev *led_cdev)
{return led_cdev->brightness;
}
函数介绍:该函数比较简单,直接获取led_classdev
结构体的亮度值即可!
3.1.2 led_set_brightness_nosleep
void led_set_brightness_nosleep(struct led_classdev *led_cdev,enum led_brightness value)
{led_cdev->brightness = min(value, led_cdev->max_brightness);if (led_cdev->flags & LED_SUSPENDED)return;led_set_brightness_nopm(led_cdev, led_cdev->brightness);
}
EXPORT_SYMBOL_GPL(led_set_brightness_nosleep);
函数介绍:与最大亮度值进行比较,并设置LED
亮度,如果LED
被挂起(进入休眠),则直接返回。所以,正如其名,nosleep
即不休眠时的函数生效。。
3.1.3 led_set_brightness_nopm
void led_set_brightness_nopm(struct led_classdev *led_cdev,enum led_brightness value)
{/* Use brightness_set op if available, it is guaranteed not to sleep */if (!__led_set_brightness(led_cdev, value))return;/* If brightness setting can sleep, delegate it to a work queue task */led_cdev->delayed_set_value = value;schedule_work(&led_cdev->set_brightness_work);
}static int __led_set_brightness(struct led_classdev *led_cdev,enum led_brightness value)
{if (!led_cdev->brightness_set)return -ENOTSUPP;led_cdev->brightness_set(led_cdev, value);return 0;
}
函数介绍:设置LED
亮度,该函数首先尝试使用__led_set_brightness
函数来设置亮度,该函数保证不会使系统进入睡眠状态。如果不成功,则将亮度设置委托给工作队列任务。
相关实现:
-
尝试调用
__led_set_brightness
接口,该函数用于未打开休眠功能。 -
如果该
__led_set_brightness
函数返回错误码,则代表了打开了休眠功能 -
休眠状态下,想要设置
LED
亮度值,需要将delayed_set_value
变量设置为所需的亮度值,然后调度set_brightness_work
任务。
4、set_brightness_delayed分析
由上面可知,如果子系统打开了休眠功能,设置
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);int ret = 0;if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {led_cdev->delayed_set_value = LED_OFF;led_stop_software_blink(led_cdev);}ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value);if (ret == -ENOTSUPP)ret = __led_set_brightness_blocking(led_cdev,led_cdev->delayed_set_value);if (ret < 0 &&/* LED HW might have been unplugged, therefore don't warn */!(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) &&(led_cdev->flags & LED_HW_PLUGGABLE)))dev_err(led_cdev->dev,"Setting an LED's brightness failed (%d)\n", ret);
}
函数介绍:set_brightness_delayed
正如其名,延时设置LED
灯的亮度。
实现思路:
- 首先通过
container_of
来获取led_classdev
结构体指针 test_and_clear_bit
对闪烁标志位进行判断,如果不支持,则关闭- 调用
__led_set_brightness
和__led_set_brightness_blocking
来设置LED
的亮度
4.1.1 __led_set_brightness
static int __led_set_brightness(struct led_classdev *led_cdev,enum led_brightness value)
{if (!led_cdev->brightness_set)return -ENOTSUPP;led_cdev->brightness_set(led_cdev, value);return 0;
}
函数介绍:非阻塞设置LED
亮度,该函数调用led-gpio.c
硬件驱动层分配的函数来操作硬件实现,用于不支持休眠时,不用考虑休眠是否打断该函数执行
4.1.2 __led_set_brightness_blocking
static int __led_set_brightness_blocking(struct led_classdev *led_cdev,enum led_brightness value)
{if (!led_cdev->brightness_set_blocking)return -ENOTSUPP;return led_cdev->brightness_set_blocking(led_cdev, value);
}
函数介绍:阻塞设置LED
亮度,该函数调用led-gpio.c
硬件驱动层分配的函数来操作硬件实现,用于在支持休眠时,避免休眠打断该函数执行。
这两个函数,如果看源码的话,还是有点绕的,因为
__led_set_brightness_blocking
内部竟然也直接调用了__led_set_brightness
,所以这里也尽量还原代码原本的意思。
这篇就先讲到这里,当然该文件中还有一些xxx_blink
相关的函数,主要用于管理闪烁,我们放到后面再了解。
5、总结
上面我们了解到核心层的主要作用:通过延迟工作队列来设置 LED
亮度,并通过计时器来进行软件闪烁。
5.1 代码实现流程
led_timer_function(drivers/leds/led-core.c)|--> led_get_brightness // 获取亮度值|--> led_set_brightness_nosleep // 设置LED亮度|--> led_set_brightness_nopm // 在非休眠状态下设置|--> __led_set_brightness // |--> led_cdev->brightness_set// 硬件驱动层实现 set_brightness_delayed(drivers/leds/led-core.c)|--> __led_set_brightness // 非阻塞函数,调用该接口设置LED亮度后立即返回|--> led_cdev->brightness_set|--> gpio_led_set(drivers/leds/leds-gpio.c) // 最终调用的函数|--> __led_set_brightness_blocking // 阻塞函数,调用该接口设置LED亮度后必须等待设置完成,才返回|--> led_cdev->brightness_set_blocking|--> gpio_led_set_blocking
核心层的接口,大都是提供给外部使用的,这些函数也都通过EXPORT_SYMBOL_GPL
宏定义来导出的,即:向上提供了操作的接口。——乘上
并且这些函数底层实现,都关联到了led-gpio.c
硬件驱动层,即:调用底层的相关接口——启下
点赞+关注,永远不迷路