1、驱动框架介绍
(1)内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,然后把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程师来实现,这就叫驱动框架。
(2)内核维护者在内核中设计了一些统一管控系统资源的体系,这些体系让内核能够对资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。譬如系统中所有的GPIO就属于系统资源,每个驱动模块如果要使用某个GPIO就要先调用特殊的接口先申请,申请到后使用,使用完后要释放。
(3)一些特定的接口函数、一些特定的数据结构,这些是驱动框架的直接表现。
2、LED框架相关文件
(1)drivers/leds目录,这个目录就是驱动框架规定的LED这种硬件的驱动源码放的路径。
(2)led-class.c和led-core.c,这两个文件加起来属于LED驱动框架的第一部分,这两个文件是内核开发者提供的,他们描述的是内核中所有厂家的不同LED硬件的相同部分的逻辑。
(3)leds-xxxx.c,这个文件是LED驱动框架的第2部分,是由不同厂商的驱动工程师编写添加的,厂商驱动工程师结合自己公司的硬件的不同情况来对LED进行操作,使用第一部分提供的接口来和驱动框架
进行交互,最终实现驱动的功能。
3、LED框架分析
3.1、内核支持LED框架
make menuconfig
Device Drivers
LED Support
LED Class Support #选择y或者m
(1)执行"make menuconfig"命令,一般选择"LED Class Support"为y,直接编译进内核;选择为y会后,再次编译内核就会把led-class.c编译进内核;
(2)或者直接去修改内核配置文件的"CONFIG_LEDS_CLASS=y"配置项;
3.2、LED框架的加载和卸载
//导出LED驱动的注册/卸载函数
EXPORT_SYMBOL_GPL(led_classdev_register);
EXPORT_SYMBOL_GPL(led_classdev_unregister);
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds"); //创建"leds"设备类
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->suspend = led_suspend; //设备休眠的函数指针
leds_class->resume = led_resume; //设备唤醒的函数指针
leds_class->dev_attrs = led_class_attrs;//初始化设备属性
return 0;
}
static void __exit leds_exit(void)
{
class_destroy(leds_class); //销毁"leds"设备类
}
subsys_initcall(leds_init); //加载LED框架
module_exit(leds_exit); //卸载LED框架
(1)LED框架的代码和一本的字符驱动几乎一样,既可以编译进内核又可以单独编译成ko文件,但是需要注意驱动的加载函数用subsys_initcall宏声明;
(2)我们利用LED驱动框架写led驱动,就是利用led_classdev_register()函数来注册一个驱动,重点是理解led_classdev_register()函数;
3.3、subsys_initcall宏
#define subsys_initcall(fn) __define_initcall("4",fn,4)
subsys_initcall(leds_init);
(1)效果:把leds_init函数放到".initcall4.init"段,具体宏分析参考:《内核加载驱动机制详解(module_init & module_exit)》;
(2)我们自己编写的LED驱动加载时用"module_init"宏,这样我们的LED驱动代码会被放在".initcall6.init"段;
(3)“.initcall4.init"段会比”.initcall6.init"段先加载,而我们写的LED驱动代码是依赖内核的LED框架的,所以内核LED框架被先加载也是必须的;
3.4、struct class结构体和struct device_attribute结构体
参考博客:《struct class结构体》;
3.5、内核LED框架创建leds类
/* interface for exporting device attributes */
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);
};
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,
};
(1)在leds_init()创建leds类时,将led_class_attrs变量赋予leds_class->dev_attrs,作为类的设备属性;
(2)效果:每一个创建的属于LED类的设备,在设备文件夹下面都会有brightness、max_brightness、trigger文件,在读取这些文件时调用对应的show方法,在写入这些文件时调用store方法,如果对应的show/store方法是NULL,则不支持读取/写入;
(3)brightness:用来控制亮度;max_brightness:保存允许的最大亮度;trigger:触发方式;
4.用LED框架注册led设备
4.1、注册和卸载接口
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
"%s", led_cdev->name);
······
return 0;
}
EXPORT_SYMBOL_GPL(led_classdev_register);
void led_classdev_unregister(struct led_classdev *led_cdev)
{
······
device_unregister(led_cdev->dev);
······
}
EXPORT_SYMBOL_GPL(led_classdev_unregister);
(1)LED框架中导出了led_classdev_register()函数和led_classdev_unregister()函数,给我们注册led驱动;
(2)注册接口:主要功能就是调用device_create()函数在leds类下面创建名字为led_cdev->name的设备;
(3)卸载接口:主要功能就是调用device_unregister()函数注销之前注册驱动时创建的设备;
总结:注册led驱动的重点就是构建注册接口led_classdev_register()函数需要的struct led_classdev结构体;
4.2、struct led_classdev结构体
struct led_classdev {
const char *name; //将来在/sys/class/leds/目录下,创建名字为name的设备
int brightness; //led设备的亮度
int max_brightness; //led设备的最大亮度
int flags; //led设备的标志位
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness); //设置led设备亮度的方法
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); //获取led设备亮度的方法
struct device *dev; //设备结构体,用来在内核中表示设备
struct list_head node; /* LED Device list */
······
};
(1)上面是结构体的部分成员变量,在LED框架中,就是用struct led_classdev结构体来描述一个led设备;
(2)如何填充struct led_classdev结构体是注册led设备的重点;
4.3、怎么添加自己的LED驱动
红框里的就是之前别人添加好的LED驱动代码,我们只需要仿造别人的添加过程去添加自己的驱动代码;
大致步骤:
(1)将自己的led驱动源码文件放到kernel/drivers/leds目录下;
(2)在kernel/drivers/leds目录的Makefile和Kconfig文件中支持我们新添加的驱动源文件;
5、用LED驱动框架注册led设备的示例代码分析
参考博客:《用LED驱动框架注册led设备的示例代码》;