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

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

*本文参考资料:

*        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\n", led_cdev->name); //在内核启动过程中打印所注册设备类的名称 return 0;
}

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

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

(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\n", 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\n", 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);

Linux内核的LED设备驱动框架【转】的更多相关文章

  1. Linux驱动框架之misc类设备驱动框架

    1.何为misc设备 (1)misc中文名就是杂项设备\杂散设备,因为现在的硬件设备多种多样,有好些设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于 杂散设备,也就是misc设备,例如像a ...

  2. 一步步理解linux字符设备驱动框架(转)

    /* *本文版权归于凌阳教育.如转载请注明 *原作者和原文链接 http://blog.csdn.net/edudriver/article/details/18354313* *特此说明并保留对其追 ...

  3. Linux设备驱动框架设计

    引子 Linux操作系统的一大优势就是支持数以万计的芯片设备,大大小小的芯片厂商工程师都在积极地向Linux kernel提交设备驱动代码.能让这个目标得以实现,这背后隐藏着一个看不见的技术优势:Li ...

  4. 宋宝华:Linux设备驱动框架里的设计模式之——模板方法(Template Method)

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前言 <设计模式>这本经典 ...

  5. Linux学习 : 总线-设备-驱动模型

    platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver.Linux 2.6的设备驱动模型中,把I2C.RTC.LCD等都归纳为pl ...

  6. Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化

    我们知道,SPI数据传输可以有两种方式:同步方式和异步方式.所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返 ...

  7. Linux与Windows的设备驱动模型对比

    Linux与Windows的设备驱动模型对比 名词缩写: API 应用程序接口(Application Program Interface ) ABI 应用系统二进制接口(Application Bi ...

  8. platform设备驱动框架

    驱动框架 通过使用platform设备驱动框架,实现led驱动与设备操作的分离.     我们关注led_drv里面的 struct platform_driver led_drv里面的.probe函 ...

  9. Linux SPI总线和设备驱动架构之三:SPI控制器驱动

    通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:1. ...

随机推荐

  1. UVA A Spy in the Metro

    点击打开题目 题目大意: 在一个有n个站台的地铁线路里,给你列车通向每相邻两个车站所花费的时间,从0时刻开始,从1号站出发,要在T这个时间点上,到达n号站,给你m1辆从1开到n的列车及其出发时间,和m ...

  2. xpath写法大全(适用于selenium、robotframework)

    1.//input[contains(@id, 'txttags')] 定位出来是个ID,但是ID后面的“102”是个随机数,所以用定位ID的方法就不行了,用firepath生成的xpath也会包括这 ...

  3. php--->php 缓冲区 buffer 原理

    php 缓冲区 buffer 原理 1.缓冲流程 从php脚本echo(print.print_r...)内容之后,是如何显示给用户的呢,下面看看流程 echo.print => php out ...

  4. layui+springmvc实现文件异步上传

    一.单文件上传 前端代码 <div class="layui-upload"> <div class="layui-upload-list"& ...

  5. hash模式与history模式

    随着 ajax 的使用越来越广泛,前端的页面逻辑开始变得越来越复杂,特别是spa的兴起,前端路由系统随之开始流行. 从用户的角度看,前端路由主要实现了两个功能(使用ajax更新页面状态的情况下): 记 ...

  6. Percona-XtraDB-Cluster-57 安装操作记录

    一.PXC集群的一些特性 Percona官网服务器位于境外,访问很困难.本次安装使用的是其官网提供的最新版本5.7.23-31.31.1.el7,当前日期为2018.10.10. PXC集群中,存储引 ...

  7. 深入解读大厂java面试必考基本功-HashMap集合

    课程简介 HashMap集合在企业开发中是必用的集合同时也是面试官面试率很高的集合,因为HashMap里面涉及了很多的知识点,可以比较全面考察面试者的基本功,想要拿到一个好offer,这是一个迈不过的 ...

  8. 《ASP.NET Core 高性能系列》静态文件中间件

    一.概述 静态文件(如 HTML.CSS.图片和 JavaScript等文件)是 Web程序直接提供给客户端的直接加载的文件. 较比于程序动态交互的代码而言,其实原理都一样(走Http协议), ASP ...

  9. redis 常用命令行

    一.key 相关: ()redis允许模糊查询 key(keys *) 有3个通配符 *.?.[] ()randomkey:返回随机key ()type key:返回key存储的类型 ()exists ...

  10. RestTemplate远程调用方法

    概述: spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值 ...