1、前言
Platform总线是Linux内核中用于管理嵌入式系统中的设备的一种总线类型。它允许设备驱动程序通过一组标准的接口与嵌入式系统中的硬件设备进行通信。
Platform总线维护了一个驱动链表和一个设备链表,当有新的设备添加后会通过自身的match函数遍历驱动链表查看是否有驱动与设备匹配,如果匹配成功则执行驱动的probe函数;同样,当有新的驱动载入时,也会通过自身的match函数去遍历设备链表查看是否有设备匹配,如果匹配成功则执行驱动的probe函数。
2、代码框架
2.1 Platform数据结构
platform数据结构在drivers/base/platform.c中声明,其名称为"platform",match函数为platform_match:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
platform是struct bus_type数据类型,在声明时并未将全部成员赋值,struct bus_type定义在include/linux/device.h中,各个成员变量如下:
struct bus_type {
const char *name; // 名称,表示设备总线的名称
const char *dev_name; // 用于子系统枚举设备
struct device *dev_root; // 用作父设备的默认设备
// 设备总线上设备的默认属性,使用dev_groups代替
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups; // 总线的默认属性
const struct attribute_group **dev_groups; // 设备总线上设备的默认属性
const struct attribute_group **drv_groups; // 设备驱动程序在总线上的默认属性
/* 每当为总线添加新设备或驱动程序时调用,应该返回正值,如果给定设备可以由给定驱动程序处理,否则返回零。
如果确定驱动程序支持设备不可能,也可以返回错误代码。如果返回 -EPROBE_DEFER,则会将设备排队进行延迟探测*/
int (*match)(struct device *dev, struct device_driver *drv);
// 当添加设备、移除设备或生成其他一些生成uevents的操作时调用,用于添加环境变量
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
// 当向总线添加新设备或驱动程序时调用,回调特定驱动程序的探测函数以初始化匹配的设备
int (*probe)(struct device *dev);
int (*remove)(struct device *dev); // 从总线中移除设备时调用
void (*shutdown)(struct device *dev); // 在关机时调用以使设备安静
// 调用以将设备重新上线(在将其脱机后)
int (*online)(struct device *dev);
// 用于将设备脱机以进行热插拔。可能会失败
int (*offline)(struct device *dev);
// 当总线上的设备希望进入睡眠模式时调用
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev); // 用于唤醒总线上的设备
// 此总线的电源管理操作,回调特定设备驱动程序的电源管理操作
const struct dev_pm_ops *pm;
// 用于将IOMMU驱动程序实现附加到总线并允许驱动程序执行特定于总线的设置的IOMMU特定操作
const struct iommu_ops *iommu_ops;
struct subsys_private *p; // 驱动程序核心的私有数据,只有驱动程序核心可以访问此数据
struct lock_class_key lock_key; // 用于锁验证器的锁类密钥
};
2.12 驱动数据结构
platform_driver 结构体定义于文件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的指针
bool prevent_deferred_probe; // 用于防止延迟探测的布尔值
};
其中成员变量struct device_driver driver,其结构定义于include/linux/device.h中,在 Linux 内核中,每个设备驱动程序都需要包含这个结构体作为其一部分。它包含了驱动程序的名称、所属总线类型、拥有者模块、匹配表、探测、移除、关闭、挂起、恢复等操作的函数指针,以及其他与设备驱动程序相关的信息和操作。这个结构体的作用是使设备驱动程序成为内核驱动模型的一部分,并允许内核对其进行管理和调度。
一个最简单的驱动结构体需要指定probe和struct device_driver driver的name。
struct device_driver {
const char *name; // 设备驱动程序的名称
struct bus_type *bus; // 设备所属的总线类型
struct module *owner; // 拥有该驱动程序的模块
const char *mod_name; // 用于内置模块的名称
bool suppress_bind_attrs; // 禁用通过sysfs进行绑定/解绑
enum probe_type probe_type; // 用于指定探测类型(同步或异步)
const struct of_device_id *of_match_table; // 用于匹配设备的Open Firmware表
const struct acpi_device_id *acpi_match_table; // 用于匹配设备的ACPI表
int (*probe)(struct device *dev); // 用于查询特定设备的存在性,检查该驱动程序是否能够与其一起工作,并将驱动程序绑定到特定设备
int (*remove)(struct device *dev); // 用于在系统中移除设备时解绑设备与该驱动程序的关联
void (*shutdown)(struct device *dev); // 用于在系统关闭时使设备安静
int (*suspend)(struct device *dev, pm_message_t state); // 用于将设备置于睡眠模式,通常是进入低功耗状态
int (*resume)(struct device *dev); // 用于唤醒设备
const struct attribute_group **groups; // 驱动程序核心自动创建的默认属性
const struct dev_pm_ops *pm; // 与匹配此驱动程序的设备相关的设备的电源管理操作
struct driver_private *p; // 驱动程序核心的私有数据,只有驱动程序核心可以访问此数据
};
2.13 设备数据结构
platform_device 结构体定义在文件include/linux/platform_device.h中:
struct platform_device {
const char *name; // 平台设备的名称
int id; // 平台设备的 ID
bool id_auto; // 指示 ID 是否自动分配
struct device dev; // 与平台设备相关联的设备结构
u32 num_resources; // 平台设备资源的数量
struct resource *resource; // 指向平台设备资源数组的指针
const struct platform_device_id *id_entry; // 指向平台设备 ID 表的指针
char *driver_override; // 用于强制匹配特定驱动程序的名称
/* MFD cell pointer */
struct mfd_cell *mfd_cell; // 指向多功能设备单元的指针
/* arch specific additions */
struct pdev_archdata archdata; // 特定于体系结构的附加信息
};
其中struct device dev成员变量包含 了struct device_driver *driver变量,在此不再展开。一个最简单的设备数据结构需要设置好name。
3、程序测试
编写一个最简单的驱动程序和设备程序进行测试,驱动程序代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
static int mydriver_probe(struct platform_device *pdev)
{
printk("mydriver_probe exe\n");
return 0;
}
static struct platform_driver my_driver =
{
.probe = mydriver_probe,
.driver.name = "platformtest",
};
static int mydriver_init(void)
{
printk("mydriver_init \n");
return platform_driver_register(&my_driver);
}
static void mydriver_exit(void)
{
printk("mydriver_exit \n");
platform_driver_unregister(&my_driver);
return;
}
MODULE_LICENSE("GPL");
module_init(mydriver_init);
module_exit(mydriver_exit);
设备程序代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
static void my_device_release(struct device *dev)
{
return;
}
static struct platform_device my_device =
{
.name = "platformtest",
.dev.release = my_device_release,
};
static int mydevice_init(void)
{
printk("mydevice_init \n");
return platform_device_register(&my_device);
}
static void mydevice_exit(void)
{
printk("mydevice_exit \n");
platform_device_unregister(&my_device);
return;
}
MODULE_LICENSE("GPL");
module_init(mydevice_init);
module_exit(mydevice_exit);
将编译好的两个驱动程序在开发板上进行加载测试:
4、过程分析
4.1 设备分析
在设备程序中,我们声明了static struct platform_device my_device,并将name成员变量赋值为platformtest,然后调用platform_device_register进行注册。
static struct platform_device my_device =
{
.name = "platformtest",
.dev.release = my_device_release,
};
在设备加载过程中,内核会遍历整个驱动链表进行匹配,如果匹配成功则会执行驱动对应的probe函数,当驱动先加载,然后加载设备的话会执行以下完整的流程,如果先加载设备的话流程会执行到bus_for_each_drv()后匹配不到:
4.2 驱动分析
在驱动程序中,我们声明了static struct platform_driver my_driver ,并将name成员变量赋值为platformtest,然后调用platform_driver_register进行注册。
static struct platform_driver my_driver =
{
.probe = mydriver_probe,
.driver.name = "platformtest",
};
在驱动加载过程中,内核会遍历整个设备链表进行匹配,如果匹配成功则会执行驱动对应的probe函数,当设备先加载,然后加载设备的话会执行以下完整的流程,如果先加载驱动的话流程会执行到bus_for_each_drv()后匹配不到:
4.3 加载测试
在4.1和4.2的图示中,我们在每一步添加了对应的打印信息会在dmsg中打印出来,上面图示中添加的部分printk忘记添加回车和打印数值等,图片就不修改了,大致是一样的。测试先加载设备后加载驱动,执行insmod mydevice.ko后:
执行insmod mydriver.ko后:
卸载两个驱动:
测试先加载驱动后加载设备,先执行insmod mydriver.ko:
执行insmod mydevice.ko:
5、总结
由上述测试可以发现无论先加载设备还是先加载驱动,只要两者可以通过总线的match函数匹配成功就可以执行驱动程序的probe函数,在probe函数中我们可以执行传统驱动程序的相关操作,本文仅介绍一个最简单的platform驱动程序,旨在缕清框架,后续文章再讲解其他内容。