LV06-05-linux平台总线模型-04-驱动和设备资源的获取

前面我们已经知道了怎么让平台设备和平台驱动进行匹配,那么匹配的目录肯定是驱动获取设备中的资源了,那怎么获取?设备能获取驱动的信息吗,能的话,又该怎么获取呢?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、设备获取驱动的资源

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

1. struct platform_device_id

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

1
2
3
4
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() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
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的串口为例,具体看下这个结构体的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 · 苏木/imx6ull-driver-demo - 码云 - 开源中国

4.2 开发板验证

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

1
2
insmod sdriver_demo.ko
insmod sdevice_demo.ko

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

image-20250120141723359

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

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

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

image-20250120143729078

二、驱动获取设备资源

1. 怎么获取设备资源?

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

1
2
3
4
5
6
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直接指向这个对象:

1
2
3
4
5
6
7
8
9
10
11
/**
* 定义一个平台设备全局变量
*/
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() 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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() 函数定义如下:

1
2
3
4
5
6
7
8
9
/**
* 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() 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 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() 函数来获取:

1
2
3
4
static inline void *dev_get_platdata(const struct device *dev)
{
return dev->platform_data;
}

2.6 resource_size()

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

1
2
3
4
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 · 苏木/imx6ull-driver-demo - 码云 - 开源中国

3.2 开发板验证

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

1
2
insmod sdriver_demo.ko
insmod sdevice_demo.ko

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

image-20250120153400862

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