Linux设备模型10

Linux设备模型10(基于Linux6.6)---platform介绍

一、platform概述

在 Linux 内核中,Platform 总线(通常使用 platform_bus_type 来表示)是用来管理那些不属于标准总线(如 PCI、USB、I2C、SPI 等)但仍然需要被内核管理的设备。这些设备通常是与特定硬件平台直接关联的,比如嵌入式设备、SoC(System on Chip)上的硬件模块,或者一些特殊的硬件接口。

Platform 总线主要用于以下几类设备:

  • 嵌入式平台设备:如某些特定硬件或自定义模块,通常这些设备通过设备树(Device Tree)或者板级文件来描述。
  • 固定的硬件设备:如GPIO控制器、定时器、串口、内存映射设备等。
  • 没有标准接口的设备:这些设备的驱动程序可能通过平台总线直接注册。

Platform 总线通常与硬件平台密切相关,设备通过 设备树(Device Tree)或 ACPI(Advanced Configuration and Power Interface)来描述。设备树是用于描述硬件平台的一个数据结构,通常在嵌入式系统中使用,用来告诉内核关于硬件的配置信息。平台设备通常不需要依赖于 PCI、I2C 等传统总线,而是直接由平台代码提供支持。

Platform 设备和驱动的注册

  • 设备注册:平台设备通常通过 platform_device_register() 函数注册到内核中。
  • 驱动注册:与平台设备相关的驱动程序通过 platform_driver_register() 注册到内核中。

平台设备和驱动的匹配通常是通过设备的 id(ID 或 dev_name)来完成的。。

二、Platform模块的软件架构

内核中Platform设备软件架构如下:

Platform设备在内核中的实现主要包括三个部分:

Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备;
Platform Device,基于底层device模块,抽象出Platform Device,用于表示Platform设备;
Platform Driver,基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备。

其中Platform Device和Platform Driver会会其它Driver提供封装好的API,具体可参考后面的描述。

三、 Platform模块API

Platform提供的接口包括:Platform Device和Platform Driver两个数据结构,以及它们的操作函数。

3.1、数据结构

1. 用于抽象Platform设备的数据结构----“struct platform_device”: include/linux/platform_device.h

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u64		platform_dma_mask;
	struct device_dma_parameters dma_parms;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	/*
	 * Driver name to force a match.  Do not set directly, because core
	 * frees it.  Use driver_set_override() to set or clear it.
	 */
	const char *driver_override;

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

dev,真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现)。

name,设备的名称,和struct device结构中的init_name。实际上,该名称在设备注册时,会拷贝到dev.init_name中。

id,用于标识该设备的ID。内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driver的probe接口。
因此,在driver的probe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个。

id_auto,指示在注册设备时,是否自动赋予ID值。

num_resources、resource,该设备的资源描述,由struct resource(include/linux/ioport.h)结构抽象。
在Linux中,系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。
当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。

id_entry,和内核模块相关的内容,暂不说明。

mfd_cell,和MFD设备相关的内容,暂不说明。

archdata,一个奇葩的存在!!它的目的是为了保存一些architecture相关的数据。

2. 用于抽象Platform设备驱动的数据结构----“struct platform_driver”: include/linux/platform_device.h

struct platform_driver {
	int (*probe)(struct platform_device *);

	/*
	 * Traditionally the remove callback returned an int which however is
	 * ignored by the driver core. This led to wrong expectations by driver
	 * authors who thought returning an error code was a valid error
	 * handling strategy. To convert to a callback returning void, new
	 * drivers should implement .remove_new() until the conversion it done
	 * that eventually makes .remove() return void.
	 */
	int (*remove)(struct platform_device *);
	void (*remove_new)(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;
	/*
	 * For most device drivers, no need to care about this flag as long as
	 * all DMAs are handled through the kernel DMA API. For some special
	 * ones, for example VFIO drivers, they know how to manage the DMA
	 * themselves and set this flag so that the IOMMU layer will allow them
	 * to setup and manage their own I/O address space.
	 */
	bool driver_managed_dma;
};

struct platform_driver结构和struct device_driver非常类似,无非就是提供probe、remove、suspend、resume等回调函数,这里不再细说。

另外这里有一个id_table的指针,该指针和of_match_table、acpi_match_table的功能类似:提供其它方式的设备probe。
内核会在合适的时机检查device和device_driver的名字,如果匹配,则执行probe。其实除了名称之外,还有一些宽泛的匹配方式,例如这里提到的各种match table。

3.2、Platform Device提供的API

Platform Device主要提供设备的分配、注册等接口,供其它driver使用,具体包括:include/linux/platform_device.h

extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);

extern struct bus_type platform_bus_type;
extern struct device platform_bus;

extern struct resource *platform_get_resource(struct platform_device *,
					      unsigned int, unsigned int);
extern struct resource *platform_get_mem_or_io(struct platform_device *,
					       unsigned int);

extern struct device *
platform_find_device_by_driver(struct device *start,
			       const struct device_driver *drv);

extern struct platform_device *platform_device_alloc(const char *name, int id);
extern int platform_device_add_resources(struct platform_device *pdev,
					 const struct resource *res,
					 unsigned int num);
extern int platform_device_add_data(struct platform_device *pdev,
				    const void *data, size_t size);
extern int platform_device_add(struct platform_device *pdev);
extern void platform_device_del(struct platform_device *pdev);
extern void platform_device_put(struct platform_device *pdev);

platform_device_register、platform_device_unregister,Platform设备的注册/注销接口,和底层的device_register等接口类似。

arch_setup_pdev_archdata,设置platform_device变量中的archdata指针。

platform_get_resource、platform_get_irq、platform_get_resource_byname、platform_get_irq_byname,通过这些接口,可以获取platform_device变量中的resource信息,以及直接获取IRQ的number等等。

platform_device_register_full、platform_device_register_resndata、platform_device_register_simple、platform_device_register_data,其它形式的设备注册。调用者只需要提供一些必要的信息,如name、ID、resource等,Platform模块就会自动分配一个struct platform_device变量,填充内容后,注册到内核中。

platform_device_alloc,以name和id为参数,动态分配一个struct platform_device变量。

platform_device_add_resources,向platform device中增加资源描述。

platform_device_add_data,向platform device中添加自定义的数据(保存在pdev->dev.platform_data指针中)。

platform_device_add、platform_device_del、platform_device_put,其它操作接口。

3.3、Platform Driver提供的API

Platform Driver提供struct platform_driver的分配、注册等功能,具体如下:

/*
 * use a macro to avoid include chaining to get THIS_MODULE
 */
#define platform_driver_register(drv) \
	__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
					struct module *);
extern void platform_driver_unregister(struct platform_driver *);

/* non-hotpluggable platform devices may use this so that probe() and
 * its support may live in __init sections, conserving runtime memory.
 */
#define platform_driver_probe(drv, probe) \
	__platform_driver_probe(drv, probe, THIS_MODULE)
extern int __platform_driver_probe(struct platform_driver *driver,
		int (*probe)(struct platform_device *), struct module *module);

static inline void *platform_get_drvdata(const struct platform_device *pdev)
{
	return dev_get_drvdata(&pdev->dev);
}

static inline void platform_set_drvdata(struct platform_device *pdev,
					void *data)
{
	dev_set_drvdata(&pdev->dev, data);
}

platform_driver_registe、platform_driver_unregister,platform driver的注册、注销接口。

platform_driver_probe,主动执行probe动作。

platform_set_drvdata、platform_get_drvdata,设置或者获取driver保存在device变量中的私有数据。

3.4、platform_create_bundle提供的API

又是注册platform device,又是注册platform driver,看着挺啰嗦的。不过内核想到了这点,所以提供一个懒人API,可以同时注册platform driver,并分配一个platform device:include/linux/platform_device.h

#define platform_create_bundle(driver, probe, res, n_res, data, size) \
	__platform_create_bundle(driver, probe, res, n_res, data, size, THIS_MODULE)
extern struct platform_device *__platform_create_bundle(
	struct platform_driver *driver, int (*probe)(struct platform_device *),
	struct resource *res, unsigned int n_res,
	const void *data, size_t size, struct module *module);

只要提供一个platform_driver(要把driver的probe接口显式的传入),并告知该设备占用的资源信息,platform模块就会帮忙分配资源,并执行probe操作。对于那些不需要热拔插的设备来说,这种方式是最省事的了。  

 

四、 Platform模块的内部动作解析

4.1、 Platform模块的初始化

Platform模块的初始化是由drivers/base/platform.c中platform_bus_init接口完成的,该接口的实现和动作如下:drivers/base/platform.c

int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();

	error = device_register(&platform_bus);
	if (error) {
		put_device(&platform_bus);
		return error;
	}
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);

	return error;
}

 

4.2、 platform device和platform driver的注册

platform device和platform driver的注册,由platform_device_add和platform_driver_register两个接口实际实现。其内部动作分别如下。

platform_device_add的内部动作:

如果该设备没有指定父设备,将其父设备设置为platform_bus,即“/sys/devices/platform/”所代表的设备,这时该设备的sysfs目录即为“/sys/devices/platform/xxx_device”。

将该设备的bus指定为platform_bus_type(pdev->dev.bus = &platform_bus_type)。

根据设备的ID,修改或者设置设备的名称。对于多个同名的设备,可以使用ID区分,在这里将实际名称修改为“name.id”的形式。

调用resource模块的insert_resource接口,将该设备需要使用的resource统一管理起来(我们知道,在这之前,只是声明了本设备需要使用哪些resource,但resource模块并不知情,也就无从管理,因此需要告知)。

调用device_add接口,将内嵌的struct device变量添加到内核中。

platform_driver_register的内部动作:

将该driver的bus指定为platform_bus_type(drv->driver.bus = &platform_bus_type)。

如果该platform driver提供了probe、remove、shutdown等回调函数,将该它内嵌的struct driver变量的probe、remove、shutdown等指针,设置为platform模块提供函数,包括platform_drv_probe、platform_drv_remove和platform_drv_shutdown。因为probe等动作会从struct driver变量开始,经过platform_drv_xxx等接口的转接,就可以到达platform diver自身的回调函数中。

调用driver_register接口,将内嵌的struct driver变量添加到内核中。

 

4.3、 platform设备的probe

之前的文章中讲过设备的probe,都发生在向指定的bus添加device或者device_driver时,由bus模块的bus_probe_device,或者device_driver模块driver_attach接口触发。

五、示例:如何创建和注册一个平台设备

以下是一个简单的例子,演示如何在 Linux 内核中创建和注册一个平台设备,并配合相应的驱动来实现设备的探测(probe)和移除(remove)操作。

5.1、平台设备的定义和注册

平台设备通常使用 platform_device 结构体来描述,以下代码展示了如何创建和注册一个平台设备。

平台设备代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/io.h>

#define DEVICE_NAME "my_platform_device"

// 设备的资源定义
static struct resource my_device_resources[] = {
    {
        .start = 0x10000000,  // 假设的 I/O 地址
        .end = 0x10001000,    // 假设的 I/O 地址范围
        .flags = IORESOURCE_MEM,
    },
    {
        .start = 5,           // 假设的中断号
        .end = 5,
        .flags = IORESOURCE_IRQ,
    },
};

// 平台设备定义
static struct platform_device my_device = {
    .name = DEVICE_NAME,               // 设备名称
    .id = -1,                          // 设备 ID
    .num_resources = ARRAY_SIZE(my_device_resources),
    .resource = my_device_resources,   // 设备资源
};

// 模块加载时注册平台设备
static int __init my_device_init(void)
{
    int ret;

    pr_info("Registering platform device: %s\n", DEVICE_NAME);

    ret = platform_device_register(&my_device);
    if (ret) {
        pr_err("Failed to register platform device\n");
        return ret;
    }

    return 0;
}

// 模块卸载时注销平台设备
static void __exit my_device_exit(void)
{
    pr_info("Unregistering platform device: %s\n", DEVICE_NAME);
    platform_device_unregister(&my_device);
}

module_init(my_device_init);
module_exit(my_device_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example of Platform Device");

代码解释:

  1. 设备资源my_device_resources 定义了设备的资源,包括内存和中断资源。在本例中,内存资源假设在 0x100000000x10001000 之间,IRQ 号为 5。
  2. 平台设备结构my_device 是一个 platform_device 类型的结构体,其中包含了设备的名称、ID、资源和其他信息。platform_device_register 函数用于将此平台设备注册到内核。
  3. 模块加载与卸载my_device_init 函数用于加载模块时注册设备,而 my_device_exit 则在模块卸载时注销设备。

5.2、编写平台设备的驱动程序

要使设备能够正常工作,还需要编写一个驱动程序,通过 proberemove 函数与设备进行交互。以下是一个简单的驱动程序,配合前面的平台设备示例。

平台设备驱动代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/io.h>

#define DEVICE_NAME "my_platform_device"

// 驱动的 probe 函数,用于设备初始化
static int my_device_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *io_base;

    pr_info("Probing device: %s\n", DEVICE_NAME);

    // 获取设备资源 (内存映射)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        pr_err("Failed to get memory resource\n");
        return -ENODEV;
    }

    // 映射 I/O 内存区域
    io_base = ioremap(res->start, resource_size(res));
    if (!io_base) {
        pr_err("Failed to remap memory\n");
        return -ENOMEM;
    }

    pr_info("I/O base address: %p\n", io_base);

    // 在此可以对硬件进行初始化

    return 0;
}

// 驱动的 remove 函数,用于设备移除时清理资源
static int my_device_remove(struct platform_device *pdev)
{
    pr_info("Removing device: %s\n", DEVICE_NAME);

    // 清理资源,比如解除 I/O 内存映射
    // 这里假设我们已经映射了 I/O 内存区域

    return 0;
}

// 平台驱动结构体
static struct platform_driver my_device_driver = {
    .probe = my_device_probe,
    .remove = my_device_remove,
    .driver = {
        .name = DEVICE_NAME,  // 设备名称
        .owner = THIS_MODULE,
    },
};

// 模块加载时注册平台驱动
static int __init my_driver_init(void)
{
    pr_info("Registering platform driver\n");
    return platform_driver_register(&my_device_driver);
}

// 模块卸载时注销平台驱动
static void __exit my_driver_exit(void)
{
    pr_info("Unregistering platform driver\n");
    platform_driver_unregister(&my_device_driver);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example of Platform Driver");

代码解释:

  1. my_device_probe:在 probe 函数中,首先通过 platform_get_resource 获取设备的内存资源,然后通过 ioremap 映射到内核地址空间。此处还可以进行更多设备的初始化操作。
  2. my_device_remove:在 remove 函数中,通常需要释放在 probe 中分配的资源,清理内存映射等。
  3. platform_driver 结构my_device_driver 是一个 platform_driver 结构体,用于注册和注销平台驱动。在设备与驱动匹配时,内核会调用 probe 函数;当设备被移除时,会调用 remove 函数。

5.3、设备与驱动的匹配

在本例中,平台设备通过 platform_device_register 注册,而平台驱动通过 platform_driver_register 注册,驱动和设备之间的匹配由设备的名称(DEVICE_NAME)决定。内核会根据设备名称自动调用驱动的 probe 函数。

以上是一个简单的 Linux 平台设备和驱动示例。通过使用 platform_device 来定义硬件设备,和 platform_driver 来编写相应的驱动程序,我们能够在内核中实现设备的注册、初始化、资源管理和移除等操作。平台设备通常用于嵌入式开发中,能够与具体的硬件资源进行高效的交互。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值