PCI Concepts

Understand PCI device identification, BARs, and configuration space.

Device Identification

Every PCI device has:

  • Vendor ID (16-bit): Manufacturer
  • Device ID (16-bit): Specific product
  • Class code: Device type (network, storage, etc.)
static const struct pci_device_id my_pci_ids[] = {
    /* Match by vendor/device */
    { PCI_DEVICE(0x1234, 0x5678) },

    /* Match by class */
    { PCI_DEVICE_CLASS(PCI_CLASS_NETWORK_ETHERNET << 8, 0xffff00) },

    { }  /* Terminator */
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

Find device IDs with lspci -nn:

$ lspci -nn
00:1f.6 Ethernet [0200]: Intel [8086:15d8] (rev 10)
#       Class              Vendor  Device

BARs: Base Address Registers

PCI devices expose memory regions via BARs:

Device Configuration Space
├── BAR0: 0xfe000000 (64KB memory-mapped)
├── BAR1: 0xfdf00000 (1MB memory-mapped)
├── BAR2: 0x3000     (I/O ports, rare)
└── ...

In your driver:

/* Get BAR info */
resource_size_t start = pci_resource_start(pdev, 0);
resource_size_t len = pci_resource_len(pdev, 0);
unsigned long flags = pci_resource_flags(pdev, 0);

if (flags & IORESOURCE_MEM) {
    /* Memory-mapped BAR - use ioremap */
    void __iomem *regs = pci_iomap(pdev, 0, len);
    /* Access: readl(regs + OFFSET) */
}

Configuration Space

Each PCI device has 256 bytes (PCIe: 4KB) of configuration space:

Offset Size Field
0x00 2 Vendor ID
0x02 2 Device ID
0x04 2 Command
0x06 2 Status
0x10-0x24 24 BAR0-BAR5

Access configuration space:

u16 vendor, device;
pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor);
pci_read_config_word(pdev, PCI_DEVICE_ID, &device);

/* Write (careful!) */
pci_write_config_word(pdev, PCI_COMMAND, cmd);

Enable the Device

Before using a PCI device:

/* 1. Enable device (memory/IO access) */
ret = pci_enable_device(pdev);

/* 2. Request BAR regions */
ret = pci_request_regions(pdev, "my_driver");

/* 3. Set DMA mask if needed */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));

/* 4. Enable bus mastering for DMA */
pci_set_master(pdev);

MSI/MSI-X Interrupts

Modern PCI devices use MSI (Message Signaled Interrupts):

/* Enable MSI (single interrupt) */
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);

/* Or MSI-X (multiple interrupts) */
ret = pci_alloc_irq_vectors(pdev, 1, num_vecs, PCI_IRQ_MSIX);

/* Get IRQ number */
int irq = pci_irq_vector(pdev, 0);
ret = request_irq(irq, my_handler, 0, "my_driver", dev);

Prefer MSI/MSI-X over legacy interrupts. They’re faster and avoid sharing issues.

Summary

Concept Description
Vendor/Device ID Unique device identification
BAR Memory region for device access
Configuration space Device settings and capabilities
MSI/MSI-X Modern interrupt mechanism

Further Reading


Back to top

Linux Driver Development Guide is a community resource for learning kernel driver development. Not affiliated with the Linux Foundation. Content provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.