LED驱动框架源码分析:led-class.c分析

写在前面

  1. 内核版本:2.6.35
  2. 文章中贴出的源码会省略一些琐碎的、对分析无益的细节
  3. 结构体中包含其他结构体,我称之为包含关系,类似面向对象中的继承;结构体中包含其他结构体的指针,我称之为绑定关系
  4. 软件层面的框架就是提取出一个任务中整体的、通用的、固定的逻辑,通过对外暴露一定的接口来获取运行所必需的数据和具体操作逻辑(函数)
  5. led框架由内核开发者提供,位于/drivers/leds/led-class.c,其中比较重要的两个函数是leds_initled_classdev_register

1. leds_init函数分析

static struct class *leds_class;

static int __init leds_init(void)
{
    leds_class = class_create(THIS_MODULE, "leds"); //创建leds类设备
    leds_class->suspend = led_suspend;  //填充函数指针
    leds_class->resume = led_resume; //填充函数指针
    leds_class->dev_attrs = led_class_attrs; //填充device_attribute数组
    return 0;
}

该函数主要工作就是创建并填充一个名为ledsclass实体,更加细致的分析如下

1.1 关于class

//  include/linux/device.h

struct class {
    const char *name; //类的名字

    struct class_attribute *class_attrs; //class_attribute数组
    struct device_attribute *dev_attrs; //dev_attribute数组,用来描述属于该类设备所共有的属性

    //----一些函数指针----
    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);
};

class结构在语义上用来描述一类设备所具有的通用的属性和操作,一个class实体用来描述一类设备,所以leds_init函数中创建了一个名为ledsclass实体来描述led设备的共有属性和操作逻辑。
class结构不同于面向对象语言中的类的概念,在这里它只是一个普通的结构。

想要更完整地了解class,还需要进一步地理解attribute结构。
class结构中绑定class_attribute类型和device_attribute类型的数组,前者用来描述该类属性,后者用来描述属于该类的设备的共有属性;这两个结构体内部都包含了attribute结构,可以认为这两个结构是attribute子类

//  include/linux/device.h

struct class_attribute {
    struct attribute attr;

    //-----对类属性的读写方法-------
    ssize_t (*show)(struct class *class, struct class_attribute *attr,
            char *buf);
    ssize_t (*store)(struct class *class, struct class_attribute *attr,
            const char *buf, size_t count);
};

struct device_attribute {
    struct attribute attr;

    //对设备属性的读写方法
    ssize_t (*show)(struct device *dev, struct device_attribute *attr,
            char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
             const char *buf, size_t count);
};

//  include/linux/sysfs.h
struct attribute {
    const char        *name; //属性名,在sysfs中表现为文件名
    mode_t            mode; // 文件权限
};

attribute的声明位置可以看出,attribute设计为用来在sysfs中使用,即用来提供用户和内核的交互接口,attribute实体会表现为sysfs中的一个文件,用户对其的读写操作会追踪到内核中具体的showstore方法。

所以,如果希望通过读写attribute文件来控制硬件设备,驱动中就需要填充对应attribute中的函数指针,leds_init函数中使用的led_class_attrs数组中就实现了函数指针的填充,代码如下:

static struct device_attribute led_class_attrs[] = {
    __ATTR(brightness, 0644, led_brightness_show, led_brightness_store), //brightness属性,填充读写方法
    __ATTR(max_brightness, 0444, led_max_brightness_show, NULL), //max_brightness属性,填充读方法,相应的文件权限也设置为了只读
    ...
};

1.2 class_create函数分析

class_create内部调用__class_create,所以我们直接分析后者;

// driver/base/class.c

struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)
{
    //---分配堆空间---
    struct class *cls; 
    cls = kzalloc(sizeof(*cls), GFP_KERNEL); 

    //---结构体填充---
    cls->name = name;
    cls->owner = owner;
    cls->class_release = class_create_release; //填充类的释放函数,刚出生就准备好怎么死了

    __class_register(cls, key); //向内核注册方才创建的class实体
    return cls;

}

标准的先分配空间后填充流程,最后调用__class_register向内核注册创建好的类,至于具体如何注册这里就不分析了,我也没进去看,但无非是将创建的类的信息填充到内核维护的某个数组中。

至此,leds_init函数的分析就完毕了,我们再整体描述一遍其执行过程:
内核在启动过程中的某个阶段会调用leds_int函数,该函数首先使用class_create创建并注册leds类;之后填充suspendresume函数;再使用文件内定义的led_class_attrs数组填充leds类中的device_attribute数组,实现attribute文件和读写函数的绑定。

2. led_classdev_register函数分析

/*
* parent: 要创建设备的父设备
* led_cdev: 需要框架使用者提供的结构体
*/
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
    //创建device实体,填充到传入的led_classdev实体中
    led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name);
    return 0;
}

该函数主要工作为创建device实体,将其绑定到leds_class,当然device_create函数内部一定对device实体进行了注册。

// include/linux/leds.h
struct led_classdev {
    const char *name;
    int brightness; //亮度
    int max_brightness; //最大亮度
    int flags;
    struct device *dev; //绑定dev结构,也应该可以理解为父类

    void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
};

led_classdev语义上可以理解为dev的子类,是对led设备的描述。框架的使用者应当创建led_classdev实体,将其传入led_classdev_register函数。

综上,led框架的源码就算大致分析完毕了。该框架暴露给开发者的接口是led_classdev_register,开发者准备好led_classdev实体,该实体就提供了框架需要的数据(比如设备名、亮度)和操作逻辑(设置亮度等)。当然还有led_classdev_unregister接口,比较简单,这里就不分析了。

作者:poetrycoding 原文地址:https://segmentfault.com/a/1190000043334746

%s 个评论

要回复文章请先登录注册