目录
一、定义
平台总线是Linux内核中虚拟出来的一条总线,并非真实的物理导线。它主要用于连接和管理设备驱动与设备之间的通信,为它们提供一个匹配的平台。平台总线的设计目的是为了管理上的方便和统一。
二、功能
- 平台总线模型把原来的驱动C文件分为了两个C文件,分别是device.c和driver.c。
- 其中,device.c 用来存放设备硬件数据,通常是变化的。而 driver.c 用来存放设备驱动的逻辑,通常是通用的。
- 平台总线为驱动和设备提供了一个匹配的平台,使得一个驱动可以匹配多个设备,实现“一夫多妻制”。
- 在编写驱动时,使用平台总线可以减少编写重复的代码,因为驱动中相似操作与硬件差异化数据被分离。
- 平台总线有利于平台或设备的升级。例如,当板子上的芯片不变,但外设从一个串口更换为另一个串口时,只需要更改一些硬件资源,而不需要重写整个驱动。
三、主要结构体
平台总线的工作体系主要涉及三个结构体:struct platform_device , struct platform_driver 和 struct bus_type。
1、struct platform_device
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
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;
};
- name:平台总线下设备的名字。
- id:设备的ID。
- dev:所有设备通用的属性部分(父类)。
- num_resources:设备使用到的资源(如IO或中断号等)的个数。
- resource:设备使用到的资源数组的首地址。
- id_entry:设备ID表,用于多个类似的同系列产品使用同一个驱动。
- driver_override:用于强制匹配一个 driver
- archdata:自留地,用于提供扩展性,表示设备的一些属性。
注册和注销方式
int platform_device_register(struct platform_device *pdev);
int platform_device_unregister(struct platform_device *pdev);
2、struct platform_driver
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;
};
- probe:在与 device 匹配成功时调用
- remove:device 卸载 或 driver 卸载时调用
- shutdown:当系统即将关闭时,此函数会被调用。它通常用于执行一些清理操作,以确保设备在系统关闭前处于安全的状态。
- suspend:当系统进入低功耗模式(如挂起或休眠)时,此函数会被调用。它通常用于保存设备的状态,以便在系统恢复时能够恢复到之前的状态。
- resume:当系统从低功耗模式恢复时,此函数会被调用。它通常用于恢复设备在suspend函数中保存的状态,并重新初始化设备。
- driver:包含通用的驱动程序信息(父类)
- id_table:用于匹配此驱动程序可以支持的多个设备。这通常用于支持同一驱动程序下的多个不同设备。
- prevent_deferred_probe:这是一个布尔值,用于控制是否延迟设备的探测。在某些情况下,驱动程序可能希望立即探测设备而不是延迟到稍后进行。将此值设置为true可以防止设备探测被延迟
注册和注销方式
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
3、struct bus_type
struct bus_type 在内核中已经定义好了,以实现 device 和 driver 的自动匹配
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
当 device 和 driver 匹配成功时,会调用 platform_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);
/* 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);
}
由此可知,平台总线匹配的优先级为:
- 如果 pdev->driver_override 存在,那么会强制将 pdev->driver_override 和 drv->name进行匹配,如果不相同则匹配失败
- 利用设备树的 name/type/compatible和 of_match_table 进行匹配
- ACPI style 的 dev 和 drv 匹配
- 如果 pdrv->id_table 存在,那么会强制和 pdev->name 匹配
- 以上都不满足,最后用 pdev->name 和 drv->name 匹配
五、驱动获取设备信息
1、资源获取
在struct platform_device 结构体中,资源记录在 struct resource *resource 表示的数组中。
常用的资源:地址资源,中断资源
struct resource 结构体定义如下
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
- start:如果是地址资源,表示起始地址;如果是中断资源,表示中断号
- end:如果是地址资源,表示内存结束地址,如果是中断资源,start表示中断号
- name:资源标识符
- flags:资源种类:地址资源 IORESOURCE_MEM,中断资源 IORESOURCE_IRQ
定义方式举例
#define GPIOZ 0x54004000
struct resource led_resource[] = {
[0] = {
.start = GPIOZ,
.end = GPIOZ + 0x400,
.name = "led_reg",
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 13,
.end = 13,
.name = "led_irq",
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = 14,
.end = 14,
.name = "led_irq",
.flags = IORESOURCE_IRQ,
},
};
在 pdrv 中可以以如下方式获取
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//同类型资源编号从0开始
printk("reg addr is [%#x]\n", res->start);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);//同类型资源编号从0开始
printk("irq num is [%d]\n", res->start);
irq_num = platform_get_irq(pdev, 0);//获取第一个中断资源
printk("irq num is [%d]\n", irq_num);
结果如下