因为面试被问到了设备模型,所以先复习一下这里。前文实现的比如字符设备驱动存在一些弊端:
- 设备和驱动没有分离,设备的信息是硬编码在驱动中,这给驱动造成了极大的限制,通用性变得很差。
- 没有类似Windows的设备管理器,不可以方便地查看设备和驱动信息。
- 不能自动创建设备节点。
- 驱动不能自动加载。
- U盘、SD不能自动挂载。
- 没有电源管理。
8.1、解决第二点
在Linux地sys目录下详细罗列了设备、驱动和硬件相关的信息。
cd angelica:/ # cd sys
angelica:/sys # ls -l
total 0
drwxr-xr-x 2 root root 0 2010-01-01 00:00 android_brightness
drwxr-xr-x 2 root root 0 2010-01-01 00:00 android_camera
drwxr-xr-x 2 root root 0 2010-01-01 00:00 android_lcd
drwxr-xr-x 2 root root 0 2010-01-01 00:00 android_whitepoint
drwxr-xr-x 2 root root 0 2010-01-01 00:00 block ----块设备
drwxr-xr-x 2 root root 0 2010-01-01 00:00 bootinfo
drwxr-xr-x 20 root root 0 2010-01-01 00:00 bus ----所有总线
drwxr-xr-x 93 root root 0 2010-01-01 00:00 class ----输入设备、tty……
drwxr-xr-x 4 root root 0 2010-01-01 00:00 dev
drwxr-xr-x 10 root root 0 2010-01-01 00:00 devices ----系统中所有设备
drwxr-xr-x 2 root root 0 2010-01-01 00:00 display_cabc
drwxr-xr-x 2 root root 0 2010-01-01 00:00 display_hbm
drwxr-xr-x 3 root root 0 2010-01-01 00:00 firmware
drwxr-xr-x 9 root root 0 2010-01-01 00:00 fs
drwxr-xr-x 12 root root 0 2010-01-01 00:00 kernel
drwxr-xr-x 148 root root 0 2010-01-01 00:00 module
drwxr-xr-x 2 root root 0 2010-01-01 00:00 mtk_rgu
drwxr-xr-x 4 root root 0 2010-01-01 00:00 power
drwxr-xr-x 2 root root 0 2010-01-01 00:00 thermal
生成这些信息的重要内核数据结构——struct kobject。我们不需要理解kobject的详细信息,只需要知道它和sys目录下的目录和文件的关系。
当向一个内核成功添加kobj对象后,底层的代码会自动在sys目录下生成一个子目录。另外kobj可以附加一些属性,并绑定操作这些属性的方法。其附加的属性会被底层代码自动实现为对象对应目录下的文件。写一个象征性的代码:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kobject.h>
static struct kset *kset;
static struct kobject *kobj1;
static struct kobject *kobj2;
static unsigned int val = 0;
static ssize_t val_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
return snprintf(buf, PAGE_SIZE, "%d
", val);
}
static ssize_t val_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) {
char *endp;
printk("size=%d
", count);
val = simple_strtoul(buf, &endp, 10);
return count;
}
static struct kobj_attribute kobj1_val_attr = _ATTR(val, 0666, val_show, val_store);
//属性的名字是var,所绑定的读写方法是val_show, val_store,对应的文件权限是0666
static struct attribute *kobj1_attrs[] = {
&kobj1_val_attr.attr, //这组属性中只有一个属性 kobj1_val_attr
NULL,
}
static struct attribute_group kobj1_attr_group = {
.attrs = kobj1_attrs;
}
static void __init model_init(void) {
int ret;
kset = kset_create_and_add("kset", NULL, NULL); //向内核创建并添加kset
kobj1 = kobject_create_and_add("kobj1", &kset->kobj);
kobj2 = kobject_create_and_add("kobj2", &kset->kobj);
ret = sysfs_create_group(kobj1, &kobj1_attr_group); //为kobj1添加一组属性
ret = sysfs_create_link(kobj2, kobj1, "kobj1"); //在kobj2下创建了kobj1的软链接,名字是kobj1
return 0;
}
static void __exit model_exit(void) {
sysfs_remove_link(kobj2, "kobj1");
sysfs_remove_group(kobj1, &kobj1_attr_group);
kobject_del(kobj1);
kobject_del(kobj2);
kset_unregister(kset);
}
module_init(model_init);
module_exit(model_exit);
MODULE_LICENSE("GPL");
8.2、解决第一点:总线、设备和驱动
为了刻画总线、设备和驱动,Linux为这三种对象各自定义了对应的类:struct bus_type
、struct device
、struct device_driver
,这三者都内嵌了struct kobject或struct kset,于是会生成对应的总线、设备、驱动的目录。可以这样认为,总线、设备、驱动都继承自同一个基类struct kobject。将这三者分开来刻画,解决了第一个问题。设备专门用来描述设备所占有的资源信息,当设备资源改变,驱动代码可不做任何修改。
写一个象征性的代码:
/*vbus.c*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
static int vbus_match(struct device *dev, struct device_driver *drv) { //用于匹配驱动和设备的函数
return 1;
}
static struct bus_type vbus = {
.name = "vbus",
.match = vbus_match,
};
EXPORT_SYMBOL(vbus);
static void __init vbus_init(void) {
return bus_register(&vbus);
}
static void __exit vbus_exit(void) {
bus_unregister(&vbus);
}
module_init(vbus_init);
module_exit(vbus_exit);
MODULE_LICENSE("GPL");
/*vdrv.c*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
extern struct bus_type vbus;
static struct bus_type vbus = {
.name = "vdrv",
.bus = &vbus,
};
static int __init vdrv_init(void) {
return driver_register(&vdrv);
}
static void __exit vdrv_exit(void) {
driver_unregister(&vdrv);
}
module_init(vdrv_init);
module_exit(vdrv_exit);
MODULE_LICENSE("GPL");
/*vdev.c*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
extern struct bus_type vbus;
static void vdev_release(struct device *dev){}
static struct device vdev = {
.init_name = "vdrv",
.bus = &vbus,
.release = vdev_release,
};
static int __init vdev_init(void) {
return driver_register(&vdev);
}
static void __exit vdev_exit(void) {
driver_unregister(&vdev);
}
module_init(vdev_init);
module_exit(vdev_exit);
MODULE_LICENSE("GPL");
8.3、平台设备及其驱动
要满足Linux,必须满足总线、设备、驱动。但有的设备并没有对应的物理总线。为此,内核专门开发了platform总线来挂在没有物理总线的设备或不支持热插拔的设备。
8.3.1、平台设备
平台设备及其资源通常存在于BSP文件中,比如:arch/arm/目录,用struct platform_device结构表示:
struct platform_device {
const char *name; //设备的名字
int id; //设备的ID号
bool id_auto;
struct device dev; //内嵌的struct device
u32 num_resources; //平台设备使用资源个数
struct resource *resource; //平台设备的资源列表,指向资源数组中的首地址
const struct platform_device_id *id_entry; //用于驱动匹配ID,平台总线match函数优先匹配此ID,不行在尝试用name
/*MFD cell pointer*/
struct mfd_cell *mfd_cell;
/*arch specific additions*/
struct pdev_archdata archdata;
}
上面最关键的就是设备使用的资源信息描述resource,这是设备和驱动分离的关键:
struct resource {
resource_size_t start; //资源的开始:IO(起始内存地址)、中断(中断号)、DMA(DMA通道号)
resource_size_t end;
const char *name;
unsigned long flags; //在include/linux/ioport.h中
struct resource *parent, *sibling, *child; //树型结构
}
向平台注册和注销平台设备的主要函数如下:
int platform_add_devices(struct platform_device **devs, int num);
int platform_device_register(struct platform_device *pdev);
void platform_device_unegister(struct platform_device *pdev);
当平台发现有匹配的驱动时候,会调用驱动平台内的一个函数,并传递匹配平台设备结构地址,平台驱动就可以获得设备资源信息。
8.3.2、平台驱动
平台驱动使用struct platform_driver结构表示:
struct platform_driver {
int (*probe)(struct platform_device *) //总线发现匹配的平台设备时调用
int (*remove)(struct platform_device *)
/*电源管理函数*/
void (*shutdown) ……
int (* suspend) ……
int (* resume) ……
struct device_driver driver;
const struct platform_device_id *id_table; //平台驱动可以驱动的设备ID列表,可用于和平台匹配
bool prevent_deferred_probe;
}
向平台总线注册和注销平台驱动的主要函数如下:
platform_driver_register(drv)
void platform_driver_unregister(struct platform_driver *);