This lecture is part of EmbeddedPathashala’s free Linux kernel development course, and it continues our ongoing series on the PCI subsystem. In the previous lecture we walked through struct pci_dev field by field to understand how the kernel represents a discovered PCI device in memory. That answers the question “what does the kernel know about a device?” This lecture answers the next, equally important question: “how does the kernel decide which driver owns that device?” Understanding Linux PCI driver registration is the single most important skill for anyone writing a real PCI or PCIe endpoint driver, and it is a core topic in any serious free linux device drivers course.
What You Will Learn
- How the PCI core matches a physical device to a registered driver
- struct pci_device_id field by field, including the modern override_only field
- PCI_DEVICE, PCI_DEVICE_CLASS, PCI_DEVICE_SUB and PCI_VDEVICE macros
- MODULE_DEVICE_TABLE, modules.alias, and the udev hotplug path
- struct pci_driver fields: probe, remove, shutdown, and modern power management
- pci_register_driver() vs the module_pci_driver() helper macro
- Writing, building, and testing an original PCI ID-matching driver on QEMU
Prerequisites
You should be comfortable with basic kernel module programming (module_init/module_exit), and it helps to have gone through the earlier lectures in this free embedded linux course series on PCI bus topology, enumeration, and struct pci_dev. A Linux VM running under QEMU with the built-in edu educational PCI device enabled is used for the demo, but everything explained here applies equally to real PCI/PCIe hardware.
From pci_dev to pci_driver: Why Matching Matters
A PCI bus can be enumerated with dozens of devices sitting on it, each described internally by its own struct pci_dev. But a struct pci_dev by itself does not run any code — it is just data. What actually makes a device usable is a driver being bound to it. The PCI core’s job during boot, and again every time a device is hot-plugged, is to walk every registered PCI driver and ask a simple question for each one: “does any entry in your ID table describe this device?” This entire process is called device-driver matching, and it is what this lecture is really about.
This matching is not PCI-specific magic — it is built on the generic Linux driver model. Every bus type (PCI, USB, I2C, platform, and so on) implements a match() callback that the driver core calls whenever a new device or a new driver appears on that bus. For PCI, this callback is pci_bus_match(), and it compares the device’s vendor/device/class/subsystem fields against every entry of every registered driver’s id_table.
struct pci_device_id Explained
Every PCI driver ships a small, statically declared array that lists exactly which devices it is willing to handle. Each entry in that array is a struct pci_device_id. As of the current mainline kernel, the structure looks like this:
struct pci_device_id {
__u32 vendor, device; /* Vendor and Device ID or PCI_ANY_ID */
__u32 subvendor, subdevice;/* Subsystem ID or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data;/* Private data passed to the driver */
__u32 override_only; /* Match only via driver_override, not auto-probe */
};
Here is what each field really does:
- vendor / device — the 16-bit vendor ID assigned by the PCI-SIG and the vendor-assigned device ID. Together they uniquely identify a specific chip.
- subvendor / subdevice — identify the specific board or add-in card built around that chip. Many chips are reused across dozens of boards, and the subsystem ID is how you tell them apart.
- class / class_mask — a 24-bit (class, subclass, programming-interface) triplet. class_mask lets a driver match on just part of that triplet, so it can claim “every device of this class” instead of one specific chip.
- driver_data — an unsigned long the driver author is free to use however they like, typically as an index into a private per-chip configuration table.
- override_only — a newer field. When set, this entry is used only when userspace explicitly requests the binding through the
driver_overridesysfs file, never during normal automatic probing. This is useful for generic drivers likevfio-pcithat should never grab a device automatically.
The ID-Matching Macros
Filling every field by hand for every entry is tedious and error-prone, so <linux/pci_ids.h> and <linux/mod_devicetable.h> provide macros that build a correctly-formed struct pci_device_id for the common cases:
/* Match one exact vendor:device pair, ignore subsystem and class */
PCI_DEVICE(vend, dev)
/* Match an exact vendor:device pair with a driver_data payload */
PCI_VDEVICE(vendor, device)
/* Match every device belonging to a PCI class, ignore vendor/device */
PCI_DEVICE_CLASS(dev_class, dev_class_mask)
/* Match a vendor:device pair AND a specific subsystem vendor:device */
PCI_DEVICE_SUB(vend, dev, subvend, subdev)
Any field left unspecified by these macros is automatically set to PCI_ANY_ID, a wildcard value telling the matching code “accept anything here.” Every ID table must end with a zero-filled sentinel entry — this is how the kernel knows where the array ends, since there is no length field alongside it.
Comparing the Matching Strategies
| Macro | Matches On | Typical Use Case |
|---|---|---|
| PCI_DEVICE() | Exact vendor + device | One specific chip your driver was written for |
| PCI_DEVICE_CLASS() | PCI class code only | Generic class drivers, e.g. any NVMe or any USB xHCI controller |
| PCI_DEVICE_SUB() | Vendor + device + subsystem IDs | Distinguishing one OEM’s add-in card from another using the same chip |
| PCI_VDEVICE() | Exact vendor + device, with driver_data | Drivers supporting a family of closely related chips |
Exporting the ID Table to Userspace
A compiled driver’s ID table only matters to the kernel — but the hotplug system (udev) also needs to know, from userspace, which module to load for a newly appeared device, before that module is even loaded. This is solved with a single macro:
MODULE_DEVICE_TABLE(pci, my_pci_tbl);
This macro drops the entire ID table into a special ELF section inside the compiled .ko file. When the module is installed, depmod scans every module for this section and builds a plain-text table called modules.alias under /lib/modules/<kernel-version>/. From then on, whenever the kernel detects a new PCI device and emits a hotplug uevent, udev looks the device’s modalias string up in that table to find the correct module — without ever having to load every PCI driver on the system just to test-fit it.
struct pci_driver — The Registration Structure
Once the kernel finds a matching ID table, it needs to know which functions to call. That contract is struct pci_driver. The fields a typical modern driver actually fills in are:
struct pci_driver {
const char *name;
const struct pci_device_id *id_table;
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
void (*remove)(struct pci_dev *dev);
void (*shutdown)(struct pci_dev *dev);
const struct pci_error_handlers *err_handler;
struct device_driver driver; /* holds ->pm for power management */
};
- name — must be unique across all PCI drivers currently registered; registration fails otherwise.
- id_table — pointer to the ID array described above; must be non-NULL for probe() to ever fire.
- probe() — called once per matched device. Return 0 to accept ownership, or a negative errno to decline.
- remove() — called when the device disappears from the bus or the driver is unloaded. Always runs in process context.
- shutdown() — invoked on system reboot/shutdown, typically used to quiesce DMA before the machine restarts.
- driver.pm — on current kernels, suspend/resume power management is implemented as a
struct dev_pm_opsattached here, not via separate suspend()/resume() callback fields.
Registering the Driver
A filled-in struct pci_driver is registered and unregistered with:
int pci_register_driver(struct pci_driver *drv);
void pci_unregister_driver(struct pci_driver *drv);
Since nearly every PCI driver’s module_init()/module_exit() pair does nothing but call these two functions, the kernel provides a shortcut macro that generates both for you:
module_pci_driver(my_pci_driver);
Hands-On: An Original PCI ID-Matching Driver
Let’s put all of this together in a small, original driver — ep_pci_id_driver — targeting QEMU’s built-in edu educational PCI device (vendor 0x1234, device 0x11e8), the same test device used earlier in this series. This driver does nothing more than prove that matching and registration work correctly, which is exactly what you want when first learning this API.
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#define EP_EDU_VENDOR_ID 0x1234
#define EP_EDU_DEVICE_ID 0x11e8
static const struct pci_device_id ep_pci_id_tbl[] = {
{ PCI_DEVICE(EP_EDU_VENDOR_ID, EP_EDU_DEVICE_ID) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ep_pci_id_tbl);
static int ep_pci_id_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
dev_info(&pdev->dev,
"ep_pci_id_driver: matched vendor=0x%04x device=0x%04x\n",
pdev->vendor, pdev->device);
return 0;
}
static void ep_pci_id_remove(struct pci_dev *pdev)
{
dev_info(&pdev->dev, "ep_pci_id_driver: device removed\n");
}
static struct pci_driver ep_pci_id_driver = {
.name = "ep_pci_id_driver",
.id_table = ep_pci_id_tbl,
.probe = ep_pci_id_probe,
.remove = ep_pci_id_remove,
};
module_pci_driver(ep_pci_id_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("EmbeddedPathashala PCI ID matching demo driver");
A minimal Makefile to build it against your running kernel headers:
obj-m += ep_pci_id_driver.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
Building and Testing on QEMU
Boot the VM with the edu device attached (-device edu on the QEMU command line), then build and load the module:
$ make
$ sudo insmod ep_pci_id_driver.ko
$ dmesg | tail -n 3
[ 41.208813] ep_pci_id_driver: probe of 0000:00:03.0
[ 41.208820] ep_pci_id_driver: matched vendor=0x1234 device=0x11e8
$ lsmod | grep ep_pci_id_driver
ep_pci_id_driver 16384 0
Confirm that MODULE_DEVICE_TABLE did its job and depmod correctly exported the alias:
$ modinfo ep_pci_id_driver.ko | grep alias
alias: pci:v00001234d000011E8sv*sd*bc*sc*i*
$ sudo rmmod ep_pci_id_driver
$ dmesg | tail -n 1
[ 58.114402] ep_pci_id_driver: device removed
That single alias: line is exactly what gets written into modules.alias — it is the same string udev matches against a device’s own modalias when it decides which module to auto-load.
Common Mistakes and Troubleshooting
- Forgetting the terminating
{ 0, }sentinel entry — the kernel will walk past the end of your array into unrelated memory. - Omitting
MODULE_DEVICE_TABLE()— the driver still works if loaded manually with insmod, but udev will never auto-load it on hotplug. - Reusing a
namestring already registered by another PCI driver —pci_register_driver()returns-EBUSYin that case. - Using
PCI_DEVICE_CLASS()with an incorrectclass_mask— an overly broad mask silently grabs devices you never intended to claim. - Assuming probe() runs in interrupt context — it always runs in process context, so blocking calls and sleeping allocations are safe there.
Best Practices
- Prefer
module_pci_driver()over hand-writing init/exit boilerplate. - Use
devm_-managed resources inside probe() so remove() has less manual cleanup to get wrong. - Implement power management through
driver.pmanddev_pm_opsrather than legacy suspend/resume fields. - Keep the ID table as narrow as PCI_DEVICE() allows unless you deliberately intend to be a generic class driver.
- Always verify the generated alias with
modinfobefore shipping — a typo in vendor/device IDs silently breaks hotplug matching only, not manual loading, so it is easy to miss.
Summary and Key Takeaways
PCI driver registration boils down to two data structures working together: struct pci_device_id tells the kernel which devices you support, and struct pci_driver tells it which functions to call once a match is found. MODULE_DEVICE_TABLE() is the bridge that lets this same information drive userspace hotplug loading through depmod and modules.alias. Everything else — pci_register_driver(), module_pci_driver(), probe/remove — exists to plug your driver cleanly into that matching pipeline. With this piece in place, the PCI series is ready to move from “getting probed” to actually talking to device hardware: BARs, MMIO, and interrupts, most of which this course has already begun covering.
Frequently Asked Questions
What happens if two PCI drivers both claim the same vendor/device ID?
Only the first driver that successfully registers and whose probe() returns 0 owns the device. A second driver’s probe() is never called for a device already claimed by another driver.
Is MODULE_DEVICE_TABLE mandatory for the driver to work at all?
No. Without it, you can still load the driver manually with insmod and it will bind correctly. What you lose is automatic hotplug loading by udev, since there is no alias entry for it to match against.
Can one driver support multiple unrelated chips?
Yes — this is exactly what the id_table array and driver_data field are for. List every supported vendor/device pair with a different driver_data value, then branch on that value inside probe().
Why does remove() return void while probe() returns int?
probe() can legitimately fail — wrong hardware revision, resource conflict, and so on — so it needs to report that. remove() is a best-effort teardown the kernel cannot meaningfully retry, so on current kernels it has no return value.
What is override_only used for in struct pci_device_id?
It marks an ID table entry as usable only through the driver_override sysfs mechanism, not through normal automatic probing. Generic pass-through drivers use this so they never accidentally grab a device a more specific driver should own.
Do I need class_mask if I only care about one exact device?
No. PCI_DEVICE() already sets class and class_mask to PCI_ANY_ID for you. class_mask only matters when you deliberately use PCI_DEVICE_CLASS() to match a whole category of devices.
Where can I practice this without real PCI hardware?
QEMU’s built-in edu educational PCI device (enabled with -device edu) is ideal for exactly this kind of practice, and it is what this free linux kernel development course uses throughout the PCI series.
Continue the Free Linux Kernel Development Course
Next, we move from matching to actually talking to the hardware — mapping BARs and issuing MMIO reads/writes from inside probe().
Browse All Lectures Next Lecture