【LED子系统】五、核心层详解(二)

news/2024/12/4 20:04:52/
img
个人主页:董哥聊技术
我是董哥,高级嵌入式软件开发工程师,从事嵌入式Linux驱动开发和系统开发,曾就职于世界500强公司!
创作理念:专注分享高质量嵌入式文章,让大家读有所得!
img

文章目录

    • 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实现以及作用。

image-20230417084033734

我们接着从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

实现思路

  1. 调用INIT_WORK宏定义,将工作队列指针和处理函数作为参数传递
  2. 最终通过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的亮度情况,实现亮灭的效果。

实现思路

  1. 首先通过from_timer接口,底层还是通过container_of函数,获取led_classdev结构体指针
  2. 判断blink_delay_onblink_delay_off延时是否为0,如果为0,默认为关闭LED
  3. 通过led_get_brightness获取亮度值,闪烁逻辑如下:
    1. 如果亮度为0,则设置亮度值,更新延时为亮延时:delay = led_cdev->blink_delay_on
    2. 如果亮度不为0,则设置亮度为0,brightness = LED_OFF;然后设置延时为灭延时:delay = led_cdev->blink_delay_off
  4. 调用led_set_brightness_nosleep设置LED亮度
  5. 调用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函数来设置亮度,该函数保证不会使系统进入睡眠状态。如果不成功,则将亮度设置委托给工作队列任务。

相关实现

  1. 尝试调用__led_set_brightness接口,该函数用于未打开休眠功能。

  2. 如果该__led_set_brightness函数返回错误码,则代表了打开了休眠功能

  3. 休眠状态下,想要设置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灯的亮度。

实现思路

  1. 首先通过container_of来获取led_classdev结构体指针
  2. test_and_clear_bit对闪烁标志位进行判断,如果不支持,则关闭
  3. 调用__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硬件驱动层,即:调用底层的相关接口——启下

点赞+关注,永远不迷路

img
欢迎关注【嵌入式艺术】,董哥原创!

http://www.ppmy.cn/news/70362.html

相关文章

鸿蒙Hi3861学习十五-Huawei LiteOS-M(Socket客户端)

一、简介 在网络编程的时候&#xff0c;不管是客户端还是服务端&#xff0c;都离不开Socket。那什么是Socket&#xff0c;这里做个简单介绍。详细的内容&#xff0c;可以参考这篇文章&#xff1a;WIFI学习一&#xff08;socket介绍&#xff09;_wifi socket_t_guest的博客-CSDN…

JSON+AJAX+ThreadLocal+文件上传下载

文章目录 JSON和AJAX文档介绍1. JSON介绍1.1 JSON快速入门1.2 JSON和字符串转换1.2.1 JSON转字符串1.2.2 字符串转JSON1.2.3 JSON和字符串转换细节 1.3 JSON在java中使用1.3.1 Java对象和JSON字符串转换1.3.2 List对象和JSON字符串转换1.3.3 Map对象和JSON字符串转换 2. Ajax介…

leetcode-二叉树专题

1. 判断相同 100. 相同的树 class Solution { public:bool isSameTree(TreeNode* p, TreeNode* q) {if(!p&&!q) return true;if(!p||!q) return false;if(p->val!q->val) return false;return isSameTree(p->left,q->left)&&isSameTree(p->ri…

gl-opendrive插件(车俩3D仿真模拟自动驾驶)

简介 本插件基于免费opendrive开源插件、Threejs和Webgl三维技术、vue前端框架&#xff0c;blender开源建模工具等进行二次开发。该插件由本人独立开发以及负责&#xff0c;目前处于demo阶段&#xff0c;功能还需待完善&#xff0c;由于开发仓促代码还需优化。 因此&#xff…

基于卷积的图像分类识别(七):SENet

系列文章目录 本专栏介绍基于深度学习进行图像识别的经典和前沿模型&#xff0c;将持续更新&#xff0c;包括不仅限于&#xff1a;AlexNet&#xff0c; ZFNet&#xff0c;VGG&#xff0c;GoogLeNet&#xff0c;ResNet&#xff0c;DenseNet&#xff0c;SENet&#xff0c;MobileN…

Redis的日常使用小结

一、数据类型 五大数据类型String型&#xff1a;String 是redis中最基本的数据类型,二进制安全的,即它可以包含任何数据,如序列化的对象、jpg图片,大小上限是512M。Hash型(存储消耗高于字符串): 键值对集合,适合存储对象&#xff0c;类似 Java的Map<String,Object>。Lis…

简单实现小程序授权登录功能

本人给大家带来了关于微信小程序的相关知识&#xff0c;其中主要介绍了怎么实现小程序授权登录功能的相关内容&#xff0c;下面一起来看一下&#xff0c;希望对大家有帮助。 在我们平时工作、学习、生活中&#xff0c;微信小程序已成为我们密不可分的一部分&#xff0c;我们仔细…

成都欢蓬信息:抖音电商去年GMV增速超80%

在今年的抖音电商生态大会上&#xff0c;抖音电商交出了年度“成绩单”。 5月16日&#xff0c;抖音电商总裁魏雯雯披露&#xff0c;近一年抖音电商GMV&#xff08;成交额&#xff09;增幅超80%。其中&#xff0c;商城GMV同比增长277%&#xff0c;电商搜索GMV同比增长159%&#…