Device-Driver Matching
Matching is the step that decides whether a given
struct deviceand a givenstruct device_driverbelong together — before anything is probed or bound. In the Linux unified device model the decision is delegated entirely to the bus: the device-driver core never compares IDs itself, it just callsdrv->bus->match(dev, drv)and acts on the boolean answer (base.h:164, dd.c, v6.12). Each bus implementsmatch()with whatever identity its hardware exposes: PCI compares 16-bit vendor/device IDs, USB compares vendor/product plus class/interface fields, the platform bus tries Device-Treecompatiblestrings then ACPI hardware IDs then a name. The samematch()function also drives module autoloading: theMODULE_DEVICE_TABLEmacro emits the device-ID table into the module’s metadata, a build step turns it into wildcardmodaliasstrings, and udev uses the device’s modalias tomodprobethe right driver on demand. This note owns the match step; the bind sequence it gates is Driver Binding and the Probe Flow.
This note is pinned to Linux 6.12, a maintained long-term-support (LTS) series released 2024-11-17; mainline has since moved on to the 7.x series. Every match callback, ID-table struct, and alias-generation routine quoted below was read from raw.githubusercontent.com at tag v6.12 on 2026-09-04. Where this note quotes live output from a running system, that system is a Fedora 44 machine on kernel 7.1.8-200.fc44.x86_64 — the sysfs paths and modalias formats shown there are stable across both series, but the version gap is stated wherever live evidence appears.
The device model is an object graph; sysfs is that graph made visible
This note and sysfs and the Kernel Object Hierarchy are two halves of one subject. The device model —
struct device,struct device_driver,struct bus_type— is a reference-counted in-memory object graph built out of embeddedstruct kobjectnodes. sysfs is the projection of that same graph into a filesystem: every kobject in the graph is a directory under/sys, every field a driver chooses to export is a file, and every relationship the graph encodes (this device is on that bus, this device is bound to that driver) is a symlink. Matching is the operation that adds an edge to the graph, and you watch it happen by watching/sys/bus/<bus>/devices/<dev>/driverappear. Read this note for the edge-creation rule; read the sysfs note for the shape of the graph and how it is rendered.
Mental Model
Think of matching as a bus-specific identity check at the door. The generic device-driver core is a bouncer who does not know how to read any particular ID — it just hands the device and driver to the bus and asks “do these two go together?” The bus is the one who knows the format: PCI reads a vendor/device number off the card, USB reads a descriptor, platform reads a compatible string from the Device Tree. The answer is a single bit. Critically, a match is necessary but not sufficient — it only earns the device a chance to be probed; the driver’s .probe() can still decline (covered in Driver Binding and the Probe Flow).
flowchart TB CORE["driver_match_device(drv, dev)<br/>(base.h)"] -->|"delegates to"| BM{"drv->bus->match ?"} BM -->|"NULL (no match fn)"| YES["return 1<br/>(everything matches —<br/>e.g. legacy buses)"] BM -->|"set"| BUSM["bus->match(dev, drv)"] subgraph PLAT["platform_match() precedence ladder"] OVR["1. driver_override set?<br/>→ exact name only"] --> OF["2. OF: compatible vs<br/>of_match_table"] OF --> ACPI["3. ACPI: HID/CID vs<br/>acpi_match_table"] ACPI --> IDT["4. id_table:<br/>strcmp(pdev->name, id->name)"] IDT --> NAME["5. fallback:<br/>strcmp(pdev->name, drv->name)"] end BUSM -.->|"platform bus"| PLAT BUSM -.->|"PCI bus"| PCI["pci_bus_match:<br/>vendor/device/subsys/class<br/>vs pci_device_id table"] BUSM -.->|"USB"| USB["usb match:<br/>match_flags-gated fields"]
The match dispatch and the platform precedence ladder. What it shows: driver_match_device is a thin shim that returns 1 if the bus has no match callback, otherwise defers to bus->match. The platform bus’s ladder is the richest example, trying five mechanisms in a fixed order. The insight to take: matching is polymorphic over the bus — the core logic is trivial; all the intelligence lives in per-bus match() functions, and within a bus the precedence order (override → firmware → ID table → name) is itself meaningful and load-bearing.
The Object Graph Being Matched
Before dissecting match() it is worth drawing the objects it operates on, because the whole design falls out of one C idiom: a struct kobject embedded inside a larger struct. The kobject supplies a name, a parent pointer, a reference count (struct kref), and a type descriptor; the embedding struct supplies the actual data. container_of() recovers the outer struct from a kobject pointer. That single trick gives the kernel an object system — inheritance by embedding, polymorphism by function pointers in the type descriptor, and lifetime by refcount — without C++.
classDiagram class kobject { +const char* name +kobject* parent +kset* kset +kobj_type* ktype +kernfs_node* sd +kref kref +state_in_sysfs : 1 bit } class kset { +list_head list +spinlock_t list_lock +kobject kobj +kset_uevent_ops* uevent_ops } class kobj_type { +release(kobject*) +sysfs_ops* sysfs_ops +attribute_group** default_groups } class device { +kobject kobj +device* parent +bus_type* bus +device_driver* driver +device_private* p +const char* driver_override +void* platform_data } class device_driver { +const char* name +bus_type* bus +module* owner +of_device_id* of_match_table +acpi_device_id* acpi_match_table +probe(device*) int +remove(device*) void +bool suppress_bind_attrs +driver_private* p } class bus_type { +const char* name +match(device*, device_driver*) int +uevent(device*, kobj_uevent_env*) int +probe(device*) int +attribute_group** dev_groups +bool need_parent_lock } class pci_dev { +device dev +u16 vendor +u16 device +u32 class } class pci_driver { +device_driver driver +pci_device_id* id_table +pci_dynids dynids } kobject "1" --* "1" kset : embeds its own kobject kobject --> kobj_type : ktype kobject --> kset : member of kobject --> kobject : parent device --* kobject : embeds device --> bus_type : bus device --> device_driver : driver (set by bind) device_driver --> bus_type : bus bus_type ..> device : match(dev, drv) bus_type ..> device_driver : match(dev, drv) pci_dev --* device : embeds pci_driver --* device_driver : embeds
The device-model object graph and the embedding relationships that build it. What it shows: struct kobject is the universal node — it carries the parent pointer that builds the /sys tree, the kref that owns the object’s lifetime, and the sd pointer into kernfs where the directory actually lives. struct device and struct device_driver are not subclasses in any language sense; device embeds a kobject as a plain member (solid diamond), and a bus-specific type like struct pci_dev in turn embeds a struct device. bus_type is not embedded by anything — it is a shared vtable that both a device and a driver point at, and its match() is the only function in this picture that decides whether the dashed device → driver edge gets drawn. The insight to take: matching is edge creation in a graph whose nodes are all the same primitive. container_of() on the way in, function pointer on the way out — that is the entire object system, and it is why the driver core can be written once and reused for every bus in the tree. The kobject/kset/kobj_type triangle at the top of this diagram is dissected in sysfs and the Kernel Object Hierarchy; the device/driver/bus triangle at the bottom is this note’s subject.
Two fields in that diagram carry the whole of this note. bus_type.match is the decision function. device.driver_override is userspace’s veto over it. Everything else is bookkeeping.
The Dispatch Shim — driver_match_device
Every match in the kernel funnels through one inline helper in drivers/base/base.h (base.h:164-168):
static inline int driver_match_device(const struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}Two things to read off this. First, if the bus provides no match callback, the helper returns 1 — unconditional match. This is why a bus with no match will try every driver against every device (some simple/legacy buses rely on this and let .probe() do all the filtering). Second, the return convention: the binding core in dd.c interprets the result as 0 = no match (skip), > 0 = match (proceed to probe), and the special -EPROBE_DEFER = “the match itself wants to defer” — used when the bus needs a not-yet-available resource (e.g. firmware/fwnode not ready) to even decide (dd.c, v6.12). The binding.rst specification states the contract plainly: “Instead of trying to derive a complex state machine and matching algorithm, it is up to the bus driver to provide a callback to compare a device against the IDs of a driver. The bus returns 1 if a match was found; 0 otherwise” (binding.rst §device_register, v6.12).
The Two Entry Points, and Why They Treat a Deferral Differently
driver_match_device is reached from exactly two directions, and the asymmetry between them is easy to miss and worth understanding, because it explains an entire class of “why did my other driver stop being tried?” puzzles.
- Device side — a new device was registered (
device_add()→bus_probe_device()→device_initial_probe()→__device_attach()), so the core walks every registered driver on that bus looking for one that claims it. The per-driver callback is__device_attach_driver(). - Driver side — a new driver was registered (
driver_register()→bus_add_driver()→driver_attach()), so the core walks every device already on that bus looking for ones this driver claims. The per-device callback is__driver_attach(). This is the path a freshlymodprobe-d module takes.
Both call driver_match_device first, but they handle -EPROBE_DEFER from the match itself differently (dd.c, v6.12):
/* __device_attach_driver() — walking drivers for one device */
ret = driver_match_device(drv, dev);
if (ret == 0) {
return 0; /* no match: try the next driver */
} else if (ret == -EPROBE_DEFER) {
dev_dbg(dev, "Device match requests probe deferral\n");
dev->can_match = true;
driver_deferred_probe_add(dev);
/*
* Device can't match with a driver right now, so don't attempt
* to match or bind with other drivers on the bus.
*/
return ret; /* <-- ABORTS the whole walk */
}
/* __driver_attach() — walking devices for one driver */
ret = driver_match_device(drv, dev);
if (ret == -EPROBE_DEFER) {
dev->can_match = true;
driver_deferred_probe_add(dev);
/*
* Driver could not match with device, but may match with
* another device on the bus.
*/
return 0; /* <-- CONTINUES to the next device */
}A deferral on the device side stops the search entirely — the core has decided that this device cannot be matched right now, so trying other drivers would be wasted work and might bind the wrong one. A deferral on the driver side stops only this pairing — the same driver may still legitimately claim a different device on the bus. Both paths set dev->can_match = true, a flag consumed by the sync_state() machinery so that a supplier device knows a consumer is still coming and must not tear down bootloader-configured hardware yet.
flowchart TB subgraph DEVSIDE["Device side — a device was registered"] DA["device_add(dev)"] --> BPD["bus_probe_device()"] BPD --> DIP["device_initial_probe()<br/>→ __device_attach()"] DIP --> WALKD["bus_for_each_drv():<br/>__device_attach_driver(drv, dev)"] end subgraph DRVSIDE["Driver side — a driver was registered"] DR["driver_register(drv)<br/>(module_init of a modprobe'd .ko)"] --> BAD["bus_add_driver()"] BAD --> DAT["driver_attach()"] DAT --> WALKV["bus_for_each_dev():<br/>__driver_attach(dev, drv)"] end WALKD --> DMD["driver_match_device(drv, dev)<br/>= drv->bus->match ? bus->match(dev,drv) : 1"] WALKV --> DMD DMD -->|"0 — no match"| SKIP["skip this pair<br/>(keep walking)"] DMD -->|"-EPROBE_DEFER"| DEF{"which side?"} DEF -->|"device side"| STOPALL["deferred_probe_add(dev)<br/>ABORT the whole driver walk"] DEF -->|"driver side"| CONT["deferred_probe_add(dev)<br/>continue to next device"] DMD -->|"other negative errno"| ERRSKIP["log via dev_dbg;<br/>treat as no match"] DMD -->|"> 0 — match"| APROBE{"driver_allows_async_probing?"} APROBE -->|"yes"| ASYNC["async_schedule_dev(<br/>__driver_attach_async_helper)"] APROBE -->|"no"| DPD["driver_probe_device(drv, dev)"] ASYNC --> DPD DPD --> RP["really_probe() → drv->probe()<br/>see Driver Binding and the Probe Flow"] RP -->|"0"| BOUND["driver_bound(dev):<br/>edge created, sysfs links appear"] RP -->|"-EPROBE_DEFER"| PEND["driver_deferred_probe_add(dev)<br/>→ pending list"] RP -->|"-ENODEV / -ENXIO"| DECLINE["driver declined this unit<br/>(a normal, silent outcome)"] RP -->|"other errno"| PFAIL["probe_failed: unwind devres,<br/>remove sysfs links, log"]
Every route from a registration event to a bound driver, with all six exits drawn. What it shows: two symmetric entry points converge on one three-line shim; the shim’s result fans out into no-match, two different flavours of deferral, an error, and a match — and only the match arm reaches .probe(), which itself has four distinct outcomes. The insight to take: “my driver never bound” has at least six mechanically different causes, and they are distinguishable. A 0 from match() is silent and leaves no trace; a -EPROBE_DEFER from probe() leaves the device on a list you can read; an -ENODEV from probe() is a deliberate decline that looks identical from userspace to never having matched at all. Knowing which arm you are on is the whole of diagnosing a binding failure.
The Match Rules, Bus by Bus
This is the reference table the rest of the note elaborates. Every row was read from the named function at v6.12; the “rule” column states what the function actually compares, in the order it compares it. Note how few of these are the same, and how several buses converge on the same three-step ladder (override → firmware → ID table) despite having been written years apart by different people.
| Bus | match() function (file) | What identity is compared, in order | Wildcard mechanism |
|---|---|---|---|
| PCI / PCIe | pci_bus_match() → pci_match_device() (pci-driver.c) | 1. driver_override (if set, name must equal drv->name, else no match) · 2. driver’s dynamic ID list (new_id) · 3. static id_table, honouring override_only · 4. if driver_override is set and nothing matched, a synthetic pci_device_id_any | PCI_ANY_ID (~0) per field; class_mask bitmask for the 24-bit class triplet |
| USB (interface) | usb_device_match() → usb_match_id() (usb/core/driver.c) | usb_match_device() (vendor, product, bcdDevice range, device class/subclass/protocol) then usb_match_one_id_intf() (interface class/subclass/protocol/number); then the driver’s dynamic IDs | match_flags bitmask — a field is compared only if its flag bit is set |
| USB (device) | usb_device_match(), device branch | If the usb_device_driver has neither an id_table nor a ->match, return 1 and let .probe() decide; otherwise usb_driver_applicable() | as above |
| Platform | platform_match() (platform.c) | 1. driver_override · 2. OF compatible vs of_match_table · 3. ACPI _HID/_CID vs acpi_match_table · 4. id_table name strcmp · 5. strcmp(pdev->name, drv->name) | none — exact string equality at every rung |
| I²C | i2c_device_match() (i2c-core-base.c) | 1. OF compatible (i2c_of_match_device(), which falls back to i2c_of_match_device_sysfs()) · 2. ACPI · 3. i2c_match_id() name compare. No driver_override rung | the sysfs fallback strips the vendor prefix at the , — a driver claiming "ti,tmp102" also matches a client instantiated by name as tmp102 (i2c-core-of.c) |
| SPI | spi_match_device() (spi.c) | 1. spi->driver_override · 2. OF · 3. ACPI · 4. spi_match_id(sdrv->id_table, spi->modalias) · 5. strcmp(spi->modalias, drv->name) | none |
| Open Firmware / Device Tree (used by the buses above, not a bus itself) | of_driver_match_device() → of_match_node() (of/base.c) | Scores every table entry against the node’s compatible list and returns the highest-scoring one, not the first | position in the node’s own compatible list acts as specificity; empty name/type/compatible fields are skipped |
| ACPI (likewise a matcher, not a bus) | acpi_driver_match_device() (acpi/bus.c) | If the driver has an acpi_match_table: _HID then each _CID against acpi_device_id.id, plus a class-code path via cls/cls_msk. If it has no acpi_match_table: acpi_of_match_device() matches the ACPI object’s _DSD compatible property against the driver’s of_match_table | cls_msk bitmask; ?? for don’t-care class bytes in the generated alias; the _DSD path compares with strcasecmp |
| virtio | virtio_dev_match() (virtio.c) | Loop over id_table until ids[i].device == 0; virtio_id_match() compares device ID then vendor | VIRTIO_DEV_ANY_ID (0xffffffff) works on both the device and vendor fields |
| auxiliary | auxiliary_match() → auxiliary_match_id() (auxiliary.c) | Splits dev_name() at the last . and strncmps the prefix against id->name — so a device named mlx5_core.eth.0 matches a driver claiming mlx5_core.eth | the .<instance> suffix is what gets stripped; no other wildcards |
any bus with ->match == NULL | (none) | driver_match_device() returns 1 unconditionally | everything matches; .probe() does all filtering |
Bus-by-bus match rules at v6.12. What it shows: the real comparison performed by each bus, in execution order, with its wildcard convention. The insight to take: three families exist and they are worth naming. (1) Register-identity buses (PCI, USB, virtio) read a number off the hardware and compare it with a wildcard-aware bitmask — matching is a numeric predicate. (2) Firmware-described buses (platform, I²C, SPI) have no readable identity at all, so they compare strings supplied by a Device Tree or ACPI table and fall back to a bare name; note that platform, I²C and SPI implement the same OF-then-ACPI-then-id_table ladder by hand, three separate times. (3) Naming-convention buses (auxiliary) encode the match in the device’s own name. The one thing every row shares is that match() returns a bit and nothing else — the format of identity is entirely a bus’s private business.
Three rows deserve a flag.
I²C has no driver_override rung, so the userspace “force this driver” trick that works on PCI, platform, and SPI simply does not exist for an I²C client — you must instantiate the device by name through the adapter’s new_device file instead. The USB device branch returns 1 when the driver has no id_table and no ->match, which is the same “no filter means match everything” escape hatch that driver_match_device provides at the core level, re-implemented one layer down.
The third is the least known and the most surprising. ACPI can match a driver that has no ACPI table at all, by comparing Device Tree compatible strings. acpi_driver_match_device() checks drv->acpi_match_table first; if the driver does not have one, it calls acpi_of_match_device(), which reads the ACPI object’s _DSD compatible property and compares each string against the driver’s of_match_table (acpi/bus.c, v6.12):
/* Now we can look for the driver DT compatible strings */
for (i = 0; i < nval; i++, obj++) {
const struct of_device_id *id;
for (id = of_match_table; id->compatible[0]; id++)
if (!strcasecmp(obj->string.pointer, id->compatible)) {
if (of_id)
*of_id = id;
return true;
}
}This is the kernel side of the PRP0001 Device Tree namespace link, and the kernel’s ACPI enumeration guide documents it precisely: “if PRP0001 is returned by _HID, the ACPI subsystem will look for the ‘compatible’ property in the device object’s _DSD and will use the value of that property to identify the corresponding device in analogy with the original DT device identification algorithm” (enumeration.rst §“Using DT namespace link”, v6.12). A firmware author who wants an ACPI-described board to reuse an existing Device-Tree-only driver writes:
Device (TMP0)
{
Name (_HID, "PRP0001") /* the DT namespace link ID */
Name (_DSD, Package () {
ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
Package () {
Package () { "compatible", "ti,tmp75" },
}
})
Method (_CRS, 0, Serialized) { /* ... I2cSerialBusV2 (0x48, ...) ... */ }
}The ti,tmp75 driver needs no ACPI awareness whatsoever — its of_match_table is what gets searched. The specification hook this satisfies is that ACPI mandates every device object carry a _HID or _ADR from a specification-prescribed namespace, and the Device Tree namespace is not one of them; PRP0001 is the officially reserved ID that means “look in _DSD instead.” If PRP0001 appears in _CID rather than _HID, its position in the _CID package sets the relative priority of the compatible strings against the other IDs — device IDs preceding it are checked first.
Note the comparison in acpi_of_match_device() is strcasecmp — case-insensitive — and so is ordinary Device Tree matching: of_compat_cmp(s1, s2, l) is defined as plain strcasecmp(s1, s2), silently discarding the length argument its callers pass (include/linux/of.h, v6.12). A compatible string differing only in case still matches, on both paths.
ID Tables — The Common Mechanism
The dominant matching mechanism is the ID table: a driver declares a static, null-terminated array of structs describing the hardware identities it supports, and the bus’s match() walks that array comparing fields. Each bus has its own struct, all defined in include/linux/mod_devicetable.h (mod_devicetable.h, v6.12).
PCI — struct pci_device_id (mod_devicetable.h:44-50):
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID */
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
__u32 override_only;
};The match is field-by-field with a wildcard sentinel PCI_ANY_ID (= ~0). The actual comparison is pci_match_one_device() (pci.h, v6.12): it succeeds only if vendor matches or is PCI_ANY_ID, device matches or is PCI_ANY_ID, subvendor and subdevice likewise, and the class matches under the mask: !((id->class ^ dev->class) & id->class_mask). Walking the symbol-by-symbol: id->class ^ dev->class is the bitwise XOR — bits that differ between the table entry’s class and the device’s class become 1; & id->class_mask keeps only the bits the driver said it cares about; !(...) succeeds when no cared-about bit differs. A class_mask of 0 means “ignore class entirely.” This is how a driver can match by class (e.g. “any USB host controller”) rather than a specific device. pci_match_device() checks dynamic IDs (added via the new_id sysfs file) before the static table, then the static id_table, honoring driver_override and the override_only flag (pci-driver.c:136-178).
The class field is worth unpacking because it is where most PCI matching mistakes live. PCI stores device classification as a 24-bit triplet packed into one u32, and class_mask selects which bytes participate:
| Byte (of the 24-bit class) | Shift | Name | Example |
|---|---|---|---|
| high | >> 16 | base class | 0x02 = Network controller |
| middle | >> 8 | sub-class | 0x00 = Ethernet |
| low | >> 0 | programming interface (“prog-if”) | 0x00 |
The Ethernet NIC in this machine reads class = 0x020000 — base 02, sub 00, prog-if 00 — which is exactly what cat /sys/bus/pci/devices/0000:bf:00.0/class returns on a live system (kernel 7.1.8, 2026-09-04). A driver that wants “any Ethernet NIC regardless of vendor” writes { PCI_DEVICE_CLASS(0x020000, 0xffff00) }: mask 0xffff00 keeps the base and sub-class bytes and zeroes the prog-if byte, so any prog-if value matches. PCI_ANY_ID handles the vendor/device/subsystem fields; the mask handles class. There is no PCI_ANY_ID for class — a class_mask of 0 is the way to say “ignore class.”
flowchart TB START["pci_match_one_device(id, dev)"] --> V{"id->vendor == PCI_ANY_ID<br/>|| == dev->vendor ?"} V -->|"no"| NO["return NULL<br/>(this table entry does not match;<br/>caller advances to the next)"] V -->|"yes"| D{"id->device == PCI_ANY_ID<br/>|| == dev->device ?"} D -->|"no"| NO D -->|"yes"| SV{"id->subvendor == PCI_ANY_ID<br/>|| == dev->subsystem_vendor ?"} SV -->|"no"| NO SV -->|"yes"| SD{"id->subdevice == PCI_ANY_ID<br/>|| == dev->subsystem_device ?"} SD -->|"no"| NO SD -->|"yes"| C{"!((id->class ^ dev->class)<br/>& id->class_mask) ?"} C -->|"a cared-about class bit differs"| NO C -->|"no cared-about bit differs"| YES["return id<br/>(match — driver_data now<br/>reachable via id->driver_data)"]
pci_match_one_device() as a five-test conjunction with a single shared failure exit. What it shows: the four identity fields are compared with an explicit PCI_ANY_ID escape each, and the class is compared through a mask instead. The insight to take: the class test !((id->class ^ dev->class) & id->class_mask) reads mechanically as: XOR produces a 1 in every bit position where the table entry and the device disagree; & class_mask discards the disagreements in bit positions the driver said it does not care about; ! succeeds when nothing survives. A class_mask of 0 therefore always succeeds — which is why a driver that forgets to set class_mask while setting class gets a match rule that ignores class entirely, silently.
USB — struct usb_device_id (mod_devicetable.h:128-147): richer, because USB devices have both device-level and interface-level descriptors. It carries idVendor, idProduct, a bcdDevice_lo/bcdDevice_hi range, device class/subclass/protocol, and interface class/subclass/protocol — but the key field is match_flags, a bitmask telling the matcher which of those fields are significant for this entry. The flags (USB_DEVICE_ID_MATCH_VENDOR, ..._PRODUCT, ..._INT_CLASS, etc.) are defined right below the struct (mod_devicetable.h:157-167). A class driver (say a USB mass-storage driver) sets only the interface-class flags and matches any vendor with that interface class; a quirk driver sets vendor+product to match one specific gadget.
The matcher itself is a chain of guarded comparisons, each of the identical shape “if this flag is set and this field disagrees, fail” (usb/core/driver.c, v6.12):
int usb_match_device(struct usb_device *dev, const struct usb_device_id *id)
{
if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
id->idVendor != le16_to_cpu(dev->descriptor.idVendor))
return 0;
/* ...idProduct... */
/* No need to test id->bcdDevice_lo != 0, since 0 is never
greater than any unsigned number. */
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) &&
(id->bcdDevice_lo > le16_to_cpu(dev->descriptor.bcdDevice)))
return 0;
if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) &&
(id->bcdDevice_hi < le16_to_cpu(dev->descriptor.bcdDevice)))
return 0;
/* ...bDeviceClass, bDeviceSubClass, bDeviceProtocol... */
return 1;
}Two things are unusual here and appear on no other bus. First, le16_to_cpu() on every field: USB descriptors are little-endian on the wire and must be byte-swapped before comparison, because the identity being matched was read off a cable, not out of a CPU-native struct. Second, bcdDevice_lo/bcdDevice_hi implement a range rather than an equality — this is the only ID table in the tree that matches an interval, and it exists so a quirk can be scoped to “firmware revisions 0x0100 through 0x0210 of this gadget” without enumerating each.
match_flags bit | Field compared | Compared against | Typical user |
|---|---|---|---|
USB_DEVICE_ID_MATCH_VENDOR | idVendor | device descriptor | every vendor-specific driver |
USB_DEVICE_ID_MATCH_PRODUCT | idProduct | device descriptor | quirk / single-model drivers |
USB_DEVICE_ID_MATCH_DEV_LO | bcdDevice_lo ≤ device’s bcdDevice | device descriptor | firmware-revision-scoped quirks |
USB_DEVICE_ID_MATCH_DEV_HI | bcdDevice_hi ≥ device’s bcdDevice | device descriptor | firmware-revision-scoped quirks |
USB_DEVICE_ID_MATCH_DEV_CLASS | bDeviceClass | device descriptor | hub drivers, whole-device classes |
USB_DEVICE_ID_MATCH_DEV_SUBCLASS | bDeviceSubClass | device descriptor | rare |
USB_DEVICE_ID_MATCH_DEV_PROTOCOL | bDeviceProtocol | device descriptor | rare |
USB_DEVICE_ID_MATCH_INT_CLASS | bInterfaceClass | interface descriptor | class drivers: usb-storage, usbhid, CDC |
USB_DEVICE_ID_MATCH_INT_SUBCLASS | bInterfaceSubClass | interface descriptor | class drivers narrowing a subclass |
USB_DEVICE_ID_MATCH_INT_PROTOCOL | bInterfaceProtocol | interface descriptor | e.g. bulk-only mass storage |
USB_DEVICE_ID_MATCH_INT_NUMBER | bInterfaceNumber | interface descriptor | multi-function gadgets where only one interface is claimed |
The usb_device_id.match_flags bitmask. What it shows: which descriptor field each bit enables, and which descriptor — device or interface — it is read from. The insight to take: USB is the only bus in the table above where matching happens at two different granularities. A USB gadget is one struct usb_device but N struct usb_interface children, and interface drivers bind to interfaces, not to devices — which is why one physical webcam can simultaneously be driven by uvcvideo (video interface) and snd-usb-audio (audio interface). Setting a DEV_* flag when you meant an INT_* flag is the classic USB ID-table bug, and it fails silently as a non-match.
usb_match_one_id_intf() adds one guard that is pure defensive policy rather than identity comparison: if the device’s bDeviceClass is USB_CLASS_VENDOR_SPEC and the table entry sets any INT_* flag without also setting USB_DEVICE_ID_MATCH_VENDOR, the match is refused outright. The reasoning is in the comment: “The interface class, subclass, protocol and number should never be checked for a match if the device class is Vendor Specific, unless the match record specifies the Vendor ID.” A vendor-specific device is free to reuse standard interface-class numbers for non-standard purposes, so a generic class driver must not claim it on interface class alone.
Platform — struct platform_device_id (mod_devicetable.h:607-610): the simplest of all — just a name string and driver_data. platform_match_id() does a plain strcmp(pdev->name, id->name) down the table and, on a hit, stashes the matching entry in pdev->id_entry so the driver can read its driver_data later (platform.c:1079-1090). The driver_data field, present in nearly every ID struct, is the standard channel for the table to carry per-variant configuration (register offsets, feature flags) into the driver without a switch on the ID.
Firmware Matching — Device Tree compatible and ACPI HID/CID
For non-discoverable, board-described hardware (SoC peripherals), the identity comes not from a register but from firmware. Two schemes dominate.
Open Firmware / Device Tree — of_match_table. A driver sets drv->of_match_table to an array of struct of_device_id (mod_devicetable.h:282-287):
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};The match is by the compatible string: the device’s of_node (from the Device Tree) carries a compatible property like "ti,am335x-uart", and of_driver_match_device() → of_match_device() → of_match_node() finds the best (most-specific) entry whose compatible string equals one of the device’s (of/device.c:19-33). The full mechanics — vendor-prefix convention, the most-specific-first ordering of a node’s compatible list, fallback strings — are a topic of their own; see Device Tree Bindings and compatible Strings. The data pointer plays the same role as driver_data, carrying per-variant config.
OF matching is scored, not first-fit. This is the single most misunderstood thing about compatible matching, and it is worth reading the arithmetic. __of_match_node() does not return the first table entry that matches — it evaluates every entry and returns the highest-scoring one (of/base.c, v6.12):
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}
return best_match;
}And the score is computed as:
/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}
if (type && type[0]) { if (!__of_node_is_type(device, type)) return 0; score += 2; }
if (name && name[0]) { if (!of_node_name_eq(device, name)) return 0; score++; }Walk score = INT_MAX/2 - (index << 2) symbol by symbol. index is the position of the matching string within the device node’s own compatible list, counting from 0. index << 2 is index * 4. INT_MAX/2 (1,073,741,823 on a 32-bit int) is a large base so that a compatible match always outranks the small +2/+1 bonuses added for a type or name match. Subtracting index * 4 means an earlier position in the device’s list scores higher. Device Tree convention writes compatible from most-specific to least-specific, so index 0 is the exact chip and later entries are the families it is register-compatible with. The << 2 leaves three spare units between adjacent positions — enough headroom for the +2 and +1 bonuses to break ties within a position without ever letting a name bonus jump a device across positions.
Consider a node and a driver table:
/* device node */
uart@44e09000 {
compatible = "ti,am335x-uart", "ti,omap3-uart", "ns16550a";
};/* the driver's of_match_table */
static const struct of_device_id omap_serial_of_match[] = {
{ .compatible = "ns16550a", .data = &generic_cfg },
{ .compatible = "ti,omap3-uart", .data = &omap3_cfg },
{ }
};flowchart LR subgraph NODE["device node's compatible list (most → least specific)"] I0["index 0<br/>ti,am335x-uart"] --> I1["index 1<br/>ti,omap3-uart"] --> I2["index 2<br/>ns16550a"] end subgraph TBL["driver's of_match_table (declaration order is irrelevant)"] E0["entry 0<br/>compatible = ns16550a"] E1["entry 1<br/>compatible = ti,omap3-uart"] end I2 -.->|"matches"| E0 I1 -.->|"matches"| E1 E0 --> S0["score = INT_MAX/2 - (2<<2)<br/>= 1073741823 - 8<br/>= 1073741815"] E1 --> S1["score = INT_MAX/2 - (1<<2)<br/>= 1073741823 - 4<br/>= 1073741819"] S0 --> CMP{"best_score wins"} S1 --> CMP CMP -->|"1073741819 > 1073741815"| WIN["best_match = entry 1<br/>(ti,omap3-uart)<br/>→ driver gets .data = &omap3_cfg"]
A worked of_match_node() scoring pass. What it shows: both table entries match something in the node’s compatible list, but the one matching an earlier (more specific) position scores higher and wins, regardless of the order the entries were written in the driver’s table. The insight to take: unlike PCI’s first-fit walk down id_table, OF matching is a max-score selection driven by the device’s list order, not the driver’s. A driver author cannot control precedence by reordering their table — the board’s Device Tree does. This is also why the selected entry’s .data pointer matters: a driver supporting both a generic 16550 and the OMAP3 variant gets handed the OMAP3 configuration automatically, without a switch.
One trap hides in that code: of_compat_cmp(cp, compat, strlen(compat)) looks like a bounded comparison, but the default definition of the macro is strcasecmp(s1, s2) — the third argument is discarded (include/linux/of.h, v6.12). The comparison is therefore a full-string, case-insensitive equality, not a prefix test. "ti,omap3-uart-extra" does not match "ti,omap3-uart".
ACPI — acpi_match_table. On x86 and modern Arm servers, hardware is described by ACPI. A driver sets drv->acpi_match_table to struct acpi_device_id entries (mod_devicetable.h:217-222):
struct acpi_device_id {
__u8 id[ACPI_ID_LEN]; /* ACPI_ID_LEN = 16 */
kernel_ulong_t driver_data;
__u32 cls;
__u32 cls_msk;
};The id is matched against the ACPI device’s _HID (Hardware ID) and its _CID (Compatible ID) — acpi_driver_match_device() succeeds if any of the device’s HID/CID strings equals an entry’s id. (There is also a class-code path via cls/cls_msk, analogous to PCI class matching.) The HID/CID enumeration semantics live in ACPI Device Enumeration.
Precedence — The Platform Bus Ladder
When a single device exposes several identities (a platform device can have an OF node, an ACPI companion, and a name), the order in which match() tries them is load-bearing. platform_match() is the canonical example (platform.c:1335-1358):
static int platform_match(struct device *dev, const struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
if (of_driver_match_device(dev, drv)) /* OF style match first */
return 1;
if (acpi_driver_match_device(dev, drv)) /* then ACPI */
return 1;
if (pdrv->id_table) /* then the id table */
return platform_match_id(pdrv->id_table, pdev) != NULL;
return (strcmp(pdev->name, drv->name) == 0); /* fall back to name */
}Reading the ladder rung by rung: (1) driver_override wins absolutely — if userspace wrote a driver name into the device’s driver_override sysfs file, only that named driver may bind and all other mechanisms are skipped (this is the vfio-platform/passthrough mechanism, mirrored by PCI’s driver_override). (2) Device-Tree compatible is tried next. (3) ACPI HID/CID. (4) The id_table name match. (5) Finally, a bare strcmp(pdev->name, drv->name) — the legacy “the device’s name equals the driver’s name” fallback. The order encodes a policy: explicit userspace override beats firmware description, firmware description beats a hand-registered ID table, and the name match is the last resort. PCI’s ladder is shorter but the same shape: driver_override first, then dynamic IDs, then the static id_table (pci-driver.c, v6.12).
Drawn side by side, the four ladders make the shared policy obvious — and make the exceptions stand out.
flowchart TB subgraph PL["platform_match()"] P1["1. driver_override"] --> P2["2. OF compatible"] --> P3["3. ACPI HID/CID"] --> P4["4. id_table name"] --> P5["5. strcmp(pdev->name, drv->name)"] end subgraph SP["spi_match_device()"] S1["1. spi->driver_override"] --> S2["2. OF compatible"] --> S3["3. ACPI HID/CID"] --> S4["4. spi_match_id(id_table, modalias)"] --> S5["5. strcmp(spi->modalias, drv->name)"] end subgraph I2["i2c_device_match()"] C0["(no override rung)"]:::gap --> C2["1. OF compatible<br/>(+ vendor-prefix-stripping<br/>sysfs fallback)"] --> C3["2. ACPI HID/CID"] --> C4["3. i2c_match_id() name"] --> C5["(no name fallback)"]:::gap end subgraph PC["pci_match_device()"] Q1["1. driver_override<br/>(name mismatch => NULL)"] --> Q2["2. dynids list (new_id)"] --> Q3["3. static id_table,<br/>skipping override_only entries<br/>unless an override is set"] --> Q4["4. if override set and nothing hit:<br/>synthetic pci_device_id_any"] end classDef gap fill:#eee,stroke-dasharray: 4 3,color:#666
Four bus match ladders aligned by rung. What it shows: platform and SPI are near-identical five-rung ladders written independently; I²C is the same ladder with the first and last rungs missing; PCI has no firmware rungs at all because a PCI device carries its identity in silicon. The insight to take: the recurring pattern is userspace override → firmware description → in-kernel table → bare name, ordered from most authoritative to least. Where a bus omits a rung it is a real capability gap, not a simplification: an I²C client cannot be overridden from userspace and cannot fall back to a bare name match, which is exactly why I²C devices must be instantiated with the right name in the first place. PCI’s fourth rung — synthesising pci_device_id_any when an override is set and nothing matched — is the mechanism that lets vfio-pci bind a card whose ID appears in no table anywhere.
MODULE_DEVICE_TABLE, modalias, and udev Autoloading
The same ID table that drives in-kernel matching also drives on-demand module loading — this is the elegant part. A driver wraps its table in the MODULE_DEVICE_TABLE macro:
static const struct pci_device_id mydrv_pci_tbl[] = {
{ PCI_DEVICE(0x8086, 0x1533) }, /* one specific device: Intel I210 */
{ PCI_DEVICE_CLASS(0x020000, 0xffff00) }, /* any Ethernet NIC, any prog-if */
{ 0, } /* terminator */
};
MODULE_DEVICE_TABLE(pci, mydrv_pci_tbl);MODULE_DEVICE_TABLE(type, name) expands (when building as a module) to an aliased symbol the build tooling can find (module.h:247-254):
#define MODULE_DEVICE_TABLE(type, name) \
extern typeof(name) __mod_##type##__##name##_device_table \
__attribute__ ((unused, alias(__stringify(name))))How a C struct becomes an alias string
At build time modpost runs scripts/mod/file2alias.c over every compiled .o, finds the aliased __mod_<type>__<name>_device_table symbols, and walks each table entry through a per-bus do_*_entry() function that emits a MODULE_ALIAS("…") line into the module’s .modinfo section. The workhorse is a nine-line macro that decides, per field, whether to print a hex value or a * (file2alias.c, v6.12):
#define ADD(str, sep, cond, field) \
do { \
strcat(str, sep); \
if (cond) \
sprintf(str + strlen(str), \
sizeof(field) == 1 ? "%02X" : \
sizeof(field) == 2 ? "%04X" : \
sizeof(field) == 4 ? "%08X" : "", \
field); \
else \
sprintf(str + strlen(str), "*"); \
} while(0)Read the three consequences off that macro. Field width is derived from sizeof, not from the semantic width — pci_device_id.vendor is a __u32, so a 16-bit PCI vendor ID is printed as eight hex digits: v00008086, not v8086. Hex is uppercase (%08X). And the wildcard is a bare * in place of the whole field, which is why matching a concrete device alias against a driver alias is an fnmatch-style glob, not a numeric comparison.
do_pci_entry() then assembles the fields in a fixed order and unpacks the class triplet by hand:
/* Looks like: pci:vNdNsvNsdNbcNscNiN or <prefix>_pci:vNdNsvNsdNbcNscNiN. */
switch (override_only) {
case 0: strcpy(alias, "pci:"); break;
case PCI_ID_F_VFIO_DRIVER_OVERRIDE: strcpy(alias, "vfio_pci:"); break;
}
ADD(alias, "v", vendor != PCI_ANY_ID, vendor);
ADD(alias, "d", device != PCI_ANY_ID, device);
ADD(alias, "sv", subvendor != PCI_ANY_ID, subvendor);
ADD(alias, "sd", subdevice != PCI_ANY_ID, subdevice);
baseclass = (class) >> 16; baseclass_mask = (class_mask) >> 16;
subclass = (class) >> 8; subclass_mask = (class_mask) >> 8;
interface = class; interface_mask = class_mask;
if ((baseclass_mask != 0 && baseclass_mask != 0xFF)
|| (subclass_mask != 0 && subclass_mask != 0xFF)
|| (interface_mask != 0 && interface_mask != 0xFF)) {
warn("Can't handle masks in %s:%04X\n", filename, class_mask);
return 0; /* <-- NO ALIAS IS EMITTED */
}
ADD(alias, "bc", baseclass_mask == 0xFF, baseclass);
ADD(alias, "sc", subclass_mask == 0xFF, subclass);
ADD(alias, "i", interface_mask == 0xFF, interface);
add_wildcard(alias);Two facts here are worth more than the format string. First, override_only changes the alias prefix from pci: to vfio_pci: — the mechanism by which a driver can declare “this ID exists, but never autoload me for it; I bind only when forced.” Second, class_mask must be byte-granular or the alias is silently dropped. A mask like 0xffff00 (bytes FF FF 00) is fine; a partial-byte mask like 0xfff000 makes modpost print Can't handle masks in … and return 0, emitting no alias at all. The in-kernel match still works — pci_match_one_device() happily ANDs any mask — so the driver binds correctly when loaded but never autoloads. This is a real and nasty divergence between the two consumers of the same table.
| Bus | Alias format | Generating function |
|---|---|---|
| PCI | pci:vNdNsvNsdNbcNscNiN (or vfio_pci:… when override_only is set) | do_pci_entry() |
| USB | usb:vNpNdNdcNdscNdpNicNiscNipNinN | do_usb_entry() — one alias per bcdDevice sub-range |
| Open Firmware | of:N<name>T<type>C<compatible>, plus a second …C* wildcard alias per entry | do_of_entry_multi() |
| ACPI | acpi*:<HID>:*, or acpi*:bbsspp:* with ?? for don’t-care class bytes | do_acpi_entry() |
| platform | platform:<name> | do_platform_entry() |
| HID | hid:bNvNpN | do_hid_entry() |
Modalias formats per bus at v6.12, read from the generating functions rather than from observed strings. What it shows: every bus has its own alias grammar, and the letters are field mnemonics fixed by the ADD() calls (v=vendor, d=device, sv/sd=subsystem vendor/device, bc/sc/i=base class / sub-class / prog-if). The insight to take: the OF row is the odd one — do_of_entry_multi() emits two aliases per table entry, the exact one and a trailing-C* wildcard variant, so that a device whose compatible list has extra trailing strings still resolves. Nothing else in the table double-emits.
The Loop, Closed on a Live Machine
The runtime loop: the kernel exposes each device’s modalias as /sys/.../<dev>/modalias and includes a MODALIAS= line in the device’s uevent. When the device appears, udev reads that modalias, runs modprobe <modalias>, and modprobe resolves it against /lib/modules/<ver>/modules.alias (globbing the device’s concrete modalias against the driver’s wildcard alias) to find and insert the module — whose module_init calls *_register_driver(), which enters driver_attach() and triggers the exact same bus->match walk described above, now with the just-loaded driver present (platform.c, v6.12, shows the platform uevent emitting MODALIAS). One table does double duty: kernel-internal matching and userspace autoload.
Here is the entire loop closed with real values from the Ethernet controller in this machine (Fedora 44, kernel 7.1.8-200.fc44.x86_64, read 2026-09-04):
$ cat /sys/bus/pci/devices/0000:bf:00.0/modalias
pci:v000010ECd00008126sv0000F111sd0000000Abc02sc00i00
$ cat /sys/bus/pci/devices/0000:bf:00.0/uevent
DRIVER=r8169
PCI_CLASS=20000
PCI_ID=10EC:8126
PCI_SUBSYS_ID=F111:000A
PCI_SLOT_NAME=0000:bf:00.0
MODALIAS=pci:v000010ECd00008126sv0000F111sd0000000Abc02sc00i00
$ modinfo r8169 | grep 8126
alias: pci:v000010ECd00008126sv*sd*bc*sc*i*
$ grep 8126 /lib/modules/$(uname -r)/modules.alias
alias pci:v000010ECd00008126sv*sd*bc*sc*i* r8169
$ modprobe -R "$(cat /sys/bus/pci/devices/0000:bf:00.0/modalias)"
r8169Line by line: the device’s modalias is fully concrete — every field is a value, because it was generated from what the hardware actually reports (vendor 0x10EC Realtek, device 0x8126, subsystem F111:000A, class 020000 split as bc02 sc00 i00). The driver’s alias, generated by do_pci_entry() from a { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8126) } table entry, has * wherever the entry used PCI_ANY_ID. depmod copied that alias into modules.alias, where modprobe globs the first string against the second and gets r8169. This machine’s modules.alias holds 29,415 such lines. The uevent file additionally shows the loop has already run to completion: DRIVER=r8169 is present, meaning the match succeeded and .probe() returned 0.
sequenceDiagram autonumber participant HW as Hardware participant BUS as Bus driver<br/>(PCI/USB core) participant CORE as Driver core<br/>(drivers/base/) participant SYSFS as sysfs (/sys) participant UDEV as systemd-udevd participant MODP as modprobe participant DRV as Driver module HW->>BUS: device appears<br/>(hotplug IRQ / bus rescan) BUS->>BUS: read identity from hardware<br/>(config space / descriptors) BUS->>CORE: device_add(&dev) CORE->>SYSFS: create /sys/devices/.../0000:bf:00.0/<br/>+ symlink under /sys/bus/pci/devices/ CORE->>CORE: bus_probe_device() → __device_attach()<br/>walk all registered pci drivers Note over CORE: driver_match_device() returns 0 for every one —<br/>the module is not loaded yet CORE->>UDEV: kobject_uevent(KOBJ_ADD) over netlink<br/>ACTION=add DEVPATH=... MODALIAS=pci:v000010ECd00008126... UDEV->>MODP: modprobe pci:v000010ECd00008126sv0000F111sd0000000Abc02sc00i00 MODP->>MODP: glob against the modules.alias file<br/>hits pci:v000010ECd00008126sv*sd*bc*sc*i* → r8169 MODP->>DRV: init_module(r8169.ko) DRV->>CORE: pci_register_driver() → driver_register()<br/>→ bus_add_driver() → driver_attach() CORE->>CORE: bus_for_each_dev(): __driver_attach(dev, drv)<br/>driver_match_device() → pci_bus_match() → MATCH CORE->>DRV: really_probe() → drv.probe(dev) DRV-->>CORE: 0 (success) CORE->>SYSFS: driver_bound(): create device/driver symlinks,<br/>add dev_groups attributes CORE->>UDEV: kobject_uevent(KOBJ_BIND) UDEV->>SYSFS: read back /sys/class/net/IFACE/ ,<br/>apply rules, rename to enp191s0
A cold-plug device arriving with no driver loaded, and the full round trip to a bound driver. What it shows: the match runs twice. The first pass (step 5) fails for every registered driver because the module is not resident; the uevent goes out anyway, and the second pass (step 11) — triggered from the driver side by driver_register() inside the freshly-inserted module — is the one that succeeds. The insight to take: matching is not a single event but an idempotent operation the core re-runs on every registration in either direction, which is exactly what makes hotplug and on-demand module loading work without any explicit coordination between udev and the kernel. Note also that udev is a consumer of the modalias, never its author: the string in MODALIAS= was manufactured by the bus’s ->uevent() callback from the same fields match() compares.
Forgetting MODULE_DEVICE_TABLE is a classic bug with a distinctive signature: the driver works perfectly when you insmod it by hand and never loads on its own, because step 9 above has nothing to resolve. A stale modules.alias (a module installed without a subsequent depmod -a) produces the identical symptom from a different cause.
Overriding the Match From Userspace
The match rule is compiled into the kernel, but three sysfs files let an administrator bend or bypass it at runtime. They are not interchangeable, and picking the wrong one is a common source of frustration.
Name matching is the platform/legacy fallback already shown: strcmp(pdev->name, drv->name). It is fragile (any two unrelated things sharing a name collide) and is why long, namespaced names matter; it survives mainly for old board-file platform devices.
new_id — teach a running driver a new ID. Writing into /sys/bus/pci/drivers/<drv>/new_id calls new_id_store() → pci_add_dynid(), appending a struct pci_dynid to the driver’s dynids list, which pci_match_device() consults before the static table (pci-driver.c, v6.12). The documented write format is seven whitespace-separated hex fields, VVVV DDDD SVVV SDDD CCCC MMMM PPPP — vendor, device, subsystem vendor, subsystem device, class, class mask, and private driver data — of which “the Vendor ID and Device ID fields are required, the rest are optional,” and “upon successfully adding an ID, the driver will probe for the device and attempt to bind to it” (Documentation/ABI/testing/sysfs-bus-pci, v6.12). So a matching device already sitting unbound on the bus binds on the spot:
# echo "8086 10f5" > /sys/bus/pci/drivers/foo/new_idremove_id is the inverse. This changes the rule for one driver, for all devices.
driver_override — force one device to consider only one driver. Writing a driver name into /sys/bus/pci/devices/<addr>/driver_override (or the platform/SPI equivalent) makes every subsequent match for that device return true only for the named driver. It changes the rule for one device, for all drivers — the mirror image of new_id. The implementation is shared across buses in driver_set_override() (drivers/base/driver.c, v6.12) and has three behaviours worth knowing:
/* The stored value will be used in sysfs show callback (sysfs_emit()),
* which has a length limit of PAGE_SIZE and adds a trailing newline.
* Thus we can store one character less to avoid truncation during sysfs show. */
if (len >= (PAGE_SIZE - 1))
return -EINVAL;
/* Compute the real length of the string in case userspace sends us a
* bunch of \0 characters like python likes to do. */
len = strlen(s);
if (!len) { /* empty string -> clear the override */
... *override = NULL; ...
}
cp = strnchr(s, len, '\n');
if (cp) len = cp - s; /* trailing newline is stripped */
new = kstrndup(s, len, GFP_KERNEL);
...
if (cp != s) *override = new;
else { kfree(new); *override = NULL; } /* a bare "\n" also clears it */Clearing an override requires writing an empty string or a bare newline: echo > driver_override restores standard matching, exactly as the ABI documentation specifies (“may be cleared with an empty string (echo > driver_override). This returns the device to standard matching rules binding” — Documentation/ABI/testing/sysfs-bus-pci, v6.12). Writing any other string that names no loaded driver pins the device to nothing — and that is a documented, deliberate idiom, not an accident: the same ABI entry states the interface “allows devices to opt-out of driver binding using a driver_override name such as none.” So echo none > driver_override is the supported way to say “leave this device alone.” The PAGE_SIZE - 1 bound and the Python-\0-padding comment in driver_set_override() are both scar tissue from real bug reports.
Two further behaviours from that ABI entry are easy to get wrong. Writing to driver_override does not unbind the device from whatever driver currently holds it, and it does not load the named driver — which is why the passthrough recipe below needs all three steps in order. And “only a single driver may be specified in the override, there is no support for parsing delimiters”: the file holds one name, never a list. The interface was added in April 2014 by Alex Williamson, the VFIO maintainer, which is the clearest possible statement of what it was built for; new_id predates it by a decade (December 2003).
bind / unbind — attach or detach, but never override. /sys/bus/<bus>/drivers/<drv>/bind takes a device name and attempts to bind it. It is emphatically not a way around the match rule, and the source says so in a comment (bus.c, v6.12):
/*
* Manually attach a device to a driver.
* Note: the driver must want to bind to the device,
* it is not possible to override the driver's id table.
*/
static ssize_t bind_store(struct device_driver *drv, const char *buf, size_t count)
{
...
dev = bus_find_device_by_name(bus, NULL, buf);
if (dev && driver_match_device(drv, dev)) { /* <-- match still gates it */
err = device_driver_attach(drv, dev);
...
}A bind of a non-matching pair returns -ENODEV. unbind is unconditional by comparison — it only checks dev->driver == drv — but a driver can opt out of both files entirely by setting suppress_bind_attrs = true in its device_driver, which is what low-level drivers whose removal would wedge the machine (IOMMU drivers, some clock controllers) do.
flowchart TB Q{"What do you want to change?"} Q -->|"this driver should also<br/>claim an unlisted device ID"| NID["write to<br/>/sys/bus/pci/drivers/DRV/new_id<br/>= 'vendor device'"] Q -->|"this device must go to<br/>a specific driver, no other"| OVR["write to<br/>/sys/bus/pci/devices/ADDR/driver_override<br/>= 'drivername'"] Q -->|"attach/detach a pair that<br/>already matches"| BND["write device name to<br/>drivers/DRV/bind or /unbind"] NID --> S1["scope: one DRIVER,<br/>all devices"] OVR --> S2["scope: one DEVICE,<br/>all drivers"] BND --> S3["scope: one PAIR,<br/>rule unchanged"] S1 --> E1["pci_add_dynid() prepends to dynids;<br/>driver_attach() re-runs immediately"] S2 --> E2["driver_set_override(); every later<br/>match returns true only for that name"] S3 --> E3["bind_store() still calls<br/>driver_match_device() — refuses -ENODEV<br/>if the pair does not match"] E3 -.->|"blocked by"| SUP["suppress_bind_attrs = true<br/>→ the bind/unbind files do not exist"]
The three userspace levers, chosen by scope. What it shows: new_id widens one driver’s rule, driver_override narrows one device’s rule, and bind/unbind merely execute an attach or detach without touching any rule. The insight to take: the reason bind “does not work” for people is almost always that they reached for the pair-level lever when they needed a rule-level one. bind is the last step of a forcing workflow, never the whole of it — and if the bind file is missing entirely, the driver set suppress_bind_attrs.
Taking a Device Away From Its Native Driver
Chaining those levers is exactly how device passthrough works. To hand a PCI device to a virtual machine you must first detach it from whatever host driver claimed it and attach it to a stub — vfio-pci — that does nothing but expose the device’s regions and interrupts to userspace. The canonical sequence uses two of the three levers:
# 1. Tell the *device* that only vfio-pci may ever claim it.
echo vfio-pci > /sys/bus/pci/devices/0000:c2:00.0/driver_override
# 2. Detach the current (native) driver. The device is now unbound.
echo 0000:c2:00.0 > /sys/bus/pci/devices/0000:c2:00.0/driver/unbind
# 3. Ask vfio-pci to take it. bind_store() calls driver_match_device(),
# which now returns true because of the override in step 1.
echo 0000:c2:00.0 > /sys/bus/pci/drivers/vfio-pci/bindStep 1 is what makes step 3 legal — without the override, vfio-pci’s ID table would not contain this device and bind would return -ENODEV. It also makes the detachment sticky: if the device is later rescanned or the host driver is reloaded, pci_match_device() still returns NULL for every driver but vfio-pci. The kernel supports the same intent from the other end through PCI_ID_F_VFIO_DRIVER_OVERRIDE: a variant driver can list an ID in its table with override_only set, which (a) makes pci_match_device() accept that entry only when driver_override is also set, and (b) makes file2alias.c emit the alias under a vfio_pci: prefix instead of pci: so it can never be autoloaded by udev. The device-assignment story end to end — IOMMU groups, the container/group/device file descriptors, DMA mapping — is VFIO Framework, with the isolation constraints in IOMMU Groups and Device Isolation and The IOMMU and DMA Remapping.
Deferred Probe: When Matching Succeeds Too Early
A successful match says the device and driver belong together. It says nothing about whether the rest of the system is ready — and on a System-on-Chip, it usually is not. The driver core’s own comment states the problem exactly (dd.c, v6.12):
Sometimes driver probe order matters, but the kernel doesn’t always have dependency information which means some drivers will get probed before a resource it depends on is available. For example, an SDHCI driver may first need a GPIO line from an i2c GPIO controller before it can be initialized. If a required resource is not available yet, a driver can request probing to be deferred by returning
-EPROBE_DEFERfrom its probe hook.
This is the mechanism that trips people up most, because it is invisible when it works and produces a device that simply never appears when it does not. The machinery is small and worth knowing in full.
Two lists and a workqueue. dd.c keeps deferred_probe_pending_list and deferred_probe_active_list, both protected by deferred_probe_mutex. A -EPROBE_DEFER from probe() puts the device on the pending list via driver_deferred_probe_add(). Nothing retries it immediately — retrying at once would just spin. Instead, any successful probe anywhere in the system calls driver_deferred_probe_trigger(), which splices the entire pending list onto the active list, bumps an atomic deferred_trigger_count, and queues deferred_probe_work on system_unbound_wq. The work function walks the active list and re-drives bus_probe_device() for each device.
The reasoning behind “any successful probe” is that a successful probe is the only event that can plausibly have made a missing resource appear. It is a deliberately coarse trigger — the core does not know which dependency was satisfied — and it is why deferred probing converges by repeated sweeps rather than by targeted wakeups. driver_probe_device() guards against a race in that scheme:
static int driver_probe_device(const struct device_driver *drv, struct device *dev)
{
int trigger_count = atomic_read(&deferred_trigger_count);
int ret;
atomic_inc(&probe_count);
ret = __driver_probe_device(drv, dev);
if (ret == -EPROBE_DEFER || ret == EPROBE_DEFER) {
driver_deferred_probe_add(dev);
/* Did a trigger occur while probing? Need to re-trigger if yes */
if (trigger_count != atomic_read(&deferred_trigger_count) &&
!defer_all_probes)
driver_deferred_probe_trigger();
}
...
}If some other CPU completed a successful probe while this one was inside ->probe(), this device was added to the pending list too late to be caught by that trigger and would sit there forever. Comparing the counter before and after and re-triggering closes the window.
The timeout. Deferral cannot be unbounded, or a genuinely absent supplier would hang boot forever. driver_deferred_probe_timeout defaults to 10 seconds when CONFIG_MODULES=y and to 0 when modules are disabled, and is overridable on the kernel command line with deferred_probe_timeout=. When it expires, deferred_probe_timeout_work_func() calls fw_devlink_drivers_done(), sets the timeout to 0, triggers one final sweep, and then prints a dev_warn for every device still stranded — "deferred probe pending: <reason>". Drivers that opt into driver_deferred_probe_check_state() instead of returning -EPROBE_DEFER directly get a graded answer:
| Condition | Return | Meaning |
|---|---|---|
!CONFIG_MODULES and initcalls done | -ENODEV | no module can ever appear; give up, warn “ignoring dependency for device, assuming no driver” |
| timeout has expired and initcalls done | -ETIMEDOUT | we waited; warn “deferred probe timeout, ignoring dependency” |
| otherwise | -EPROBE_DEFER | keep waiting |
driver_deferred_probe_check_state() decision table, from its kernel-doc at v6.12. What it shows: the three distinct answers a dependency check can give and the condition that selects each. The insight to take: the difference between -EPROBE_DEFER and -ETIMEDOUT is time, not correctness — the same missing supplier produces both, and the second is the kernel giving up. A driver that returns raw -EPROBE_DEFER forever will never produce the -ETIMEDOUT diagnostic, which is why the helper exists.
Reading the state. dd.c registers a debugfs file, debugfs_create_file("devices_deferred", 0444, NULL, NULL, &deferred_devs_fops), whose deferred_devs_show() prints one line per pending device — the device name and its recorded deferred_probe_reason, if a driver set one via dev_err_probe(). So the answer to “why did my device never show up?” is usually one command:
# cat /sys/kernel/debug/devices_deferredIt requires root (debugfs is 0700 on a stock Fedora install; the read above returned Permission denied for an unprivileged user on the test machine). An empty file means nothing is waiting — the device’s absence has some other cause, and you are back on the match side of this note. -EPROBE_DEFER also interacts with fw_devlink, which derives supplier/consumer edges from the firmware description ahead of probing so the core can order probes correctly instead of discovering the ordering by trial; see Device Links and Deferred Probing and EPROBE_DEFER for that machinery in depth.
The Bound / Unbound Lifecycle
Everything above is easier to hold in mind as a state machine over one struct device. The states are not an enum in the source — they are implied by the values of dev->driver, dev->can_match, and the device’s membership in the deferred lists — but they are exactly what you observe through sysfs.
stateDiagram-v2 [*] --> Registered : device_add()<br/>kobject created, /sys dir appears Registered --> Matching : bus_probe_device()<br/>→ __device_attach() Matching --> Unbound : no driver matched<br/>(match returned 0 for all) Matching --> Deferred : match returned -EPROBE_DEFER<br/>dev->can_match = true Matching --> Probing : match returned > 0<br/>→ driver_probe_device() Probing --> Bound : probe() returned 0<br/>→ driver_bound() Probing --> Deferred : probe() returned -EPROBE_DEFER<br/>→ pending list Probing --> Unbound : probe() returned -ENODEV/-ENXIO<br/>(declined) or another errno (failed) Deferred --> Matching : any successful probe anywhere<br/>→ deferred_probe_trigger() Deferred --> Unbound : deferred_probe_timeout expired<br/>→ dev_warn "deferred probe pending" Unbound --> Matching : a new driver registers<br/>(modprobe) → driver_attach() Unbound --> Matching : echo id > new_id<br/>or driver_override then bind Bound --> Unbound : echo dev > .../driver/unbind<br/>→ device_driver_detach() → drv->remove() Bound --> Unbound : rmmod → driver_unregister() Bound --> [*] : device_del()<br/>hardware removed / bus rescan Unbound --> [*] : device_del() note right of Bound Observable in sysfs: /sys/.../DEV/driver symlink exists /sys/bus/B/drivers/DRV/DEV symlink exists uevent file contains DRIVER= end note note right of Unbound Observable in sysfs: no driver symlink modalias still present and correct end note
A device’s life as seen by the driver core, with every transition labelled by the call that causes it. What it shows: binding is not a one-way door — Unbound is a fully legitimate resting state that a device can leave at any time, and Deferred is a distinct state with its own exit conditions and its own timeout. The insight to take: the two observable notes at the bottom are the diagnostic. A device in Bound has a driver symlink; a device in Unbound or Deferred does not, and the two are told apart only by /sys/kernel/debug/devices_deferred. Every transition out of Unbound back into Matching re-runs bus->match from scratch, which is why loading a module, writing new_id, and setting driver_override all “just work” on a device that has been sitting there since boot.
Failure Modes
- Driver loads but never binds. The most common cause is a missing or wrong ID-table entry — the device’s real vendor/device or
compatiblestring is not in the table. Diagnose by reading/sys/.../<dev>/modaliasand comparing againstMODULE_ALIASstrings (modinfo <module>). For Device Tree, compare the node’scompatibleagainst the driver’sof_match_table. - Driver autoloads in dev but not in the field. Forgot
MODULE_DEVICE_TABLE, ordepmodwas never run somodules.aliasis stale — the in-kernel match works but the modalias→modprobe bridge is broken. - Wrong variant config. Two devices share an ID-table entry but need different
driver_data; the table is too coarse. Symptom: subtle register-offset bugs on one variant. Fix: split the entry and give each its owndriver_data. - Match succeeds, probe fails with
-ENODEV. A correct match where the driver, on closer inspection in.probe(), finds it cannot drive this specific unit — matching only gates probing, it does not guarantee success (see Driver Binding and the Probe Flow). bindvia sysfs refused. Manualbindstill runsdriver_match_deviceand rejects a non-matching pair (bus.c, v6.12); to force it you must usenew_id/driver_override, notbind.- Match rule is legal in-kernel but generates no modalias. A
pci_device_idwhoseclass_maskis not byte-granular (e.g.0xfff000) makesmodpostemitCan't handle masks in …and skip the alias entirely. The driver binds when loaded and never autoloads. - Device stuck in deferred probe. Nothing in
/sys/bus/*/drivers/*/mentions the device and itsdriversymlink is absent, but it is present under/sys/devices/. Read/sys/kernel/debug/devices_deferred(root) for the pending list and the recorded reason. bind/unbindfiles missing. The driver setsuppress_bind_attrs = true. There is no override for this short of patching the driver.driver_overrideset but nothing bound. Writing the file neither unbinds the incumbent driver nor loads the named one; both are your job. If the named driver is not resident, the device binds to nothing — which is the documentednoneopt-out behaviour arriving by accident.
A Diagnosis Order That Works
Work down this list; each row rules out one arm of the dispatch flowchart above.
| Symptom / question | Command | What the answer tells you |
|---|---|---|
| Is the device even enumerated? | ls /sys/bus/<bus>/devices/ | Absent ⇒ this is an enumeration problem, not a matching one — see PCI and PCIe Enumeration / Device Tree / ACPI Device Enumeration |
| What identity does the kernel think it has? | cat /sys/.../<dev>/modalias | The exact string udev will hand to modprobe. Compare it character by character with the driver’s aliases |
| Does any installed module claim it? | modprobe -R "$(cat …/modalias)" | Prints the module name, or nothing. Nothing ⇒ no alias covers this device |
| Does the intended module claim it? | modinfo <mod> | grep alias | If the ID is missing here but present in the driver’s C source, suspect a missing MODULE_DEVICE_TABLE or a dropped alias (byte-granular class_mask) |
Is modules.alias current? | depmod -a then retry | A module installed without depmod has aliases in .modinfo but not in modules.alias |
| Is it bound? | readlink /sys/.../<dev>/driver | A symlink ⇒ bound. Absent ⇒ Unbound or Deferred |
| Unbound or deferred? | cat /sys/kernel/debug/devices_deferred | Listed ⇒ waiting on a supplier. Not listed ⇒ nothing matched, or probe() declined |
Did probe() decline? | dmesg | grep -i <driver> | -ENODEV from probe is often silent; dev_err_probe() messages appear here |
| Was it overridden? | cat /sys/.../<dev>/driver_override | A non-empty value pins the device to one driver and blocks every other match |
The single highest-yield step is the second: printing the device’s modalias and the driver’s aliases side by side. Nine binding bugs in ten are visible as a difference between two strings.
A Note on Stale Documentation
Documentation/driver-api/driver-model/binding.rst is the specification this note quotes for the match() contract, and that part of it is accurate. Its “Device Class” section is not: it states that “Device drivers belong to one and only one class, and that is set in the driver’s devclass field. devclass_add_device is called to enumerate the device within the class.” Neither identifier exists in v6.12 — a whole-tree identifier search for devclass_add_device returns nothing, and devclass survives only as a local variable name in one Xen driver (Elixir identifier search, v6.12). The devclass API was replaced by struct class and class_register()/device_add_class_symlinks() long ago, and the document was never updated. The same file’s sysfs section says a symlink “can be created (though this isn’t done yet)” for links that have existed for two decades.
This is worth stating explicitly because in-tree documentation is normally the most trustworthy source available, and it silently is not here. Where this note and binding.rst disagree, this note follows the code.
Uncertain
Verify: that
devclass_add_deviceexists nowhere in the v6.12 tree. Reason: the search was performed with Bootlin’s Elixir cross-reference rather than by grepping a local checkout, so it inherits whatever indexing gaps Elixir has; a symbol defined only inside a preprocessor branch Elixir does not index could in principle be missed. The positive finding — thatdevclassappears at v6.12 only as a local variable indrivers/xen/xenbus/xenbus_probe_frontend.c— is what Elixir returned. To resolve:git grep -n devclass_add_devicein a checkout at tagv6.12. The conclusion (thatbinding.rst’s Device Class section describes a removed API) is robust either way, sincestruct classandclass_register()are demonstrably the mechanism indrivers/base/class.c. uncertain
Alternatives and When to Choose Them
Within a bus you usually do not choose the mechanism — it is dictated by how the hardware is enumerated (PCI ⇒ vendor/device IDs, DT board ⇒ compatible, ACPI platform ⇒ HID). The real choices are about specificity: match a single device (vendor+product) for a quirk driver, match by class (class mask, or USB interface-class flags) for a generic driver that handles a whole hardware category, or match by compatible-string fallbacks for forward/backward SoC compatibility. Prefer the most specific match that still covers the hardware you intend to support; over-broad class matches can claim devices a more specialized driver should own. For “bind this thing to a driver that doesn’t know it,” reach for new_id/driver_override rather than patching the table.
| Situation | Choose | Why |
|---|---|---|
| One chip, one driver, discoverable bus | exact vendor/device entry | Narrowest possible claim; no risk of stealing another device |
| One family of chips differing only in register offsets | one entry per variant, distinguished by driver_data / .data | Keeps the switch out of the driver and the knowledge in the table |
| “Any device implementing standard class X” | class match (class/class_mask, or USB INT_* flags) | The generic driver of last resort; always ensure a more specific driver exists for devices needing quirks, since a class match will claim them otherwise |
| A firmware revision range needs a workaround | USB bcdDevice_lo/bcdDevice_hi | The only interval match in the tree; avoids one entry per revision |
| A SoC block that is register-compatible with an older one | list both compatible strings on the node, most-specific first | OF scoring picks the best available driver automatically; adding a new driver later needs no DT change |
| ACPI board, driver only has a DT table | _HID = "PRP0001" + _DSD compatible | Reuses the DT driver unmodified |
| Bind a driver to hardware it has never heard of, once | new_id | Reversible, no reboot, no rebuild |
| Take a device away from its native driver permanently | driver_override + unbind + bind | Sticky across rescan; the passthrough idiom |
| Prevent any driver from claiming a device | driver_override set to a non-existent name (none) | Documented opt-out |
Choosing a match rule, by intent. What it shows: the specificity ladder from a single-device claim up to a whole-class claim, and the three runtime overrides. The insight to take: the two failure directions are asymmetric. Too specific fails loudly — a new revision of the chip does not bind and someone files a bug. Too broad fails quietly — a generic driver claims a device that needed a quirk, and the device half-works. When in doubt, be too specific.
Production Notes
The PCI override_only flag in pci_device_id exists precisely so a driver can list a device that should bind only when explicitly forced via driver_override and never automatically — used to avoid greedy generic drivers stealing devices at boot (pci-driver.c, v6.12). Its build-time counterpart, the vfio_pci: alias prefix, is what keeps such an ID out of modules.alias so udev can never trip over it. The driver_override mechanism is the foundation of device passthrough: VFIO workflows write vfio-pci (or vfio-platform) into a device’s driver_override, then unbind and bind to hand the device to a VM — see VFIO Framework, IOMMU Groups and Device Isolation and The IOMMU and DMA Remapping.
The modalias system is also why kernel and initramfs must stay in sync: a NIC whose driver lives in a module that is not in the initramfs (and whose modalias is therefore not resolvable early) is a recurring cause of “no network in initramfs” boot failures — the device enumerates, its modalias is correct, but modprobe cannot find the module image. dracut --hostonly shrinks the initramfs by including only modules whose aliases match hardware present at build time, which is exactly why a hostonly initramfs breaks when the disk is moved to different hardware.
Scale matters for the alias table. The test machine’s modules.alias holds 29,415 lines, and modprobe glob-matches a device’s concrete modalias against all of them. This is a linear scan of a text file done once per device at boot, which is cheap in absolute terms but is why udev’s modalias rule is one of the busier things in early boot on a machine with hundreds of devices.
Match tables are an ABI-adjacent surface. Documentation/ABI/testing/sysfs-bus-pci documents new_id, remove_id, bind, unbind, and driver_override as userspace interfaces with defined write formats — the new_id entry specifies the seven whitespace-separated fields (vendor device subvendor subdevice class class_mask driver_data) and notes that all but the first two are optional. Scripts that do device assignment depend on these paths and formats, which is why they are documented rather than left as implementation detail; the tiering system behind that promise is covered in sysfs and the Kernel Object Hierarchy.
Adding an ID to a driver is a routine upstream patch. Because ID tables are pure data, “support the new revision of this chip” is frequently a one-line patch adding a { PCI_DEVICE(...) } entry, and such patches are backported to stable trees aggressively. This is the practical reason a device can be unsupported on one point release and supported on the next with no code change — and the reason new_id exists as a way to test the hypothesis before writing the patch.
See Also
- sysfs and the Kernel Object Hierarchy — the companion note. The device model is an object graph; sysfs is that graph rendered as a filesystem. Every match this note describes is observable as a symlink appearing under
/sys, and thekobject/kset/ktypetriangle at the top of this note’s class diagram is dissected there - The Linux Device Model — the model as a whole: why 2.4’s per-bus ad-hoc code was replaced by one unified graph
- struct device — the node being matched; holds
bus,driver,driver_override, and the embedded kobject - Driver Binding and the Probe Flow — what happens after a successful match:
really_probe()and the ordered bind sequence - struct bus_type and Bus Registration — defines the
matchcallback this note dissects, plusuevent(which emitsMODALIAS) - struct device_driver — holds
of_match_table,acpi_match_table, and the bus-specificid_table - Device Attributes and sysfs Files — how
modalias,driver_override,new_idandbindare implemented asshow/storecallbacks - Uevents and the Kernel-Userspace Netlink Channel — the
MODALIAS=line and the netlink transport that carries it to udev - udev and Device Management and udev Rules and Predictable Device Naming — the userspace half of the autoload loop
- Device Links — supplier/consumer edges and
fw_devlink, the ordering machinery that makes most deferrals unnecessary - VFIO Framework — where
driver_overrideleads: taking a device away from its native driver and handing it to a VM - IOMMU Groups and Device Isolation · The IOMMU and DMA Remapping — the isolation constraints that make passthrough safe
- PCI and PCIe Enumeration — where the vendor/device/class numbers this note compares actually come from
- Device Tree — the firmware description that supplies
compatiblestrings - Device Tree Bindings and compatible Strings — the
compatible-string match in depth: vendor prefixes, fallback lists, most-specific-first - ACPI Device Enumeration —
_HID/_CIDsemantics behindacpi_match_table - Platform Devices and Drivers —
platform_matchis the worked precedence-ladder example here - Deferred Probing and EPROBE_DEFER — when the match itself returns
-EPROBE_DEFER - Module Loading insmod modprobe and kmod — the
modprobe/modules.aliasside of modalias autoloading - Module Parameters and Metadata Macros —
MODULE_DEVICE_TABLEis one of theMODULE_*metadata macros - Linux Device Drivers and Device Model MOC — §2, “Buses, Drivers, and the Probe/Bind Flow”