1. 简介:
初次体验灯的跳动方式是 间隔 0.5s 和 1s 后都亮一下(刚开始的理解总是浅显的。。。)
内核中 make menuconfig 中需要配置上 CONFIG_LEDS_TRIGGER_HEARTBEAT
代码主要涉及到的文件:
driver/leds/下的
led-triggers.c // 对外提供函数
Ledtrig-heartbeat.c //实现心跳灯
2. 分析:
理解这部分,我们从两个主要的问题来进行:
匹配过程是怎样的?
定时器运行过程是怎样的?
匹配过程:
我们知道 mach-mini2440.c 文件是 platfrom 设备文件,设备文件的名字是 "s3c24xx_led"。
则我们要找到驱动中相同名字的模块文件,即 leds-s3c24xx.c 文件。leds-s3c24xx.c 文件是led的 platfrom 驱动文件,
匹配上后开始执行 probe 方法。probe中主要做的是
①创建了一个 struct s3c24xx_gpio_led 结构体,并初始化相应部分,其中需要注意的一点是 led->cdev.default_trigger = pdata->def_trigger; default_trigger是用于在链表中进行比较的依据
②初始化硬件IO口
③注册 led_classdev: led_classdev_register(&dev->dev, &led->cdev);
static int s3c24xx_led_probe(struct platform_device *dev)
{
struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;
struct s3c24xx_gpio_led *led;
int ret;
led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);
if (led == NULL) {
dev_err(&dev->dev, "No memory for device\n");
return -ENOMEM;
}
platform_set_drvdata(dev, led);
led->cdev.brightness_set = s3c24xx_led_set;
led->cdev.default_trigger = pdata->def_trigger;
led->cdev.name = pdata->name;
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
led->pdata = pdata;
/* no point in having a pull-up if we are always driving */
if (pdata->flags & S3C24XX_LEDF_TRISTATE) {
s3c2410_gpio_setpin(pdata->gpio, 0);
s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);
} else {
s3c2410_gpio_pullup(pdata->gpio, 0);
s3c2410_gpio_setpin(pdata->gpio, 0);
s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);
}
/* register our new led device */
ret = led_classdev_register(&dev->dev, &led->cdev);
if (ret < 0) {
dev_err(&dev->dev, "led_classdev_register failed\n");
kfree(led);
return ret;
}
return 0;
}
注册 led_classdev 中将 led_cdev 加入到 leds_list 链表中,并在 led_trigger_set_default(led_cdev); 中比较 strcmp(led_cdev->default_trigger, trig->name) ,如果匹配成功,执行
led_trigger_set(led_cdev, trig);
-> if (trigger->activate) trigger->activate(led_cdev); 这里做了什么在下边介绍
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
"%s", led_cdev->name);
if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev);
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list); // 这里 将 led_cdev 添加进 leds_list 链表中,在 led-triggers.c 文件的183行进行匹配(心跳灯部分)
up_write(&leds_list_lock);
if (!led_cdev->max_brightness)
led_cdev->max_brightness = LED_FULL;
led_update_brightness(led_cdev);
init_timer(&led_cdev->blink_timer);
led_cdev->blink_timer.function = led_timer_function;
led_cdev->blink_timer.data = (unsigned long)led_cdev;
#ifdef CONFIG_LEDS_TRIGGERS
led_trigger_set_default(led_cdev);
#endif
printk(KERN_DEBUG "Registered led device: %s\n",
led_cdev->name);
return 0;
}
定时器的运行过程:
在内核配置上相应的选项后
ledtrig-heartbeat.c 便会编译进内核,此模块的入口函数是 heartbeat_trig_init()
此入口函数中注册了一个 led 的 trigger。
其中 led_trigger_register() 函数中通过轮询 leds_list链表找到上图上的 .name 是 heartbeat 的节点,并调用 led_trigger_set() 函数
led_trigger_set() 函数中会执行一次 struct led_trigger 中的 activate 方法。
struct led_trigger 中的 activate 方法中便是具体的定时器实现的灯的闪烁。函数中初始化定时器,并开始执行一次。
定时器的回调函数中实现亮灯和灭灯,并相应的继续更改定时器的超时时间。开发人员把闪烁人为的分成了4个象限(phase),根据 phase 来决定执行那个象限。
static void led_heartbeat_function(unsigned long data)
{
struct led_classdev *led_cdev = (struct led_classdev *) data;
struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
unsigned long brightness = LED_OFF;
unsigned long delay = 0;
/* 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);
/* 亮 70ms */
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
brightness = led_cdev->max_brightness;
break;
case 1:
/* 灭 (period/4 - 70)ms */
delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
heartbeat_data->phase++;
break;
case 2:
/* 亮 70ms */
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
brightness = led_cdev->max_brightness;
break;
default:
/* 灭 (period-period/4 - 70)ms */
delay = heartbeat_data->period - heartbeat_data->period / 4 -
msecs_to_jiffies(70);
heartbeat_data->phase = 0; // 并重新从 0 相位开始
break;
}
led_set_brightness(led_cdev, brightness);
mod_timer(&heartbeat_data->timer, jiffies + delay); // delay时间后再次出发此函数
}
其中各个象限时间的计算如下:
FSHIFT = 11;
avenrun[0] 是 uptime 命令出来的第一个参数【这里暂时理解不是很深】
root@lip:~$ uptime
23:55:41 up 17 min, 1 users, load average: 0.00, 0.01, 0.00
体现出来就是跟任务量有关,如果长期空闲,这个值会比较小,如果任务量变大,会慢慢升上去,相应的闪烁的频率也会快。
heartbeat_data->period = 300 + (6720 << 11) / (5 * avenrun[0] + (7 << 11));
heartbeat_data->period = 300 + (6720 * 2^11) / (5 * avenrun[0] + (7 * 2^11))
= 300+13762560/(5* avenrun[0] +14336)
= 300 + 13762560/14336 = 300+960 = 1260
这个值对应注释中的 f(0)=1260
case 0 定时 70 ms,此时先亮这些个时间
case 1 定时 (period/4 - 70)ms = 315-70 ms,此时先灭这些个时间
case 2 定时 70 ms,此时先亮这些个时间
case 3 定时 (period-period/4 - 70)ms = 1260 - 315-70,此时先灭这些个时间
case 0 + case 1 + case 2 + case 3 = period/4 + period-period/4 = period
做了个简单的实验,在 linux 下运行 grep 命令,长时间的运行一段时间,发现心跳灯的闪烁频率变快,可以看到,此心跳的功能确实是模拟的心跳的功能,根据运行的任务的轻重,灯的闪烁频率会相应的改变。