Linux 驱动的 platform 架构主要用于嵌入式设备中,它是 Linux 内核设备模型的一部分,旨在简化设备驱动的开发。Platform 设备和 platform 驱动架构提供了一种通用的方法来处理嵌入式系统中没有总线的设备。这种架构的关键在于将硬件和驱动程序分离,通过平台设备模型定义设备和驱动之间的接口。
Platform 设备和驱动的概念
-
Platform 设备 (platform_device):
- 指那些没有标准总线(如 PCI、USB 等)连接的设备。Platform 设备通常是片上系统(SoC)中的外设或其他集成到主板中的硬件。
- Platform 设备通常通过设备树(Device Tree)或内核代码静态地声明。
-
Platform 驱动 (platform_driver):
- 是专门为某个或一类 platform 设备编写的驱动程序,负责与具体的硬件交互。
- 它需要提供一组回调函数,供内核在设备初始化、挂载和卸载时调用。
Platform 架构的优势
- 硬件和驱动解耦:通过设备树描述硬件,驱动不需要在代码中硬编码设备的资源,增强了代码的可移植性和灵活性。
- 简化驱动开发:开发者只需实现设备的初始化、资源分配和释放,而平台架构处理设备和驱动的匹配。
- 内核资源管理:通过统一的资源管理机制,避免资源冲突,提高系统稳定性。
Platform 设备和驱动的注册
-
Platform 设备注册:
Platform 设备通常在设备树文件(DTS 文件)中声明。内核在启动时解析这些文件并注册设备。设备树会描述设备的各种属性,比如内存地址、IRQ 中断等。示例设备树:
my_device@10000000 {compatible = "my_vendor,my_device";reg = <0x10000000 0x1000>; // 内存映射地址和大小interrupt-parent = <&intc>;interrupts = <5>; };
也可以通过内核代码静态注册平台设备:
struct platform_device my_device = {.name = "my_device",.id = -1,.resource = my_device_resources,.num_resources = ARRAY_SIZE(my_device_resources), };platform_device_register(&my_device);
-
Platform 驱动注册:
驱动程序通过platform_driver
结构体和其注册函数与设备绑定。该结构体中定义了各种回调函数,如设备探测(probe)和移除(remove)等。驱动示例:
static int my_probe(struct platform_device *pdev) {// 硬件初始化和资源获取return 0; }static int my_remove(struct platform_device *pdev) {// 释放资源return 0; }static struct platform_driver my_driver = {.probe = my_probe,.remove = my_remove,.driver = {.name = "my_device",.owner = THIS_MODULE,}, };module_platform_driver(my_driver);
Device Tree 和 Platform 驱动的匹配:
-
compatible
字段是设备树中用于匹配设备和驱动的关键属性,驱动中的of_match_table
定义了支持的设备类型。示例:
static const struct of_device_id my_of_match[] = {{ .compatible = "my_vendor,my_device", },{}, };MODULE_DEVICE_TABLE(of, my_of_match);static struct platform_driver my_driver = {.probe = my_probe,.remove = my_remove,.driver = {.name = "my_device",.of_match_table = my_of_match,.owner = THIS_MODULE,}, };
基于 Linux Platform 架构的简单设备驱动例程
结合了设备的注册、驱动的加载和卸载等基本功能。我们将创建一个虚拟的 my_platform_device
设备,并编写一个驱动程序与其匹配,使用设备树进行配置和驱动匹配。
1. 平台设备驱动例程代码
我们将编写两个文件:一个是设备驱动代码,另一个是设备树配置(如果目标设备使用设备树)。
1.1 驱动程序(my_platform_driver.c
)
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>// 用于存储设备的虚拟地址
struct my_platform_data {void __iomem *base_addr;
};// 设备探测函数 (probe)
static int my_platform_probe(struct platform_device *pdev)
{struct resource *res;struct my_platform_data *data;printk(KERN_INFO "my_platform_driver: probe called\n");// 分配私有数据结构data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);if (!data)return -ENOMEM;// 获取设备资源 (内存地址)res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) {printk(KERN_ERR "my_platform_driver: no memory resource\n");return -ENODEV;}// 映射设备寄存器data->base_addr = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(data->base_addr))return PTR_ERR(data->base_addr);platform_set_drvdata(pdev, data);printk(KERN_INFO "my_platform_driver: device initialized successfully\n");return 0;
}// 设备移除函数 (remove)
static int my_platform_remove(struct platform_device *pdev)
{struct my_platform_data *data = platform_get_drvdata(pdev);// 释放映射的寄存器if (data && data->base_addr)devm_iounmap(&pdev->dev, data->base_addr);printk(KERN_INFO "my_platform_driver: device removed\n");return 0;
}// 设备树匹配表
static const struct of_device_id my_of_match[] = {{ .compatible = "my_vendor,my_device", },{},
};
MODULE_DEVICE_TABLE(of, my_of_match);// 平台驱动结构
static struct platform_driver my_platform_driver = {.driver = {.name = "my_platform_driver",.of_match_table = my_of_match,.owner = THIS_MODULE,},.probe = my_platform_probe,.remove = my_platform_remove,
};// 模块初始化
static int __init my_platform_init(void)
{return platform_driver_register(&my_platform_driver);
}// 模块退出
static void __exit my_platform_exit(void)
{platform_driver_unregister(&my_platform_driver);
}module_init(my_platform_init);
module_exit(my_platform_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Linux platform driver example");
1.2 设备树文件(my_device.dts
)
如果目标硬件使用设备树(例如嵌入式设备),在设备树文件中描述设备信息。
/my_device@10000000 {compatible = "my_vendor,my_device";reg = <0x10000000 0x1000>; // 内存地址和大小interrupt-parent = <&intc>;interrupts = <5>;
};
2. Makefile
为了编译驱动,我们需要一个简单的 Makefile
。
obj-m += my_platform_driver.oKDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)all:$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KDIR) M=$(PWD) clean
3. 驱动的使用步骤
Step 1:编写驱动和设备树文件
- 将上述驱动程序代码保存为
my_platform_driver.c
。 - 如果使用设备树,将设备树片段保存为
my_device.dts
并编译为设备树二进制文件(my_device.dtb
)。
Step 2:编译驱动
-
编写并保存
Makefile
文件。 -
在终端中运行以下命令来编译驱动程序:
make
这将生成一个名为
my_platform_driver.ko
的内核模块文件。
Step 3:加载设备树
如果你使用设备树来描述设备,确保设备树已经加载到目标设备中。在嵌入式系统中,通常通过引导加载程序(如 U-Boot)加载设备树文件。
你可以通过以下命令检查设备树是否加载成功:
ls /sys/firmware/devicetree/base/my_device@10000000
如果设备树已成功加载,你将看到设备在 /sys/firmware/devicetree/base/
中的相关信息。
Step 4:加载驱动模块
使用以下命令将编译好的驱动加载到内核中:
sudo insmod my_platform_driver.ko
加载驱动后,可以使用 dmesg
命令查看内核日志,确保驱动成功加载并初始化设备:
dmesg | tail
你应该看到类似如下的日志输出:
[ 1234.56789] my_platform_driver: probe called
[ 1234.56790] my_platform_driver: device initialized successfully
Step 5:验证驱动是否工作
你可以检查 /sys/bus/platform/drivers/my_platform_driver/
目录,确认驱动是否正确绑定到设备:
ls /sys/bus/platform/drivers/my_platform_driver/
该目录下应该有匹配到的设备实例。
Step 6:卸载驱动模块
如果不再需要该驱动,可以使用以下命令卸载模块:
sudo rmmod my_platform_driver
同样,你可以通过 dmesg
检查卸载的日志输出:
dmesg | tail
日志中应显示设备被成功移除的消息:
[ 5678.98765] my_platform_driver: device removed
Step 7:清理
如果你希望清理生成的文件,可以运行以下命令:
make clean
4. 总结
- 编写驱动代码:包括
probe
和remove
函数来管理设备的初始化和卸载。 - 设备树匹配:使用设备树
compatible
字段匹配驱动。 - 编译驱动:使用
Makefile
编译生成.ko
文件。 - 加载和卸载驱动:使用
insmod
加载,rmmod
卸载。 - 验证:通过
dmesg
日志和sysfs
目录检查设备的绑定和驱动状态。
通过这些步骤,你可以编写、编译、加载并测试一个基于 Linux platform 架构的设备驱动。