第八章——Linux设备模型(1)

 因为面试被问到了设备模型,所以先复习一下这里。前文实现的比如字符设备驱动存在一些弊端:

  1. 设备和驱动没有分离,设备的信息是硬编码在驱动中,这给驱动造成了极大的限制,通用性变得很差。
  2. 没有类似Windows的设备管理器,不可以方便地查看设备和驱动信息。
  3. 不能自动创建设备节点。
  4. 驱动不能自动加载。
  5. U盘、SD不能自动挂载。
  6. 没有电源管理。

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

static ssize_t val_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) {
	char *endp;
	printk("size=%d\n", 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_typestruct devicestruct 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 *);
posted @ 2020-04-24 15:05  hansenn  阅读(229)  评论(0编辑  收藏  举报