bus:
总线作为主机和外设的连接通道,有些总线是比较规范的,形成了很多协议。如PCI,USB,1394,IIC等。任何设备都可以选择合适的总线连接到主机。当然主机也可能就是CPU本身。
driver:
驱动程序是在CPU运行时,提供操作的软件接口。所有的设备必须有与之配套驱动程序才能正常工作。一个驱动程序可以驱动多个类似或者完全不同的设备。
device:
设备就是连接在总线上的物理实体。设备是有功能之分的。具有相同功能的设备被归到一个类(CLASS中)。如音频设备(和声音相关的都算),输入设备(鼠标,键盘,游戏杆等)
在linux驱动管理中,可以将驱动程序中用到的数据和代码分离开来:
设备是数据 驱动是代码
这种分离好处是:如果设备的参数变了,或者更改了同类的设备,那么只需要修改数据,二不需要修改代码,就能驱动设备了。而设备和驱动的关联靠的是总线。
内核使用platform_device结构体来描述平台设备,内核源码定义在include/linux/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;
/* 省略部分成员 */
};
参数:
name: 设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
id: 指定设备的编号,Linux支持同名的设备,而同名设备之间则是通过该编号进行区分;
dev: Linux设备模型中的device结构体,linux内核大量使用了面向对象思想,platform_device通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
num_resources: 记录资源的个数,当结构体成员resource存放的是数组时,需要记录resource数组的个数,内核提供了宏定义ARRAY_SIZE用于计算数组的个数;
resource: 平台设备提供给驱动的资源,如irq,dma,内存等等。
id_entry: 平台总线提供的另一种匹配方式,原理依然是通过比较字符串,这里的id_entry用于保存匹配的结果;
平台设备的工作是为驱动程序提供设备信息,设备信息包括硬件信息和软件信息两部分。
1.我们定义并初始化好platform_device结构体后,需要把它注册、挂载到平台设备总线上。我们使用platform_device_register的注册函数:
int platform_device_register(struct platform_device *pdev);
参数 :pdev是platform_device类型的结构体指针,成功返回0,失败返回<0
2.相反我们需要移除,卸载某个平台设备时,我们使用platform_device_unregister函数通知平台总线去移除这个设备:
void platform_device_unregister(struct platform_device *pdev)
参数:pdev: platform_device类型的结构体指针,无返回值
内核中使用platform_driver结构体来描述平台驱动,platform_driver结构体在内核源码/include/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;
};
参数:
probe: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。我们一般通过该函数,对设备进行一系列的初始化。
remove: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是probe函数实现操作的逆过程。
shutdown: 彻底关电
suspend:休眠驱动设备,可能是低功耗状态
resume:唤醒驱动设备
driver: Linux设备模型中用于抽象驱动的device_driver结构体,platform_driver继承该结构体,也就获取了设备模型驱动对象的特性;
id_table: 表示该驱动能够兼容的设备类型。
初始化platform_driver后,通过platform_driver_register()函数注册平台驱动:
int platform_driver_register(struct platform_driver *drv);
参数: drv: platform_driver类型结构体指针 返回值:成功0 失败负数
当卸载驱动模块时,需要注销已注册的平台驱动,platform_driver_unregister()函数用于注销已注册的平台驱动:
void platform_driver_unregister(struct platform_driver *drv);
参数: drv: platform_driver类型结构体指针
上面的内容是最基本的平台驱动框架,只需要实现probe函数、remove函数,初始化platform_driver结构体,并调用platform_driver_register进行注册即可。
在Linux设备驱动模型中,总线是负责匹配设备和设备驱动的。当有新的设备或者新的驱动加入到中线中时,总线(bus)辉调用platform_match函数对新增的设备或驱动进行配对。
总线是处理器与设备之间通道,在设备模型中,所有的设备都通过总线相连。
总线上有两条链表klist_devices、klist_drivers;
每次出现一个设备就要向总线注册,添加到总线的klist_devices链表中;
每次出现一个驱动也要向总线注册,添加到总线的klist_drivers中。
比如系统初始化的时候,会扫描连接了哪些设备,并为每一个设备建立起一个struct device的变量,每一次有一个驱动程序,就要准备一个struct device_driver结构的变量。把这些变量统统加入相应的链表,device 插入devices 链表,driver插入drivers链表。这样通过总线就能找到每一个设备,每一个驱动。
假如计算机里只有设备却没有对应的驱动,那么设备无法工作。反过来,倘若只有驱动却没有设备,驱动也起不了任何作用。
Linux内核用platform_bus_type函数来描述平台总线,定义在driver/base/platform.c中,总线在linux内核启动时自动注册:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
这里重点是platform总线的match函数指针,该函数指针指向的函数将负责实现平台总线和平台设备的匹配过程。对于每个驱动总线, 它都必须实例化该函数指针。
platform_match函数在内核源码/driver/base/platform.c中:
static int platform_match(struct device *dev, struct device_driver *drv)
{
/*调用了to_platform_device()和to_platform_driver()宏*/
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 */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_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);
}
#define to_platform_device(x) (container_of((x), struct platform_device, dev)
#define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver))
通过contain_of宏获取正在匹配的platform_device,platform_driver.
platform总线提供了四种匹配方式,优先级分别是:设备树机制>ACPI匹配模式>id_table方式>字符串比较
平台设备通过结构体resource来抽象表示一些硬件的信息,软件信息利用设备结构体device中的platform_data保存。
platform_get_resource()函数通常会在驱动的probe函数中执行,用于获取平台的设备提供的资源结构体:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
参数:
dev: 指定要获取哪个平台设备的资源;
type: 指定获取资源的类型,如IORESOURCE_MEM、IORESOURCE_IO等;
num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,
为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
返回值:
成功: struct resource结构体类型指针
失败: NUL
如果资源的类型是IORE类型的IORESOURECE_IRQ,平台设备驱动还会通过platform_get_req()函数获取中断引脚:
int platform_get_irq(struct platform_device *pdev, unsigned int num)
参数:
pdev: 指定要获取哪个平台设备的资源;
num: 指定要获取的资源编号。
返回值:
成功: 可用的中断号
失败: 负数