platform bus平台总线详解

往期内容

  1. 驱动中的device和device_driver结构体-CSDN博客
  2. bus总线的相关结构体和注册逻辑-CSDN博客
  3. 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;
};
  1. const char *name:
    • 设备的名称。这个名称在设备注册时会被复制到其 struct device 中的 init_name 字段。它用于唯一标识设备,并且在设备和驱动的匹配过程中扮演着关键角色。多个设备可以具有相同的名称,但内核会利用其他属性(如 ID)来区分它们。
  2. **int id:
    • 设备的唯一标识符。它用于区分同一总线上的不同设备。在驱动的 probe 函数中,通过 ID 可以判断设备的具体信息,尤其是在多个同名设备的情况下。
    • 内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driver的probe接口。因此,在driver的probe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个。
  3. bool id_auto:
    • 指示该设备的 ID 是否由内核自动分配。如果为 true,则在设备注册时无需手动指定 ID,内核会为其分配一个唯一的 ID。这使得开发者在注册设备时更为方便。
  4. struct device dev:
    • 这是一个通用的设备结构,包含了设备的所有基础属性和操作方法。platform_device 是一个特殊的设备,因此它继承了 struct device 的所有功能。这个字段用于与内核的设备模型集成。
  5. u32 num_resources:
    • 设备使用的资源数量。这个字段指明了设备在使用的资源(如中断、内存地址等)的个数。
  6. struct resource *resource:
    • 指向设备使用的资源的指针。资源由 struct resource 描述,它抽象了设备所需的硬件资源,如 I/O 地址、内存地址、IRQ 号等。通过这个结构,内核可以管理这些资源的分配和释放,确保没有两个设备同时占用同一资源。
    • 在Linux中,系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。
  7. const struct platform_device_id *id_entry:
    • 指向与该平台设备相关的设备 ID 匹配表。该表定义了哪些驱动程序可以与此设备匹配。驱动程序在加载时会检查其 ID 列表,以便找到适合的设备。
  8. char *driver_override:
    • 用于强制设备与特定驱动匹配的驱动名称。如果某个设备需要特定的驱动而不想由内核自动选择,开发者可以在此字段中设置驱动名称。
  9. struct mfd_cell *mfd_cell:
    • 指向多功能设备(MFD)单元的指针。MFD 设备允许多个子设备通过同一个驱动进行管理,适用于复杂的硬件结构。
  10. 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;
};
  1. int (*probe)(struct platform_device *);:
    • 指向一个函数,该函数在驱动程序与设备匹配时被调用。probe 函数的主要作用是初始化设备、分配资源、注册字符设备等。它是设备驱动的入口点,执行设备的设置和配置操作。
  2. int (*remove)(struct platform_device *);:
    • 指向一个函数,该函数在设备被移除时被调用。通常,这个函数负责释放 probe 中分配的资源,注销字符设备等。它确保在设备被卸载时正确清理和释放所有相关资源。
  3. void (*shutdown)(struct platform_device *);:
    • 指向一个函数,在系统关闭或设备驱动被卸载时调用。这个函数通常用于保存设备状态和进行最后的清理,以确保设备可以安全关闭。
  4. int (*suspend)(struct platform_device *, pm_message_t state);:
    • 指向一个函数,在设备进入低功耗状态时被调用。该函数会处理设备的挂起逻辑,如关闭某些功能、保存设备状态等。pm_message_t 参数用于指示挂起的类型(如休眠、待机等)。
  5. **int (*resume)(struct platform_device *);**:
    • 指向一个函数,在设备从挂起状态恢复时调用。该函数负责重新初始化设备并恢复其之前的状态,以便设备可以继续正常操作。
  6. struct device_driver driver;:
    • 这是一个基础的设备驱动结构,包含了设备驱动的通用属性和功能。通过这个字段,platform_driver 继承了设备驱动的基本功能,包括驱动的名称、所属总线等。
  7. const struct platform_device_id *id_table;:
    • 指向与该平台驱动匹配的设备 ID 表。它的功能与 of_match_tableacpi_match_table 类似,为驱动提供其他匹配设备的方式。通过这个表,内核可以根据设备 ID 来判断驱动是否适用于特定设备,而不仅仅依赖于设备名称。
  8. 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:指向目标设备的指针。
● 返回值: 返回指向设备关联数据的指针。如果没有数据,则返回 NULLstatic 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_deviceplatform_driver 的注册过程是通过 platform_device_addplatform_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;
}
  1. 父设备设置:
    • 如果设备没有指定父设备,则将其父设备设置为 platform_bus,即 /sys/devices/platform/ 代表的设备。
    • 这意味着设备的 sysfs 目录会被创建为 /sys/devices/platform/xxx_device
  2. 设置设备总线:
    • 将设备的 bus 指针指定为 platform_bus_type
pdev->dev.bus = &platform_bus_type;
  1. 名称和 ID 处理:
    • 根据设备的 ID 修改或设置设备的名称。对于多个同名的设备,通过 ID 区分设备。在这里,将实际名称修改为 name.id 的形式,例如:
spi_device1
  1. 资源管理:
    • 调用 resource 模块的 insert_resource 接口,将该设备需要使用的资源统一管理起来。此时,资源模块会知道该设备所需的资源,这样它就可以进行相应的管理。
  2. 添加设备到内核:
    • 调用 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);
}
  1. 设置驱动总线:
    • 将驱动的 bus 指针指定为 platform_bus_type
drv->driver.bus = &platform_bus_type;
  1. 回调函数指针设置:
    • 如果该平台驱动提供了 proberemoveshutdown 等回调函数,则将它内嵌的 struct driver 变量的这些指针设置为平台模块提供的函数,例如:
      • probe 函数指针设置为 platform_drv_probe
      • remove 函数指针设置为 platform_drv_remove
      • shutdown 函数指针设置为 platform_drv_shutdown
    • 通过这种方式,probe 等动作将从 struct driver 变量开始,经过 platform_drv_xxx 等接口的转接,最终调用到平台驱动自身的回调函数中。
  2. 驱动注册到内核:
    • 调用 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

dd.c

匹配主要发生在__driver_attach中:

最后通过这张图就可以简单了解到匹配的四种方式

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值