文章目录
- 1、前言
- 2、触发器
- 3、heartbeat触发器的注册注销
- 4、heartbeat触发器相关定义和实现
- 4.1 heartbeat_panic_nb
- 4.2 heartbeat_reboot_nb
- 4.3 heartbeat_led_trigger
- 4.4 heartbeat_trig_groups实现
- 4.5 struct heartbeat_trig_data
- 4.6 heartbeat_trig_activate
- 4.7 heartbeat_trig_deactivate
- 4.8 led_heartbeat_function
- 5、总结
1、前言
上面几篇文章,我们详细解释了核心层的相关实现,主要包括:
led-core.c
、led-class.c
、led-triggers.c
下面我们来主要了解一下
LED
触发器的实现
在上文中,我们提及到led-triggers.c
中,只是对外提供了注册注销接口,闪烁设置接口,那么要想去实现闪烁功能,必定要有一个地方去调用这些函数!
2、触发器
我们在第一章已经了解到在drivers/leds/trigger/ledtrig-xxx.c
中,都是关于触发器的实现,常见的触 发方式如下表所示:
触发方式 | 说明 |
---|---|
none | 无触发方式 |
disk-activity | 硬盘活动 |
nand-disk | nand flash活动 |
mtd | mtd设备活动 |
timer | 定时器 |
heartbeat | 系统心跳 |
不同的触发方式,以控制LED
实现不同的效果!下面我们以ledtrig-heartbeat.c
为例分析,打开该文件!
3、heartbeat触发器的注册注销
static int __init heartbeat_trig_init(void)
{
int rc = led_trigger_register(&heartbeat_led_trigger); // 注册到LED子系统框架中
if (!rc) {
atomic_notifier_chain_register(&panic_notifier_list,
&heartbeat_panic_nb); // 注册panic通知链路
register_reboot_notifier(&heartbeat_reboot_nb); // 注册到重启通知链路
}
return rc;
}
static void __exit heartbeat_trig_exit(void)
{
unregister_reboot_notifier(&heartbeat_reboot_nb);
atomic_notifier_chain_unregister(&panic_notifier_list,
&heartbeat_panic_nb);
led_trigger_unregister(&heartbeat_led_trigger);
}
module_init(heartbeat_trig_init);
module_exit(heartbeat_trig_exit);
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
MODULE_DESCRIPTION("Heartbeat LED trigger");
MODULE_LICENSE("GPL v2");
我们分析驱动,一般都以
module_init
为入口函数,
函数介绍:module_init
该函数主要将heartbeat_led_trigger
定义的触发器注册进入led_classdev
中,说明白点,也就是LED
子系统中。
实现思路:
- 调用
led_trigger_register
接口,将定义的触发器,添加到LED
子系统中 - 调用
atomic_notifier_chain_register
接口,将heartbeat_panic_nb
注册到panic_notifier_list
链路中,在内核发生panic
时将调用该链。 - 调用
register_reboot_notifier
接口,注册heartbeat_reboot_nb
通知函数,在系统重新启动时将调用该notifier
4、heartbeat触发器相关定义和实现
在
heartbeat_trig_init
函数中,我们看到了heartbeat_led_trigger
变量,heartbeat_panic_nb
、heartbeat_reboot_nb
等一系列未知函数,那么下面我们分析这些变量的定义和实现!
4.1 heartbeat_panic_nb
static struct notifier_block heartbeat_panic_nb = {
.notifier_call = heartbeat_panic_notifier,
};
static int heartbeat_panic_notifier(struct notifier_block *nb,
unsigned long code, void *unused)
{
panic_heartbeats = 1;
return NOTIFY_DONE;
}
函数介绍:该函数主要用于kernel
出发panic
后执行的函数,这里设置panic_heartbeats = 1
后,关闭LED
灯
4.2 heartbeat_reboot_nb
static struct notifier_block heartbeat_reboot_nb = {
.notifier_call = heartbeat_reboot_notifier,
};
static int heartbeat_reboot_notifier(struct notifier_block *nb,
unsigned long code, void *unused)
{
led_trigger_unregister(&heartbeat_led_trigger);
return NOTIFY_DONE;
}
函数介绍:该函数主要用于系统重启时,调用该函数,来注销掉此触发器。
4.3 heartbeat_led_trigger
static struct led_trigger heartbeat_led_trigger = {
.name = "heartbeat",
.activate = heartbeat_trig_activate,
.deactivate = heartbeat_trig_deactivate,
.groups = heartbeat_trig_groups,
};
led_trigger
结构体在之前文章有分析过, 这里创建heartbeat_led_trigger
心跳触发器,然后并配置相关激活函数和sysfs
对应的设备文件。
4.4 heartbeat_trig_groups实现
static ssize_t led_invert_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct heartbeat_trig_data *heartbeat_data =
led_trigger_get_drvdata(dev);
return sprintf(buf, "%u\n", heartbeat_data->invert);
}
static ssize_t led_invert_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct heartbeat_trig_data *heartbeat_data =
led_trigger_get_drvdata(dev);
unsigned long state;
int ret;
ret = kstrtoul(buf, 0, &state);
if (ret)
return ret;
heartbeat_data->invert = !!state;
return size;
}
static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
static struct attribute *heartbeat_trig_attrs[] = {
&dev_attr_invert.attr,
NULL
};
ATTRIBUTE_GROUPS(heartbeat_trig);
函数介绍:led_invert_show
和led_invert_store
是sysfs
文件系统中,对外开放的文件节点,这两个函数实现了文件节点的读写。
实现思路:
- 调用
led_trigger_get_drvdata
接口,获取heartbeat_trig_data
相关数据 - 读写
heartbeat_data
的invert
属性
这里牵涉到
heartbeat_trig_data
结构体,我们来了解下
4.5 struct heartbeat_trig_data
往下分析前,我们先看一下这个结构体
struct heartbeat_trig_data {
struct led_classdev *led_cdev;
unsigned int phase;
unsigned int period;
struct timer_list timer;
unsigned int invert;
};
led_cdev
:指向struct led_classdev
对象的指针phase
:表示心跳当前阶段period
:表示心跳周期timer
:用于调度心跳函数的struct timer_list
对象invert
:表示是否反转心跳。
4.6 heartbeat_trig_activate
static int heartbeat_trig_activate(struct led_classdev *led_cdev)
{
struct heartbeat_trig_data *heartbeat_data;
heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
if (!heartbeat_data)
return -ENOMEM;
led_set_trigger_data(led_cdev, heartbeat_data);
heartbeat_data->led_cdev = led_cdev;
timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0);
heartbeat_data->phase = 0;
if (!led_cdev->blink_brightness)
led_cdev->blink_brightness = led_cdev->max_brightness;
led_heartbeat_function(&heartbeat_data->timer);
set_bit(LED_BLINK_SW, &led_cdev->work_flags);
return 0;
}
函数介绍:该函数的主要功能是为激活对应的LED
触发器。
实现方式:
- 定义
heartbeat_trig_data
结构体变量,表示触发器的相关数据 - 调用
led_set_trigger_data
接口,将结构体heartbeat_trig_data
和led_classdev
关联起来 - 调用
timer_setup
接口,配置触发器的定时器函数 - 最后调用
set_bit
,设置工作位为LED_BLINK_SW
,表示触发器正常工作
4.7 heartbeat_trig_deactivate
static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
{
struct heartbeat_trig_data *heartbeat_data =
led_get_trigger_data(led_cdev);
del_timer_sync(&heartbeat_data->timer);
kfree(heartbeat_data);
clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
}
函数介绍:该函数的主要功能是将对应的LED
触发器失效
实现方式:
- 调用
del_timer_sync
接口,删除定时器 - 调用
clear_bit
接口,清除LED_BLINK_SW
位,表示定时器关闭
4.8 led_heartbeat_function
static void led_heartbeat_function(struct timer_list *t)
{
struct heartbeat_trig_data *heartbeat_data =
from_timer(heartbeat_data, t, timer); // 获取heartbeat_trig_data数据结构指针
struct led_classdev *led_cdev;
unsigned long brightness = LED_OFF;
unsigned long delay = 0;
led_cdev = heartbeat_data->led_cdev;
if (unlikely(panic_heartbeats)) {
led_set_brightness_nosleep(led_cdev, LED_OFF);
return;
}
if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
led_cdev->blink_brightness = led_cdev->new_blink_brightness; // 判断是否亮度有变化
/* acts like an actual heart beat -- ie thump-thump-pause... */
switch (heartbeat_data->phase) {
case 0:
/*
* The hyperbolic function below modifies the
* heartbeat period length in dependency of the
* current (1min) load. It goes through the points
* f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
*/
heartbeat_data->period = 300 +
(6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
heartbeat_data->period =
msecs_to_jiffies(heartbeat_data->period);
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
if (!heartbeat_data->invert)
brightness = led_cdev->blink_brightness;
break;
case 1:
delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
heartbeat_data->phase++;
if (heartbeat_data->invert)
brightness = led_cdev->blink_brightness;
break;
case 2:
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
if (!heartbeat_data->invert)
brightness = led_cdev->blink_brightness;
break;
default:
delay = heartbeat_data->period - heartbeat_data->period / 4 -
msecs_to_jiffies(70);
heartbeat_data->phase = 0;
if (heartbeat_data->invert)
brightness = led_cdev->blink_brightness;
break;
}
led_set_brightness_nosleep(led_cdev, brightness);
mod_timer(&heartbeat_data->timer, jiffies + delay);
}
函数介绍:led_heartbeat_function
是定时器的核心函数,也是控制心跳LED
触发器的关键函数。
实现思路:
- 通过调用
from_timer
来获取结构体heartbeat_trig_activate
的首地址空间 - 调用
test_and_clear_bit
,判断是否亮度发生变化,如果变化,赋值新的亮度 - 中间的
case
主要适配CPU
不同的负载来稳定产生一个稳定的定时效果,并且根据heartbeat_data->invert
的值,设置亮灭效果。(PS:注意这里判断heartbeat_data->invert
的时候,是有无!
的区别来实现亮灭的) - 最后调用
led_set_brightness_nosleep
将闪烁亮度和延迟设置进去。
5、总结
该部分主要实现了心跳灯的效果,表示Linux
正常运行,正常情况下,我们这里只需要将该驱动编译进去内核即可实现该效果!
这个驱动程序主要有两个步骤:
- 实现驱动框架,赋值相关函数
- 产生一个定时器,循环亮灭
好啦,到这里我们基本将LED
子系统全部详细了解完毕了,真的可谓麻雀虽小,五脏俱全呀,仅仅一个LED
子系统,总结下来也花费不少时间,同时也希望大家多多点赞,收藏,支持一波!
点赞+关注,永远不迷路