probe函数中一般完成一下任务:
1、通知内核设备执行DMA的寻址能力,说明设备支持64位还是32位的DMA地址。如果不支持64位的地址,则尝试32位的:
err = dma_set_mask(pci_dev_to_dev(pdev), DMA_BIT_MASK(64));
if (!err) {
err =
dma_set_coherent_mask(pci_dev_to_dev(pdev),
DMA_BIT_MASK(64));
if (!err)
pci_using_dac = 1;
} else {
err = dma_set_mask(pci_dev_to_dev(pdev), DMA_BIT_MASK(32));
if (err) {
err = dma_set_coherent_mask(pci_dev_to_dev(pdev),
DMA_BIT_MASK(32));
if (err) {
dev_err(pci_dev_to_dev(pdev),
"No usable DMA configuration, aborting\n");
goto err_dma;
}
}
}
2、给设备分配IO内存并映射内存,一般的设备都是通过IO内存映射设备寄存器和设备内存。
有些设备使用IO端口映射设备寄存器,x86处理器上一共有64KB的IO空间,其中有些IO端口已作为固定的用途,比如80口,作为数码管的显示,比如CF8和CFC用作读取PCI设备配置空间寄存器。如果设备需要IO端口,使用如下函数分配IO端口:
request_region(start,n,name)
IO端口分配后可以直接操作,使用如下函数:
inb(),outb()
inw(),outw()
inl(),outl()
如果设备需要IO内存,根据设备需要的IO内存大小,分配IO内存:pci_request_selected_regions(pdev, bars, name);
在操作IO内存之前,需要映射IO内存,之后可以使用readl或者writel等读写内存的函数操作该块内存:
ioremap(mmio_start, mmio_len);
每个PCI设备最多可以支持6个BAR(Base Address Register),但大部分设备不会使用这么多空间。
PCI设备复位之后,该寄存器存放PCI设备需要使用的IO空间基地址,这段空间是IO空间还是内存空间,网卡设备一般使用的是内存空间,也可以称为IO内存。该信息是设备出厂时已经设置好的。
BIOS扫描PCI设备时,会根据系统中的硬件配置为PCI设备分配地址空间,BIOS为所有PCI设备分配的地址空间都不会冲突,之后该信息会传递给操作系统。
系统软件对PCI总线进行配置时,首先获取BAR空间寄存器中的初始化信息,之后根据处理器的配置,将合理的基地址写入相应的BAR寄存器。系统软件还可以使用该寄存器或者PCI设备使用的BAR空间的长度,其方法是向BAR寄存器写入0xFFFFFFFF,之后读取该寄存器。
在设备驱动加载之前,设备所需要的地址空间还不会真正的分配,需要在驱动程序中给设备分配IO空间,最后进行ioremap才能访问。
在系统下,可以通过如下命令查看设备使用的IO内存:
$ lspci | grep "Ethernet controller"
02:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
06:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
$ cat /proc/iomem | grep "02:00.0"
fea00000-fea1ffff : 0000:02:00.0
fea20000-fea23fff : 0000:02:00.0
从上面结果可以看出,该设备使用了6个BAR中的2个BAR,即BAR0和BAR1,该设备申请了两块IO内存,BAR0的范围为:fea00000-fea1ffff,大小为128KB,用来映射设备寄存器,BAR1的范围为fea20000-fea23fff,大小为32KB,用来映射flash。设备需要的空间大小是由硬件指定的,但是这两块IO内存的起始地址是在BIOS启动阶段PCI扫描时由BIOS分配的。在e1000e网卡驱动中有如下代码:
BAR0用来映射设备寄存器,即设备有关寄存器都映射到内存空间,我们可以通过操作内存来操作设备寄存器,pci_resource_start(pdev, 0)就是用来获取BAR0的起始地址:
BAR0用来映射设备寄存器,即设备有关寄存器都映射到内存空间,我们可以通过操作内存来操作设备寄存器,pci_resource_start(pdev, 0)就是用来获取BAR0的起始地址:
mmio_start = pci_resource_start(pdev, 0);
mmio_len = pci_resource_len(pdev, 0);
err = -EIO;
adapter->hw.hw_addr = ioremap(mmio_start, mmio_len);
BAR1用来映射flash,pci_resource_start(pdev, 1)用来获取BAR1的起始地址:
if ((adapter->flags & FLAG_HAS_FLASH) &&
(pci_resource_flags