Linux设备模型10(基于Linux6.6)---platform介绍
一、platform概述
在 Linux 内核中,Platform 总线(通常使用 platform_bus_type 来表示)是用来管理那些不属于标准总线(如 PCI、USB、I2C、SPI 等)但仍然需要被内核管理的设备。这些设备通常是与特定硬件平台直接关联的,比如嵌入式设备、SoC(System on Chip)上的硬件模块,或者一些特殊的硬件接口。
Platform 总线主要用于以下几类设备:
- 嵌入式平台设备:如某些特定硬件或自定义模块,通常这些设备通过设备树(Device Tree)或者板级文件来描述。
- 固定的硬件设备:如GPIO控制器、定时器、串口、内存映射设备等。
- 没有标准接口的设备:这些设备的驱动程序可能通过平台总线直接注册。
Platform 总线通常与硬件平台密切相关,设备通过 设备树(Device Tree)或 ACPI(Advanced Configuration and Power Interface)来描述。设备树是用于描述硬件平台的一个数据结构,通常在嵌入式系统中使用,用来告诉内核关于硬件的配置信息。平台设备通常不需要依赖于 PCI、I2C 等传统总线,而是直接由平台代码提供支持。
Platform 设备和驱动的注册
- 设备注册:平台设备通常通过
platform_device_register()函数注册到内核中。 - 驱动注册:与平台设备相关的驱动程序通过
platform_driver_register()注册到内核中。
平台设备和驱动的匹配通常是通过设备的 id(ID 或 dev_name)来完成的。。
二、Platform模块的软件架构
内核中Platform设备软件架构如下:

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模块API
Platform提供的接口包括:Platform Device和Platform Driver两个数据结构,以及它们的操作函数。
3.1、数据结构
1. 用于抽象Platform设备的数据结构----“struct 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;
/*
* Driver name to force a match. Do not set directly, because core
* frees it. Use driver_set_override() to set or clear it.
*/
const char *driver_override;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
dev,真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现)。
name,设备的名称,和struct device结构中的init_name。实际上,该名称在设备注册时,会拷贝到dev.init_name中。
id,用于标识该设备的ID。内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driver的probe接口。
因此,在driver的probe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个。
id_auto,指示在注册设备时,是否自动赋予ID值。
num_resources、resource,该设备的资源描述,由struct resource(include/linux/ioport.h)结构抽象。
在Linux中,系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。
当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。
id_entry,和内核模块相关的内容,暂不说明。
mfd_cell,和MFD设备相关的内容,暂不说明。
archdata,一个奇葩的存在!!它的目的是为了保存一些architecture相关的数据。
2. 用于抽象Platform设备驱动的数据结构----“struct platform_driver”: include/linux/platform_device.h
struct platform_driver {
int (*probe)(struct platform_device *);
/*
* Traditionally the remove callback returned an int which however is
* ignored by the driver core. This led to wrong expectations by driver
* authors who thought returning an error code was a valid error
* handling strategy. To convert to a callback returning void, new
* drivers should implement .remove_new() until the conversion it done
* that eventually makes .remove() return void.
*/
int (*remove)(struct platform_device *);
void (*remove_new)(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;
/*
* For most device drivers, no need to care about this flag as long as
* all DMAs are handled through the kernel DMA API. For some special
* ones, for example VFIO drivers, they know how to manage the DMA
* themselves and set this flag so that the IOMMU layer will allow them
* to setup and manage their own I/O address space.
*/
bool driver_managed_dma;
};
struct platform_driver结构和struct device_driver非常类似,无非就是提供probe、remove、suspend、resume等回调函数,这里不再细说。
另外这里有一个id_table的指针,该指针和of_match_table、acpi_match_table的功能类似:提供其它方式的设备probe。
内核会在合适的时机检查device和device_driver的名字,如果匹配,则执行probe。其实除了名称之外,还有一些宽泛的匹配方式,例如这里提到的各种match table。
3.2、Platform Device提供的API
Platform Device主要提供设备的分配、注册等接口,供其它driver使用,具体包括:include/linux/platform_device.h
extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);
extern struct bus_type platform_bus_type;
extern struct device platform_bus;
extern struct resource *platform_get_resource(struct platform_device *,
unsigned int, unsigned int);
extern struct resource *platform_get_mem_or_io(struct platform_device *,
unsigned int);
extern struct device *
platform_find_device_by_driver(struct device *start,
const struct device_driver *drv);
extern struct platform_device *platform_device_alloc(const char *name, int id);
extern int platform_device_add_resources(struct platform_device *pdev,
const struct resource *res,
unsigned int num);
extern int platform_device_add_data(struct platform_device *pdev,
const void *data, size_t size);
extern int platform_device_add(struct platform_device *pdev);
extern void platform_device_del(struct platform_device *pdev);
extern void platform_device_put(struct platform_device *pdev);
platform_device_register、platform_device_unregister,Platform设备的注册/注销接口,和底层的device_register等接口类似。
arch_setup_pdev_archdata,设置platform_device变量中的archdata指针。
platform_get_resource、platform_get_irq、platform_get_resource_byname、platform_get_irq_byname,通过这些接口,可以获取platform_device变量中的resource信息,以及直接获取IRQ的number等等。
platform_device_register_full、platform_device_register_resndata、platform_device_register_simple、platform_device_register_data,其它形式的设备注册。调用者只需要提供一些必要的信息,如name、ID、resource等,Platform模块就会自动分配一个struct platform_device变量,填充内容后,注册到内核中。
platform_device_alloc,以name和id为参数,动态分配一个struct platform_device变量。
platform_device_add_resources,向platform device中增加资源描述。
platform_device_add_data,向platform device中添加自定义的数据(保存在pdev->dev.platform_data指针中)。
platform_device_add、platform_device_del、platform_device_put,其它操作接口。
3.3、Platform Driver提供的API
Platform Driver提供struct platform_driver的分配、注册等功能,具体如下:
/*
* use a macro to avoid include chaining to get THIS_MODULE
*/
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);
extern void platform_driver_unregister(struct platform_driver *);
/* non-hotpluggable platform devices may use this so that probe() and
* its support may live in __init sections, conserving runtime memory.
*/
#define platform_driver_probe(drv, probe) \
__platform_driver_probe(drv, probe, THIS_MODULE)
extern int __platform_driver_probe(struct platform_driver *driver,
int (*probe)(struct platform_device *), struct module *module);
static inline void *platform_get_drvdata(const struct platform_device *pdev)
{
return dev_get_drvdata(&pdev->dev);
}
static inline void platform_set_drvdata(struct platform_device *pdev,
void *data)
{
dev_set_drvdata(&pdev->dev, data);
}
platform_driver_registe、platform_driver_unregister,platform driver的注册、注销接口。
platform_driver_probe,主动执行probe动作。
platform_set_drvdata、platform_get_drvdata,设置或者获取driver保存在device变量中的私有数据。
3.4、platform_create_bundle提供的API
又是注册platform device,又是注册platform driver,看着挺啰嗦的。不过内核想到了这点,所以提供一个懒人API,可以同时注册platform driver,并分配一个platform device:include/linux/platform_device.h
#define platform_create_bundle(driver, probe, res, n_res, data, size) \
__platform_create_bundle(driver, probe, res, n_res, data, size, THIS_MODULE)
extern struct platform_device *__platform_create_bundle(
struct platform_driver *driver, int (*probe)(struct platform_device *),
struct resource *res, unsigned int n_res,
const void *data, size_t size, struct module *module);
只要提供一个platform_driver(要把driver的probe接口显式的传入),并告知该设备占用的资源信息,platform模块就会帮忙分配资源,并执行probe操作。对于那些不需要热拔插的设备来说,这种方式是最省事的了。
四、 Platform模块的内部动作解析
4.1、 Platform模块的初始化
Platform模块的初始化是由drivers/base/platform.c中platform_bus_init接口完成的,该接口的实现和动作如下: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);
return error;
}
4.2、 platform device和platform driver的注册
platform device和platform driver的注册,由platform_device_add和platform_driver_register两个接口实际实现。其内部动作分别如下。
platform_device_add的内部动作:
如果该设备没有指定父设备,将其父设备设置为platform_bus,即“/sys/devices/platform/”所代表的设备,这时该设备的sysfs目录即为“/sys/devices/platform/xxx_device”。
将该设备的bus指定为platform_bus_type(pdev->dev.bus = &platform_bus_type)。
根据设备的ID,修改或者设置设备的名称。对于多个同名的设备,可以使用ID区分,在这里将实际名称修改为“name.id”的形式。
调用resource模块的insert_resource接口,将该设备需要使用的resource统一管理起来(我们知道,在这之前,只是声明了本设备需要使用哪些resource,但resource模块并不知情,也就无从管理,因此需要告知)。
调用device_add接口,将内嵌的struct device变量添加到内核中。
platform_driver_register的内部动作:
将该driver的bus指定为platform_bus_type(drv->driver.bus = &platform_bus_type)。
如果该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自身的回调函数中。
调用driver_register接口,将内嵌的struct driver变量添加到内核中。
4.3、 platform设备的probe
之前的文章中讲过设备的probe,都发生在向指定的bus添加device或者device_driver时,由bus模块的bus_probe_device,或者device_driver模块driver_attach接口触发。
五、示例:如何创建和注册一个平台设备
以下是一个简单的例子,演示如何在 Linux 内核中创建和注册一个平台设备,并配合相应的驱动来实现设备的探测(probe)和移除(remove)操作。
5.1、平台设备的定义和注册
平台设备通常使用 platform_device 结构体来描述,以下代码展示了如何创建和注册一个平台设备。
平台设备代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#define DEVICE_NAME "my_platform_device"
// 设备的资源定义
static struct resource my_device_resources[] = {
{
.start = 0x10000000, // 假设的 I/O 地址
.end = 0x10001000, // 假设的 I/O 地址范围
.flags = IORESOURCE_MEM,
},
{
.start = 5, // 假设的中断号
.end = 5,
.flags = IORESOURCE_IRQ,
},
};
// 平台设备定义
static struct platform_device my_device = {
.name = DEVICE_NAME, // 设备名称
.id = -1, // 设备 ID
.num_resources = ARRAY_SIZE(my_device_resources),
.resource = my_device_resources, // 设备资源
};
// 模块加载时注册平台设备
static int __init my_device_init(void)
{
int ret;
pr_info("Registering platform device: %s\n", DEVICE_NAME);
ret = platform_device_register(&my_device);
if (ret) {
pr_err("Failed to register platform device\n");
return ret;
}
return 0;
}
// 模块卸载时注销平台设备
static void __exit my_device_exit(void)
{
pr_info("Unregistering platform device: %s\n", DEVICE_NAME);
platform_device_unregister(&my_device);
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example of Platform Device");
代码解释:
- 设备资源:
my_device_resources定义了设备的资源,包括内存和中断资源。在本例中,内存资源假设在0x10000000到0x10001000之间,IRQ 号为 5。 - 平台设备结构:
my_device是一个platform_device类型的结构体,其中包含了设备的名称、ID、资源和其他信息。platform_device_register函数用于将此平台设备注册到内核。 - 模块加载与卸载:
my_device_init函数用于加载模块时注册设备,而my_device_exit则在模块卸载时注销设备。
5.2、编写平台设备的驱动程序
要使设备能够正常工作,还需要编写一个驱动程序,通过 probe 和 remove 函数与设备进行交互。以下是一个简单的驱动程序,配合前面的平台设备示例。
平台设备驱动代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#define DEVICE_NAME "my_platform_device"
// 驱动的 probe 函数,用于设备初始化
static int my_device_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *io_base;
pr_info("Probing device: %s\n", DEVICE_NAME);
// 获取设备资源 (内存映射)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
pr_err("Failed to get memory resource\n");
return -ENODEV;
}
// 映射 I/O 内存区域
io_base = ioremap(res->start, resource_size(res));
if (!io_base) {
pr_err("Failed to remap memory\n");
return -ENOMEM;
}
pr_info("I/O base address: %p\n", io_base);
// 在此可以对硬件进行初始化
return 0;
}
// 驱动的 remove 函数,用于设备移除时清理资源
static int my_device_remove(struct platform_device *pdev)
{
pr_info("Removing device: %s\n", DEVICE_NAME);
// 清理资源,比如解除 I/O 内存映射
// 这里假设我们已经映射了 I/O 内存区域
return 0;
}
// 平台驱动结构体
static struct platform_driver my_device_driver = {
.probe = my_device_probe,
.remove = my_device_remove,
.driver = {
.name = DEVICE_NAME, // 设备名称
.owner = THIS_MODULE,
},
};
// 模块加载时注册平台驱动
static int __init my_driver_init(void)
{
pr_info("Registering platform driver\n");
return platform_driver_register(&my_device_driver);
}
// 模块卸载时注销平台驱动
static void __exit my_driver_exit(void)
{
pr_info("Unregistering platform driver\n");
platform_driver_unregister(&my_device_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example of Platform Driver");
代码解释:
my_device_probe:在probe函数中,首先通过platform_get_resource获取设备的内存资源,然后通过ioremap映射到内核地址空间。此处还可以进行更多设备的初始化操作。my_device_remove:在remove函数中,通常需要释放在probe中分配的资源,清理内存映射等。platform_driver结构:my_device_driver是一个platform_driver结构体,用于注册和注销平台驱动。在设备与驱动匹配时,内核会调用probe函数;当设备被移除时,会调用remove函数。
5.3、设备与驱动的匹配
在本例中,平台设备通过 platform_device_register 注册,而平台驱动通过 platform_driver_register 注册,驱动和设备之间的匹配由设备的名称(DEVICE_NAME)决定。内核会根据设备名称自动调用驱动的 probe 函数。
以上是一个简单的 Linux 平台设备和驱动示例。通过使用 platform_device 来定义硬件设备,和 platform_driver 来编写相应的驱动程序,我们能够在内核中实现设备的注册、初始化、资源管理和移除等操作。平台设备通常用于嵌入式开发中,能够与具体的硬件资源进行高效的交互。
521

被折叠的 条评论
为什么被折叠?



