一,platform device概述
在Linux2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,
会寻找与之匹配的驱动;相反的,在系统每注册一个设备的时候,会寻找与之匹配的设备,而匹配由总线完成。
一个现实的Linux设备和驱动通常都需要挂接在一种总线上,而对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,
但是在嵌入式系统里面,在SoC系统中集成的独立外设控制器、挂接在SoC内存空间的外设等却不依附于此类总线。基于这一背景,Linux
发明了一种虚拟总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver。
所谓的platform_device并不是与字符设备、块设备和网络设备并存的概念,而是linux系统提供的一种附加手段,例如,我们通常把在
SoC内部集成的I2C、RTC、LCD、看门狗等控制器都归纳为platform_device,而他们本身就是字符设备。这些设备有一个基本的特征:可以通过CPU bus直接寻址(例如在嵌入式系统常见的“寄存器”)。
二,platform模块的软件架构
内核中Platform设备有关的实现位于include/linux/platform_device.h和drivers/base/platform.c两个文件中,它的软件架构如下:
由图片可知,Platform设备在内核中的实现主要包括三个部分:
Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备;
Platform Device,基于底层device模块,抽象出Platform Device,用于表示Platform设备;
Platform Driver,基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备。
其中Platform Device和Platform Driver会给其它Driver提供封装好的API,具体可参考后面的描述。
三,platform bus/platform device/platform driver结构体
1,platform_driver
// msm_kernel\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; /* 另外这里有一个id_table的指针,该指针和of_match_table、acpi_match_table的功能类似:提供其它方式的设备probe */
bool prevent_deferred_probe;
ANDROID_KABI_RESERVE(1);
};
2,platform_device
// msm_kernel\include\linux\platform_device.h
struct platform_device {
const char *name; /* 设备的名称,和struct device结构中的init_name意义相同。实际上,该名称在设备注册时,会拷贝到dev.init_name中 */
int id; /* 用于标识该设备的ID。内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driver的probe接口。
因此,在driver的probe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个*/
bool id_auto; /* 指示在注册设备时,是否自动赋予ID值 */
struct device dev; /* 真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现) */
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource; /* 该设备的资源描述,由struct resource(include/linux/ioport.h)结构抽象 */
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;
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
};
3,platform bus
// msm_kernel\drivers\base\platform.c
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,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
四,platform bus/platform device/platform driver注册
1,platform bus注册
struct device platform_bus = {
.init_name = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup(); /* 清除所有和Early device/driver相关的代码。因为执行到这里的时候,证明系统已经完成了Early阶段的启动,转而进行正常的设备初始化、启动操作,所以不再需要Early Platform相关的东西。 */
error = device_register(&platform_bus); /* 在sysfs中创建/sys/devices/platform目录,所有的platform设备都会包含在此目录下 */
if (error) {
put_device(&platform_bus);
return error;
}
error = bus_register(&platform_bus_type); /* 在sysfs中创建/sys/bus/platform目录并在此目录中创建如下attributes和devices目录,drivers目录 */
/*
lynkco:/sys/bus/platform # ls
devices drivers drivers_autoprobe drivers_probe uevent
*/
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
2,platform device注册
platform_device_register - add a platform device to device hierarchy
// msm_kernel\drivers\base\platform.c
platform_device_register(struct platform_device *pdev)
----device_initialize(&pdev->dev);
----platform_device_add(pdev);
--------pdev->dev.parent = &platform_bus; //该设备的sysfs目录/sys/devices/platform/xxx_device
--------pdev->dev.bus = &platform_bus_type; //该设备的bus type定义为platform_bus_type
--------dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); //对于多个同名的设备,可以使用ID区分,在这里将实际名称修改为“name.id”的形式
--------insert_resource(p, r); /* 调用resource模块的insert_resource接口,将该设备需要使用的resource统一管理起来(我们知道,在这之前,只是声明了本设备需要使用哪些resource,但resource模块并不知情,也就无从管理,因此需要告知)。 */
--------device_add(&pdev->dev); // 将内嵌的struct device变量添加到内核中
......
3,platform driver注册
platform_driver_register - register a driver for platform-level devices
// msm_kernel\drivers\base\platform.c
platform_driver_register(drv)
----__platform_driver_register(drv, THIS_MODULE)
--------drv->driver.bus = &platform_bus_type; //该driver的bus type设置为platform_bus_type
------------drv->driver.probe = platform_drv_probe; /* 如果该platform driver提供了probe、remove、shutdown等回调函数,将该它内嵌的struct driver变量的probe、remove、shutdown等指针,设置为platform模块提供函数,包括platform_drv_probe、platform_drv_remove和platform_drv_shutdown。因为probe等动作会从struct driver变量开始,经过platform_drv_xxx等接口的转接就可以到达platform diver自身的回调函数中。*/
------------drv->driver.remove = platform_drv_remove;
------------drv->driver.shutdown = platform_drv_shutdown;
--------driver_register(&drv->driver); //将内嵌的struct driver变量添加到内核中
......
五,platform device/platform driver提供的API
1,platform driver提供的API
extern int platform_driver_register(struct platform_driver *);
extern void platform_driver_unregister(struct platform_driver *); //platform driver的注册、注销接口
extern int platform_driver_probe(struct platform_driver *driver,
int (*probe)(struct platform_device *)); //主动执行probe动作
static inline void *platform_get_drvdata(const struct platform_device *pdev);
static inline void platform_set_drvdata(struct platform_device *pdev,
void *data); //设置或者获取driver保存在device变量中的私有数据
2,platform device提供的API
extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *); //Platform设备的注册/注销接口,和底层的device_register等接口类似
extern struct resource *platform_get_resource(struct platform_device *,
unsigned int, unsigned int);
extern int platform_get_irq(struct platform_device *, unsigned int);
extern struct resource *platform_get_resource_byname(struct platform_device *,
unsigned int,
const char *);
extern int platform_get_irq_byname(struct platform_device *, const char *); //通过这些接口,可以获取platform_device变量中的resource信息,以及直接获取IRQ的number等等
extern int platform_device_add_resources(struct platform_device *pdev,
const struct resource *res,
unsigned int num); //向platform device中增加资源描述
extern int platform_device_add_data(struct platform_device *pdev,
const void *data, size_t size); //向platform device中添加自定义的数据(保存在pdev->dev.platform_data指针中)
六,platform device resource/platform data的定义与获取
1,struct resource结构体介绍
在platform device结构体的定义中关于device resource的定义如下
u32 num_resources;
struct resource *resource;
它们描述了platform_device的资源,资源本身由resource结构体描述
/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
我们通常关心start、end和flags这3个字段,它们分别标明了资源的开始值、结束值和类型,flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。
start、end的含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;
当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了一个中断号,开始值和结束值相同。
对于同种类型的资源而言,可以有多份,例如说某设备占据了两个内存区域,则可以定义两个IORESOURCE_MEM资源。
系统中所有的platform_device,都可以在/sys/devices/platform/路径下查看。另外,系统中所有的platform_device,有来自设备树的,也有来自.c文件中注册的。那么,我们怎么知道哪些platform_device是来自设备树,哪些是来自.c文件中注册的?
可以查看该platform_device的相关目录下,是否有of_node,如果有of_node,那么这个platform_device就来自于设备树;否则,来自.c文件。
2,来自.c文件中注册的platform_device
2.1 example
// msm_kernel\arch\arm\mach-ep93xx\core.c
static struct usb_ohci_pdata ep93xx_ohci_pdata = {
.power_on = ep93xx_ohci_power_on,
.power_off = ep93xx_ohci_power_off,
.power_suspend = ep93xx_ohci_power_off,
};
static struct resource ep93xx_ohci_resources[] = {
DEFINE_RES_MEM(EP93XX_USB_PHYS_BASE, 0x1000),
DEFINE_RES_IRQ(IRQ_EP93XX_USB),
};
static struct platform_device ep93xx_ohci_device = {
.name = "ohci-platform",
.id = -1,
.num_resources = ARRAY_SIZE(ep93xx_ohci_resources),
.resource = ep93xx_ohci_resources,
.dev = {
.dma_mask = &ep93xx_ohci_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &ep93xx_ohci_pdata,
},
};
platform_device_register(&ep93xx_ohci_device);
设备除了可以在BSP中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断、内存等标准资源以外,还可能有一些配置信息,而这些配置信息也依赖于板,不适宜直接放置在设备驱动上。因此platform也提供了platform_data的支持,platform_data的形式是由每个驱动自定义的,如对于usb ohci设备而言,platform_data为一个usb_ohci_pdata结构体,完成定义后将可以将PM operation相关的接口信息放入platform_data中。
在usb ohci驱动msm-kernel/drivers/usb/host/ohci-platform.c的probe()函数中,通过如下方式就拿到了platform_data:
static int ohci_platform_probe(struct platform_device *dev)
{
struct usb_hcd *hcd;
struct resource *res_mem;
struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev);
struct ohci_platform_priv *priv;
... ...
}
2.2 memory resource资源的定义
#define DEFINE_RES_MEM(_start, _size) \
DEFINE_RES_MEM_NAMED((_start), (_size), NULL)
#define DEFINE_RES_MEM_NAMED(_start, _size, _name) \
DEFINE_RES_NAMED((_start), (_size), (_name), IORESOURCE_MEM)
/* helpers to define resources */
#define DEFINE_RES_NAMED(_start, _size, _name, _flags) \
{ \
.start = (_start), \
.end = (_start) + (_size) - 1, \
.name = (_name), \
.flags = (_flags), \
.desc = IORES_DESC_NONE, \
}
2.3 irq resource资源的定义
#define DEFINE_RES_IRQ(_irq) \
DEFINE_RES_IRQ_NAMED((_irq), NULL)
#define DEFINE_RES_IRQ_NAMED(_irq, _name) \
DEFINE_RES_NAMED((_irq), 1, (_name), IORESOURCE_IRQ)
2.4 IO resources flags
/*
* IO resources have these defined flags.
*
* PCI devices expose these flags to userspace in the "resource" sysfs file,
* so don't move them.
*/
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
3,来自设备树的platform_device
在Linux内核启动时,内核通过 of_platform_populate() 函数,将dts中的device node创建成platform device。为后续和各类驱动的platform driver匹配做准备。
of_platform_device_create_pdata
----of_device_alloc
--------of_address_to_resource(np, num_reg, &temp_res)
--------num_irq = of_irq_count(np);
--------dev->num_resources = num_reg + num_irq;
--------dev->resource = res;
--------of_address_to_resource(np, i, res);
--------of_irq_to_resource_table(np, res, num_irq); //将dts中的address和irq等信息转化到resource结构体中
--------dev->dev.bus = &platform_bus_type;
----dev->dev.platform_data = platform_data;
----of_device_add(dev)
--------device_add(&ofdev->dev);
具体解析转化过程会在后续的dts章节中详细分析。
4,get resource API实现及使用
4.1 get resource API实现
platform_get_resource_byname:
/**
* platform_get_resource_byname - get a resource for a device by name
* @dev: platform device
* @type: resource type
* @name: resource name
*/
struct resource *platform_get_resource_byname(struct platform_device *dev,
unsigned int type,
const char *name)
{
u32 i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (unlikely(!r->name))
continue;
if (type == resource_type(r) && !strcmp(r->name, name)) //匹配type和name
return r;
}
return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource_byname);
platform_get_resource:
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type
* @num: resource index
*
* Return: a pointer to the resource or NULL on failure.
*/
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) //匹配type和index
return r;
}
return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);
4.2 get resource API使用示例
get memory resource使用示例:
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
}
adata->acp3x_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!adata->acp3x_base)
return -ENOMEM;
get irq resource 使用示例:
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
return -ENODEV;
}
adata->i2s_irq = res->start;
status = devm_request_irq(&pdev->dev, adata->i2s_irq, i2s_irq_handler,
irqflags, "ACP3x_I2S_IRQ", adata);
if (status) {
dev_err(&pdev->dev, "ACP3x I2S IRQ request failed\n");
return -ENODEV;
}
get resource by name 使用示例:
struct resource *r;
/* card: irq assigned to the card itself. */
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "card");
sock->card_irq = r ? r->start : 0;
/* stschg: irq which trigger on card status change (optional) */
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "stschg");
sock->stschg_irq = r ? r->start : -1;
/* 36bit PCMCIA Memory area address */
r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-mem");
if (!r) {
printk(KERN_ERR "pcmcia%d has no 'pseudo-mem' resource!\n",
sock->nr);
goto out0;
}
sock->phys_mem = r->start;
由以上分析可知,在设备驱动中引入platform的概念至少有如下好处:
1)使得设备被挂接在一个总线上,符合Linux2.6以后内核的设备模型。其结果是使配套的sysfs节点、设备电源管理都成为可能。
2)隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
3)让一个驱动支持多个设备实例。
参考链接:
Linux设备模型(8)_platform设备
dts展开为platform_device结构过程分析-腾讯云开发者社区-腾讯云