匹配过程及其问题
1、匹配过程
在linux驱动中,目前学到了三种编写代码的方式。
第一种:传统编写。驱动和硬件写在一起,让linux内核和硬件交织在一起,这是早期linux开发的编写方式,不推荐使用,可以在初学时看看,也可以不看,不影响。
第二种:使驱动和设备硬件分开编写。pdrv主要编写linux的内核代码,pdev主要关于硬件的资源。分开有利管理,但是当设备多了之后,又出现了问题,代码太多,太繁。
第三种:使用设备树。驱动的代码基本不变,修改设备硬件的代码,把使用的设备硬件汇总为一个设备树的结构,每个节点都是一个设备硬件,代码简单、方便、便于移植。
第一种不说了。主要讲第二、三种的匹配过程,阅读一下内核源码,看一下整个流程。(目前刚刚开始学习不久,仅仅学到设备树,所以就目前掌握的开始写和搜的,如果以后学习到更深,在回来补充)
平台总线的平台驱动和平台设备匹配过程
1、首先先讲解两个函数。平台设备注册int platform_device_register(struct platform_device *);平台驱动注册platform_driver_register(drv)。这是两个注册函数,将我们自定义的平台设备和平台驱动注册到平台总线中去。他们会在平台总线中相遇,至于怎么找到双方,那就要有一个信物,两者个持一个,匹配上了就会成功。这个信物就是name(名称),下面会讲解。
平台设备注册:int platform_device_register(struct platform_device pdev)*
1
2
3
4
5
6int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
setup_pdev_dma_masks(pdev);
return platform_device_add(pdev);
}在平台设备注册函数中主要完成下面任务:
1、初始化struct platform_device结构体中的struct device结构体。[它将平台设备纳入到 Linux 设备模型中,使其能够被设备模型管理和访问。](#####1、在平台设备注册函数中,为什么要初始化struct device结构体?为什么说将平台设备纳入到 Linux 设备模型中?)
2、关于DMA的,这个函数用于设置设备的 DMA (Direct Memory Access) 掩码。DMA 允许设备直接访问内存,而无需 CPU 的干预,从而提高数据传输效率。DMA 掩码用于限制设备可以访问的内存地址范围。
3、这个函数是平台设备的注册的真正执行着,将平台设备添加到平台总线的设备列表中。触发驱动程序等到匹配。
在platform_device_add(pdev);函数中,找到源码解读。
1、参数检查和初始化- 首先检查pdev指针是否为空。
- 如果pdev的父设备为空,则将其设置成平台总线。(可见平台总线相当于根节点,下面挂着一串平台设备列表)
- 设备pdev的总线类型为平台总线类型(platform_bus_type)(这个平台总线类型是平台匹配的重要部分)。
2、设置设备名称
- 根据
pdev->id
的值,使用dev_set_name
函数设备pdev
的设备名称。 - 如果
pdev->id
为默认值,则将名称设置为pdev->name.pdev->id
。 - 如果
pdev->id
为PLATFORM_DEVID_NONE
,则将名称设置为pdev->name
。 - 如果
pdev->id
为PLATFORM_DEVID_AUTO
,则将名称设置为pdev->name.pdev->id.auto
。
3、注册设备资源
- 遍历
pdev->resource
数组,该数组中存放着设备使用的资源信息(例如内存地址、中断号等) - 为每个资源设置名称
- 为每个资源设置父资源
4、添加设备
- 打印注册信息
- 调用
device_add
函数将设备添加到linux设备模型中。
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80int platform_device_add(struct platform_device *pdev)
{
u32 i;
int 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) {
ret = insert_resource(p, r);
if (ret) {
dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r);
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--) {
struct resource *r = &pdev->resource[i];
if (r->parent)
release_resource(r);
}
err_out:
return ret;
}注册平台设备需要一个struct platform_device *pdev结构体,具体内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
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;
};
平台驱动注册函数platform_driver_register(drv)
在linux内核代码中定义了一个宏定义关于平台驱动注册函数。
1
2
3
4#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);在其中我们可以看到platform_driver_register是通过宏定义代表__platform_driver_register这个函数的。在函数中有两个参数分别是platform_driver *、struct module *。其中我们重要关心的是platform_driver 结构体,这里的和上面已经讲过的platform_device一样,是在linux内核定义好的设备框架基础上田间自定义的构建成的。
在__platform_driver_register结构体中的源码如下:
1
2
3
4
5
6
7
8
9
10
11int __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);
}首先对driver进行一些初始化。在其中我们关心的是
drv->driver.bus = &platform_bus_type;
、drv->driver.probe = platform_drv_probe;
、drv->driver.remove = platform_drv_remove;
这三个行代码,其中driver.bus指定了总线类型,也是与设备匹配的关键。下面两个probe、remove两个是一个中间层函数,他们会获取platform_device的信息,然后调用我们在platform_device中自定义的probe函数。
2、匹配中的各种问题
1、在平台设备注册函数中,为什么要初始化struct device结构体?为什么说将平台设备纳入到 Linux 设备模型中?
1、struct device是linux设备模型的基础,所有的设备都必须通过struct device纳入到设备模型中才能够被内核有效的管理。
2、linux设备模型是内核中用于管理所有设备的框架,它提供了一套统一的接口和机制。在linux中所有的设备,例如平台设备、PCI设备、USB设备,这些统一是在框架的基础上写入一些自定义的构成的。
2、平台总线类型和平台匹配的关系。
1、平台总线类型的源码如下,其中 struct bus_type platform_bus_type
定义了平台总线的类型。在liunx设备模型中,总线类型是链接设备和驱动程序的桥梁。平台总线是一中虚拟的总线,用于管理那些无法通过标准总线(如PCI、USB)枚举的设备。
1 | struct bus_type platform_bus_type = { |
2、在上面说的两个结构体中,有两个函数关乎与匹配相关。.match = platform_match,
、int (*match)(struct device *dev, struct device_driver *drv);
。下面是 platform_match
的代码。
- 在开始时传入设备和驱动的结构体。
- 检查
driver_override
参数是 否被设置。driver_override
允许用户强制指定设备要使用的驱动程序。 - 首先尝试使用设备树,在
of_driver_match_device
函数中会根据设备树中的compatible
属性里匹配设备和驱动程序。成功则直接返回。 - 然后在尝试ACPI(Advanced Configuration and Power Interface)进行匹配。在
acpi_driver_match_device
函数中会根据ACPI描述符进行匹配。成功则直接返回。 - 如果定义了
id_table
尝试使用ID表进行匹配。 - 如果上面的匹配都失败了,则尝试使用设备的名称和驱动的名称进行匹配。
1 | static int platform_match(struct device *dev, struct device_driver *drv) |