0%

驱动和设备匹配过程

匹配过程及其问题

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
        6
        int 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->idPLATFORM_DEVID_NONE,则将名称设置为pdev->name
          • 如果pdev->idPLATFORM_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
          80
          int 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
          18
          struct 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
          11
          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);
          }

          首先对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
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
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;

int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);

int (*online)(struct device *dev);
int (*offline)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

int (*num_vf)(struct device *dev);

int (*dma_configure)(struct device *dev);

const struct dev_pm_ops *pm;

const struct iommu_ops *iommu_ops;

struct subsys_private *p;
struct lock_class_key lock_key;

bool need_parent_lock;
};

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}