Skip to content

LV020-驱动和设备资源的获取

前面我们已经知道了怎么让平台设备和平台驱动进行匹配,那么匹配的目录肯定是驱动获取设备中的资源了,那怎么获取?设备能获取驱动的信息吗,能的话,又该怎么获取呢?

一、设备获取驱动的资源

前面在学习平台设备和平台驱动的时候,我们有看到这个 struct platform_device_id 结构体,它在 struct platform_devicestruct platform_driver 都有,设备获取驱动资源可以通过这个来进行,接下来就来了解一下吧。

1. struct platform_device_id

先来回顾一下这个结构体,struct platform_device_id 结构体定义如下:

c
struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};

struct platform_device_id 这个结构体中,有两个成员,第一个是数组用于指定驱动的名称,总线进行匹配时,会依据该结构体的 name 成员与 struct platform_device 中的成员 name 进行比较匹配, 另一个成员变量 driver_data,则是用于来保存设备的配置。我们知道在同系列的设备中,往往只是某些寄存器的配置不一样,为了减少代码的冗余, 尽量做到一个驱动可以匹配多个设备的目的。

2. 平台设备怎么得到这个结构体数据?

前面学习 id 匹配的时候,完成 id 匹配的是这个 platform_match_id() 函数:

c
static const struct platform_device_id *platform_match_id(
			const struct platform_device_id *id,
			struct platform_device *pdev)
{
	while (id->name[0]) {
		if (strcmp(pdev->name, id->name) == 0) {
			pdev->id_entry = id;
			return id;
		}
		id++;
	}
	return NULL;
}

我们可以看一下传进来的都是什么:

image-20250120113558619

可以看到,传进来的指针 id 指向的是 platform_driver.id_table,而指针 pdev 则是 platform_device 。在函数内部,遍历 platform_driver.id_table 中的成员,当 platform_driver.id_table[i].nameplatform_device.name 匹配的相等的时候,就将 platform_device.id_entry 这个指针指向 platform_driver.id_table[i] 的地址:

image-20250120135653502

当匹配成功的时候,我们就可以以访问 platform_device.id_entry 来获取驱动中的数据了,即访问平台设备的 platform_device.id_entry. driver_data 成员就是访问平台驱动的 platform_driver.id_table[i].driver_data 成员。

3. 应用场景

可以以以 imx 的串口为例,具体看下这个结构体的作用:

c
static struct imx_uart_data imx_uart_devdata[] = {
    [IMX1_UART] = {
        .uts_reg = IMX1_UTS,
        .devtype = IMX1_UART,
    },
    [IMX21_UART] = {
        .uts_reg = IMX21_UTS,
        .devtype = IMX21_UART,
    },
    [IMX6Q_UART] = {
        .uts_reg = IMX21_UTS,
        .devtype = IMX6Q_UART,
    },
};

static struct platform_device_id imx_uart_devtype[] = {
    {
        .name = "imx1-uart",
        .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX1_UART],
    },
    {
        .name = "imx21-uart",
        .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX21_UART],
    },
    {
        .name = "imx6q-uart",
        .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX6Q_UART],

    },
    {
        /* sentinel */
    }
};

在上面的代码中,支持三种设备的串口,支持 imx1、imx21、imx6q 三种不同系列芯片,他们之间区别在于串口的 test 寄存器地址不同。 当总线成功配对平台驱动以及平台设备时,会将对应的 id_table 条目赋值给平台设备的 id_entry 成员,而平台驱动的 probe 函数是以平台设备为参数, 这样的话,就可以拿到当前设备串口的 test 寄存器地址了。

4. 设备获取驱动资源 demo

4.1 demo 源码

06_platform_bus/05_id_match_get_drv_info

4.2 开发板验证

将编译完的 sdriver_demo.ko、sdevice_demo.ko 拷贝到开发板,然后执行以下命令加载:

shell
insmod sdriver_demo.ko
insmod sdevice_demo.ko

然后就会看到以下打印信息:

image-20250120141723359

可以看到驱动中的 xxx_probe()函数执行,然后会有提示驱动和设备匹配成功。为了方便查看平台设备获取驱动中资源的情况,我们可以看一下 /sys/bus/platform/devices/sdev 中的属性文件的值:

shell
cat /sys/bus/platform/devices/sdev/sdev_attr_reg
cat /sys/bus/platform/devices/sdev/sdev_attr_type
image-20250120141916436

其实可以在匹配之前看一下的:

image-20250120143729078

二、驱动获取设备资源

1. 怎么获取设备资源?

这个就比较简单了,因为传到驱动中的是平台设备的哪个全局变量,我们可以直接访问平台设备的资源数组。

c
static int sdrv_probe(struct platform_device *pdev)
{
    PRT("probing platform device & driver!pdev->name=%s\n", pdev->name);
    // 添加设备特定的操作
    return 0;
}

例如上面的 sdrv_probe()函数,我们可以通过 pdev 直接访问的,也就是说 pdev 直接指向这个对象:

c
/**
 * 定义一个平台设备全局变量
 */
static struct platform_device g_sdev_platform = {
    .name = PLATFORM_DEV_NAME,                     // 设备名称
    .id = -1,                                      // 设备 ID
    .num_resources = ARRAY_SIZE(g_sdev_resources), // 资源数量
    .resource = g_sdev_resources,                  // 资源数组
    .dev.release = sdev_release,                   // 释放资源的回调函数
    .id_entry = &g_sdev_id,                        // 用于 ID 匹配时获取驱动返回的匹配信息
};

通过 platform_device.resource 就可以获取资源数组中所有数据,通过 platform_device.num_resources 可以获取到设备一共有多少个资源。

2. 获取资源的函数

linux 内核提供了几个函数来帮我们获取资源。其实前面都了解设备信息的时候都了解过了,就那几个函数。

2.1 platform_get_resource()

platform_get_resource() 定义如下:

c
struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num)
{
	int i;

	for (i = 0; i < dev->num_resources; i++) {
		struct resource *r = &dev->resource[i];

		if (type == resource_type(r) && num-- == 0)
			return r;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);

该函数返回该 dev 中某类型(type)资源中的第几个(num,其中 num 的值从 0 开始) 。

2.2 platform_get_irq()

platform_get_irq() 函数定义如下:

c
/**
 * platform_get_irq - get an IRQ for a device
 * @dev: platform device
 * @num: IRQ number index
 */
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
	//......
}

返回该 dev 所用的第几个(num)中断。

2.3 platform_get_resource_byname()

platform_get_resource_byname() 定义如下:

c
/**
 * platform_get_resource_byname - get a resource for a device by name
 * @dev: platform device
 * @type: resource type
 * @name: resource name
 */
struct resource *platform_get_resource_byname(struct platform_device *dev,
					      unsigned int type,
					      const char *name)
{
	int i;

	for (i = 0; i < dev->num_resources; i++) {
		struct resource *r = &dev->resource[i];

		if (unlikely(!r->name))
			continue;

		if (type == resource_type(r) && !strcmp(r->name, name))
			return r;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource_byname);

通过名字(name)返回该 dev 的某类型(type)资源。

2.4 platform_get_irq_byname()

c
/**
 * platform_get_irq_byname - get an IRQ for a device by name
 * @dev: platform device
 * @name: IRQ name
 */
int platform_get_irq_byname(struct platform_device *dev, const char *name)
{
	struct resource *r;

	if (IS_ENABLED(CONFIG_OF_IRQ) && dev->dev.of_node) {
		int ret;

		ret = of_irq_get_byname(dev->dev.of_node, name);
		if (ret > 0 || ret == -EPROBE_DEFER)
			return ret;
	}

	r = platform_get_resource_byname(dev, IORESOURCE_IRQ, name);
	return r ? r->start : -ENXIO;
}
EXPORT_SYMBOL_GPL(platform_get_irq_byname);

通过名字(name)返回该 dev 的中断号。

2.5 dev_get_platdata()

对于存放在 device 结构体中成员 platform_data 的软件信息,我们可以使用 dev_get_platdata() 函数来获取:

c
static inline void *dev_get_platdata(const struct device *dev)
{
	return dev->platform_data;
}

2.6 resource_size()

resource_size() 函数用于获取资源的大小,定义如下:

c
static inline resource_size_t resource_size(const struct resource *res)
{
	return res->end - res->start + 1;
}

3. 驱动获取设备资源 demo

3.1 demo 源码

06_platform_bus/06_drv_get_dev_resource

3.2 开发板验证

将编译完的 sdriver_demo.ko、sdevice_demo.ko 拷贝到开发板,然后执行以下命令加载:

shell
insmod sdriver_demo.ko
insmod sdevice_demo.ko

然后就会看到以下打印信息:

image-20250120153400862

可以看到驱动中的 xxx_probe()函数执行,然后会有提示驱动和设备匹配成功。然后便会在 xxx_probe()函数中分两种方式打印出从平台设备获取的资源。