【基础】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

  

复制代码
 1  struct led_classdev {
 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实
   // 现了,那么会优先调用他来硬件加速,否则core 会调用一个软件用定时器模拟的闪烁。在实际的应用中,软件模拟
   //的闪烁是不可取的。因为他要求cpu要一直工作,这对于像手机这样的电池供电的设备是不可接受的。
30 
31      struct device        *dev;  // 属于linux 设备模型的东西,每一个设备都有这么一个device
32      struct list_head     node;             /*  LED Device list  */ 拥有这个struct list_head 结构
    //的设备可以用它把本身挂到一个属于他的链表中,方便core管理,其实大部分的注册函数都几乎会把向他注册的东东挂到一个他关心的链表中。
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

 

这样就写了一个最简单的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

 

复制代码
 1 #include <linux/kernel.h>
 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 宏定义的一个函数

复制代码
 1  static  int __init leds_init( void)
 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的属性等类的特性。所以现在有必要看一下这个重量级的注册函数了。

复制代码
 1  /* *
 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;

我们分析一下这几个函数或数据。

复制代码
static  struct device_attribute 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为基础创建的设备就拥有这些属性,就会在生成相应的属性文件。

复制代码
 1  static ssize_t led_brightness_store( struct device *dev,
 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) 结果键盘灯就亮了。

猜你喜欢

转载自blog.csdn.net/antchen88/article/details/79196635
led