一、linux驱动的分离与分层
1、分离思想就是将linux驱动与设备分离开来。在现实开发过程中,一般的主机控制器驱动都由相应的半导体厂家编写,设备驱动也一般由设备器件厂家编写,我们开发只需要提供设备信息即可,这样就是将设备信息从设备驱动中分离出来,驱动使用标准方法去获取设备信息(可能从设备树),根据获取到设备信息去初始化设备,一般采用驱动-总线-设备的模式来实现驱动分离的模式。
2、分层的思想类似于网络中的七层模型,不同的层负责不同的工作,驱动的分层也类似于此,借助分层的模型可以极大简化驱动编写。
二、驱动-总线-设备模型
分离与分层采用驱动-总线-设备的驱动框架来实现。总线就像一个桥梁一样连接着驱动和设备,一般总线不需要我们去编写,由内核提供,驱动与设备需要我们去编写。使用的过程是,当向总线注册驱动的时候,总线会去所有设备中查看是否有与之匹配的设备信息,同样的,向总线注册设备的时候也会去驱动中查找是否有相匹配的驱动信息。有的的SOC的外设没有总线概念为此linux使用了platforn虚拟总线。
一、platform总线
1、linux内核采用bus_type结构体表示总线,结构体定义在include/linux/device.h,结构体内容为:
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
/* 这个match函数很重要 */
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
2、其中的match函数很重要,它的功能是实现驱动与设备之间的匹配。总线通过此函数根据注册的驱动来查看设备或者根据注册的设备来查找驱动,match函数有两个参数分别是dev和drv,参数类型分别是device(设备)与device_driver(设备驱动)。
3、platform总线是bus_type的一个具体实例,定义在drivers/base/platform.c:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
4、其中也有一个match函数,具体为platform_match函数,函数定义在drivers/base/platform.c:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
//1、设备树匹配法
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
//2、ACPI匹配法
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
//3、id_table匹配法
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
//4、直接比较设备与驱动的name字段
return (strcmp(pdev->name, drv->name) == 0);
}
5、可以看出有四种匹配方法,一般使用第四种比较方便,一般的驱动会兼容有设备树与无设备树的匹配方法。所以一般第一种方法都会存在,第三与第四种存在一种即可。
二、platform驱动
1、linux采用platform_driver结构体来表示platform驱动,结构体定义在include/linux/platform_device.h:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
2、probe函数是一个很重要的函数,当驱动与设备匹配成功以后该函数就会自动运行。
3、driver是一个device-driver结构体变量,此处采用面向对象的思想,device_driver是一个基类,platform_driver继承了这个基类,后在此基础添加了一些特有的成员变量。
4、id_table表是用于platform总线匹配驱动与设备的时候采用的第三种方法,表其实是一个数组,每一个元素都为platform-device_id。device_driver结构体定义在include/linux/device.h:
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
5、of_match_table就是采用设备树匹配法的时候用到的匹配表,也是数组,每一个元素为of_device_id结构体类型,结构体定义在include/linux/ mod_devicetable.h中:
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
6、 其中的compatible非常重要,采用设备树的时候就通过设备树中的此属性值和of_match_table中每一个元素中的compatible成员进行比较,如果有相等则表示设备与驱动匹配成功。
7、编写platform驱动的步骤为:先定义一个platform_driver结构体变量,然后实现结构体内的各个成员变量,重点是实现匹配方法以及probe函数,因为当驱动与设备匹配上后probe函数就会自动运行,所以我们可以把驱动的内容放到probe函数中去。定义好platform_driver结构体变量后需要在驱动入口函数里调用函数去注册一个platform驱动,函数为platform_driver_register(struct platform_driver *driver),参数就是要注册的platform驱动。卸载驱动的时候需要platform_driver_unregister 函数卸载platform驱动,函数原型为platform_driver_unregister(struct platform_driver *drv)。
三、platform设备
1、linux采用platform_device来描述设备,需要注意的是如果采用设备树匹配设备的话就不需要使用这个结构体描述设备了。结构体platform_device定义在文件include/linux/platform_device.h中:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
2、num_resources表示资源数量,一般为“strucr resource *resource”定义的资源大小。resource表示资源、设备信息,linux采用resource结构体表示资源,结构体为:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
3、start与end分别表示资源的起始和终止信息,对于内存类的资源则表示为内存的起始与终止地址,name是资源的名字,flags表示资源的类型,资源类型定义在include/linux/ioport.h中。
4、如若要使用platform_device变量来描述设备信息,需要使用函数platform_device_register将设备信息注册到linux内核中去,不使用要注销相应的设备,使用函数platform_device_unregister,函数原型分别为:
int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);