linux下的基于platform驱动的框架
在 Linux 驱动开发中,Platform 驱动框架是针对 “无总线设备”(如 SOC 内部集成的定时器、UART、GPIO 控制器等)设计的核心框架,其核心思想是 “设备与驱动分离”,通过内核提供的总线(platform_bus_type)实现设备与驱动的自动匹配,大幅提升驱动的可复用性和系统的可扩展性。以下从框架核心概念、工作流程、关键数据结构、实现步骤及进阶特性展开详述。
一、Platform 框架的核心定位与优势
在理解 Platform 框架前,需先明确其与 “传统总线驱动”(如 PCI、USB)的区别:
- 传统总线设备:设备通过物理总线(如 PCI 插槽)连接,总线本身提供设备枚举能力(如 PCI 总线可自动扫描总线上的设备)。
- Platform 设备:多为 SOC 内部外设(如 ARM 芯片的 I2C 控制器、SPI 控制器),无物理总线枚举机制,需手动向内核 “注册” 设备信息。
Platform 框架的核心优势:
- 解耦设备与驱动:设备信息(如寄存器地址、中断号)与驱动逻辑分离,同一驱动可适配不同硬件配置的同类型设备。
- 统一匹配机制:通过内核提供的
platform_bus作为 “中间总线”,标准化设备与驱动的匹配流程。 - 简化驱动开发:内核封装了总线管理、匹配、资源分配等通用逻辑,开发者只需聚焦设备的具体操作(如寄存器读写、中断处理)。
二、Platform 框架的核心组成
Platform 框架由三部分构成:Platform 设备(Device)、Platform 驱动(Driver)、Platform 总线(Bus),三者的关系如下:
- Platform 总线:作为 “中介”,维护设备链表和驱动链表,负责触发设备与驱动的匹配。
- Platform 设备:描述硬件信息(如寄存器基地址、中断号、设备名称),需注册到总线的设备链表。
- Platform 驱动:包含驱动逻辑(如 probe/remove 函数、中断处理、设备操作接口),注册到总线的驱动链表后,由总线触发匹配。
1. 核心数据结构
Linux 内核通过以下关键结构体定义 Platform 框架的三大组件,均定义在 <linux/platform_device.h> 中:
(1)Platform 设备:struct platform_device
描述硬件设备的核心信息,重点是 “资源(resource)”(如地址、中断、时钟等):
struct platform_device {
const char *name; // 设备名称(用于与驱动匹配)
int id; // 设备编号(同一类型多个设备时区分,如 i2c-0、i2c-1)
bool id_auto; // 自动分配设备编号
struct device dev; // 内嵌的通用设备结构体(所有设备的基类)
u32 num_resources; // 资源数量
struct resource *resource; // 资源数组(存储设备的硬件资源)
const struct platform_device_id *id_entry; // 设备 ID 表(用于多设备匹配)
/* 其他私有成员... */
};
其中,struct resource 是描述硬件资源的关键结构体,定义在 <linux/ioport.h> 中,支持多种资源类型:
struct resource {
resource_size_t start; // 资源起始地址(如寄存器基地址、中断号)
resource_size_t end; // 资源结束地址(如寄存器末地址)
const char *name; // 资源名称
unsigned long flags; // 资源类型(核心字段)
struct resource *parent, *sibling, *child; // 资源链表
};
常见 flags 类型:
IORESOURCE_MEM:内存映射资源(如 SOC 外设的寄存器地址空间)。IORESOURCE_IRQ:中断资源(如设备的中断号)。IORESOURCE_DMA:DMA 通道资源。IORESOURCE_IO:IO 端口资源(x86 架构常用,ARM 架构少用)。
(2)Platform 驱动: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; // 驱动支持的设备 ID 表(多设备匹配关键)
bool prevent_deferred_probe; // 禁止延迟探测
};
probe函数:驱动的 “入口”,负责初始化硬件(如申请 IO 内存、注册中断、初始化外设)。remove函数:驱动的 “出口”,负责释放资源(如释放 IO 内存、注销中断、关闭外设)。id_table:驱动支持的设备列表(可匹配多个同类型设备),优先级高于name匹配。
(3)Platform 总线:struct bus_type
内核定义的全局总线结构体,名为 platform_bus_type,定义在 drivers/base/platform.c 中,核心是 “匹配函数” 和 “总线属性”:
struct bus_type platform_bus_type = {
.name = "platform", // 总线名称(ls /sys/bus/ 可查看)
.match = platform_match, // 设备与驱动的匹配函数(核心)
.uevent = platform_uevent, // 生成 uevent 事件(用于用户空间热插拔)
.pm = &platform_dev_pm_ops, // 电源管理操作
};
EXPORT_SYMBOL_GPL(platform_bus_type);
其中,platform_match 函数是设备与驱动匹配的核心逻辑,匹配顺序如下(优先级从高到低):
- 若驱动和设备均提供
of_match_table(设备树匹配表),则通过设备树的compatible属性匹配。 - 若驱动提供
id_table且设备提供id_entry,则通过id_table中的设备名称匹配。 - 直接比较设备的
name与驱动的driver.name,名称相同则匹配。
三、Platform 驱动的完整工作流程
Platform 框架的工作流程可分为 “设备注册”“驱动注册”“匹配与 probe”“资源释放” 四个阶段,具体如下:
阶段 1:Platform 设备注册
设备需向内核注册硬件信息(如资源、名称),注册方式分为静态注册(内核编译时)和动态注册(运行时),现代 Linux 更推荐基于设备树(Device Tree) 自动生成设备(无需手动注册)。
(1)传统手动注册(静态 / 动态)
-
静态注册:通过
PLATFORM_DEVICE_DECLARE宏在编译时定义设备,适用于内核内置设备:// 定义资源(如 UART 寄存器地址 0x12340000-0x12340fff,中断号 10) static struct resource myuart_resources[] = { [0] = { .start = 0x12340000, .end = 0x12340fff, .flags = IORESOURCE_MEM, .name = "myuart_regs", }, [1] = { .start = 10, .end = 10, .flags = IORESOURCE_IRQ, .name = "myuart_irq", }, }; // 静态声明 Platform 设备 PLATFORM_DEVICE_DECLARE(myuart, "myuart", myuart_resources, ARRAY_SIZE(myuart_resources)); -
动态注册:运行时通过
platform_device_register()函数注册设备,适用于可热插拔设备(少见):struct platform_device *pdev; pdev = platform_device_alloc("myuart", 0); // 分配设备(name=myuart,id=0) if (!pdev) return -ENOMEM; // 设置设备资源 platform_device_add_resources(pdev, myuart_resources, ARRAY_SIZE(myuart_resources)); // 注册设备到 platform 总线 int ret = platform_device_add(pdev); if (ret) platform_device_put(pdev); // 注册失败则释放设备
(2)基于设备树的自动注册(主流方式)
现代 ARM/Linux 系统中,设备信息通过设备树(.dts) 描述,内核启动时会自动解析设备树中的 compatible 属性,生成对应的 platform_device 并注册到总线。
示例:设备树中定义一个 UART 设备:
dts
/* 设备树节点:描述 myuart 设备 */
myuart: uart@12340000 {
compatible = "vendor,myuart"; // 兼容属性(用于与驱动的 of_match_table 匹配)
reg = <0x12340000 0x1000>; // 内存资源(基地址 0x12340000,长度 0x1000)
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>; // 中断资源(中断号 10,高电平触发)
clocks = <&clk 24>; // 时钟资源(可选)
};
内核启动时,会解析该节点:-->解析得到的数据,放到设备结构体platform_device
- 根据
reg生成IORESOURCE_MEM类型的资源。 - 根据
interrupts生成IORESOURCE_IRQ类型的资源。 - 生成
platform_device,其dev.of_node指向该设备树节点,name由内核自动分配。
阶段 2:Platform 驱动注册
驱动通过 platform_driver_register() 函数注册到 platform_bus 的驱动链表,注册时内核会触发总线的 match 函数,检查是否有已注册的设备与之匹配。
示例:注册一个支持上述 UART 设备的驱动:
#include <linux/platform_device.h>
#include <linux/of.h> // 设备树相关头文件
// 设备树匹配表(compatible 属性匹配)
static const struct of_device_id myuart_of_match[] = {
{ .compatible = "vendor,myuart" }, // 与设备树节点的 compatible 一致
{ /* Sentinel */ } // 表结束标记
};
MODULE_DEVICE_TABLE(of, myuart_of_match); // 告知内核该匹配表(用于模块加载时自动匹配)
// probe 函数:设备与驱动匹配成功后调用
static int myuart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *mem_res, *irq_res;
void __iomem *base; // 映射后的虚拟地址
printk(KERN_INFO "myuart probe: device matched!\n");
// 1. 获取设备资源(从 platform_device 的 resource 数组中)
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取第 0 个内存资源
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); // 获取第 0 个中断资源
if (!mem_res || !irq_res) {
dev_err(dev, "failed to get resources\n");
return -ENODEV;
}
// 2. 申请并映射 IO 内存(将物理地址映射为内核虚拟地址,避免直接操作物理地址)
base = devm_ioremap_resource(dev, mem_res); // devm_ 前缀:资源自动随设备释放
if (IS_ERR(base)) return PTR_ERR(base);
platform_set_drvdata(pdev, base); // 将映射后的地址存入设备私有数据,供后续使用
// 3. 注册中断(示例:简化版,实际需实现中断处理函数)
if (devm_request_irq(dev, irq_res->start, myuart_irq_handler, 0, "myuart_irq", pdev)) {
dev_err(dev, "failed to request irq\n");
return -EBUSY;
}
// 4. 初始化外设(如设置波特率、数据位、停止位等,通过读写映射后的虚拟地址实现)
writel(0x1234, base + 0x04); // 向寄存器偏移 0x04 写入 0x1234(示例)
return 0;
}
// remove 函数:设备移除或驱动卸载时调用
static int myuart_remove(struct platform_device *pdev)
{
void __iomem *base = platform_get_drvdata(pdev); // 获取私有数据(映射地址)
printk(KERN_INFO "myuart remove: releasing resources\n");
// 此处无需手动释放 devm_ 申请的资源(内核自动释放)
// 若使用非 devm_ 函数(如 ioremap、request_irq),需手动释放(如 iounmap、free_irq)
return 0;
}
// 定义 Platform 驱动结构体
static struct platform_driver myuart_driver = {
.probe = myuart_probe,
.remove = myuart_remove,
.driver = {
.name = "myuart_driver", // 驱动名称(备用匹配方式)
.of_match_table = myuart_of_match, // 设备树匹配表(优先匹配)
.owner = THIS_MODULE,
},
// .id_table = ... // 若支持多设备,可在此定义 id 表
};
// 驱动模块加载函数:注册驱动
static int __init myuart_driver_init(void)
{
return platform_driver_register(&myuart_driver);
}
// 驱动模块卸载函数:注销驱动
static void __exit myuart_driver_exit(void)
{
platform_driver_unregister(&myuart_driver);
}
module_init(myuart_driver_init);
module_exit(myuart_driver_exit);
MODULE_LICENSE("GPL"); // 必须声明 GPL 协议,否则内核拒绝加载
MODULE_DESCRIPTION("My UART Platform Driver");
MODULE_AUTHOR("Your Name");
阶段 3:设备与驱动的匹配与 Probe
当驱动注册到 platform_bus 后,内核会执行以下步骤:
- 遍历
platform_bus上的所有已注册设备(platform_device)。- 对每个设备,调用
platform_match函数,检查驱动是否支持该设备(按前文匹配顺序)。- 若匹配成功,内核调用驱动的
probe函数,完成设备初始化。probe函数执行成功后,设备进入 “就绪” 状态,可通过驱动提供的接口(如字符设备节点)与用户空间交互。
阶段 4:资源释放(Remove/Shutdown)
当发生以下情况时,内核会触发资源释放:
- 驱动卸载:调用
rmmod卸载驱动模块时,内核遍历该驱动匹配的所有设备,调用remove函数。 - 设备移除:若设备支持热插拔,移除设备时,内核调用对应驱动的
remove函数。 - 系统关机:调用驱动的
shutdown函数,关闭外设硬件。
remove 函数的核心任务是释放 probe 函数中申请的资源(如 IO 内存、中断、字符设备节点等)。若使用 devm_ 前缀的函数(如 devm_ioremap_resource、devm_request_irq),内核会自动释放资源,无需手动处理;若使用非 devm_ 函数,则需手动释放。
四、关键辅助函数
在 Platform 驱动开发中,内核提供了大量辅助函数,简化资源操作和设备管理,常用函数如下:
| 函数分类 | 函数原型 | 功能描述 |
|---|---|---|
| 资源获取 | struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num) | 获取设备的指定类型、指定序号的资源(如内存、中断) |
| 资源获取(设备树) | struct resource *platform_get_resource_byname(struct platform_device *pdev, unsigned int type, const char *name) | 按资源名称获取资源(适用于多资源场景) |
| IO 内存映射 | void __iomem *devm_ioremap_resource(struct device *dev, const struct resource *res) | 申请并映射 IO 内存(devm_ 自动释放) |
| 中断申请 | int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id) | 申请中断(devm_ 自动释放) |
| 私有数据管理 | void platform_set_drvdata(struct platform_device *pdev, void *data) | 向设备存入私有数据(如映射后的地址) |
| 私有数据管理 | void *platform_get_drvdata(struct platform_device *pdev) | 从设备获取私有数据 |
| 设备树匹配 | const struct of_device_id *of_match_device(const struct of_device_id *matches, const struct device *dev) | 手动检查设备是否与设备树匹配表匹配 |
五、Platform 框架的进阶特性
1. 多设备匹配(id_table)
若驱动需支持多个同类型但名称不同的设备,可通过 id_table 实现。例如,一个 UART 驱动支持 “vendor,myuart-v1” 和 “vendor,myuart-v2” 两个设备:
// 定义设备 ID 表
static const struct platform_device_id myuart_id_table[] = {
{ "myuart-v1", 0 }, // 设备名称 1,私有数据 0
{ "myuart-v2", 1 }, // 设备名称 2,私有数据 1
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(platform, myuart_id_table); // 告知内核该 ID 表
// 驱动结构体中指定 id_table
static struct platform_driver myuart_driver = {
.probe = myuart_probe,
.remove = myuart_remove,
.id_table = myuart_id_table, // 多设备匹配表
.driver = {
.name = "myuart-drv",
.of_match_table = myuart_of_match,
},
};
匹配时,内核会优先使用 id_table 中的名称与设备的 name 匹配,匹配成功后,pdev->id_entry 会指向匹配的 id_table 项,probe 函数可通过 pdev->id_entry->driver_data 获取私有数据,区分不同设备版本。
2. 电源管理
Platform 框架内置电源管理支持,通过 struct platform_driver 中的 suspend 和 resume 函数实现设备的休眠与唤醒:
// 设备休眠时调用:保存设备状态(如寄存器值),关闭硬件
static int myuart_suspend(struct platform_device *pdev, pm_message_t state)
{
void __iomem *base = platform_get_drvdata(pdev);
u32 reg_val = readl(base + 0x04); // 保存寄存器值
platform_set_drvdata(pdev, (void *)reg_val); // 暂存状态
writel(0x0000, base + 0x04); // 关闭设备
return 0;
}
// 设备唤醒时调用:恢复设备状态
static int myuart_resume(struct platform_device *pdev)
{
void __iomem *base = platform_get_drvdata(pdev);
u32 reg_val = (u32)base; // 恢复之前保存的寄存器值
writel(reg_val, base + 0x04); // 恢复设备状态
return 0;
}
// 驱动结构体中注册电源管理函数
static struct platform_driver myuart_driver = {
.suspend = myuart_suspend,
.resume = myuart_resume,
/* 其他成员... */
};
3. 延迟探测(Deferred Probe)
若设备依赖的资源(如时钟、电源)尚未准备就绪(如驱动加载时,时钟控制器驱动尚未加载),probe 函数可返回 -EPROBE_DEFER,告知内核 “暂时无法初始化设备,稍后重试”。内核会将设备加入延迟探测队列,待依赖资源就绪后,重新触发 probe 函数。
示例:
static int myuart_probe(struct platform_device *pdev)
{
struct clk *clk;
// 获取时钟资源(若时钟未准备好,clk_get 会失败)
clk = devm_clk_get(&pdev->dev, "uart_clk");
if (IS_ERR(clk)) {
if (PTR_ERR(clk) == -EPROBE_DEFER)
return -EPROBE_DEFER; // 延迟探测
return PTR_ERR(clk);
}
/* 其他初始化逻辑... */
return 0;
}
六、总结
Platform 驱动框架是 Linux 内核中针对 “无总线设备” 的标准化解决方案,其核心是 “设备与驱动分离”,通过 platform_bus 实现统一匹配。开发者在实际开发中,需重点关注:
- 设备信息描述:通过设备树或手动注册,提供准确的硬件资源(地址、中断、时钟)。
- 驱动逻辑实现:聚焦
probe/remove函数,完成资源申请、硬件初始化与释放。 - 匹配机制选择:优先使用设备树
compatible属性匹配,其次使用id_table或name匹配。
掌握 Platform 框架,是理解 Linux 其他驱动框架(如 AMBA、PCIe)的基础,也是嵌入式 Linux 驱动开发的核心技能之一。
更多推荐

所有评论(0)