PCI 驱动编程基础

Posted Li-Yongjun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PCI 驱动编程基础相关的知识,希望对你有一定的参考价值。

lspci

lspci 可以列出系统中所有的 PCI 设备。

$ lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01)
00:02.0 VGA compatible controller: VMware SVGA II Adapter
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 02)
00:04.0 System peripheral: InnoTek Systemberatung GmbH VirtualBox Guest Service
00:05.0 Multimedia audio controller: Intel Corporation 82801AA AC’97 Audio Controller (rev 01)
00:07.0 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 08)
00:0c.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB xHCI Host Controller
00:0d.0 SATA controller: Intel Corporation 82801HM/HEM (ICH8M/ICH8M-E) SATA Controller [AHCI mode] (rev 02)

以加粗那行 PCI 设备为例
00 是 Bus ID
03 是 Device ID
0 是 Function ID

lspci -v 显示设备详细的信息

$ lspci -v
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 02)
Subsystem: Intel Corporation PRO/1000 MT Desktop Adapter
Flags: 66MHz, medium devsel, IRQ 19
Memory at f0200000 (32-bit, non-prefetchable) [size=128K]
I/O ports at d020 [size=8]
Capabilities:
Kernel driver in use: e1000
Kernel modules: e1000

还是以上面的 PCI 设备为例:

  • Flags: 66MHz, medium devsel, IRQ 19:中断号为 19
  • Memory at f0200000:地址为 f0200000
  • Kernel driver in use: e1000 :表示该设备使用的驱动为 e1000

lspci -x 以十六进制显示 PCI 配置空间 (configuration space) 的前64个字节映象 (标准头部信息)

$ lspci -x
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 02)
00: 86 80 0e 10 03 00 30 02 02 00 00 02 00 40 00 00
10: 00 00 20 f0 00 00 00 00 21 d0 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 86 80 1e 00
30: 00 00 00 00 dc 00 00 00 00 00 00 00 09 01 ff 00

配置空间

标准头信息占 64 字节,内容如下

对照 lspci -x 显示的信息,整理如下

配置寄存器地址空间,64 字节头

#define PCI_STD_HEADER_SIZEOF    64

#define PCI_VENDOR_ID         0x00    /* 16 bits */
#define PCI_DEVICE_ID         0x02    /* 16 bits */

#define PCI_COMMAND           0x04    /* 16 bits */
#define PCI_STATUS            0x06    /* 16 bits */

#define PCI_CLASS_REVISION    0x08    /* High 24 bits are class, low 8 revision */
#define PCI_REVISION_ID       0x08    /* Revision ID */
#define PCI_CLASS_PROG        0x09    /* Reg. Level Programming Interface */

#define PCI_CACHE_LINE_SIZE   0x0c    /* 8 bits */
#define PCI_LATENCY_TIMER     0x0d    /* 8 bits */
#define PCI_HEADER_TYPE       0x0e    /* 8 bits */
#define PCI_BIST              0x0f    /* 8 bits */

#define PCI_BASE_ADDRESS_0    0x10    /* 32 bits */
#define PCI_BASE_ADDRESS_1    0x14    /* 32 bits [htype 0,1 only] */
#define PCI_BASE_ADDRESS_2    0x18    /* 32 bits [htype 0 only] */
#define PCI_BASE_ADDRESS_3    0x1c    /* 32 bits */
#define PCI_BASE_ADDRESS_4    0x20    /* 32 bits */
#define PCI_BASE_ADDRESS_5    0x24    /* 32 bits */

#define PCI_CARDBUS_CIS        0x28
#define PCI_SUBSYSTEM_VENDOR_ID 0x2c
#define PCI_SUBSYSTEM_ID      0x2e
#define PCI_ROM_ADDRESS       0x30    /* Bits 31..11 are address, 10..1 reserved */

#define PCI_CAPABILITY_LIST   0x34    /* Offset of first capability list entry */

#define PCI_INTERRUPT_LINE    0x3c    /* 8 bits */
#define PCI_INTERRUPT_PIN     0x3d    /* 8 bits */
#define PCI_MIN_GNT           0x3e    /* 8 bits */
#define PCI_MAX_LAT           0x3f    /* 8 bits */

PCI 驱动 demo

环境:Ubuntu 20.04 虚拟机,有个 Intel 网卡,也就是上面例子中的 00:03.0 设备,默认使用的驱动是 e1000,我们写个 demo,"驱动"这个设备,所以先将原驱动卸载

$ sudo rmmod e1000

my_pci.c

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

#define DRV_NAME "my_pci_driver"

struct pci_card 
	// 端口读写变量
	resource_size_t io_start;
	resource_size_t io_end;
	long range;
	long flags;
	void __iomem *ioaddr;
	int irq;
;

void my_pci_get_configs(struct pci_dev *dev)

	uint8_t val1;
	uint16_t val2;
	uint32_t val4;

	pci_read_config_word(dev, PCI_VENDOR_ID, &val2);
	printk("vendor id = 0x%x\\n", val2);

	pci_read_config_word(dev, PCI_DEVICE_ID, &val2);
	printk("device id = 0x%x\\n", val2);

	pci_read_config_byte(dev, PCI_REVISION_ID, &val1);
	printk("revision id = 0x%x\\n", val1);

	pci_read_config_dword(dev, PCI_CLASS_REVISION, &val4);
	printk("class = 0x%x\\n", val4 >> 8);

	pci_read_config_byte(dev, PCI_CACHE_LINE_SIZE, &val1);
	printk("cache line size = 0x%x\\n", val1);

	pci_read_config_byte(dev, PCI_LATENCY_TIMER, &val1);
	printk("latency time = 0x%x\\n", val1);

	pci_read_config_byte(dev, PCI_HEADER_TYPE, &val1);
	printk("header type = 0x%x\\n", val1);

	pci_read_config_byte(dev, PCI_BIST, &val1);
	printk("bist = 0x%x\\n", val1);

	pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &val4);
	printk("base address 0 = 0x%x\\n", val4);

	pci_read_config_dword(dev, PCI_BASE_ADDRESS_1, &val4);
	printk("base address 1 = 0x%x\\n", val4);

	pci_read_config_dword(dev, PCI_BASE_ADDRESS_2, &val4);
	printk("base address 2 = 0x%x\\n", val4);

	pci_read_config_dword(dev, PCI_BASE_ADDRESS_3, &val4);
	printk("base address 3 = 0x%x\\n", val4);

	pci_read_config_dword(dev, PCI_BASE_ADDRESS_4, &val4);
	printk("base address 4 = 0x%x\\n", val4);

	pci_read_config_dword(dev, PCI_BASE_ADDRESS_5, &val4);
	printk("base address 5 = 0x%x\\n", val4);

	pci_read_config_dword(dev, PCI_CARDBUS_CIS, &val4);
	printk("card bus cis = 0x%x\\n", val4);

	pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &val2);
	printk("subsystem vendor id = 0x%x\\n", val2);

	pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &val2);
	printk("subsystem id = 0x%x\\n", val2);

	pci_read_config_byte(dev, PCI_CAPABILITY_LIST, &val1);
	printk("capability list = 0x%x\\n", val1);

	pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &val1);
	printk("interrupt line = 0x%x\\n", val1);

	pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &val1);
	printk("interrupt pin = 0x%x\\n", val1);

	pci_read_config_byte(dev, PCI_MIN_GNT, &val1);
	printk("min gnt = 0x%x\\n", val1);

	pci_read_config_byte(dev, PCI_MAX_LAT, &val1);
	printk("max lat = 0x%x\\n", val1);


static irqreturn_t my_pci_interrupt(int irq, void *dev_id)

	struct pci_card *mypci = (struct pci_card *)dev_id;
	printk("irq = %d, mypci_irq = %d\\n", irq, mypci->irq);

	return IRQ_HANDLED;


static int probe(struct pci_dev *pci_dev, const struct pci_device_id *ent)

	int rc;
	struct pci_card *my_pci = NULL;

	printk("%s() line:%d\\n", __FUNCTION__, __LINE__);

	rc = pci_enable_device(pci_dev);
	if (rc) 
		printk("pci enable device failed!\\n");
		goto out_mypci;
	

	my_pci = kmalloc(sizeof(struct pci_card),
					 GFP_KERNEL);  // GFP_KERNEL 是内核内存分配时最常用的,无内存可用时可引起休眠。
	if (my_pci == NULL) 
		printk("kmalloc error!\\n");
		rc = -ENOMEM;
		goto out_mypci;
	

	my_pci->irq = pci_dev->irq;
	if (my_pci->irq < 0) 
		printk("IRQ is %d, it is invalid\\n", my_pci->irq);
		goto out_mypci;
	

	my_pci->io_start = pci_resource_start(pci_dev, 0);	// bar0
	my_pci->io_end = pci_resource_end(pci_dev, 0);
	my_pci->range = my_pci->io_end - my_pci->io_start + 1;
	my_pci->flags = pci_resource_flags(pci_dev, 0);
	printk("io_start = 0x%llx\\n", my_pci->io_start);
	printk("io_end = 0x%llx\\n", my_pci->io_end);
	printk("range = 0x%lx\\n", my_pci->range);
	printk("flags = 0x%lx\\n", my_pci->flags);
	printk("PCI base addr 0 is io%s.\\n", (my_pci->flags & IORESOURCE_MEM) ? "mem" : "port");

	rc = pci_request_regions(pci_dev, DRV_NAME);
	if (rc) 
		printk("pci request regions error!\\n");
		goto out_mypci;
	

	my_pci->ioaddr = pci_ioremap_bar(pci_dev, 0);
	if (my_pci->ioaddr == NULL) 
		printk("ioremp error!\\n");
		rc = ENOMEM;
		goto out_regions;
	

	rc = request_irq(my_pci->irq, my_pci_interrupt, IRQF_SHARED, DRV_NAME, my_pci);
	if (rc) 
		printk(KERN_ERR "can't get assigned IRQ %d!\\n", my_pci->irq);
		goto out_iounmap;
	

	pci_set_drvdata(pci_dev, my_pci);
	printk("probe success. PCI ioport addr start addr 0x%llx, ioaddr is 0x%p, interrupt No. %d\\n",
		   my_pci->io_start, my_pci->ioaddr, my_pci->irq);
	my_pci_get_configs(pci_dev);

	return 0;

out_iounmap:
	iounmap(my_pci->ioaddr);
out_regions:
	pci_release_regions(pci_dev);
out_mypci:
	if (my_pci)
		kfree(my_pci);

	return rc;


static void remove(struct pci_dev *pci_dev)

	struct pci_card *my_pci;

	printk("%s() line:%d\\n", __FUNCTION__, __LINE__);

	my_pci = pci_get_drvdata(pci_dev);
	if (my_pci) 
		free_irq(my_pci->irq, my_pci);
		iounmap(my_pci->ioaddr);
		kfree(my_pci);
	

	pci_release_regions(pci_dev);

	pci_disable_device(pci_dev);

	printk("device is removed successfully\\n");


static const struct pci_device_id my_pci_tbl[] = 
	PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x100e),
;

static struct pci_driver mv_pci_driver = 
	.name = DRV_NAME,
	.id_table = my_pci_tbl,
	.probe = probe,
	.remove = remove,
;

static int my_pci_init(void)

	printk("%s() line:%d\\n", __FUNCTION__, __LINE__);

	return pci_register_driver(&mv_pci_driver);


static void my_pci_exit(void)

	printk("%s() line:%d\\n", __FUNCTION__, __LINE__);

	pci_unregister_driver(&mv_pci_driver);


module_init(my_pci_init);
module_exit(my_pci_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m = my_pci.o

all:
	make -C /lib/modules/$(shell uname -r)/build/ M=$PWD modules
clean:
	make -C /lib/modules/$(shell uname -r)/build/ M=$PWD clean

$ sudo rmmod e1000
$ sudo insmod my_pci.ko
$ dmesh
[48344.049257] my_pci_init() line:204
[48344.049285] probe() line:102
[48344.049746] io_start = 0xf0200000
[48344.049748] io_end = 0xf021ffff
[48344.049749] range = 0x20000
[48344.049749] flags = 0x40200
[48344.049750] PCI base addr 0 is iomem.
[48344.049807] probe success. PCI ioport addr start addr 0xf0200000, ioaddr is 0x000000000e21ae12, interrupt No. 19
[48344.049817] vendor id = 0x8086
[48344.049823] device id = 0x100e
[48344.049830] revision id = 0x2
[48344.049837] class = 0x20000
[48344.049843] cache line size = 0x0
[48344.049850] latency time = 0x40
[48344.049856] header type = 0x0
[48344.049863] bist = 0x0
[48344.049869] base address 0 = 0xf0200000
[48344.049876] base address 1 = 0x0
[48344.049882] base address 2 = 0xd021
[48344.049888] base address 3 = 0x0
[48344.049895] base address 4 = 0x0
[48344.049901] base address 5 = 0x0
[48344.049908] card bus cis = 0x0
[48344.049914] subsystem vendor id = 0x8086
[48344.049921] subsystem id = 0x1e
[48344.049927] capability list = 0xdc
[48344.049933] interrupt line = 0x9
[48344.049940] interrupt pin = 0x1
[48344.049946] min gnt = 0xff
[48344.049953] max lat = 0x0

$ sudo rmmod my_pci
[51661.250413] my_pci_exit() line:211
[51661.250433] remove() line:175
[51661.250939] device is removed successfully

可以看到,驱动代码从配置空间读到的内容,和 lspci -x 显示的一致,说明驱动程序编写正确,并且通过 pci_ioremap_bar() 将 bar0 的地址(0xf0200000)映射到了虚拟内存地址(0x000000000e21ae12),之后就可以通过这个地址操作 bar0 寄存器了。
不过,这个 “PCI 驱动” 几乎不具备任何实际能力,这里仅作为一个演示,带领大家步入 PCI 驱动的大门。

以上是关于PCI 驱动编程基础的主要内容,如果未能解决你的问题,请参考以下文章

linux驱动---用I/O命令访问PCI总线设备配置空间

driverworks开发pci的驱动程序怎样获取资源的io空间地址

xHci-PCI驱动设计

pcl数据捕获和信号处理控制器应该装啥驱动

有关PCI设备初始化的说明

由python打开计算机编程的大门