linux led sub system 一
led class 设备驱动是linux的光学设备驱动,通过sys/class/leds/ 提供节点给用户空间。一般用在手机等系统中控制三色指示灯,键盘,背光等设备。以下就android 手机系统为例做一分析
1 userspace how to use
内核模块注册了led class 设备后,会在sys/class/leds/ 目录下生成注册时所用的名字的文件节点。
进入adb shell ,ls 一下
camera:flash0
camera:flash1
gpio24_red
gpio26_blue
lcd-backlight
led_drv0
led_drv1
led_drv2:green
led_psenso:keypad
这些就是我的开发手机上注册的led 设备。比如进入gpio26_blue 这个目录下 ls
brightness
device
max_brightness
power
subsystem
trigger
uevent
现在比较关心的是 max_brightness brightness 这两个文件。读取max_brightness 可得知这个led设备所支持的最大的亮度。向brightness 写入0 led 灭 写入小于max_brightness 的值会设置led的亮度。还有一个需要交代的是trigger 这个文件,用它可以实现一些触发事件。比较常用的一个是timer触发。用它实现led的闪烁。
2 代码结构
kernel/driver/leds/
几乎所有的代码分析,都要首先看的两个文件 kconfig makfile。他们像地图一样指引这我们如何或从哪里开始看代码。
但有时单纯的看kconfig 并不能知道那些feature 被编译,因为他们有依赖,甚至有些feature 被定义在一个叫做XXXX_perdefconfig 的文件中。幸运的是如果你编译了你的kernel 那么编译器会生成一个叫做 .config 的文件,这里汇集了所有的被定义的feature。然后结合要分析的代码的kconfig makfile ,文件结构就很清晰了。
led class 设备由三部分组成 ,一是core,二是led class 设备,三是trigger。led class 设备和trigger向core注册,core维护着led class 设备 及 trigger。
3 从led class 设备开始
一个简单的方法就是在module的初始化函数中,注册一个led class 设备。首先需要准备一个 struct led_classdev
类型的数据,然后调用led_classdev_register 把它注册到led core 中。这样就可以用我上面提到的方法访问这个led了。
剩下的任务就是具体的准备struct led_classdev 这个数据或设备了。让我们看看他有些什么 leds.h
2 const char *name; //led 设备的名字,注册这个设备后会出现在sys/class/leds/下
3 int brightness;//当前led灯的亮度,最大值为下面的变量,为0时代表led灭
4 int max_brightness;//最大亮度,系统定义了一个enum 用于表明这个变量的范围
5 int flags;//这个标识的高16bit是控制信息,低16bit是状态信息。主要用来控制suspend
6 //具体的取值见下面的定义
7 /* Lower 16 bits reflect status */
8 #define LED_SUSPENDED (1 << 0)
9 /* Upper 16 bits reflect control information */
10 #define LED_CORE_SUSPENDRESUME (1 << 16)
11
12 /* Set LED brightness level */
13 /* Must not sleep, use a workqueue if needed */
14 void (*brightness_set)(struct led_classdev *led_cdev,
15 enum led_brightness brightness);//设置led亮度的函数指针,需要driver开发者
16 /* Get LED brightness level */
17 enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
18
19 /*
20 * Activate hardware accelerated blink, delays are in milliseconds
21 * and if both are zero then a sensible default should be chosen.
22 * The call should adjust the timings in that case and if it can't
23 * match the values specified exactly.
24 * Deactivate blinking again when the brightness is set to a fixed
25 * value via the brightness_set() callback.
26 */
27 int (*blink_set)(struct led_classdev *led_cdev,
28 unsigned long *delay_on,
29 unsigned long *delay_off);//硬件闪烁函数,在实现闪烁时,如果这个函数drive实
30
31 struct device *dev; // 属于linux 设备模型的东西,每一个设备都有这么一个device
32 struct list_head node; /* LED Device list */ 拥有这个struct list_head 结构
33 const char *default_trigger; /* Trigger to use */
34 //以下都是trigger 相关的东西,在分析trigger是详细说明
35 unsigned long blink_delay_on, blink_delay_off;
36 struct timer_list blink_timer;
37 int blink_brightness;
38
39 #ifdef CONFIG_LEDS_TRIGGERS
40 /* Protects the trigger data below */
41 struct rw_semaphore trigger_lock;
42
43 struct led_trigger *trigger;
44 struct list_head trig_list;
45 void *trigger_data;
46 #endif
47 };
这个结构抽象了一个led设备,需要driver的开发者实现其中的全部或部分。各个变量的含义及用法见我在代码中的注释。知道了这个设备的结构含义,准备这样一个设备就不难了。
for example
2 .name = "keyboard-backlight",
3 .brightness_set = msm_keypad_bl_led_set,
4 .brightness = LED_OFF,
5 };
6
7 static int msm_pmic_led_probe()
8 {
9 int rc;
10
11 rc = led_classdev_register(NULL, &msm_kp_bl_led);
12 if (rc) {
13
14 return rc;
15 }
16
17 return rc;
18 }
19 static int __init msm_pmic_led_init(void)
20 {
21 return msm_pmic_led_probe();
22 }
23 module_init(msm_pmic_led_init);
这样就写了一个最简单的led设备驱动,运行后会在sys/class/leds/下生成keyboard-backlight节点。userspace 就可以操作键盘灯了。
4 该看看core了。
现在到时候看一下makefile了
# LED Core
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
以上三个文件就是led core的主要内容。很明显led-trigger.c 这个文件是负责管理led的trigger的。暂时不管他了。很幸运文件不多
先看一下led-core.c
2 #include <linux/list.h>
3 #include <linux/module.h>
4 #include <linux/rwsem.h>
5 #include <linux/leds.h>
6 #include "leds.h"
7
8 DECLARE_RWSEM(leds_list_lock);
9 EXPORT_SYMBOL_GPL(leds_list_lock);
10
11 LIST_HEAD(leds_list);
12 EXPORT_SYMBOL_GPL(leds_list);
这段代码很给力,去掉头文件就是几个变量的定义。
第8,9行 定义了一把锁,望文生义,这把锁锁的就是11 12 行定义的leds_list这个队列了。leds_list这个列表头把所有向core注册的led class 设备组成双向链表。
led-class.c
阅读linux 的code ,最好不过的就是从那些放在init节里的函数了,因为他们在系统启动时会调用,本模块的init节是 subsys_initcall 宏定义的一个函数
2 {
3 leds_class = class_create(THIS_MODULE, "leds");
4 if (IS_ERR(leds_class))
5 return PTR_ERR(leds_class);
6 leds_class->suspend = led_suspend;
7 leds_class->resume = led_resume;
8 leds_class->dev_attrs = led_class_attrs;
9 return 0;
10 }
11
12 static void __exit leds_exit(void)
13 {
14 class_destroy(leds_class);
15 }
16
17 subsys_initcall(leds_init);
18 module_exit(leds_exit);
我们常见的还有一个是module_init , subsys_initcall 会比module_init 要早一些。关于init节的知识可以参考如下链接
http://blog.163.com/liuqiang_mail@126/blog/static/10996887520124741925773/
目前可以肯定在系统启动时leds_init 会被调用。纵览代码,让只不过做了以下几个工作
1 创建了一个类leds ,于是在sys/class/ 下便有了leds的节点了。
2 给几个成员赋值,挂起和唤醒,及设备属性。
做完这些事后init就很高兴的结束了。很令人失望,从init好像看不出什么来,他就是创建了leds的类,类是设备的类,设备是类的设备,可以想象当我们调用注册函数向core注册led设备时,这个led设备就属于这个leds类了。既然注册的led设备属于leds类,那么led设备就有这个类的所有特征,不然就不属于这个类。换句话说,我们注册的led设备会拥有led_class_attrs的属性等类的特性。所以现在有必要看一下这个重量级的注册函数了。
2 * led_classdev_register - register a new object of led_classdev class.
3 * @parent: The device to register.
4 * @led_cdev: the led_classdev structure for this device.
5 */
6 int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
7 {
8 led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
9 "%s", led_cdev->name);
10 if (IS_ERR(led_cdev->dev))
11 return PTR_ERR(led_cdev->dev);
12
13 #ifdef CONFIG_LEDS_TRIGGERS
14 init_rwsem(&led_cdev->trigger_lock);
15 #endif
16 /* add to the list of leds */
17 down_write(&leds_list_lock);
18 list_add_tail(&led_cdev->node, &leds_list);
19 up_write(&leds_list_lock);
20
21 if (!led_cdev->max_brightness)
22 led_cdev->max_brightness = LED_FULL;
23
24 led_update_brightness(led_cdev);
25
26 init_timer(&led_cdev->blink_timer);
27 led_cdev->blink_timer.function = led_timer_function;
28 led_cdev->blink_timer.data = (unsigned long)led_cdev;
29
30 #ifdef CONFIG_LEDS_TRIGGERS
31 led_trigger_set_default(led_cdev);
32 #endif
33
34 printk(KERN_DEBUG "Registered led device: %s\n",
35 led_cdev->name);
36
37 return 0;
38 }
39 EXPORT_SYMBOL_GPL(led_classdev_register);
这个函数接受一个struct devices的指针来表明我们要注册的struct led_classdev 属于的父设备,如果没有,调用这个函数置为NULL就可以了。第二个参数就是struct led_classdev,是我们要向ledcore注册的led设备。第8行, 这行代码创建一个设备,这个设备struct device 是linux设备模型中的通用设备,任何其他定义的设备都应给包含一个strcut device,或他的一个指针。 第一个参数就是前面init时创建的led class,就是应为这个参数的传入,这个设备拥有了led类的说有特征,包括他的属性,电源管理等。在init时我们有如下赋值
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
leds_class->dev_attrs = led_class_attrs;
我们分析一下这几个函数或数据。
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
__ATTR(max_brightness, 0644, led_max_brightness_show,
led_max_brightness_store),
#ifdef CONFIG_LEDS_TRIGGERS
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
__ATTR_NULL,
};
这个属性结构定义了这个ledclass设备数据有的属性,亮度,最大亮度,trigger。并定义了设置属性和读取属性的函数吗,及权限。那么当我们已这个ledclass为基础创建的设备就拥有这些属性,就会在生成相应的属性文件。
2 struct device_attribute *attr, const char *buf, size_t size)
3 {
4 struct led_classdev *led_cdev = dev_get_drvdata(dev);
5 ssize_t ret = -EINVAL;
6 char *after;
7 unsigned long state = simple_strtoul(buf, &after, 10);
8 size_t count = after - buf;
9
10 if (isspace(*after))
11 count++;
12
13 if (count == size) {
14 ret = count;
15
16 if (state == LED_OFF)
17 led_trigger_remove(led_cdev);
18 led_set_brightness(led_cdev, state);
19 }
20
21 return ret;
22 }
这时属性的设置函数,他的第一个结构体就是拥有这个属性的设备的指针。第四行,dev_get_drvdata,这个函数取到我们自定义的的led设备,比如redled。为什么这个device的驱动数据是我们自定义的led呢,返回注册函数
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
7 {
8 led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
9 "%s", led_cdev->name);
看到了吧,第4个参数就是我们创建设备是传近去的,device_create会把这个参数设置到设备的驱动数据中去,所以dev_get_drvdata就可以取到了。聪明的你当然可以想到通过container_of 也可以取到我们自定义的设备。
第5个参数是设备号,如果不为0,会在dev目录下生成设备节点。最后是设备名,如redled
接着把这个设备挂到leds_list上,说明这个设备已归ledclass管理了。
26 27 28 ,初始化了一个定时器,后面分析timer trigger时用到,用作led闪烁用的。
到这里就暂告一段落了,下面从整体流程上做一总结。
当我们准备了
led子系统就绪后,会在sys/class/目录下生成leds的一个类。
static struct led_classdev msm_kp_bl_led = {
2 .name = "keyboard-backlight",
3 .brightness_set = msm_keypad_bl_led_set,
4 .brightness = LED_OFF,
5 };
一个led设备后,我们调用led_classdev_register(NULL, &msm_kp_bl_led);
注册这个设备,于是得到sys/class/leds/keyboard-backlight的设备,同时这个设备拥有了如下的属性
brightness
max_brightness
trigger
于是我们就可以修改他的属性,如brightness
echo 255 > brightness
接着设置属性的函数调用led_brightness_store ---------> led_set_brightness(led_cdev, state) 结果键盘灯就亮了。