文章目录

  • 一、设备驱动模型的三大组件
    • 1. 总线
      • 1. 总线、设备、驱动关系
      • 2. 总线数据结构 bus_type
      • 3.bus_type声明实例
      • 4.总线私有数据bus_type_private
      • 5.总线注册bus_register()
    • 2. 总线属性和总线方法
      • 1. 总线的属性bus_attribute
      • 2. 创建和删除总线属性
      • 3. 总线上的方法
    • 3. 设备
      • 1. device结构体
      • 2. 设备注册和注销
      • 3. 设备属性
    • 4. 驱动
      • 1. 设备驱动device_driver
      • 2. 驱动举例
      • 3. 驱动程序注册和注销
      • 4. 驱动的属性
  • 二、平台设备模型
    • 1. 平台设备模型
    • 2. 平台设备
      • 1. 平台设备结构体(platform_device)
      • 2. 平台设备的资源
      • 3. 平台设备相关的操作函数
    • 3. 平台设备驱动
      • 1. probe() 函数
      • 2. remove()函数
      • 3. shutdown()、suspend()和resume()函数
    • 4. 平台设备驱动的注册和注销
      • 1. 注册函数 platform_driver_register()
      • 2. 注销函数 platform_driver_unregister()
    • 5. 混杂设备
    • 6. 混杂设备的注册和注销
      • 1. 注册函数misc_register()
      • 2. 注销函数misc_deregister()
  • 三、 Linux设备驱动和设备匹配过程
    • 1. 匹配过程

一、设备驱动模型的三大组件

设备驱动模型有三个重要组件,分别是总线(bus type)、设备(device)和驱动(driver)。
下面对这三个重要组件进行分别介绍。


1. 总线

从硬件结构上来讲,物理总线有数据总线和地址总线。物理总线是处理器与一个或者
多个设备之间的通道。在设备驱动模型中,所有设备都通过总线连接,此处的总线与物理总线不同,总线是物理总线的一个抽象,同时还包含一些硬件中不存在的虚拟总线。在设备驱动模型中,驱动程序是附属在总线上的。下面将首先介绍总线、设备和驱动之间的关系。

1. 总线、设备、驱动关系

在设备驱动模型中,总线、设备和驱动三者之间紧密联系。如图11.6 所示,在/sys目
录下,有一个bus目录,所有的总线都在bus目录下有一个新的子目录。一般一个总线目录有一个设备目录、一个驱动目录和一些总线属性文件。设备目录中包含挂接在该总线上的设备,驱动目录包含挂接在总线上的驱动程序。设备和驱动程序之间通过指针互相联系。这些关系从图11.6中可以看出。

如图11.6所示,总线上的设备链表有3个设备,设备1、设备2和设备3。总线上的
驱动链表也有3个驱动程序,驱动1、驱动2和驱动3。其中虚线箭头表示设备与驱动的绑定关系,这个绑定是在总线枚举设备时设置的。这里,设备1与驱动2绑定,设备2与驱动1绑定,设备3与驱动3绑定。

2. 总线数据结构 bus_type

在Linux设备模型中,总线用 bus_type 表示。内核支持的每一条总线都由一个 bus_type 对象来描述。其代码如下:

  • 第02行的 name 成员是总线的名字,例如PCI。
  • 第03~05行分别是三个属性,与kobject对应的属性类似。设备驱动模型的每个层次都有一个属性。
  • 第06~15行是总线匹配、探测、电源管理等相关的函数。在具体用到时,将详细
    解释。
  • 第16行是devpm_ops是与电源管理相关的函数集合。
  • 第I7行的bus type private表示的是总线的私有数据。
3.bus_type声明实例

在Linux中,总线不仅是物理总线的抽象,还代表一些虚拟的总线。例如,平台设备总线(platform)就是虚拟总线。值得注意的是bus_type中的很少成员需要自己定义,内核负责完成大部分的功能。例如c97声卡的总线定义就非常简单,如果去掉电源管理的函数,那么ac97总线就只有match() 函数的定义了。其总线代码如下:

4.总线私有数据bus_type_private

总线私有数据结构 bus_type_private 包含3个主要的成员。一个kset的类型的 subsys
容器,表示一条总线的主要部分;一个总线上的驱动程序容器drivers_kset;一个总线上的设备容器 devices_kset。

5.总线注册bus_register()

如果为驱动程序定义了一条新的总线,那么需要调用 bus_register() 函数进行注册。这
个函数有可能会调用失败,所以有必要检测它的返回值。如果函数调用成功,那么一条新的总线将被添加到系统中。可以在sysfs文件系统的/sys/bus目录下看到它。该函数的代码如下:




bus_register() 函数对 bus_type 进行注册,当从系统中删除一条总线时,应该使用
bus_unregister() 函数。该函数的原型如下:


2. 总线属性和总线方法

bus_type 中还包含表示总线属性和总线方法的成员。属性使用成员 bus_attrs 表示,相对该成员介绍如下:

1. 总线的属性bus_attribute

在Linux设备驱动模型中,几乎每一层都有添加属性的函数,bus_type也不例外。总
线属性用bus_attribute表示,由bus_type的bus_attrs指针指向。bus_attribute属性如以下代码所示。

bus_attribute 中的 attribute 属性与 kobject 中的属性结构体是一样的。bus_attribute 总线属性也包含两个显示和设置属性值的函数,分别是show() 和store() 函数。可以使用BUS_ATTR宏来初始化一个 bus_attribute 结构体,该宏的定义如下:


对该宏进行扩展,就能得到 bus_attr_config_time 属性如下的定义:

2. 创建和删除总线属性

注意:下面两个函数,与 kobject 时的情况一样, sysfs_create_file(struct kobject *kobj, const struct attribute *attr) 和 sysfs_remove_file(struct kobject *kobj, const struct attribute *attr) 。用来在默认属性文件之外,增加额外特殊的属性文件。

创建总线的属性,需要调用 bus_create_file(struct bus_type *, struct bus_attribute *)函数,该函数的原型如下:

当不需要某个属性时,可以使用 bus_remove_file(struct bus_type *, struct bus_attribute *) 函数删除该属性,该函数的原型如下:

3. 总线上的方法

在 bus_type 结构体中,定义了许多方法。这些方法都是与总线相关的,例如电源管理,新设备与驱动匹配的方法。这里主要介绍 match() 函数和 uevent() 函数,其他函数在驱动中几乎不需要使用。match() 函数的原型如下:

当一条总线上的新设备或者新驱动被添加时,会一次或多次调用该函数。如果指定的
驱动程序能够适用于指定的设备,那么该函数返回非0值,否则,返回0。当定义一种新总线时,必须实现该函数,以使内核知道怎样匹配设备和驱动程序。一个match() 函数的例子如下:



当用户空间产生热插拔事件前,可能需要内核传递一些参数给用户程序,这里只能使
用环境变量来传递参数。传递环境变量的函数由 uevent() 实现,该函数的原型如下:

该函数只有在内核支持热插拔事件 (CONFIG_HOTPLUG) 时,才有用,否则该函数
被定义为NULL值。以amba_uevent() 函数为例,该函数只有在支持热插拔时,才被定义。函数体中调用了add_uevent_var() 函数添加了一个新的环境变量,代码如下:



3. 设备

在Linux设备驱动模型中,每一个设备都由一个device结构体来描述。device 结构包含了设备所具有的一些通用信息。对于驱动开发人员来说,当遇到新设备时,需要定义一个新的设备结构体,将 device 作为新结构体的成员。这样就可以在新结构体中定义新设备的一些信息,而设备通用的信息就使用 device 结构体来表示。使用device结构体的另一个好处是,可以通过device轻松地将新设备加入设备驱动模型的管理中。下面对device结构体进行简要的介绍。

1. device结构体

device中的大多函数被内核使用,驱动开发人员不需要关注,这里只对该结构体的主
要成员进行介绍。该结构体的主要成员如下:

  • 第03行指向父设备,设备的父子关系表示,子设备离开了父设备就不能工作。
  • 第05行的 bus_id 字段,表示总线上一个设备的名字。例如 PCI 设备使用了标准的
    PCID格式,其格式为:域编号、总线编号、设备编号和功能编号。
  • 第08行的 device_type 结构中包含了一个用来对设备操作的函数。
  • 第09行的 bus 指针指向设备所属的总线。
  • 第10行的 driver 指针指向设备的驱动程序。
  • 第16行的 release 函数。当指向设备的最后一个引用被删除时,内核会调用该方法。所有向内核注册的device结构都必须有一个 release() 方法,否则内核就会打印出错信息。

device_type 与 kobj_type 有相似的结构:


2. 设备注册和注销

设备必须注册之后,才能使用。在注册device结构前,至少要设置parent、bus_id、
bus 和 release成员。常用的注册和注销函数如下代码所示。


为了使读者对设备注册有一个清楚的认识,下面的代码完成一个简单的设备注册。

这段代码完成一个设备的注册,其 parent 和 bus 成员都是NULL。设备的名字是test_device。释放函数test_device_release() 并不做任何实质的工作。这段代码调用成功后,会在sysfs文件系统的/sys/devices目录中,看到一个新的目录test_device,该目录就对应这里注册的设备。设备的注销函数是device_unregister()。该函数的原型如下:

3. 设备属性

每一个设备都可以有相关的一些属性,在sysfs文件系统中以文件的形式来表示。设备属性的定义如下:

在写程序时,可以使用宏 DEVICE_ATTR 定义 attribute 结构,这个宏的定义如下:


该宏使用 dev_attr 作为前缀构造属性名,并传递属性的读写模式,读函数和写函数。另外可以使用下面两个函数对属性文件进行实际的处理。

device_create_file() 函数用来在device所在的目录下创建一个属性文件;
device_remove_file() 函数用来在device所在的目录下删除一个属性文件。

4. 驱动

在设备驱动模型中,记录了注册到系统中的所有设备。有些设备可以使用,有些设备不可以使用,原因是设备需要与对应的驱动程序绑定才能使用,本节将重点介绍设备驱动程序。

1. 设备驱动device_driver

一个设备对应一个最合适的设备驱动程序。但是,一个设备驱动程序就有可能适用多个设备。设备驱动模型自动地探测新设备的产生,并为其分配最合适的设备驱动程序,这样新设备就能够使用了。驱动程序由以下结构体定义:

  • 第03行的 bus 指针指向驱动所属的总线。
  • 第07行的probe() 函数用来探测设备。也就是当总线设备驱动发现一个可能由它处理的设备时,会自动调用probe()方法。在这个方法中会执行一些硬件初始化工作。
  • 第08行的remove() 函数在移除设备时调用。同时,如果驱动程序本身被卸载,那么它所管理的每一个设备都会调用remove0方法。
  • 第09~11行是当内核改变设备供电状态时,内核自动调用的函数。
  • 第12行是驱动所属的属性组,属性组定义了一组驱动共用的属性。
  • 第14行表示驱动的私有数据,可以用来存储与驱动相关的其他信息。driver private结构体定义如下:

2. 驱动举例

在声明一个device_driver时,一般需要probe()、remove()、name、bus()、supsend()和 resume() 等成员。下面是一个PCI的例子:


该驱动被挂接在平台总线(platform_bus_type)上,这是一个很简单的例子。但是在现实中,大多数驱动程序会带有自己特定的设备信息,这些信息不是 device_driver 可以全部包含的。比较典型的例子是pci_driver。.


pci_driver 是由 device_driver 衍生出来的,pci_driver中包含了PCI设备特有的信息。

3. 驱动程序注册和注销

设备驱动的注册与注销函数如下所示。


driver_register() 函数的功能是向设备驱动程序模型中插入一个新的device_driver 对象。当注册成功后,会在sysfs文件系统下创建一个新的目录。该函数的代码如下:


driver_unregister()函数用来注销驱动程序。该函数首先从驱动组中删除该驱动,然后再从总线中移除该驱动程序,代码如下:

4. 驱动的属性

驱动的属性可以使用 driver_attribute 表示,该结构体的定义如下:

使用下面的函数可以在驱动所属目录创建和删除一个属性文件。属性文件中的内容可以用来控制驱动的某些特性,这两个函数是:


二、平台设备模型

1. 平台设备模型

Linux2.6 起引入了一套新的驱动管理和注册模型,即平台设备 platform_device 和平台驱动 platform_driver。Linux 中大部分的设备驱动,都可以使用这套机制,设备用platform_device 表示,驱动用 platform_driver 表示。

平台设备模型与传统的device和driver模型相比,一个十分明显的优势在于平台设备模型将设备本身的资源注册进内核,由内核统一管理。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。通过平台设备模型开发底层设备驱动的大致流程如图13.2所示。


2. 平台设备

在 Linux 设备驱动中,有一种设备叫做平台设备。平台设备是指处理器上集成的额外功能的附加设备,如Watch Dog、IIC、IIS、RTC、ADC等设备。这些格外功能设备是为了节约硬件成本、减少产品功耗、缩小产品形状而集成到处理器内部的。需要注意的是,平台设备并不是与字符设备、块设备和网络设备并列的概念,而是一种平行的概念,其从另一个角度对设备进行了概括。如果从内核开发者的角度来看,平台设备的引入是为了更容易地开发字符设备、块设备和网络设备驱动。

1. 平台设备结构体(platform_device)

平台设备用 platform_device 结构体来描述,其结构体的定义如下:

s3c2440 处理器的内部硬件大多与 s3c2410 处理器的相同,所以内核并没有对s3c2440 处理器的驱动代码进行升级,而沿用了 s3c2410 处理器的驱动。在文件 linux-2.6.29.4\arch\arm\plat-s3c24xx\devs.c 中定义了处理器的看门狗平台设备,代码如下:

struct platform device s3c_device wdt =
{.name          = "s3c2410-wdt",                   /*平台设备的名字*.id            = -1,                              /*一般设为-1*/.num_resources = ARRAY_SIZE(s3c_wdt_resource),    /*资源数量*/.resource      = s3c_wdt_resource,                /*资源的指针*/
}:
2. 平台设备的资源

为了便于统一管理平台设备的资源,在 platform_device 结构体中定义了平台设备所使用的资源。这些资源都是与特定处理器相关的,需要驱动开发者查阅相关的处理器数据手册来编写。s3c2440处理器的看门狗资源如下代码所示。


从代码中可以看出 s3c2440 处理器只使用了 I/O 内存和 IRQ 资源。这里的 I/O 内存指向看门狗的WTCON、WTDAT和WTCNT寄存器。为了更清楚地了解资源的概念,将资源结构体resource列出如下:


resource 结构的 start 和 end 的类型是无符号整型。在32位平台上是32位整型;在64位平台上是64位整型。flags 标志表示资源的类型,有I/O端口(IORESOURCE_IO)、内存资源(IORESOURCE_MEM)、中断号(IORESOURCE_IRQ) 和 DMA资源(IORESOURCE_DMA) 等。parent、sibling和child指针用于将资源构建成一个树,加快内核的资源访问和管理,驱动程序员不需要关心。

3. 平台设备相关的操作函数

通过 platform_add_devices() 函数可以将一组设备添加到系统中,其主要完成以下两个功能:

  1. 分配平台设备所使用的资源,并将这些资源挂接到资源树中。
  2. 初始化device设备,并将设备注册到系统中。

其函数的原型如下:


该函数的第1个参数 devs 是平台设备数组的指针,第2个参数是平台设备的数量。

通过platform_get_resource() 函数可以获得平台设备中的resource资源。

该函数的第1个参数 dev 是平台设备的指针。第2个参数 type 是资源的类型,这些类型可以是I/O端口(IORESOURCE_IO)、内存资源(IORESOURCE_MEM)、中断号(IORESOURCE_IRQ) 和 DMA资源(IORESOURCE_DMA)等。第3个参数num是同种资源的索引。例如一个平台设备中,有3个IORESOURCE_MEM 资源,如果要获得第2个资源,那么需要使num等于 1 。从平台设备 pdev 中获得第一个内存资源的例子如下所示。


3. 平台设备驱动

每一个平台设备都对应一个平台设备驱动,这个驱动用来对平台设备进行探测、移除、关闭和电源管理等操作。平台设备用驱动platform device结构体来描述,其定义如下:


在文件 linux-2.6.29.4/drivers/watchdog/s3c2410wdt.c 中定义了处理器的看门狗平台设备驱动。其中一些函数没有用处,所以没有定义,代码如下:


1. probe() 函数

一般来说,在内核启动时,会注册平台设备和平台设备驱动程序。内核将在适当的时候,将平台设备和平台设备驱动程序连接起来。连接的方法,是用系统中的所有平台设备和所有己经注册的平台驱动进行匹配。匹配函数由platform_match() 实现,代码如下:

该函数比较平台设备的name字段驱动的name字段,相同时,表示匹配成功:不同时,表示驱动不匹配该设备。函数匹配成功返回1,失败时返回0。该函数将由内核自己调用,当设备找到对应的驱动时,会触发probe() 函数,所以probe() 函数一般是驱动加载成功后调用的第一个函数,在该函数中可以申请设备所需要的资源。

2. remove()函数

如果设备可以移除,为了减少所占用的系统资源,那么应该实现remove()函数。该函数一般与probe() 函数对应,在probe() 函数中申请的资源,应该在remove() 函数中释放。

3. shutdown()、suspend()和resume()函数

shutdown() 函数当设备断电或者关闭时被调用:suspend() 函数使设备处于低功耗状态:resume() 函数使设备从低功耗恢复到正常状态。


4. 平台设备驱动的注册和注销

内核关于平台设备最主要的两个函数是注册和注销函数,本节将对这两个函数进行
说明。

1. 注册函数 platform_driver_register()

需要将平台设备驱动注册到系统中才能使用,内核提供了 platform_driver_register() 函数实现这个功能。该函数如下:

在平台驱动 platform_driver 和其父结构 driver 中相同的方法。如果平台设备驱动中定义probe() 方法,那么内核将会调用平台驱动中的方法;如果平台设备驱动中没有定义probe() 方法,那么将调用 driver 中的对应方法。platform_driver_register() 函数用来完成这种功能,并注册设备驱动到内核中。platform_driver 注册到内核后,内核调用驱动的关系如图13.3 所示。

2. 注销函数 platform_driver_unregister()

当模块卸载时需要调用函数 platform_driver_unregister() 注销平台设备驱动,该函数的原型如下:

/*** platform_driver_unregister - unregister a driver for platform-level devices* @drv: platform driver structure*/
void platform_driver_unregister(struct platform_driver *drv)
{driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_unregister);

5. 混杂设备

混杂设备并没有一个明确的定义。由于设备号比较紧张,所以一些不相关的设备可以使用同一个主设备,不同的次设备号。主设备号通常是10。由于这个原因,一些设备也可以叫做混杂设备。混杂设备用结构体miscdevice来表示,代码如下所示:

混杂设备中的一个重要成员是fops, 其是一个file operations的指针。这里的
file_operations 结构与 cdev 中的结构一样。那么为什么有了 cdev 还需要 miscdevice 呢,这只是从另一种更灵活的方式来写设备驱动而已。开发者完全可以用 cdev 代替miscdevice 实现看门狗驱动程序。看门狗的混杂设备定义如下代码所示。


6. 混杂设备的注册和注销

驱动程序中需要对混杂设备进行注册和注销,内核提供了misc_register() 和misc_deregister() 两个函数。

1. 注册函数misc_register()

混杂设备的注册非常简单,只需要调用misc_register() 函数,并传递一个混杂设备的指针就可以了。其原型如下:

该函数内部检查次设备号是否合法,如果次设备号被占用,则返回设备忙状态。如果
miscdevice 的成员 minor 为255,则尝试动态申请一个次设备号。当次设备号可用时,函数会将混杂设备注册到内核的设备模型中去。

2. 注销函数misc_deregister()

与misc_registe() 函数对应的是 misc_deregister() 函数,其原型如下:


三、 Linux设备驱动和设备匹配过程

在写驱动时我们都会用到一些注册函数比如:platform_driver_registerspi_register_driveri2c_add_driver,接下来我们就一路追踪看看内核是怎样将我们的驱动和设备匹配起来并且调用我们写的probe 函数,在此我们用 platform_driver 为例子。

struct platform_driver {int   (*probe)(struct platform_device *);  //驱动和设备匹配成功之后调用int   (*remove)(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;
};struct device_driver {const char        *name;  //与设备匹配时会用到的驱动名,一样就匹配成功struct bus_type   *bus;  //在注册函数platform_driver_register中指定struct module     *owner;const char        *mod_name;    /* used for built-in modules */bool suppress_bind_attrs;    /* disables bind/unbind via sysfs */enum probe_type probe_type;const struct of_device_id    *of_match_table; //与设备树匹配,名称一样就匹配成功const struct acpi_device_id   *acpi_match_table;int  (*probe) (struct device *dev);  //调用注册函数指定int  (*remove) (struct device *dev);void (*shutdown) (struct device *dev);int  (*suspend) (struct device *dev, pm_message_t state);int  (*resume) (struct device *dev);const struct attribute_group  **groups;const struct dev_pm_ops  *pm;struct  driver_private *p;
};

简单来说,我们在编写驱动时指定的 struct platform_device_id *id_table、const char *name、const struct of_device_id *of_match_table 将是判断我们的驱动和设备是否能匹配的依据。


1. 匹配过程

static int platform_drv_probe(struct device *_dev)
{struct platform_driver *drv = to_platform_driver(_dev->driver);struct platform_device *dev = to_platform_device(_dev);int ret;if (ACPI_HANDLE(_dev))acpi_dev_pm_attach(_dev, true);ret = drv->probe(dev);if (ret && ACPI_HANDLE(_dev))acpi_dev_pm_detach(_dev, true);if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {dev_warn(_dev, "probe deferral not supported\n");ret = -ENXIO;}return ret;
}
---------------------------------------------------------------------
#define platform_driver_register(drv) \__platform_driver_register(drv, THIS_MODULE)---------------------------------------------------------------------
int __platform_driver_register(struct platform_driver *drv,struct module *owner)
{drv->driver.owner = owner;drv->driver.bus = &platform_bus_type;  驱动的总线类型指向platform_bus_typedrv->driver.probe = platform_drv_probe;drv->driver.remove = platform_drv_remove;drv->driver.shutdown = platform_drv_shutdown;return driver_register(&drv->driver);  //在此函数中进行注册
}

在这个函数中指定了 drv->driver.bus = &platform_bus_type

struct bus_type platform_bus_type = {.name      = "platform",.dev_groups = platform_dev_groups,.match       = platform_match,  //最后匹配的时候会调用.uevent     = platform_uevent,.pm      = &platform_dev_pm_ops,.force_dma  = true,
};

我们继续来看 driver_register 如何一步步调用到我们的 platform_bus_type .match (platform_match) 函数。


/*** driver_register - register driver with bus* @drv: driver to register** We pass off most of the work to the bus_add_driver() call,* since most of the things we have to do deal with the bus* structures.*/
int driver_register(struct device_driver *drv)
{int ret;struct device_driver *other;BUG_ON(!drv->bus->p);//检测总线的操作函数和驱动的操作函数是否同时存在if ((drv->bus->probe && drv->probe) ||(drv->bus->remove && drv->remove) ||(drv->bus->shutdown && drv->shutdown))printk(KERN_WARNING "Driver '%s' needs updating - please use ""bus_type methods\n", drv->name);//查找这个驱动是否已经在总线上注册,并增加引用计数,若已经注册,则返回提示信息other = driver_find(drv->name, drv->bus);if (other) {printk(KERN_ERR "Error: Driver '%s' is already registered, ""aborting...\n", drv->name);return -EBUSY;}ret = bus_add_driver(drv);  //如果之前没注册,就在此注册if (ret)return ret;ret = driver_add_groups(drv, drv->groups);if (ret) {bus_remove_driver(drv);return ret;}kobject_uevent(&drv->p->kobj, KOBJ_ADD);return ret;
}

可以看到 driver_register () 中调用了 bus_add_driver 完成注册,我们继续跟进去看看。

struct driver_private {struct kobject kobj;struct klist klist_devices;struct klist_node knode_bus;struct module_kobject *mkobj;struct device_driver *driver;
};
#define to_driver(obj) container_of(obj, struct driver_private, kobj)static void driver_release(struct kobject *kobj)
{struct driver_private *drv_priv = to_driver(kobj);pr_debug("driver: '%s': %s\n", kobject_name(kobj), __func__);kfree(drv_priv);
}static struct kobj_type  driver_ktype = {.sysfs_ops   = &driver_sysfs_ops,.release   = driver_release,
};---------------------------------------------------------/*** bus_add_driver - Add a driver to the bus.* @drv: driver.*/
int bus_add_driver(struct device_driver *drv)
{struct bus_type *bus;struct driver_private *priv;int error = 0;//用于增加该bus所属的顶层bus的kobject的引用计数,返回的是其所属的顶层bus的指针。bus = bus_get(drv->bus);if (!bus)return -EINVAL;pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);priv = kzalloc(sizeof(*priv), GFP_KERNEL);if (!priv) {error = -ENOMEM;goto out_put_bus;}klist_init(&priv->klist_devices, NULL, NULL);priv->driver = drv;drv->p = priv;//指向顶层的bus的p->drivers_kset//设置私有数据的父容器,在这一步中,设置了kset为platform下的drivers_kset结构,也就是drivers那个目录 priv->kobj.kset = bus->p->drivers_kset;error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name);if (error)goto out_unregister;klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);if (drv->bus->p->drivers_autoprobe) {//drivers_autoprobe在初始化的时候定义为1,//在我们注册总线bus_register()中,将值置为1.//系统则会调用下面的driver_attach函数进行驱动与设备的匹配if (driver_allows_async_probing(drv)) {pr_debug("bus: '%s': probing driver %s asynchronously\n",drv->bus->name, drv->name);async_schedule(driver_attach_async, drv);} else {error = driver_attach(drv);if (error)goto out_unregister;}}module_add_driver(drv->owner, drv);error = driver_create_file(drv, &driver_attr_uevent);if (error) {printk(KERN_ERR "%s: uevent attr (%s) failed\n",__func__, drv->name);}error = driver_add_groups(drv, bus->drv_groups);if (error) {/* How the hell do we get out of this pickle? Give up */printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",__func__, drv->name);}if (!drv->suppress_bind_attrs) {error = add_bind_files(drv);if (error) {/* Ditto */printk(KERN_ERR "%s: add_bind_files(%s) failed\n",__func__, drv->name);}}return 0;out_unregister:kobject_put(&priv->kobj);/* drv->p is freed in driver_release()  */drv->p = NULL;
out_put_bus:bus_put(bus);return error;
}

root@xxx:/home/admin# ls /sys/bus/platform/drivers
acpi-ged          bxt_whiskey_cove_pmic  cht_dollar_cove_ti_pmic  cpld_i2c            gpio_ich         ipmi_ssif            pcspkr                vesa-framebuffer
acpi-wmi          byt_crystal_cove_pmic  cht_whiskey_cove_pmic    crystal_cove_pwm    i2c_designware   iTCO_wdt             reg-dummy
alarmtimer        byt_gpio               clk-fch                  dw-apb-uart         icelake-pinctrl  lewisburg-pinctrl    serial8250
amd_gpio          cedarfork-pinctrl      clk-lpt                  efi-framebuffer     intel_pmc_bxt    mlx-wdt              sunrisepoint-pinctrl
axp288_pmic_acpi  cherryview-pinctrl     clk-pmc-atom             geminilake-pinctrl  intel_rapl_msr   of_fixed_clk         syscon
broxton-pinctrl   cht_crystal_cove_pmic  coretemp                 gpio-clk            ipmi             of_fixed_factor_clk  tigerlake-pinctrl

代码比较长,但我们要抓住重点,可以看到 bus_add_driver 调用了driver_attach来继续完成注册的任务,那我们继续看看这个函数:

/*** driver_attach - try to bind driver to devices.* @drv: driver.** Walk the list of devices that the bus has on it and try to* match the driver with each one.  If driver_probe_device()* returns 0 and the @dev->driver is set, we've found a* compatible pair.*/
int driver_attach(struct device_driver *drv)
{return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}跟进bus_for_each_dev可以看到最终调用的是 __driver_attach
那继续看函数__driver_attachstatic int __driver_attach(struct device *dev, void *data)
{struct device_driver *drv = data;int ret;/** Lock device and try to bind to it. We drop the error* here and always return 0, because we need to keep trying* to bind to devices and some drivers will return an error* simply if it didn't support the device.** driver_probe_device() will spit a warning if there* is an error.*///当设备和驱动的名字不匹配的时候返回的是0,然后就会调用下面的return 0;ret = driver_match_device(drv, dev);if (ret == 0) {/* no match */return 0;} else if (ret == -EPROBE_DEFER) {dev_dbg(dev, "Device match requests probe deferral\n");driver_deferred_probe_add(dev);} else if (ret < 0) {dev_dbg(dev, "Bus failed to match device: %d", ret);return ret;} /* ret > 0 means positive match */if (dev->parent)   /* Needed for USB */device_lock(dev->parent);device_lock(dev);if (!dev->driver)driver_probe_device(drv, dev);//调用探测函数进行探测,并且调用platform_driver中的probe函数device_unlock(dev);if (dev->parent)device_unlock(dev->parent);return 0;
}

可以看到调用了函数 driver_match_device ,并且下面也做出了判断是否匹配成功,成功就调用了下面的 driver_probe_device 来调用 probe 函数,我们先来看看函数 driver_match_device 黎明就在眼前。

static inline int driver_match_device(struct device_driver *drv,struct device *dev)
{return drv->bus->match ? drv->bus->match(dev, drv) : 1; //调用match函数,如果没用则默认返回1
}

就这一句,又回到最初的起点,platform_match 在最开始调用注册函数时指定的。


/*** platform_match - bind platform device to platform driver.* @dev: device.* @drv: driver.** Platform device IDs are assumed to be encoded like this:* "<name><instance>", where <name> is a short description of the type of* device, like "pci" or "floppy", and <instance> is the enumerated* instance of the device, like '0' or '42'.  Driver IDs are simply* "<name>".  So, extract the <name> from the platform_device structure,* and compare it against the name of the driver. Return whether they match* or not.*/
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);
}

我们可以看到匹配的顺序:
1、先用设备树中的 compatible 属性和 platform_driver 中的 driver 中的 of_match_table 来匹配
2、再用 platform_driver 中的 id_table 中的 name 和 platform_device 中的 name来匹配
3、最后用 platform_driver 中的 driver 中的 name 和 platform_device 中的name来匹配


参考链接

linux驱动 平台设备驱动模型相关推荐

  1. Linux内核平台设备驱动模型platform_device和platform_driver框架

    关于Linux平台设备驱动模型,并不是创建新的设备分类,是在原有的字符设备基础上使用,将设备和驱动分开,生成两个.ko文件.         Linux内核维护一个全局设备链表,对应的总线会将驱动和设 ...

  2. Linux Platform平台设备驱动模型

    Linux总线设备驱动模型主要包含总线.设备.驱动三个部分. 现实总线:一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI.USB.I2C.SPI等的设备而言,这自然不是问 ...

  3. 设备驱动,字符设备驱动、(总线)设备驱动模型、sysfs文件系统、平台设备驱动

    以下内容转载于微信公众号:嵌入式企鹅圈.如有侵权,请告知删除. 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术. 对于初学者来说会非常 ...

  4. 字符设备驱动、平台设备驱动、设备驱动模型、sysfs的比较和关联

    参考原文:https://www.kancloud.cn/yueqian_scut/emlinux/106829 对原文笔误地方做了修改.重新排版 目录 字符设备驱动.平台设备驱动.设备驱动模型.sy ...

  5. Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍

    文章目录 一.Linux设备模型介绍 (1)设备驱动模型总体介绍 (2)设备驱动模型文件表现 (3)设备驱动模型工作原理 [1]总线 [2]设备 [3]驱动 [4]注册流程 二.平台设备驱动介绍 (1 ...

  6. linux驱动开发篇(四)—— platform平台设备驱动

    linux系列目录: linux基础篇(一)--GCC和Makefile编译过程 linux基础篇(二)--静态和动态链接 ARM裸机篇(一)--i.MX6ULL介绍 ARM裸机篇(二)--i.MX6 ...

  7. Linux驱动学习-平台设备驱动probe函数-20220410

    1.最简单的杂项设备 /* * @Descripttion: 最简单的杂项设备驱动 * @version: * @Author: topeet */ #include <linux/init.h ...

  8. 嵌入式Linux设备驱动程序开发指南9(平台设备驱动)——读书笔记

    平台设备驱动 九.平台设备驱动 9.1 平台设备驱动概述 9.2 GPIO驱动 9.2.1 简介 9.2.2 硬件名称 9.2.3 引脚控制器 9.2.4 引脚控制子系统 9.2.5 GPIO控制器驱 ...

  9. 零基础学Linux内核之设备驱动篇(8)_设备模型

    零基础学Linux内核系列文章目录 前置知识篇 1. 进程 2. 线程 进程间通信篇 1. IPC概述 2. 信号 3. 消息传递 4. 同步 5. 共享内存区 编译相关篇 1. GCC编译 2. 静 ...

  10. Linux和Windows设备驱动架构比较

    毕业后一直在学操作系统, 有时候觉得什么都懂了,有时候又觉得好像什么都不懂,但总体来说自认为对操作系统实现机制的了解比周围的人还是要多一些.去年曾花了几个星期的晚上时间断断续续翻译了这篇对Linux和 ...

最新文章

  1. Educational Codeforces Round 39 B Weird Subtraction Process
  2. mysql limit offset
  3. 就业形势如此的严峻,你为何不努力?
  4. [css] 用css3实现伪3D的文字效果
  5. 基于顺序存储结构的图书信息表的最贵图书的查找(C++)
  6. ta-lib 里的蜡烛图形态函数源码
  7. python如何收集数据库_利用Python操作mysql数据库
  8. 销量之王,去年程序员最爱看的技术书就是它
  9. 回溯____蓝桥 棋盘
  10. rails errors样式自定义
  11. oracle里的or 短路么,Oracle CASE短路不能分组工作
  12. vue3代码的组织形式
  13. 用 TypeScript 写一个轻量级的 UI 框架之八:表单控件之富文本编辑器
  14. 东南大学计算机考研代码,东南大学代码_东南大学专业代码_2021东南大学招生代码,报考代码...
  15. win10 -- 注册机认为是病毒,死活不让下载 --那就关了它的自带杀毒软件
  16. 精细化运营:RFM模型在手游付费用户分群中的优化与应用
  17. html图片轮播思路,css3如何实现轮播图?css3实现轮播图片的方法
  18. 记一次某众测的靶场考核
  19. 量化交易之HFT篇 - 高频做市模型源码(.cpp文件)
  20. 硬盘分区MBR和GPT知识详解

热门文章

  1. lucene全文检索包括双层PDF
  2. 365锦鲤助手修改版,砍价小程序
  3. 双层pdf怎么制作(可以复制里面文字)纸质书如何制作扫描图片书签目录?
  4. Aspose.PSD for Java 21.6 Crack
  5. MDK/KEIL4/KEIL5 安装教程
  6. MT4本地跟单使用说明
  7. 自学Python:批量转换WORD文档为PDF
  8. python实现协同过滤推荐算法完整代码示例
  9. mysql数据脱敏_数据脱敏|静态脱敏|动态脱敏—数据库脱敏_产品
  10. C#之调用大华工业相机SDK采集图像并在Halcon窗口中显示