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

news2024/12/25 12:48:44
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_LOCKDEP
    struct 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_LOCKDEP
    struct 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;					//	设置新的亮度值
        else
            brightness = 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;												//	更新亮度值为0
        delay = 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.coloradmin.cn/o/526943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于B/S架构、可替代付费商业软件的一站式量化交易平台

产品简介 这是一个面向程序员的量化交易软件&#xff0c;用于期货、股票、外汇、炒币等多种交易场景&#xff0c;实现自动交易。已对接了CTP接口&#xff08;国内期货&#xff09;、老虎证券接口&#xff08;美股港股&#xff09;。 功能特性&#xff1a; 一站式平台&#x…

Protell99SE祭文

Protell99SE祭文 大概是在21年前的今天&#xff0c;我和你结合在一起&#xff0c;陪伴走过无数的设计。 我的感觉&#xff0c;大概是在2021年吧&#xff0c;你逐渐离我远去。啊&#xff0c;Protel99SE时代一去不复返了。 我用了你21年&#xff0c;虽着AD软件的到来&#xff…

【C++】19.C++11

1.C11 auto 范围for 新容器 线程库列表初始化右值引用和移动语义 lambda表达式容器支持花括号列表初始化 本质是增加一个initializer_list的构造函数initializer_list支持花括号 2.列表初始化 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <ve…

​数字化转型升级之工业元宇宙与AIGC

月説小飞象交流会 生活就是&#xff0c;面对复杂&#xff0c;保持欢喜。心烦时&#xff0c;记住三句话&#xff1a;1、算了吧。2、没关系。3、会过去的。 内部交流│24期 数字化转型升级 工业元宇宙与AIGC data analysis ●●●● 分享人&#xff1a;李铁军 ‍ 现如今数字化不再…

定风波、渡重山、至未来:2023中国数字能源生态大会开启的新旅程

全球碳中和的时代背景下&#xff0c;面向3060发展目标&#xff0c;新使命、新技术、新应用的到来&#xff0c;都给能源产业带来了持续变革的必要性与可能性。 2023年5月11日&#xff0c;在2023中国数字能源生态大会上&#xff0c;华为数字能源技术有限公司总裁侯金龙发表了“融…

【微电网】含风、光、储联合发电的微电网优化调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

2023 年 IntelliJ IDEA 下载安装教程,超详细图文教程,亲测可用

. IDEA 下载 1、打开浏览器输入https://www.jetbrains.com/&#xff0c;进入 Jetbrains官网&#xff0c;点击 Developer Tools&#xff0c;再点击 Intellij IDEA 2、点击中间的 Download&#xff0c;进入IDEA下载界面 3、选择左边的 Ultimate 版本进行下载安装。Ultimate 版…

心法利器[84] | 最近面试小结

心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会&#xff0c;与大家一起成长。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。 2022年新一版的文章合集已经发布&#xff0c;累计已经60w字了&#xff0c;获取方式看这里&…

让Chat-GPT成为你的微信小助理

前言 最近公司裁员风波&#xff0c;很不幸成为了裁员名单中的一员&#xff1b;此时又恰逢一波AIGC浪潮&#xff0c;首当其冲的就是GPT这样的大语言模型的诞生&#xff0c;是整个AI领域的一个质的飞跃。正好在这样一个空挡期&#xff0c;我就基于Chat-GPT 做了一些深入的实践&a…

ChatGPT是什么?

ChatGPT是什么&#xff1f; ChatGPT是一个基于人工智能技术的聊天机器人平台&#xff0c;旨在为用户提供智能化、高效率的交互体验。ChatGPT能够理解用户输入的自然语言&#xff0c;根据语义分析和机器学习算法生成相应的回答。它可以回答用户的问题、提供建议、进行闲聊等&am…

前端技术搭建井字游戏(内含源码)

The sand accumulates to form a pagoda ✨ 写在前面✨ 功能介绍✨ 页面搭建✨ 样式设置✨ 逻辑部分 ✨ 写在前面 上周我们实通过前端基础实现了飞机大战游戏&#xff0c;今天还是继续按照我们原定的节奏来带领大家完成一个井字游戏游戏&#xff0c;功能也比较简单简单&#x…

路径规划 | 图解快速随机扩展树RRT算法(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 什么是RRT算法&#xff1f;2 图解RRT算法原理3 算法仿真与实现3.1 ROS C实现3.2 Python实现3.3 Matlab实现 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全局规划(图搜索、采样法、…

〖Python网络爬虫实战㉕〗- Ajax数据爬取之Ajax 案例实战

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

Gradle下载、安装、配置

1. Gradle下载 1.1 Gradle下载地址&#xff1a;https://docs.gradle.org/current/userguide/installation.html#installing_manually 1.2 点击Download 1.3 选择想要下载的版本&#xff0c;点击binary-only即可下载 2. Gradle安装&#xff08;注意&#xff1a;安装gradle之前…

【C语言】三子棋小游戏的思路及实现(内附代码)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 分文件实现 2.分步骤实现 2.1 游戏菜单 2.2 创建棋盘 2.3 初始化棋盘 2.4 打印棋盘 2.5 玩家下棋 2.6 电脑下棋 2.7 判断输赢 3. 附完整代码 3.1 test.c 3.2 game.h 3.2 game.c 1. 分文件实现 当我…

对称加密、非对称加密、数字签名、消息摘要的简单学习

对称加密、非对称加密、数字签名、消息摘要的简单学习 前言对称加密算法DES特点&#xff1a;为什么不使用&#xff1a; 3DES&#xff08;Triple DES 或者 DESede&#xff09;特点&#xff1a;使用场景&#xff1a;为什么不用&#xff1a; AES&#xff08;Advanced Encryption S…

聊一聊模板方法模式

统一抽取&#xff0c;制定规范&#xff1b; 一、概述 模板方法模式&#xff0c;又叫模板模式&#xff0c;属于23种设计模式中的行为型模式。在抽象类中公开定义了执行的方法&#xff0c;子类可以按需重写其方法&#xff0c;但是要以抽象类中定义的方式调用方法。总结起来就是&…

c语言实现栈(顺序栈,链栈)

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:讲解用c语言实现:“数据结构之"栈”,分别从"顺序栈…

区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测

区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 1.Matlab实现基于QRCNN-BiGRU分位数回…

MySQL视图与联集

一、VIEW&#xff08;视图&#xff09; 1、 概念 可以被当作是虚拟表或存储查询 视图跟表格的不同是&#xff0c;表格中有实际储存资料&#xff0c;而视图是建立在表格之上的一个架构&#xff0c;它本身并不实际储存资料。 临时表在用户退出或同数据库的连接断开后就自动消…