Linux platform总线驱动基础知识
platform总线驱动模型
在Linux2.6版本以后的设备驱动编写时,一般需关心三部分内容,即 总线(bus)、设备(device) 和 驱动(driver) 三个实体。总线是设备和驱动之间的桥梁,Linux通过总线将设备与驱动两部分绑定起来。当向系统注册一个驱动时,总线会寻找与之匹配的设备,如果有的话会将两者联系起来;相反的,当向系统注册一个设备时,总线会在驱动中查找有没有与之匹配的设备,如果有的话也将两者联系起来。
在真实的项目开发中,一般Linux设备和驱动通常挂接在一类总线上,但还是有一部分外设不依赖于类似I2C、SPI、USB等此类总线。基于此,产生了称为platform的一类总线,相应的设备称为platform_device,驱动称为platform_driver。platform_device并不是和Linux字符设备、块设备、网络设备并列的概念,而是一种附加手段。类似于将SoC内部集成的I2C、PWM、LCD等控制器归纳为platform_device。
平台总线注册和匹配方式
在Linux设备驱动中,总线相对比较重要,它主要完成匹配设备和驱动,每当有新的设备或新的驱动加入到platform总线上时,总线便会调用platform_math函数对新增的设备进行配对。
Linux内核通过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,
.probe = platform_probe,
.remove = platform_remove,
.shutdown = platform_shutdown,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
在上述结构体中,match函数指针相对重要,该函数指针指向的函数将负责实现platform总线驱动和playform总线设备的匹配过程。对于每个驱动总线,他都必须实例化该函数指针。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 */
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);
}
platform总线在Linux内核启动时自动进行注册,即内核自动调用函数platform_bus_init
完成注册。
platform总线提供了四种存在先后顺序的匹配方式:设备树>ACPI匹配模式>id_table方式>字符串比较
。
\drivers\base\platform.c
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error) {
put_device(&platform_bus);
return error;
}
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
在上述platform总线初始化中,bus_register
函数完成向Linux内核注册platform平台总线。
平台设备(platform_device
)
platform_device主要是为驱动程序提供设备信息,设备信息包括硬件信息
和软件信息
两部分。
-
硬件信息
硬件信息主要是驱动程序使用到的寄存器、中断号、内存、IO资源等。
-
软件信息
包括I2C设备的涉笔地址、片选设备的片选信号、以太网的MAC地址等信息。
platform设备主要维护platform_device
结构体,结构体原型如下所示。
\include\linux\platform_device.h
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
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
设备名称,需要于platform_driver的name值一致,否则纵向将匹配不成功。
-
id
id号,用于给插入该总线相同name的设备进行编号,如果只有一个设备,填入-1。
-
dev
内嵌的device结构体。Linux内核采用大量的面向对象的编程思想,platform_device通过继承该结构体,可复用他的相关代码,方便管理平台设备。其中成员platform_data,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)。
-
num_resources
表示资源的数量,当结构体成员resources存放的是数组时,一般记录resources数组的个数,内核通过宏定义ARRAY_SIZE计算数组的个数。
-
resources
平台设备提供给驱动的资源、信息,例如寄存器。
-
id_entry
平台总线提供的另一种匹配方式,原理是比较字符串。
platform_device
涉及的常用函数
\drivers\base\platform.c
/*
* 平台设备注册,将设备挂接在platform总线上
* [pdev] platform_device类型结构体指针
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
setup_pdev_dma_masks(pdev);
return platform_device_add(pdev);
}
/*
* 平台设备注销
* [pdev] platform_device类型结构体指针
*/
void platform_device_unregister(struct platform_device *pdev)
{
platform_device_del(pdev);
platform_device_put(pdev);
}
在platform_device
结构体中resource
结构体相对比较重要,这是实现驱动和设备分离的关键,它主要用于描述硬件信息,包括终端号、寄存器物理地址等,其结构体原型如下:
\include\linux\ioport.h
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
资源的开始值,对于I/O内存来说是起始的内存地址,对于中断资源来说是起始的中断号,对于DMA资源来说是起始的DMA通道号
-
end
资源的结束值。
-
flags
用于指定该资源的类型,在Linux中,资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型,最常见的有以下几种:
资源宏定义 | 描述 |
---|---|
IORESOURCE_IO | 用于IO地址空间,对应于IO端口映射方式 |
IORESOURCE_MEM | 用于外设的可直接寻址的地址空间 |
IORESOURCE_IRQ | 用于指定该设备使用某个中断号 |
IORESOURCE_DMA | 用于指定使用的DMA通道 |
当有的设备可能有多个资源,通常使用platform_get_resource
函数来获取资源,原型如下:
\drivers\base\platform.c
/*
* 获取设备信息
* [dev] 指定要获取哪个平台设备的资源;
* [type] 指定获取资源的类型,如IORESOURCE_MEM、IORESOURCE_IO等;
* [num] 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
u32 i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);
平台驱动(platform_driver
)
platform驱动核心为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;
};
结构体常用成员含义:
-
probe
函数指针,当驱动与设备匹配成功后probe函数将会执行。驱动开发者需要在驱动程序中初始化该函数指针,一般通过该函数对设备进行一系列的初始化操作,类似于字符设备驱动中的init函数。
-
remove
函数指针,用于移除某个平台时调用,驱动开发者需要在驱动程序中初始化该函数指针,该函数实现的操作,通常是probe函数实现操作的逆过程,类似于字符设备驱动中的exit函数。
-
driver
Linux设备模型中用于抽象驱动的device_driver结构体,platform_driver继承该结构体,也就获取了设备模型驱动对象的特性。
-
id_table
id_table是个表,元素类型为platform_device_id类型,表示该驱动能够兼容的设备类型。
platform_driver
常用函数
\include\linux\platform_device.h
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
\driver\base\platform.c
/*
* 注册平台驱动
* [drv] platform_driver类型结构体指针
* [owner] 一般为THIS_MODULE
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
return driver_register(&drv->driver);
}
/*
* 注销平台驱动
* [drv] platform_driver类型结构体指针
*/
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
参考
《Linux设备驱动开发详解 基于最新的Linux4.0内核》
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.pdf》
《[野火]i.MX Linux开发实战指南》
[驱动注册]platform_driver_register()与platform_device_register()
🎪
🔊
⏲