linux led sub system 二---------trigger
前面一篇随笔大略的分析了led class设备。其中关于trigger的部分提了一下就略过了。现在具体的做个分析,ledtrigger比led class dev 要复杂的多。做点笔记记录下来以备以后用到。
trigger 中文的翻译叫做触发。既然叫trigger,一定有一个事件或条件达到时led出现一个状态(点亮,亮度改变,闪烁)。做个事件可以来自userspace的请求,或kenel产生的事件,如休眠,cpu空闲等。而这些事件或条件就是我们要注册的trigger。每个led可以由有若干了trigger。可以在注册led设备时指定默认的trigger ,也可以由userspace指定,切换。当trigger发生时,led会产生相应的trigger定义的动作。
既然trigger是led的,那么在ledclass dev中一定有些记录。
const char *default_trigger; /* Trigger to use */
2 /* Protects the trigger data below */
3 struct rw_semaphore trigger_lock;
4
5 struct led_trigger *trigger;
6 struct list_head trig_list;
7 void *trigger_data;
8 #endif
如上代码,default_trigger 是这个led的默认的trigger名。如果在注册led设备时给予了他值,那么这个led就会在default_trigger 的条件下执行动作。
trigger_lock 一把锁,保护trig_list用的。trigger,一个led可以有许多trigger 这个值指向当前trigger。
trig_list。这个led说拥有的所有trigger的一个链表还是为了把这个led设备挂在trigger中的一个节点,这里得不到任何的信息,只能看后面的代码了。
trigger_data当前trigger的私有数据。
现在可以看一下ledtrigger这个人物了
2 /* Trigger Properties */
3 const char *name;
4 void (*activate)(struct led_classdev *led_cdev);
5 void (*deactivate)(struct led_classdev *led_cdev);
6
7 /* LEDs under control by this trigger (for simple triggers) */
8 rwlock_t leddev_list_lock;
9 struct list_head led_cdevs;
10
11 /* Link to next registered trigger */
12 struct list_head next_trig;
13 };
第一个成员是这个trigger的名字,不超过50个字符。
仅接着是trigger激活和取消的函数。从函数参数可以推测,这两个函数是针对特定的led的。
第九行是一个链表头,可以推测他把所有属于他trigge的led通过trig_list都链接起来。第12行应是自身的一个链表,用它把所有向ledtrigger注册的trigger链接起来。
接着我们从具体的一个trigger(定时器触发)出发,一步步的理清思路。
ledtrigger_timer.c
2 .name = "timer",
3 .activate = timer_trig_activate,
4 .deactivate = timer_trig_deactivate,
5 };
6
7 static int __init timer_trig_init(void)
8 {
9 return led_trigger_register(&timer_led_trigger);
10 }
11
12 static void __exit timer_trig_exit(void)
13 {
14 led_trigger_unregister(&timer_led_trigger);
15 }
16
17 module_init(timer_trig_init);
18 module_exit(timer_trig_exit);
这段代码也清楚的说明了如何取写一个trigger。首先定义一个trigger。然后注册就可以了。重点是实现activate和deactivate函数。
这两个函数如何实现,稍后分析,先来看一下注册函数。
2 {
3 struct led_classdev *led_cdev;
4 struct led_trigger *trig;
5
6 rwlock_init(&trigger->leddev_list_lock);
7 INIT_LIST_HEAD(&trigger->led_cdevs);
8
9 down_write(&triggers_list_lock);
10 /* Make sure the trigger's name isn't already in use */
11 list_for_each_entry(trig, &trigger_list, next_trig) {
12 if (!strcmp(trig->name, trigger->name)) {
13 up_write(&triggers_list_lock);
14 return -EEXIST;
15 }
16 }
17 /* Add to the list of led triggers */
18 list_add_tail(&trigger->next_trig, &trigger_list);
19 up_write(&triggers_list_lock);
20
21 /* Register with any LEDs that have this as a default trigger */
22 down_read(&leds_list_lock);
23 list_for_each_entry(led_cdev, &leds_list, node) {
24 down_write(&led_cdev->trigger_lock);
25 if (!led_cdev->trigger && led_cdev->default_trigger &&
26 !strcmp(led_cdev->default_trigger, trigger->name))
27 led_trigger_set(led_cdev, trigger);
28 up_write(&led_cdev->trigger_lock);
29 }
30 up_read(&leds_list_lock);
31
32 return 0;
33 }
10-20 检查这个trigger是否已经注册,如果注册则返回已存在。
否则,把这个trigger加入到trigger_list列表中。
22-30 遍历所有led设备,如果发现某个led的默认trigger是本trigger,那么就把这个led设备通过他的trig_list 挂在 struct list_head led_cdevs 上。
这个过程是通过led_trigger_set(led_cdev, trigger);来完成的。
2 {
3 unsigned long flags;
4
5 /* Remove any existing trigger */
6 if (led_cdev->trigger) {
7 write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
8 list_del(&led_cdev->trig_list);
9 write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
10 flags);
11 if (led_cdev->trigger->deactivate)
12 led_cdev->trigger->deactivate(led_cdev);
13 led_cdev->trigger = NULL;
14 led_brightness_set(led_cdev, LED_OFF);
15 }
16 if (trigger) {
17 write_lock_irqsave(&trigger->leddev_list_lock, flags);
18 list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
19 write_unlock_irqrestore(&trigger->leddev_list_lock, flags);
20 led_cdev->trigger = trigger;
21 if (trigger->activate)
22 trigger->activate(led_cdev);
23 }
24 }
这个函数做了两件事,一是移除旧的,二是添加新的。通过传递的参数,完成往trigger中添加删除led设备的功能。就是在这里trigger的activate和deactive被执行。到此trigger就算分析完了。
接着ledtrigger_timer分析trigger的两个重量级函数active和deactive
2 static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);
3
4 static void timer_trig_activate(struct led_classdev *led_cdev)
5 {
6 int rc;
7
8 led_cdev->trigger_data = NULL;
9
10 rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
11 if (rc)
12 return;
13 rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
14 if (rc)
15 goto err_out_delayon;
16
17 led_blink_set(led_cdev, &led_cdev->blink_delay_on,
18 &led_cdev->blink_delay_off);
19
20 led_cdev->trigger_data = (void *)1;
21
22 return;
23
24 err_out_delayon:
25 device_remove_file(led_cdev->dev, &dev_attr_delay_on);
26 }
27
28 static void timer_trig_deactivate(struct led_classdev *led_cdev)
29 {
30 if (led_cdev->trigger_data) {
31 device_remove_file(led_cdev->dev, &dev_attr_delay_on);
32 device_remove_file(led_cdev->dev, &dev_attr_delay_off);
33 }
34
35 /* Stop blinking */
36 led_brightness_set(led_cdev, LED_OFF);
37 }
timer_trig_activate 在本led设备下创建了两个节点,delay_on 和delay_off。在用户空间写这两个文件就会形成led的闪烁。具体原理可分析led_delay_on_store和led_delay_off_store两个属性设置函数。device_create_file(led_cdev->dev, &dev_attr_delay_on);这个函数会在这个设备led_cdev->dev下创建delay_on这个文件。
timer trigger需要user的干预才能触发闪烁,属于用户空间的请求。下面简单分析一个kernel空间事件的触发。
ledtrigger_sleep.c
代码很简单,他再init的时候注册了trigger。并注册了pm通知链。当pm状态变化时,pm通知回调会运行于是
2 unsigned long action,
3 void *ignored)
4 {
5 switch (action) {
6 case PM_HIBERNATION_PREPARE:
7 case PM_SUSPEND_PREPARE:
8 led_trigger_event(ledtrig_sleep, LED_OFF);
9 return NOTIFY_OK;
10 case PM_POST_HIBERNATION:
11 case PM_POST_SUSPEND:
12 led_trigger_event(ledtrig_sleep, LED_FULL);
13 return NOTIFY_OK;
14 }
15
16 return NOTIFY_DONE;
17 }
led_trigger_event 被调用,还有一个函数是led_trigger_blink。
2 unsigned long *delay_on,
3 unsigned long *delay_off)
4 {
5 struct list_head *entry;
6
7 if (!trigger)
8 return;
9
10 read_lock(&trigger->leddev_list_lock);
11 list_for_each(entry, &trigger->led_cdevs) {
12 struct led_classdev *led_cdev;
13
14 led_cdev = list_entry(entry, struct led_classdev, trig_list);
15 led_blink_set(led_cdev, delay_on, delay_off);
16 }
17 read_unlock(&trigger->leddev_list_lock);
18 }
这个函数遍历所有当前trigger拥有的led设备。并让其闪烁。
系统中可能注册若干trigger,但一个led在某一个时刻有且最多能有一个trigger。那么如何切换led的trigger呢。
前面分析led设备时,在注册led类是有个属性数组,里边有一项就是trigger属性。我们来看一下这个属性。
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
看一下这个属性设置函数 led_trigger_store
2 const char *buf, size_t count)
3 {
4 struct led_classdev *led_cdev = dev_get_drvdata(dev);
5 char trigger_name[TRIG_NAME_MAX];
6 struct led_trigger *trig;
7 size_t len;
8
9 trigger_name[sizeof(trigger_name) - 1] = '\0';
10 strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
11 len = strlen(trigger_name);
12
13 if (len && trigger_name[len - 1] == '\n')
14 trigger_name[len - 1] = '\0';
15
16 if (!strcmp(trigger_name, "none")) {
17 led_trigger_remove(led_cdev);
18 return count;
19 }
20
21 down_read(&triggers_list_lock);
22 list_for_each_entry(trig, &trigger_list, next_trig) {
23 if (!strcmp(trigger_name, trig->name)) {
24 down_write(&led_cdev->trigger_lock);
25 led_trigger_set(led_cdev, trig);
26 up_write(&led_cdev->trigger_lock);
27
28 up_read(&triggers_list_lock);
29 return count;
30 }
31 }
32 up_read(&triggers_list_lock);
33
34 return -EINVAL;
35 }
36 EXPORT_SYMBOL_GPL(led_trigger_store);
当user 写 trigger这个文件时。这个函数会调用。22到29完成切换过程。
他遍历trigger_list。找到trigger_name匹配的trigger。然后调用led_trigger_set把这个设备设置给这个trigger。