以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、驱动框架的含义
1、理解层面1:驱动的分层设计
设备驱动程序,是由内核驱动部分的维护者,以及驱动开发工程师协作完成的。
内核驱动部分的维护者,往往为同类的设备(比如LED、LCD、蜂鸣器等等)设计了一个成熟的、标准的、典型的框架。他们将同类设备的驱动中通用的一些功能抽离出来,作为驱动框架中的核心层,然后设计好核心层与具体操作层的接口,让驱动开发者来实现具体操作层。
核心层与具体操作层的接口,其设计原则是标准化,尽量降低驱动开发者的编程难度。
设备驱动框架,简而言之就是驱动的分层设计。
2、理解层面2:系统资源的管控
内核维护者设计了一些系统资源管控体系,这些体系能够协调各个驱动的资源分配,保证内核的稳定。比如系统中所有的GPIO就属于系统资源,某个驱动模块如果需要使用某个GPIO,就需要调用特殊的接口进行申请,申请到之后再使用,使用完之后要释放。
这也是设备驱动框架的组成部分。
二、LED驱动框架概述
下面以内核中LED驱动框架为例,说明设备驱动框架的相关内容。
1、相关文件
内核源码drivers/leds目录,是驱动框架规定的LED这种硬件的驱动应该待的地方。
2、LED驱动框架的核心层
drivers/leds目录中的led-class.c和led-core.c,其属于LED驱动框架的核心层。
它们由内核驱动部分维护者提供,描述的是所有厂家的不同LED硬件的相同部分的逻辑。
3、LED驱动框架的具体操作层
drivers/leds目录中的leds-xxxx.c文件,其属于LED驱动框架的具体操作层。
它们由不同厂商的驱动工程师编写。驱动工程师根据硬件的具体情况来对LED进行操作,使用LED驱动框架核心层提供的接口来与驱动框架进行交互,最终实现驱动的功能。
4、分析具体案例
分析驱动框架的关键,在于分析内核开发者提供了什么,驱动开发者需要完成什么。
以文件drivers/leds/leds-s3c24xx.c为例,这个文件是LED驱动框架的具体操作层。
drivers/leds目录中的led-class.c和led-core.c,这几个文件是LED驱动框架的核心层。
leds-s3c24xx.c文件调用drivers/leds/led-class.c文件的led_classdev_register()函数来完成LED驱动的注册。即驱动工程师通过调用驱动框架中提供的接口来实现自己的驱动。
注意:九鼎没有使用内核推荐的led驱动框架,它写的驱动是drivers/char/led/x210-led.c。
5、典型的驱动开发行业现状
内核驱动部分维护者,对驱动框架进行开发和维护、升级,对应着led-class.c和led-core.c。
SoC厂商(比如三星、华为等芯片厂商)的驱动工程师编写与测试设备驱动源码,提供参考版本,对应着leds-s3c24xx.c。
做产品的厂商(比如九鼎)的驱动工程师以SoC厂商提供的驱动源码leds-s3c24xx.c为基础,来做移植和调试。
三、LED驱动框架的初步分析
1、概述
以drivers/leds/leds-s3c24xx.c为例。
LED驱动框架的具体操作层指的是leds-s3c24xx.c文件。
LED驱动框架的核心层包括 led-class.c 文件、leds.h文件、led-core.c文件。
具体操作层leds-s3c24xx.c文件由于应用了驱动模型平台总线的概念,我们以后再分析。
这里先分析核心层的led-class.c文件。
2、led-class.c文件的具体分析
led-class.c文件的内容如下:
//省略部分代码static int __init leds_init(void) { //创建了类名,叫/sys/class/leds leds_class = class_create(THIS_MODULE, "leds");if (IS_ERR(leds_class))return PTR_ERR(leds_class);leds_class->suspend = led_suspend;// 关联 LED设备休眠函数leds_class->resume = led_resume;// 关联 LED设备唤醒函数// 创建设备属性文件 brightness、max_brightness、triggerleds_class->dev_attrs = led_class_attrs;return 0; }static void __exit leds_exit(void) {class_destroy(leds_class);//销毁了类名leds }subsys_initcall(leds_init); //led-class.c是一个内核模块,体现为有安装与卸载函数 module_exit(leds_exit); //所以对该文件的分析,应该从下往上阅读MODULE_AUTHOR("John Lenz, Richard Purdie"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("LED Class Interface");
1、led-class.c将被编译成一个内核模块
由代码可以看出,led-class.c有安装与卸载函数,因此它是一个内核模块。LED驱动框架可以根据需要被安装与卸载。我们应该遵照分析模块的方法,从下往上分析。
2、类名“leds”的创建与销毁
led_init()在/sys/class目录下创建“leds”这个类名;led_exit()销毁“leds”这个类名。
3、subsys_initcall(leds_init)的分析
(1)subsys_initcall是一个宏,定义在linux/init.h中。
其功能是将其声明的函数放到一个特定的段:.initcall4.init。
#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
(2)分析module_init宏,可以看出它将函数放到了.initcall6.init段中。
#define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6",fn,6)
也就是说,subsys_initcall和module_init的作用是一样的,只不过前者所声明的函数要比后者在内核启动时的执行顺序更早。
(3)内核实现先后顺序执行初始化操作的方法
将内核启动时要调用的所有函数归类,分类名就叫做“.initcalln.init”,n的值从1到8。内核开发者在编写内核代码时,将函数设置合适的级别,链接的时候,这些函数就会被放入特定的段中,内核启动时再按照(内核链接脚本中指定的)段顺序去依次执行各个段。
内核链接脚本(编译之后才有)在arch/arm/kernel/vmlinux.lds中。
4、led_class_attrs
(1)什么是attribute?
对应于将来/sys/class/leds/ 目录里的内容,这些内容一般是文件和文件夹。这些属性文件其实就是sysfs开放给应用层的一些操作接口,应用程序可以通过操作这些属性文件来操作硬件设备。(这些属性文件非常类似于/dev/目录下的设备文件,对/dev/目录下的设备文件的操作有关的API,对应着file_operations里面的函数。)
(2)attribute有什么用?
应用程序可以通过操作/sys/class/leds/目录下面的属性文件来操作硬件设备。它其实是另一条驱动实现路线(不再有cdev相关的函数操作),有区别于之前的file_operations那条线。
(3)class_create()的返回值类型是struct class类型。
该类型定义在/include/linux/device.h文件中:
struct class {const char *name; // 类的名称struct module *owner; //类所属的模块,比如usb模块、led模块等struct class_attribute *class_attrs;//类所添加的属性struct device_attribute *dev_attrs;//类所包含的设备所添加的属性struct kobject *dev_kobj;// 用于标识 类所包含的设备属于块设备还是字符设备// 用于在设备发出 uevent 消息时添加环境变量int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);char *(*devnode)(struct device *dev, mode_t *mode);// 设备节点的相对路径名void (*class_release)(struct class *class);// 类被释放时调用的函数void (*dev_release)(struct device *dev);// 设备被释放时调用的函数//设备休眠时调用的函数int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);// 设备被唤醒时调用的函数const struct kobj_ns_type_operations *ns_type;const void *(*namespace)(struct device *dev); const struct dev_pm_ops *pm; // 用于电源管理的函数struct class_private *p;// 指向 class_private 结构的指针 };
其中,结构体struct device_attribute定义在/include/linux/device.h文件中:
/* 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); };
因此led_class_attrs[ ]这个结构体数组的初始化如下:
//位于x210_kernel/drivers/leds/led-class.c文件中 static struct device_attribute led_class_attrs[] = {//文件 //对文件操作时实际对应的操作函数1 //操作函数2__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, };//位于x210_kernel/include/linux/sysfs.h文件中 #define __ATTR(_name,_mode,_show,_store) { \.attr = {.name = __stringify(_name), .mode = _mode }, \.show = _show, \.store = _store, \ }//位于x210_kernel/drivers/leds/led-class.c文件中 static ssize_t led_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) {struct led_classdev *led_cdev = dev_get_drvdata(dev);/* no lock needed for this */led_update_brightness(led_cdev);return sprintf(buf, "%u\n", led_cdev->brightness); }static ssize_t led_brightness_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {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);}return ret; }
5、设备注册函数:led_classdev_register()
(1)函数内容与作用
该函数的内容如下,它用于创建属于“leds”这个类的一个设备,其实就是去注册一个设备。
/*** led_classdev_register - register a new object of led_classdev class.* @parent: The device to register.* @led_cdev: the led_classdev structure for this device.*/ 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);if (IS_ERR(led_cdev->dev))return PTR_ERR(led_cdev->dev); #ifdef CONFIG_LEDS_TRIGGERSinit_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);if (!led_cdev->max_brightness)led_cdev->max_brightness = LED_FULL;led_update_brightness(led_cdev); #ifdef CONFIG_LEDS_TRIGGERSled_trigger_set_default(led_cdev); #endifprintk(KERN_DEBUG "Registered led device: %s\n",led_cdev->name);return 0; }
(2)struct led_classdev结构体
“leds”类别的设备,用结构体struct led_classdev表征,其定义在include/linux/leds.h中:
struct led_classdev {const char *name;int brightness;int max_brightness;int flags;/* Lower 16 bits reflect status */ #define LED_SUSPENDED (1 << 0)/* Upper 16 bits reflect control information */ #define LED_CORE_SUSPENDRESUME (1 << 16)/* Set LED brightness level *//* Must not sleep, use a workqueue if needed */void (*brightness_set)(struct led_classdev *led_cdev,enum led_brightness brightness);/* Get LED brightness level */enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);/* 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 };
其中的struct device结构体定义在/include/linux/device.h文件中:
struct device {struct device *parent; struct device_private *p; struct kobject kobj;const char *init_name; /* initial name of the device */struct device_type *type;struct mutex mutex; /* mutex to synchronize calls to* its driver.*/struct bus_type *bus; /* type of bus device is on */struct device_driver *driver; /* which driver has allocated thisdevice */void *platform_data; /* Platform specific data, devicecore doesn't touch it */struct dev_pm_info power;#ifdef CONFIG_NUMAint numa_node; /* NUMA node this device is close to */ #endifu64 *dma_mask; /* dma mask (if dma'able device) */u64 coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */struct device_dma_parameters *dma_parms;struct list_head dma_pools; /* dma pools (if dma'ble) */struct dma_coherent_mem *dma_mem; /* internal for coherent memoverride *//* arch specific additions */struct dev_archdata archdata; #ifdef CONFIG_OFstruct device_node *of_node; #endifdev_t devt; /* dev_t, creates the sysfs "dev" */spinlock_t devres_lock;struct list_head devres_head;struct klist_node knode_class;struct class *class;const struct attribute_group **groups; /* optional groups */void (*release)(struct device *dev); };
(2)补充说明
该函数是LED驱动框架中,内核开发者提供给驱动开发者的一个注册设备的接口。
当使用LED驱动框架编写驱动时,这函数的作用类似于之前使用file_operations方式去注册字符设备驱动的register_chrdev函数。