zoukankan      html  css  js  c++  java
  • Linux内核的LED设备驱动框架【转】

    /************************************************************************************

    *本文为个人学习记录,如有错误,欢迎指正。

    *本文参考资料:

    *        https://blog.csdn.net/qq_28992301/article/details/52410587

    *        https://blog.csdn.net/hanp_linux/article/details/79037610

    ************************************************************************************/

    1. 驱动框架的概念

    内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,并把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程师来实现,这就叫驱动框架。即标准化的驱动实现,统一管理系统资源,维护系统稳定。

    2. LED设备驱动框架概述

    (1)LED设备的共性:

    1)LED的亮与灭;

    2)具有相应的设备节点(设备文件)。

    (2)LED设备的不同点:

    1)LED的硬件连接方式不同(GPIO不同);

    2)LED的控制方式不同(低或高电平触发);

    3)等其他不同点。

    因此,Linux中LED的驱动框架把所有LED设备的共性给实现了,把不同的地方留给驱动工程师去做。

    (3)核心文件:

      

    /kernel/driver/leds/led-class.c 
    /kernel/driver/leds/led-core.c 
    /kernel/driver/leds/led-triggers.c 
    /kernel/include/linux/leds.h
    

    (4)辅助文件(根据需求来决定这部分代码是否需要):

      

    /kernel/driver/leds/led-triggers.c 
    /kernel/driver/leds/trigger/led-triggers.c 
    /kernel/driver/leds/trigger/ledtrig-oneshot.c 
    /kernel/driver/leds/trigger/ledtrig-timer.c 
    /kernel/driver/leds/trigger/ledtrig-heartbeat.c
    

    3. LED设备驱动框架分析

    3.1 创建leds类

    subsys_initcall是一个宏,它的功能是将其声明的函数放到一个特定的段:.initcall4.init。

    内核在启动过程中,内核需要按照先后顺序去进行初始化操作。因此,内核给是给启动时要调用的所有初始化函数归类,然后每个类按照一定的次序去调用执行。这些分类名就叫.initcalln.init,n的值从1到8。内核开发者在编写内核代码时只要将函数设置合适的级别,这些函数就会被链接的时候放入特定的段,内核启动时再按照段顺序去依次执行各个段即可。module_init()、module_exit()也是一个宏,其功能与subsys_initcall相同,只是指定的段不同。

    //所在文件/kernel/include/linux/init.h
    #define subsys_initcall(fn)        __define_initcall("4",fn,4)
    
    #define __define_initcall(level,fn,id) 
        static initcall_t __initcall_##fn##id __used 
        __attribute__((__section__(".initcall" level ".init"))) = fn
    

    LED驱动框架使用subsys_initcall宏修饰leds_init()函数,因此leds_init()函数在内核启动阶段被调用。leds_init()函数的主要工作是:调用class_create()函数在/sys/class目录下创建一个leds类目录。

    //所在文件/kernel/driver/leds/led-class.c
    static int __init leds_init(void)
    {
        leds_class = class_create(THIS_MODULE, "leds");  //在/sys/class目录下创建一个leds类目录
        if (IS_ERR(leds_class))
            return PTR_ERR(leds_class);
      /*填充leds_class*/ 
      leds_class->suspend = led_suspend; 
      leds_class->resume = led_resume; 
      leds_class->dev_attrs = led_class_attrs; //类属性 
      return 0; 
    } 
    
    subsys_initcall(leds_init);
    

    3.2 leds类属性的定义与初始化

    leds_class->dev_attrs规定了leds设备类的类属性,其中的类属性将被sysfs以文件的形式导出至/sys/class/leds目录下,用户空间通过对这些文件的访问来操作硬件设备。详见Linux设备管理:sysfs文件系统的功能及其应用。

    led_class_attrs结构体数组设置了leds设备类的属性,即led硬件操作的对象和方法。分析可知,leds类设备的操作对象一共由3个brightness(LED的亮灭状态)、max_brightness(LED最高亮度值)、trigger(LED闪烁状态)。对应的操作规则有读写,即show和store。这些操作规则内部其实调用了设备体led_classdev内的具体操作函数,譬如:当用户层试图写brightness这个对象时,会触发操作规则led_brightness_store。

    //所在文件/kernel/driver/leds/led-class.c
    static struct device_attribute led_class_attrs[] = 
    {
        __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
        __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
    #ifdef CONFIG_LEDS_TRIGGERS
        __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
    #endif
        __ATTR_NULL,
    };
    
    /*
    *所在文件/kernel/include/linux/sysfs.h
    *_name表示属性的名字,即在sys中呈现的文件。
    *_mode表示这个属性的读写权限,如0666, 分别表示user/group/other的权限都是可读可写。
    *_show表示的是对此属性的读函数,当cat这个属性的时候被调用,_stroe表示的是对此属性的写函数,当echo内容到这个属性的时候被调用。
    */
    
    #define __ATTR(_name,_mode,_show,_store) { 
        .attr = {.name = __stringify(_name), .mode = _mode },    
        .show    = _show,                    
        .store    = _store,                    
    }
    

    3.3 LED设备信息初始化

    在registerLED设备之前,需要先定义并初始化一个struct led_classdev结构体变量,该结构体包含了该LED设备的所有信息。

    初始化struct led_classdev结构体变量时,只需填充如下值即可,其余的在register过程中自动完成填充。

    --name:LED设备目录名称;

    --brightness:LED设备初始亮度;

    --max_brightness:LED设备的最大亮度;

    --void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness):该函数为实际操作LED硬件的函数,由驱动工程师根据具体的LED设备来实现;

    --enum led_brightness (*brightness_get)(struct led_classdev *led_cdev):该函数用于获取LED设备的当前亮度值,LED驱动框架已实现led_get_brightness()函数(/kernel/drivers/leds/leds.h),将该函数的函数名赋予这个指针变量即可。

    struct led_classdev {
        const char      *name;          //LED设备名
        int             brightness;     //LED设备的初始亮度
        int             max_brightness; //LED设备的最大亮度
        int             flags;
    
        /* Lower 16 bits reflect status */
    #define LED_SUSPENDED        (1 << 0)
        /* Upper 16 bits reflect control information */
    #define LED_CORE_SUSPENDRESUME    (1 << 16)
    
        void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); //设置LED设备的亮度
        enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);           //获取LED设备的当前亮度
    
        /* Activate hardware accelerated blink, delays are in
         * miliseconds and if none is provided then a sensible default
         * should be chosen. The call can adjust the timings if it can't
         * match the values specified exactly. */
        int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off);
    
        struct device        *dev;
        struct list_head     node;            /* LED Device list */
        const char        *default_trigger;    /* Trigger to use */
    
    #ifdef CONFIG_LEDS_TRIGGERS
        /* Protects the trigger data below */
        struct rw_semaphore     trigger_lock;
    
        struct led_trigger    *trigger;
        struct list_head     trig_list;
        void            *trigger_data;
    #endif
    };
    

    3.4 LED设备的register接口

    LED设备驱动框架为驱动开发者提供在/sys/class/leds这个类下创建LED设备的接口。

    当驱动调用led_classdev_register注册了一个LED设备,那么就会在/sys/class/leds目录下创建xxx设备,由sysfs创建该设备的一系列attr属性文件(brightness、max_brightness等)将被保存至该目录下供用户空间访问。

    //所在文件/kernel/driver/leds/led-class.c
    int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
    {
        //在/sys/class/leds设备类目录下创建具体的设备目录,目录名由led_cdev->name指定
        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);
        up_write(&leds_list_lock);
      
      //如果设备驱动在注册时没有设置max_brightness,则将max_brightness设置为满即255
        if (!led_cdev->max_brightness)
            led_cdev->max_brightness = LED_FULL;
    
      //如果在初始化struct_classdev *led_cdev时,设置了get_brightness方法,则读出当前的brightness并更新
        led_update_brightness(led_cdev);
    
    #ifdef CONFIG_LEDS_TRIGGERS
        led_trigger_set_default(led_cdev);
    #endif
    
        printk(KERN_DEBUG "Registered led device: %s
    ", led_cdev->name); //在内核启动过程中打印所注册设备类的名称
    
        return 0;
    }
    

    3.5 leds类属性的操作方法实现

    当用户在文件系统下读写LED设备的属性文件时,就会调用这些属性文件的show和store方法,从而操作硬件。

    image

    (1)brightness属性操作

    1)当用户cat /sys/class/leds/xxx/brightness时会调用led-class.c中的brightness_show函数。

    //所在文件/kernel/driver/leds/led-class.c
    static ssize_t led_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
    {
      //根据device结构体获取led_classdev结构体,其中包含了该LED设备的所有信息
      struct led_classdev *led_cdev = dev_get_drvdata(dev);
      //如果在初始化struct_classdev *led_cdev时,设置了get_brightness方法,则读出当前的brightness并更新
      led_update_brightness(led_cdev); 
      
      return sprintf(buf, "%u
    ", led_cdev->brightness);  //将LED当前亮度值存入buf中
    }
    
    static void led_update_brightness(struct led_classdev *led_cdev)
    {
        if (led_cdev->brightness_get)
            led_cdev->brightness = led_cdev->brightness_get(led_cdev); 
    }
    
    
    static inline int led_get_brightness(struct led_classdev *led_cdev)
    {
        return led_cdev->brightness;
    }
    

    2)当用户 echo 100 > /sys/class/leds/xxx/brightness时会调用led-class.c中的brightness_store函数。

    //所在文件/kernel/driver/leds/led-class.c
    static ssize_t led_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
    {
       //根据device结构体获取led_classdev结构体,其中包含了该LED设备的所有信息
       struct led_classdev *led_cdev = dev_get_drvdata(dev);
        ssize_t ret = -EINVAL;
        char *after;
        unsigned long state = simple_strtoul(buf, &after, 10);
        size_t count = after - buf;
    
        if (isspace(*after))
            count++;
    
        if (count == size) {
            ret = count;
    
            if (state == LED_OFF)
                led_trigger_remove(led_cdev);
            led_set_brightness(led_cdev, state);//设置LED亮度
        }
        return ret;
    }
    
    //所在文件/kernel/driver/leds/leds.h
    static inline void led_set_brightness(struct led_classdev *led_cdev, enum led_brightness value)
    {
        if (value > led_cdev->max_brightness)
            value = led_cdev->max_brightness;
        led_cdev->brightness = value;
        if (!(led_cdev->flags & LED_SUSPENDED))
            led_cdev->brightness_set(led_cdev, value);  //调用led_classdev下的LED硬件操作函数brightness_set,该函数由驱动工程师完成编写。
    }
    

    (2)max_brightness属性操作

    当用户当用户cat /sys/class/leds/xxx/max_brightness时会调用led-class.c中的led_max_brightness_show函数。

    //所在文件/kernel/driver/leds/led-class.c
    static ssize_t led_max_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
    {
        struct led_classdev *led_cdev = dev_get_drvdata(dev);
    
        return sprintf(buf, "%u
    ", led_cdev->max_brightness);//将最大亮度值保存至buf中
    }
    

    3.6 LED设备的unregister接口

    LED设备驱动框架为驱动开发者LED设备驱动的卸载接口。调用led_classdev_unregister()函数卸载LED设备驱动。

    //所在文件/kernel/driver/leds/led-class.c
    void led_classdev_unregister(struct led_classdev *led_cdev)
    {
    #ifdef CONFIG_LEDS_TRIGGERS
        down_write(&led_cdev->trigger_lock);
        if (led_cdev->trigger)
            led_trigger_set(led_cdev, NULL);
        up_write(&led_cdev->trigger_lock);
    #endif
    
        device_unregister(led_cdev->dev);  //注销设备类下的设备
    
        down_write(&leds_list_lock);
        list_del(&led_cdev->node);
        up_write(&leds_list_lock);
    }
    
    //注销设备类
    static void __exit leds_exit(void)
    {
        class_destroy(leds_class);
    }
    
    module_exit(leds_exit);
    
  • 相关阅读:
    关于第一次作业表达式求导总结
    北航OO第一单元总结
    OO课程总结
    OO第三次博客
    OO第二次博客
    OO第一次博客
    OO第一单元总结——多项式求导
    [面向对象]电梯作业优化相关
    面向对象的程序设计-模块一课程总结
    OO第二单元总结——电梯
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/12370441.html
Copyright © 2011-2022 走看看