linux 设备驱动 probe,Linux驱动模型Probe解惑

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

问题

首先来回顾下,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设备驱动情况,挑选一个测试对象。1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16$ lspci

00:00.0 Host bridge:Intel Corporation Skylake Host Bridge/DRAMRegisters (rev08)

00:02.0 VGA compatible controller: Intel Corporation HD Graphics 520 (rev07)

00:14.0 USB controller: Intel Corporation Sunrise Point-LP USB 3.0 xHCI Controller (rev21)

00:14.2 Signal processing controller: Intel Corporation Sunrise Point-LP Thermal subsystem(rev21)

00:16.0 Communication controller: Intel Corporation Sunrise Point-LP CSME HECI #1 (rev21)

00:17.0 SATA controller: Intel Corporation Sunrise Point-LP SATA Controller [AHCI mode] (rev21)

00:1c.0 PCI bridge:Intel Corporation Sunrise Point-LP PCI Express Root Port (revf1)

00:1c.2 PCI bridge:Intel Corporation Sunrise Point-LP PCI Express Root Port (revf1)

00:1f.0 ISA bridge:Intel Corporation Sunrise Point-LP LPC Controller (rev21)

00:1f.2 Memory controller: Intel Corporation Sunrise Point-LP PMC (rev21)

00:1f.3 Audio device: Intel Corporation Sunrise Point-LP HD Audio (rev21)

00:1f.4 SMBus: Intel Corporation Sunrise Point-LP SMBus (rev21)

00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection I219-V (rev21)

02:00.0 Unassigned class [ff00]: Realtek Semiconductor Co., Ltd. RTS522A PCI Express Card Reader (rev01)

04:00.0 Network controller: Intel Corporation Wireless 8260 (rev3a)

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

2

3

4

5

6

7

8$ 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:

Kernel driver in use: e1000e

Kernel modules: e1000e

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

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

代码直接从ldd3的pci_skel样例修改,只需要把id_table修改成目标网卡的vendor id和device 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

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#include

#include

#include

#include

static struct pci_device_id ids[] = {

{ PCI_DEVICE(0x8086, 0x1570), },

{ 0, }

};

MODULE_DEVICE_TABLE(pci, ids);

static unsigned char (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)

{

* Like calling request_region();

*/

printk("pci_e1000e probingn");

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:%dn", __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如下:1

2

3

4

5

6

7

8

9

10obj-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模块,加载测试。1

2$make

$sudo insmod pci_e1000e.ko

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

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

分析

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

driver_attatch.svg

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

2

3

4int driver_attach(struct device_driver *drv)

{

return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

}

我们想要的答案就在__driver_attach函数的实现里: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

29static 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 deferraln");

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了。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值