往期内容
- 驱动中的device和device_driver结构体-CSDN博客
- bus总线的相关结构体和注册逻辑-CSDN博客
- bus中设备驱动的probe触发逻辑和device、driver的添加逻辑-CSDN博客
前言
注:以下的代码皆摘自于linux 4.9.88版本的内核源码,不同版本可能有所出入。
之前的章节驱动中的device和device_driver结构体-CSDN博客讲过:Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如platform device。内核在设备模型的基础上(device和device_driver),对这些设备进行了更进一步的**封装**,抽象出paltform bus、platform device和platform driver,以便驱动开发人员可以方便的开发这类设备的驱动。
Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备;
Platform Device,基于底层device模块,抽象出Platform Device,用于表示Platform设备;
Platform Driver,基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备。
1. struct platform_device
/*include/linux/platform_device.h*/
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
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;
};
const char *name
:- 设备的名称。这个名称在设备注册时会被复制到其
struct device
中的init_name
字段。它用于唯一标识设备,并且在设备和驱动的匹配过程中扮演着关键角色。多个设备可以具有相同的名称,但内核会利用其他属性(如 ID)来区分它们。
- 设备的名称。这个名称在设备注册时会被复制到其
**int id
:- 设备的唯一标识符。它用于区分同一总线上的不同设备。在驱动的
probe
函数中,通过 ID 可以判断设备的具体信息,尤其是在多个同名设备的情况下。 - 内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driver的probe接口。因此,在driver的probe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个。
- 设备的唯一标识符。它用于区分同一总线上的不同设备。在驱动的
bool id_auto
:- 指示该设备的 ID 是否由内核自动分配。如果为
true
,则在设备注册时无需手动指定 ID,内核会为其分配一个唯一的 ID。这使得开发者在注册设备时更为方便。
- 指示该设备的 ID 是否由内核自动分配。如果为
struct device dev
:- 这是一个通用的设备结构,包含了设备的所有基础属性和操作方法。
platform_device
是一个特殊的设备,因此它继承了struct device
的所有功能。这个字段用于与内核的设备模型集成。
- 这是一个通用的设备结构,包含了设备的所有基础属性和操作方法。
u32 num_resources
:- 设备使用的资源数量。这个字段指明了设备在使用的资源(如中断、内存地址等)的个数。
struct resource *resource
:- 指向设备使用的资源的指针。资源由
struct resource
描述,它抽象了设备所需的硬件资源,如 I/O 地址、内存地址、IRQ 号等。通过这个结构,内核可以管理这些资源的分配和释放,确保没有两个设备同时占用同一资源。 - 在Linux中,系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。
- 指向设备使用的资源的指针。资源由
const struct platform_device_id *id_entry
:- 指向与该平台设备相关的设备 ID 匹配表。该表定义了哪些驱动程序可以与此设备匹配。驱动程序在加载时会检查其 ID 列表,以便找到适合的设备。
char *driver_override
:- 用于强制设备与特定驱动匹配的驱动名称。如果某个设备需要特定的驱动而不想由内核自动选择,开发者可以在此字段中设置驱动名称。
struct mfd_cell *mfd_cell
:- 指向多功能设备(MFD)单元的指针。MFD 设备允许多个子设备通过同一个驱动进行管理,适用于复杂的硬件结构。
struct pdev_archdata archdata
:- 存储架构特定的附加数据。由于不同的硬件平台有不同的需求和实现,该字段允许每种架构定义自己特有的数据结构和成员。虽然这个设计可能显得灵活,但也让代码的可维护性和清晰性受到了挑战。
struct platform_device
结构体是 Linux 内核设备模型中处理平台设备的核心数据结构。它不仅存储了设备的基本信息,如名称和 ID,还管理了设备所需的硬件资源,并提供了与驱动程序匹配的必要信息。通过这个结构,内核能够有效地管理和控制平台设备的生命周期和资源使用。
2. 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;
bool prevent_deferred_probe;
};
int (*probe)(struct platform_device *);
:- 指向一个函数,该函数在驱动程序与设备匹配时被调用。
probe
函数的主要作用是初始化设备、分配资源、注册字符设备等。它是设备驱动的入口点,执行设备的设置和配置操作。
- 指向一个函数,该函数在驱动程序与设备匹配时被调用。
int (*remove)(struct platform_device *);
:- 指向一个函数,该函数在设备被移除时被调用。通常,这个函数负责释放
probe
中分配的资源,注销字符设备等。它确保在设备被卸载时正确清理和释放所有相关资源。
- 指向一个函数,该函数在设备被移除时被调用。通常,这个函数负责释放
void (*shutdown)(struct platform_device *);
:- 指向一个函数,在系统关闭或设备驱动被卸载时调用。这个函数通常用于保存设备状态和进行最后的清理,以确保设备可以安全关闭。
int (*suspend)(struct platform_device *, pm_message_t state);
:- 指向一个函数,在设备进入低功耗状态时被调用。该函数会处理设备的挂起逻辑,如关闭某些功能、保存设备状态等。
pm_message_t
参数用于指示挂起的类型(如休眠、待机等)。
- 指向一个函数,在设备进入低功耗状态时被调用。该函数会处理设备的挂起逻辑,如关闭某些功能、保存设备状态等。
**int (*resume)(struct platform_device *);**
:- 指向一个函数,在设备从挂起状态恢复时调用。该函数负责重新初始化设备并恢复其之前的状态,以便设备可以继续正常操作。
struct device_driver driver;
:- 这是一个基础的设备驱动结构,包含了设备驱动的通用属性和功能。通过这个字段,
platform_driver
继承了设备驱动的基本功能,包括驱动的名称、所属总线等。
- 这是一个基础的设备驱动结构,包含了设备驱动的通用属性和功能。通过这个字段,
const struct platform_device_id *id_table;
:- 指向与该平台驱动匹配的设备 ID 表。它的功能与
of_match_table
和acpi_match_table
类似,为驱动提供其他匹配设备的方式。通过这个表,内核可以根据设备 ID 来判断驱动是否适用于特定设备,而不仅仅依赖于设备名称。
- 指向与该平台驱动匹配的设备 ID 表。它的功能与
bool prevent_deferred_probe;
:- 这个字段用于控制是否阻止延迟探测。延迟探测是指在设备驱动无法立即匹配设备时,内核将其推迟到稍后的时间来进行匹配。如果将此字段设置为
true
,则驱动在匹配时会立即进行,而不会被延迟处理。
- 这个字段用于控制是否阻止延迟探测。延迟探测是指在设备驱动无法立即匹配设备时,内核将其推迟到稍后的时间来进行匹配。如果将此字段设置为
struct platform_driver
提供了一种描述平台设备驱动的机制,包含了多种操作的回调函数,以支持设备的生命周期管理,如初始化、移除、挂起和恢复等。此外,它通过 id_table
提供了更灵活的设备匹配方式,允许驱动在不同的条件下识别和管理多个设备。这使得平台驱动可以更有效地与硬件交互,提供稳定和高效的设备支持。
3. API
3.1 Platform Device
Platform Device主要提供设备的分配、注册等接口,供其它driver使用。在 Linux 内核中,platform_device
代表平台设备,include/linux/platform_device.h
中提供了一系列 API 来注册、管理和使用这些设备。具体包括:
1. 设备注册与注销
int platform_device_register(struct platform_device *);
● 功能: 注册一个平台设备,使其在内核中可用。
● 参数: struct platform_device *pdev,指向要注册的设备结构体。
● 返回值: 返回 0 表示成功,负值表示错误。
void platform_device_unregister(struct platform_device *);
● 功能: 注销一个已注册的平台设备。
● 参数: struct platform_device *pdev,指向要注销的设备结构体。
● 注意: 注销后设备将不再可用。
2. 设备资源和 IRQ 管理
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
● 功能: 获取与设备关联的资源。
● 参数:
○ struct platform_device *pdev:目标设备。
○ unsigned int type:资源类型(如 I/O、内存等)。
○ unsigned int num:资源的索引。
● 返回值: 返回指向资源结构的指针,或返回 NULL 如果没有找到。
int platform_get_irq(struct platform_device *, unsigned int);
● 功能: 获取与设备关联的 IRQ。
● 参数:
○ struct platform_device *pdev:目标设备。
○ unsigned int num:IRQ 的索引。
● 返回值: 返回 IRQ 号,或负值表示错误。
struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, const char *);
● 功能: 根据资源名称获取资源。
● 参数:
○ struct platform_device *pdev:目标设备。
○ unsigned int type:资源类型。
○ const char *name:资源名称。
● 返回值: 返回指向资源结构的指针,或返回 NULL 如果没有找到。
int platform_get_irq_byname(struct platform_device *, const char *);
● 功能: 根据名称获取 IRQ。
● 参数:
○ struct platform_device *pdev:目标设备。
○ const char *name:IRQ 名称。
● 返回值: 返回 IRQ 号,或负值表示错误。
3. 设备数组管理
int platform_add_devices(struct platform_device **, int);
● 功能: 注册一个平台设备数组。
● 参数:
○ struct platform_device **pdevs:指向设备指针数组。
○ int count:数组中的设备数量。
● 返回值: 返回 0 表示成功,负值表示错误。
4. 设备注册的简化接口
struct platform_device *platform_device_register_full(const struct platform_device_info *pdevinfo);
● 功能: 完整注册平台设备,使用 platform_device_info 结构提供的详细信息。
● 参数: const struct platform_device_info *pdevinfo:设备信息结构体。
● 返回值: 返回指向已注册设备的指针,或返回 NULL 如果失败。
struct platform_device *platform_device_register_resndata(struct device *parent, const char *name, int id, const struct resource *res, unsigned int num, const void *data, size_t size);
● 功能: 注册一个带资源和数据的平台设备。
● 参数:
○ struct device *parent:设备父级。
○ const char *name:设备名称。
○ int id:设备 ID。
○ const struct resource *res:资源数组。
○ unsigned int num:资源数量。
○ const void *data:附加数据。
○ size_t size:数据大小。
● 返回值: 返回指向已注册设备的指针,或返回 NULL 如果失败。
struct platform_device *platform_device_register_simple(const char *name, int id, const struct resource *res, unsigned int num);
● 功能: 简单注册一个平台设备,提供名称、ID 和资源。
● 参数:
○ const char *name:设备名称。
○ int id:设备 ID。
○ const struct resource *res:资源数组。
○ unsigned int num:资源数量。
● 返回值: 返回指向已注册设备的指针,或返回 NULL 如果失败。
struct platform_device *platform_device_register_data(struct device *parent, const char *name, int id, const void *data, size_t size);
● 功能: 注册一个带数据的平台设备。
● 参数:
○ struct device *parent:设备父级。
○ const char *name:设备名称。
○ int id:设备 ID。
○ const void *data:附加数据。
○ size_t size:数据大小。
● 返回值: 返回指向已注册设备的指针,或返回 NULL 如果失败。
5. 设备内存和资源管理
struct platform_device *platform_device_alloc(const char *name, int id);
● 功能: 分配内存用于一个平台设备,但不注册。
● 参数:
○ const char *name:设备名称。
○ int id:设备 ID。
● 返回值: 返回指向分配的设备的指针,或返回 NULL 如果失败。
int platform_device_add_resources(struct platform_device *pdev, const struct resource *res, unsigned int num);
● 功能: 向平台设备添加资源。
● 参数:
○ struct platform_device *pdev:目标设备。
○ const struct resource *res:资源数组。
○ unsigned int num:资源数量。
● 返回值: 返回 0 表示成功,负值表示错误。
int platform_device_add_data(struct platform_device *pdev, const void *data, size_t size);
● 功能: 向平台设备添加附加数据。
● 参数:
○ struct platform_device *pdev:目标设备。
○ const void *data:附加数据。
○ size_t size:数据大小。
● 返回值: 返回 0 表示成功,负值表示错误。
int platform_device_add(struct platform_device *pdev);
● 功能: 注册一个已分配的设备。
● 参数: struct platform_device *pdev:目标设备。
● 返回值: 返回 0 表示成功,负值表示错误。
void platform_device_del(struct platform_device *pdev);
● 功能: 删除并注销设备,但不释放内存。
● 参数: struct platform_device *pdev:目标设备。
void platform_device_put(struct platform_device *pdev);
● 功能: 释放对设备的引用。
● 参数: struct platform_device *pdev:目标设备。
3.2 Platform Driver
在 Linux 内核中,platform_driver
结构体与 platform_device
结构体配合使用,提供了对平台设备驱动的管理和操作。这部分代码主要定义了与平台驱动相关的 API。以下是对这些 API 的详细说明:
1. 驱动注册与注销
int platform_driver_register(struct platform_driver *);
● 功能: 注册一个平台驱动,使其在内核中可用。
● 参数:
○ struct platform_driver *driver:指向要注册的驱动结构体。
● 返回值: 返回 0 表示成功,负值表示错误。成功注册后,内核会在适当的时候调用该驱动的 probe 函数。
void platform_driver_unregister(struct platform_driver *);
● 功能: 注销一个已注册的平台驱动。
● 参数:
○ struct platform_driver *driver:指向要注销的驱动结构体。
● 注意: 注销后驱动将不再可用,内核会停止调用该驱动的相关回调函数。
2. 驱动探测
int platform_driver_probe(struct platform_driver *driver, int (*probe)(struct platform_device *));
● 功能: 手动执行驱动的 probe 函数。
● 参数:
○ struct platform_driver *driver:指向要探测的驱动结构体。
○ int (*probe)(struct platform_device *):指向 probe 函数的指针,probe 函数的作用是初始化设备。
● 返回值: 返回 0 表示成功,负值表示错误。
3. 驱动数据管理
static inline void *platform_get_drvdata(const struct platform_device *pdev);
● 功能: 获取与平台设备关联的驱动数据。
● 参数:
○ const struct platform_device *pdev:指向目标设备的指针。
● 返回值: 返回指向设备关联数据的指针。如果没有数据,则返回 NULL。
static inline void platform_set_drvdata(struct platform_device *pdev, void *data);
● 功能: 设置与平台设备关联的驱动数据。
● 参数:
○ struct platform_device *pdev:指向目标设备的指针。
○ void *data:要与设备关联的数据指针。
● 注意: 通过 set_drvdata 函数,驱动可以存储与设备相关的上下文数据,以便后续在 probe、remove 或其他回调函数中使用。
这些 API 提供了对平台驱动的管理和操作,包括驱动的注册、注销、探测以及与设备关联的数据的设置和获取。通过这些函数,开发者能够有效地控制平台驱动的生命周期和与之相关的状态信息,增强了内核对硬件设备的支持能力。
4. platform device和platform driver的注册
在 Linux 内核中,platform_device
和 platform_driver
的注册过程是通过 platform_device_add
和 platform_driver_register
这两个接口实现的。以下是对这两个接口的详细内部动作的解析。
4.1 platform_device_add 的内部动作
Linux-4.9.88\drivers\base\platform.c:
**
* platform_device_add - add a platform device to device hierarchy
* @pdev: platform device we're adding
*
* This is part 2 of platform_device_register(), though may be called
* separately _iff_ pdev was allocated by platform_device_alloc().
*/
int platform_device_add(struct platform_device *pdev)
{
int i, ret;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
switch (pdev->id) {
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO:
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto err_out;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
break;
}
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) {
dev_err(&pdev->dev, "failed to claim resource %d\n", i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
ret = device_add(&pdev->dev);
if (ret == 0)
return ret;
failed:
if (pdev->id_auto) {
ida_simple_remove(&platform_devid_ida, pdev->id);
pdev->id = PLATFORM_DEVID_AUTO;
}
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
if (r->parent)
release_resource(r);
}
err_out:
return ret;
}
- 父设备设置:
- 如果设备没有指定父设备,则将其父设备设置为
platform_bus
,即/sys/devices/platform/
代表的设备。 - 这意味着设备的 sysfs 目录会被创建为
/sys/devices/platform/xxx_device
。
- 如果设备没有指定父设备,则将其父设备设置为
- 设置设备总线:
- 将设备的
bus
指针指定为platform_bus_type
:
- 将设备的
pdev->dev.bus = &platform_bus_type;
- 名称和 ID 处理:
- 根据设备的 ID 修改或设置设备的名称。对于多个同名的设备,通过 ID 区分设备。在这里,将实际名称修改为
name.id
的形式,例如:
- 根据设备的 ID 修改或设置设备的名称。对于多个同名的设备,通过 ID 区分设备。在这里,将实际名称修改为
spi_device1
- 资源管理:
- 调用
resource
模块的insert_resource
接口,将该设备需要使用的资源统一管理起来。此时,资源模块会知道该设备所需的资源,这样它就可以进行相应的管理。
- 调用
- 添加设备到内核:
- 调用
device_add
接口,将内嵌的struct device
变量添加到内核中,使设备在内核中可用。
- 调用
4.2 platform_driver_register 的内部动作
Linux-4.9.88\include\linux\platform_device.h
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
/**
* __platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
* @owner: owning module/driver
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
- 设置驱动总线:
- 将驱动的
bus
指针指定为platform_bus_type
:
- 将驱动的
drv->driver.bus = &platform_bus_type;
- 回调函数指针设置:
- 如果该平台驱动提供了
probe
、remove
、shutdown
等回调函数,则将它内嵌的struct driver
变量的这些指针设置为平台模块提供的函数,例如:probe
函数指针设置为platform_drv_probe
remove
函数指针设置为platform_drv_remove
shutdown
函数指针设置为platform_drv_shutdown
- 通过这种方式,
probe
等动作将从struct driver
变量开始,经过platform_drv_xxx
等接口的转接,最终调用到平台驱动自身的回调函数中。
- 如果该平台驱动提供了
- 驱动注册到内核:
- 调用
driver_register
接口,将内嵌的struct driver
变量添加到内核中,使驱动在内核中可用。
- 调用
诶?最后不还是调用到了driver_register,而driver_register最后还是会去调用bus_add_driver函数,所以说这玩意其实就是嵌套的。
5. 平台设备的probe
在Bus中讲过设备驱动的probe的触发逻辑,其实在platform中也差不多,注册添加platform_driver时会触发probe,只不过包装了一下而已
bus中设备驱动的probe触发逻辑和device、driver的添加逻辑-CSDN博客
6. 联系
drivers/base/platform.c、dd.c
其实看一个图,兴许就能明白bus、driver/device、platform_deivce/platform_driver之间的关系:
可以很清楚的看出,最后时调用了driver_attach操作,去进行遍历是否有匹配的device(主要依靠下面的_driver_attach,后面会讲到):
如果找到了最后肯定会调用到driver中的probe函数,其probe函数也在__driver_attach中体现:
最后就调用了自己驱动定义的probe函数。
7. 匹配规则
drivers/base/platform.c、dd.c
最后简单讲一下platform_device和platform_driver之间的匹配方式:
下面该函数在上面也讲到了,会去遍历是否有匹配的device,匹配就调用参四__driver_attach函数,里面会去调用bus_probe_device
函数去调用驱动程序的probe
匹配主要发生在__driver_attach中:
最后通过这张图就可以简单了解到匹配的四种方式