一、platform 基本概念整理
Linux 系统要考虑到驱动的可重用性,因此提出了驱动的分离与分层这样的软件思路。因此提出驱动、总线和设备的驱动架构,总线负责管理驱动和设备。系统中有很多的物理总线:I2c、SPI、USB等,有部分设备不存在物理总线,为了驱动架构统一,因此引入 platform 虚拟总线。
platform 虚拟总线用 platform_driver 结构体表示驱动,用 platform_device 结构体描述设备。对于所有设备都使用 bus_type 结构体类型。
1、bus_type
/**
* struct bus_type - The bus type of the device 设备的总线类型
*
* @name: The name of the bus.
* @dev_name: Used for subsystems to enumerate devices like ("foo%u", dev->id). 用于子系统枚举设备。
* @dev_root: Default device to use as the parent. 作为父设备使用的默认设备。
* @dev_attrs: Default attributes of the devices on the bus. 总线上设备的默认属性。
* @bus_groups: Default attributes of the bus. 总线的默认属性。
* @dev_groups: Default attributes of the devices on the bus. 总线上设备的默认属性。
* @drv_groups: Default attributes of the device drivers on the bus. 总线上设备驱动程序的默认属性。
* @match: Called, perhaps multiple times, whenever a new device or driver
* is added for this bus. It should return a nonzero value if the
* given device can be handled by the given driver.
* 每当为该总线添加新设备或驱动程序时,可能会多次调用。如果给定的设备可以被给定的驱动程序处理,它应该返回一个非零值。
* @uevent: Called when a device is added, removed, or a few other things
* that generate uevents to add the environment variables.
* 在添加、删除设备或生成 uevents 以添加环境变量的其他一些内容时调用。
* @probe: Called when a new device or driver add to this bus, and callback
* the specific driver's probe to initial the matched device.
* 当一个新设备或驱动添加到总线时调用,并回调特定驱动的探测来初始化匹配的设备。
* @remove: Called when a device removed from this bus. 当设备从总线上移除时调用。
* @shutdown: Called at shut-down time to quiesce the device. 在关机时调用,以停止设备。
*
* @online: Called to put the device back online (after offlining it). 调用以使设备重新联机(在它脱机之后)。
* @offline: Called to put the device offline for hot-removal. May fail. 调用将设备离线进行热移除。
*
* @suspend: Called when a device on this bus wants to go to sleep mode.
* 当总线上的设备想要进入睡眠模式时调用。
* @resume: Called to bring a device on this bus out of sleep mode.
* 调用以将此总线上的设备从睡眠模式中唤醒。
* @pm: Power management operations of this bus, callback the specific
* device driver's pm-ops. 此总线的电源管理操作,回调特定设备驱动程序的 pm-ops。
* @iommu_ops: IOMMU specific operations for this bus, used to attach IOMMU
* driver implementations to a bus and allow the driver to do
* bus-specific setup
* 此总线的 IOMMU 特定操作,用于将 IOMMU 驱动程序实现附加到总线,并允许驱动程序执行特定于总线的设置。
* @p: The private data of the driver core, only the driver core can
* touch this. 驱动核心的私有数据,只有驱动核心才能使用。
* @lock_key: Lock class key for use by the lock validator 锁验证器使用的锁类键
*
* A bus is a channel between the processor and one or more devices. For the
* purposes of the device model, all devices are connected via a bus, even if
* it is an internal, virtual, "platform" bus. Buses can plug into each other.
* A USB controller is usually a PCI device, for example. The device model
* represents the actual connections between buses and the devices they control.
* A bus is represented by the bus_type structure. It contains the name, the
* default attributes, the bus' methods, PM operations, and the driver core's
* private data.
* 总线是处理器与一个或多个设备之间的通道。
* 就设备型号而言,所有设备都通过总线连接,即使它是内部虚拟的"platform"总线。总线可以相互插入。
* 例如,USB 控制器通常是 PCI 设备。
* 设备模型表示总线与其控制的设备之间的实际连接。
* 总线由bus_type结构表示。它包含名称、默认属性、总线的方法、PM 操作和驱动程序核心的私有数据。
*/
struct bus_type {
const char *name; /* 总线名字 */
const char *dev_name;
struct device *dev_root;
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; /* 驱动属性 */
int (*match)(struct device *dev, struct device_driver *drv);
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;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
platform总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c,platform总线定义如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
platform_bus_type 就是 platform平台总线,其中 platform_match 就是匹配函数。
platform_match 函数定义在文件 drivers/base/platform.c 中,函数内容如下所示:
/**
* platform_match - bind platform device to platform driver. 将平台设备绑定到平台驱动程序。
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
* 假定平台设备 ID 的编码方式如下:
* "<name><instance>", 其中name是设备类型的简短描述,如pci或floppy,而instance是设备的枚举实例,如'0'或'42'。
* 驱动程序 ID 只是简单"<name>"。
* 因此,<name>从platform_device结构中提取 ,并将其与驱动程序的名称进行比较。
* 无论它们是否匹配,都返回。
*/
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
/* 当设置驱动程序覆盖时,只绑定到匹配的驱动程序 */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first 首先尝试 OF 样式匹配(设备树方式) */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match 然后尝试 ACPI 样式匹配 */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table 然后尝试与 id 表匹配 */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match 回退到驱动程序名称匹配 */
return (strcmp(pdev->name, drv->name) == 0);
}
platform_match 函数讲述驱动和设备的匹配有四种方法:
1、OF 类型的匹配,也就是设备树采用的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。
2、ACPI 匹配方式。
3、id_table 匹配,每个 platform_driver 结构体有一个 id_table 成员变量。
4、直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
2、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_table 表(是一个数组) */
bool prevent_deferred_probe;
};
/**
* struct device_driver - The basic device driver structure 基本设备驱动程序结构
* @name: Name of the device driver. 设备驱动程序的名称。
* @bus: The bus which the device of this driver belongs to. 此驱动程序的设备所属的总线。
* @owner: The module owner. 模块所有者。
* @mod_name: Used for built-in modules. 用于内置模块。
* @suppress_bind_attrs: Disables bind/unbind via sysfs. 通过 sysfs 禁用绑定/取消绑定。
* @of_match_table: The open firmware table. 打开的固件表。
* @acpi_match_table: The ACPI match table. ACPI 匹配表。
* @probe: Called to query the existence of a specific device,
* whether this driver can work with it, and bind the driver
* to a specific device. 调用该函数可以查询特定设备是否存在,该驱动是否可以使用该设备,以及将该驱动绑定到特定设备。
* @remove: Called when the device is removed from the system to
* unbind a device from this driver. 当设备从系统中移除时调用,以从此驱动程序中解绑定设备。
* @shutdown: Called at shut-down time to quiesce the device. 在关闭时间调用,以停止设备。
* @suspend: Called to put the device to sleep mode. Usually to a
* low power state. 调用将设备置于睡眠模式。通常为低功率状态。
* @resume: Called to bring a device from sleep mode. 调用以使设备退出睡眠模式。
* @groups: Default attributes that get created by the driver core
* automatically. 由驱动程序核心自动创建的默认属性。
* @pm: Power management operations of the device which matched
* this driver. 与此驱动程序匹配的设备的电源管理操作。
* @p: Driver core's private data, no one other than the driver
* core can touch this. 驱动核心的私有数据,除了驱动核心没有人可以碰它。
*
* The device driver-model tracks all of the drivers known to the system.
* The main reason for this tracking is to enable the driver core to match
* up drivers with new devices. Once drivers are known objects within the
* system, however, a number of other things become possible. Device drivers
* can export information and configuration variables that are independent
* of any specific device.
* 设备驱动程序模型跟踪系统已知的所有驱动程序。
* 这种跟踪的主要原因是使驱动程序核心能够将驱动程序与新设备匹配起来。
* 然而,一旦驱动程序成为系统中的已知对象,许多其他事情就成为可能。
* 设备驱动程序可以导出独立于任何特定设备的信息和配置变量。
*/
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
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;
};
3、platform_device
struct platform_device {
const char *name; /* 设备名字(用于和驱动匹配) */
int id;
bool id_auto;
struct device dev;
u32 num_resources; /* 表示 resource 大小 */
struct resource *resource; /* 表示资源,也就是设备信息,比如外设寄存器等 */
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;
};
/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
resource_size_t start; /* 资源的起始 */
resource_size_t end; /* 资源的终止 */
const char *name; /* 资源名字 */
unsigned long flags; /* 资源类型 */
struct resource *parent, *sibling, *child;
};
二、platform 驱动相关 API
1、platform_driver_register
/* 注册驱动程序
*
* drv:要注册的 platform 驱动
* 返回值:负数,失败;0,成功。
*/
int platform_driver_register(struct platform_driver *drv);
2、platform_driver_unregister
/* 注销驱动程序
*
* drv:要注销的 platform 驱动
* 返回值:无
*/
void platform_driver_unregister(struct platform_driver *drv)
三、platform 驱动源码编写思路
1、定义 platform 驱动结构体。
2、在 platform 驱动结构体中指定 probe 函数(驱动和设备匹配成功后执行)和 remove 函数(设备注销或驱动注销)。
3、在驱动模块加载函数中调用 platform_driver_register 函数。
4、在驱动模块卸载函数中调用 platform_driver_unregister 函数。
5、probe 函数完成驱动初始化。
#include "linux/init.h"
#include "linux/module.h"
#include "linux/platform_device.h"
/*
* @description : flatform驱动的probe函数,当驱动与设备匹配以后此函数就会执行
* @param - dev : platform设备
* @return : 0,成功;其他负值,失败
*/
static int led_probe(struct platform_device *dev)
{
printk("led probe!\r\n");
return 0;
}
/*
* @description : platform驱动的remove函数,移除platform驱动的时候此函数会执行
* @param - dev : platform设备
* @return : 0,成功;其他负值,失败
*/
static int led_remove(struct platform_device *dev)
{
printk("led remove!\r\n");
return 0;
}
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
},
.probe = led_probe,
.remove = led_remove,
};
/*
* @description : 驱动模块加载函数
* @param : 无
* @return : 无
*/
static int __init led_driver_init(void)
{
return platform_driver_register(&led_driver);
}
/*
* @description : 驱动模块卸载函数
* @param : 无
* @return : 无
*/
static void __exit led_driver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
四、platform 设备相关 API
1、platform_device_register
/* 注册设备
*
* drv:要注册的 platform 设备
* 返回值:负数,失败;0,成功
*/
int platform_device_register(struct platform_device *pdev)
2、platform_device_unregister
/* 注销设备
*
* drv:要注销的 platform 设备
* 返回值:无
*/
void platform_device_unregister(struct platform_device *pdev)
五、platform 设备源码编写思路
1、定义 platform 设备结构体(包括设备资源信息)。
2、在设备模块加载函数中调用 platform_device_register 函数。
3、在设备模块注销函数中调用 leddevice_exit 函数。
#include "linux/init.h"
#include "linux/module.h"
#include "linux/platform_device.h"
/*
* 设备资源信息,也就是LED0所使用的所有寄存器
*/
static struct resource led_resources[] = {
};
/* @description : 释放flatform设备模块的时候此函数会执行
* @param - dev : 要释放的设备
* @return : 无
*/
static void led_release(struct device *dev)
{
printk("led device released!\r\n");
}
/*
* platform设备结构体
*/
static struct platform_device led_device = {
.name = "imx6ul-led",
.id = -1,
.dev = {
.release = &led_release,
},
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
/*
* @description : 设备模块加载
* @param : 无
* @return : 无
*/
static int __init led_device_init(void)
{
return platform_device_register(&led_device);
}
/*
* @description : 设备模块注销
* @param : 无
* @return : 无
*/
static void __exit led_device_exit(void)
{
platform_device_unregister(&led_device);
}
module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");
六、platform 总结
platform 平台包括驱动、总线和设备 3 部分。总线负责管理驱动和设备,这部分有内核进行维护。设备主要描述硬件相关信息,和设备树作用相同,因此有设备树可以不需要这部分。驱动主要用于实现对硬件的操作,向应用层提供一些 API 接口,供上层应用使用。