问题

首先来回顾下,Linux设备驱动模型中bus、device和driver三者的关系:

  • bus是物理总线的抽象。
  • device是设备抽象,存在于bus之上。
  • driver是驱动抽象,注册到bus上,用于驱动bus上的特定device。
  • device和driver通过bus提供的match方法来匹配(通常是使用设备ID进行匹配)。
  • driver匹配到device后,调用driver的probe接口驱动device。
  • 一个driver可以驱动多个相同的设备或者不同的设备。

一个driver匹配并驱动多个device的情形比较常见,比如一个igb驱动可以驱动多块intel网卡(可以是相同型号,也可以是不同型号),这是由驱动的id_table和驱动的处理逻辑决定的。那么自然而然的一个问题是:如果系统有多个driver都可以匹配到一个device,系统会怎么处理?换句话说,Linux是否允许两个driver服务同一个device?

实验

带着这样的疑问,我做了下面这个实验。

首先,查看下系统的PCI设备驱动情况,挑选一个测试对象。

$ lspci 
00:00.0 Host bridge: Intel Corporation Skylake Host Bridge/DRAM Registers (rev 08)
00:02.0 VGA compatible controller: Intel Corporation HD Graphics 520 (rev 07)
00:14.0 USB controller: Intel Corporation Sunrise Point-LP USB 3.0 xHCI Controller (rev 21)
00:14.2 Signal processing controller: Intel Corporation Sunrise Point-LP Thermal subsystem (rev 21)
00:16.0 Communication controller: Intel Corporation Sunrise Point-LP CSME HECI #1 (rev 21)
00:17.0 SATA controller: Intel Corporation Sunrise Point-LP SATA Controller [AHCI mode] (rev 21)
00:1c.0 PCI bridge: Intel Corporation Sunrise Point-LP PCI Express Root Port (rev f1)
00:1c.2 PCI bridge: Intel Corporation Sunrise Point-LP PCI Express Root Port (rev f1)
00:1f.0 ISA bridge: Intel Corporation Sunrise Point-LP LPC Controller (rev 21)
00:1f.2 Memory controller: Intel Corporation Sunrise Point-LP PMC (rev 21)
00:1f.3 Audio device: Intel Corporation Sunrise Point-LP HD Audio (rev 21)
00:1f.4 SMBus: Intel Corporation Sunrise Point-LP SMBus (rev 21)
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection I219-V (rev 21)
02:00.0 Unassigned class [ff00]: Realtek Semiconductor Co., Ltd. RTS522A PCI Express Card Reader (rev 01)
04:00.0 Network controller: Intel Corporation Wireless 8260 (rev 3a)

可以看到00:1f.6设备是一块Intel网卡,我们就拿它试验。看看它的驱动情况:

$ lspci -v
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection I219-V (rev 21)
        Subsystem: Lenovo Ethernet Connection I219-V
        Flags: bus master, fast devsel, latency 0, IRQ 128
        Memory at f1200000 (32-bit, non-prefetchable) [size=128K]
        Capabilities: <access denied>
        Kernel driver in use: e1000e
        Kernel modules: e1000e

可以看到当前正在使用的驱动是e1000e模块。

然后,构造一个可以匹配选中网卡的PCI设备驱动,观察驱动加载时系统的行为。

代码直接从ldd3的pci_skel样例修改,只需要把id_table修改成目标网卡的vendor id和device id即可。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>


static struct pci_device_id ids[] = {
	{ PCI_DEVICE(0x8086, 0x1570), },
	{ 0, }
};
MODULE_DEVICE_TABLE(pci, ids);

static unsigned char skel_get_revision(struct pci_dev *dev)
{
	u8 revision;

	pci_read_config_byte(dev, PCI_REVISION_ID, &revision);
	return revision;
}

static int probe(struct pci_dev *dev, const struct pci_device_id *id)
{
	/* Do probing type stuff here.  
	 * Like calling request_region();
	 */
	printk("pci_e1000e probing\n");
	pci_enable_device(dev);
	
	if (skel_get_revision(dev) == 0x42)
		return -ENODEV;


	return 0;
}

static void remove(struct pci_dev *dev)
{
	/* clean up any allocated resources and stuff here.
	 * like call release_region();
	 */
}

static struct pci_driver pci_driver = {
	.name = "pci_e1000e",
	.id_table = ids,
	.probe = probe,
	.remove = remove,
};

static int __init pci_skel_init(void)
{
	printk("%s:%d\n", __func__, __LINE__);
	return pci_register_driver(&pci_driver);
}

static void __exit pci_skel_exit(void)
{
	pci_unregister_driver(&pci_driver);
}

MODULE_LICENSE("GPL");

module_init(pci_skel_init);
module_exit(pci_skel_exit);

Makefile如下:

obj-m   := pci_e1000e.o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)

all:
        $(MAKE) -C $(KERNELDIR) M=$(PWD)

clean:
        rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

我在init和probe接口中增加了打印,跟踪流程。

直接make生成pci_e1000e.ko模块,加载测试。

$ make
$ sudo insmod pci_e1000e.ko

dmesg查看驱动加载情况,发现内核只新增了一行打印:

pci_skel_init:52

说明驱动成功加载,但是压根没有进入probe接口。由此看来,内核应该不会让一个device同时被两个driver匹配上。

分析

直接从源码分析,可以看到设备驱动加载后,pci_register_driver调用流程大致如下:

其核心代码是driver_attatch,其作用是driver binding,它调用bus_for_each_dev来遍历总线上的所有设备,然后对每一个设备调用__driver_attach函数。

int driver_attach(struct device_driver *drv)
{
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

我们想要的答案就在__driver_attach函数的实现里:

static int __driver_attach(struct device *dev, void *data)

{
    struct device_driver *drv = data;
    int ret;

    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);
        device_unlock(dev);
        if (dev->parent)
                device_unlock(dev->parent);

        return 0;
}

可以看到,在driver_match_device匹配成功以后,并不是直接进行probe的,而是先要判断dev->driver是否为空,如果不为空就不进行driver_probe_device了。也就是说,如果一个device已经绑定了一个driver,就不允许尝试绑定第二个driver了。