专题2-总线设备驱动模型
一.总线模型
1.随着技术的进步,对热插拔的要求以及可移植性的要求越来越高,从Linux2.4开始虽然有了模型但是正式提出是在Linux2.6。
2.关键词是总线,驱动,设备
3.总线能够感知设备的插拔:
(1)插入新设备的时候知道有设备插入,那么就去总线上已有的驱动里面查找能够处理该新设备的驱动,一旦匹配,该驱动就有了该设备的控制权
(2)拔出的时候,总线也能感知,并且告诉相应的驱动从而使得驱动能够处理拔出事件
二.总线的描述
1.在linux/device.h里面定义的bus_type结构体类型。
2.struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
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 (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
3.最主要的是
(1)const char *name;描述的是总线名称
(2) int (*match)(struct device *dev, struct device_driver *drv);完成驱动和设备的匹配功能。判断指定的驱动程序能否处理指定的设备,能就返回非0值。
4.注册总线
int bus_register(struct bus_type *bus);
5.注销
void bus_unregister(struct bus_type *bus);
6.代码
三.驱动程序
1.描述结构
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;
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;
};
2.最主要初始化的是
(1) const char *name;描述驱动名称
(2) struct bus_type *bus;描述该驱动要挂到什么总线
(3) int (*probe) (struct device *dev);当总线对驱动和设备匹配成功的时候,总线会调用该函数对相应的设备进行一些初始化等设备相关的操作。
3.注册
int driver_register(struct device_driver *drv)
4.注销
void driver_unregister(struct device_driver *drv)
5.代码
四.设备
1.描述结构
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const 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 this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
struct dev_power_domain *pwr_domain;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations 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 mem
override */
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
dev_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.最重要的是
(1) const char *init_name; /* initial name of the device */即表示设备的名字
(2)struct bus_type *bus; /* type of bus device is on */设备所在的总线
3.注册
int device_register(struct device *dev)
4.注销
void device_unregister(struct device *dev)
5.代码
五.不同总线匹配设备和驱动的方式不一样。
通常是匹配驱动和设备的名字来决定match函数的返回值,从而决定是否匹配成功以及是否能够调用probe函数。
六.内核异常之后通常需要重启
七.根据内核源代码,发现在device注册的时候,如果init_name已经存在,则将其置为空指针,并复制到device.kobj.name,所以当我们匹配的时候如果直接使用init_name,则会出现内核级别的段错误。
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
。。。。。。
int dev_set_name(struct device *dev, const char *fmt, ...)
{
va_list vargs;
int err;
va_start(vargs, fmt);
err = kobject_set_name_vargs(&dev->kobj, fmt, vargs);
va_end(vargs);
return err;
}
。。。。。。。。
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
va_list vargs)
{
const char *old_name = kobj->name;
char *s;
if (kobj->name && !fmt)
return 0;
kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (!kobj->name)
return -ENOMEM;
/* ewww... some of these buggers have '/' in the name ... */
while ((s = strchr(kobj->name, '/')))
s[0] = '!';
kfree(old_name);
return 0;
}
可以看到
kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
就是把init_name复制到device.kobj.name。所以匹配的时候要比较kobj.name和drv.name。
八.不管先有驱动还是先有设备,都是同样的匹配模式。从而支持热插拔很好,而且移植性更高。因为只需要改动驱动和设备要挂载到的总线类型即可。在device和device_driver结构体变量初始化的地方修改。
九.平台总线驱动模型
1.Linux从2.6以后就引入了平台总线的概念。
2.平台总线是一种虚拟总线,Linux不仅可以支持实际总线,如USB,PCI总线等。也可以支持一些虚拟的总线。
3.这些虚拟总线和实际总线拥有完全相同的特性,包括可移植性以及热插拔检测等。
十.总线描述结构
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
(1)平台总线名称:platform_bus_type
(2)实现文件/drivers/base/platform.c
十一.匹配设备和驱动的函数
由上面描述结构可知是 .match = platform_match,即
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);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
可见在Linux3.0x版本的内核中有三种匹配方式。
1./*通过驱动里定义了of_device_id项,则通过这一项来比对;*
2./*如果在平台驱动中定义了id_table项,则通过对比id_table来判断,即芯片ID*/
3./*通过对比平台设备名字和平台驱动名字来判断*/
如果driver中定义了of_device_id,则通过driver中的of_device_id和device中的device_node内容进行匹配判断,匹配工作由of_match_node来完成,该函数会遍历of_device_id列表,查找是否有成员与device_node相匹配,具体由matches的name,type和compatioble来进行对比,如果找到则返回相应的表项,否则返回null.如果没有定义of_device_id,device_node或不能找到对应的匹配项,则通过第二种方式platform_device_id来进行对比匹配,通过platform_match_id来完成。platform_match_id函数遍历platfrom_device_id列表,通过比对平台设备与id的name来确定是否有匹配项,如果找到匹配的,则返回对应的id项,否则返回null。如果没有定义platform_device_id或没有找到匹配项,则通过第三种方式进行匹配,第三种方式通过比对平台设备和平台驱动的名字,如果相等,则匹配成功,否则失败。
十二.平台设备
1.描述结构(在platform_device.h中定义)
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
2.最重要的:
(1)const char * name;定义设备名称
(2) int id;设备编号,配合名称使用
(3) struct resource * resource;设备拥有的资源
(4) u32 num_resources;拥有的资源数目,即resource成员代表的数组的元素个数。
具体结构如下
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
主要的是
--》 resource_size_t start;起始值
--》 resource_size_t end;终止值
--》unsigned long flags;资源类型,包括中断号,内存(寄存器基地址)等。
(5)一般初始化设备指定资源的时候都要事先初始化一个资源数组。
3.注册设备:(挂载到平台总线)
int platform_device_register(struct platform_device *pdev);
4.注销
int platform_device_unregister(struct platform_device *pdev);
5.代码
主要是平台设备资源数组定义格式要注意,脑补C语言基础知识
十三.平台驱动
1.描述结构
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;
};
2.主要初始化的
(1) int (*probe)(struct platform_device *);表示当驱动和设备匹配的时候调用的函数,一般是硬件相关的初始化
(2) int (*remove)(struct platform_device *);表示当对应的设备从平台总线移除的时候,调用的函数。
(3) const struct platform_device_id *id_table;用来匹配设备用的。
(4) struct device_driver driver;它里面的name成员能够指定设备驱动的名字。(3)和(4)初始化一个就可以了,当然可以同时初始化。
3.注册
int platform_driver_register(struct platform_driver *);
4.注销
int platform_driver_unregister(struct platform_driver *)
5.硬件相关的初始化一般放在probe指定的函数
6.平台总线只是负责匹配设备和驱动,当找到以后,我们还需要在probe里面进行硬件相关初始化以及其他必须的工作,比如注册设备,由此可见,一个设备如果在总线上,那么他必然是一个对应的平台设备。但是,与此同时,这个设备有可能是字符设备,网卡,USB设备,所以还需要我们继续完成其他设备类型的驱动工作。比如混杂设备,网络设备等的驱动步骤。故一般情况下平台驱动的注册在平台驱动文件的注册模块,而具体的这个硬件对应的另外类型的驱动的注册,(如混杂设备类型驱动的注册)则是放在probe函数,同样的这个平台驱动的卸载也是在平台驱动文件的卸载模块,而硬件其它类型驱动的卸载也是在remove指定的函数。一般情况是总线(驱动)包裹其他类型驱动的模型。
7.在平台总线驱动中,不适合出现硬件相关太多的东西,比如寄存器地址,比如中断号,这些已经作为平台设备的一种资源在平台设备里进行了初始化,我们要做的是如何提取平台设备里的资源信息。的那个总线匹配了设备和驱动的时候,会将设备的所有信息传递给probe指定的函数,我们就可以在该函数里面调用platform_get_resource(设备名,资源类型,该类型的资源序号)来获取相应的硬件信息。返回的是一个资源结构体指针,通过访问他的start,end成员就能得到很多硬件信息了。
8.代码
(1)先要定义获取硬件资源的变量,将platform_get_resource的返回值作为它们的值
其中key_base是按键的起始地址,是已经经过地址映射得到的结果,是虚拟地址。
(2)最核心的硬件初始化程序利用上述得到的值进行
(3)获取设备(硬件)资源以及完成其他层次驱动的注册
9.不一定要把设备单独写成模块,很多时候直接将设备加到内核初始化部分。因为实际上平台设备也就是一个变量,后期开发相应的驱动程序即可。

浙公网安备 33010602011771号