Device Model

On this page

A device may appear in sysfs while its driver remains unbound; after driver unbinding, the device object might still be referenced. To read the device model, locate the relevant sections along discovery, matching, functional registration, and final release.

TargetReading Location
Objects and probeSections 1–2
References and managed resourcesSection 3
User-space view and read-only experimentsSections 4–5

1. What problem does each object solve?

ObjectQuestion it addressesExample
deviceWhich device is this? Who is the parent? What resources does it have?A PCI function, a USB interface
device_driverWhich implementation can operate it?PCI network driver, USB serial driver
bus_typeHow to discover, match, and organize devices and drivers?PCI, USB, platform
classFrom what functional perspective does the user look up devices?net, block, tty

Buses and classes represent different viewpoints. A PCI network card is first a device on the PCI bus; its network interface then provides functional objects via the network subsystem. You cannot treat /sys/class/net/eth0 as a simple rename of the PCI device object. A single physical device may also expose multiple functional objects.

kobject is the lower-level foundation for naming, hierarchy, and reference counting, which is embedded within device. kset organizes related kobjects and participates in event management. They do not constitute a replacement for the bus model's driver interface. Typically, you should use the device API provided by existing subsystems rather than building your own kobject tree for a driver.

Kernel structures change across versions; when reading include/linux/device.h and include/linux/kobject.h, focus on member responsibilities. kobject.sd points to a kernfs node, not a VFS dentry; final object release depends on the release callback and its container management. Reference counting reaching zero does not automatically imply how to release the outer structure.

2. From hardware information to probe

Discovery, binding, and release are three different stages Device Registration Enumeration / firmware Bus Matching ID / compatible / HID Driver Registration Built-in or module loading probe: Acquire resources and initialize Defer until dependencies ready Device available to subsystem remove: Stop and unbind Final release on last reference Unbinding does not mean immediate release; being visible in sysfs does not mean the device is ready for use.

Whether the device registers first or the driver registers first, matching can be triggered. The bus is responsible for matching rules: PCI commonly uses vendor/device/class, USB can match by device or interface descriptors, and platform drivers can match via firmware match tables, among other methods.

match only indicates that a candidate driver might support the device; probe must also acquire resources such as clocks, power supplies, registers, and interrupts, initialize the hardware, and register with the functional subsystem. If any step fails, no partially initialized device should be left running.

If a dependent regulator, clock, or other supplier is not yet ready, you can return -EPROBE_DEFER. The core will retry when subsequent conditions are met. This indicates a temporary lack of dependencies and should not be used to mask permanently unsupported hardware or erroneous parameters. Device links can also express supplier/consumer relationships, affecting the order of probing and power management.

Deferred probing retries initialization, it does not prolong a half-initialized state

Suppose a sensor consumer has been enumerated, but the regulator driver it references has not finished registering. The first probe might have already established a private context, but then obtaining the regulator returns -EPROBE_DEFER. This probe must exit and clean up the resources attempted in this instance; when subsequent probing occurs due to changes in dependency conditions, it should restart from a consistent unbound state.

MomentConsumer StateResources for this attempt
Enumerated, not yet probedDevice exists, unboundNo successful driver instance
First probe fails to acquire supplierReturns EPROBE_DEFER, binding not completedReclaim managed resources; handle manual resources as well
Supplier becomes ready laterCan trigger subsequent probing pathsCannot assume previous private buffers are still valid
Subsequent probe succeedsDriver binding and functional registration completedResources from this new attempt persist until unbinding

Therefore, repeated probe logs may indicate retries due to dependencies, not necessarily that the device was enumerated multiple times. Conversely, rewriting unsupported devices or incorrect firmware parameters uniformly as EPROBE_DEFER makes real errors look like permanently missing dependencies. Drivers should preserve the error semantics of resource APIs and locate issues by combining device links, deferred reasons, and original return values; initialization order should not rely on adding fixed sleep times to accidentally succeed.

How firmware descriptions and bus enumeration cooperate

  • Device Tree: Describes addresses, interrupts, clocks, and device connections; compatible indicates compatible interfaces. Common on ARM and RISC-V, but not exclusive to these architectures.
  • ACPI: Provides device identity, resources, and power management methods; common on x86, and also used by arm64 servers.
  • PCI/USB Enumeration: Discovers devices and reads identifiers via bus protocols. PCI root bridges, configuration space access methods, and interrupt routing may still rely on ACPI or DT; it cannot be summarized as "self-enumerating devices completely do not need firmware descriptions."

The platform bus is often used for on-chip devices that cannot self-enumerate via such bus protocols. A platform device is a real object in the device model, which does not mean the hardware is simulated; DT nodes do not all become platform devices, and the bus to which the node belongs determines the instantiation method.

3. Object lifetime and driver binding lifetime

get_device() / put_device() manage device references. device_unregister() revokes registration and releases the registrant's reference; if references remain, the object can continue to exist until the final put triggers the ultimate release.

BoundaryWhat ends
removeDriver service and binding relationship
releaseMemory lifetime of the device object itself
References still existOnly indicates that object memory can continue to exist

References do not bring unplugged hardware back online, nor do they guarantee that registers are still accessible.

device_register() includes initialization and addition steps. Even if registration fails, the caller usually needs put_device() to return the reference obtained during initialization; you cannot casually kfree() an object already managed by the device model. Allocation and release rules for specific containers should follow the rules of the owning subsystem.

Saving struct device does not mean saving driver private data

Suppose the registrant holds a device reference, and an asynchronous user obtains another reference. When the driver unbinds, devm private memory can be released, but the device reference held by the asynchronous user keeps the device object itself alive. If the asynchronous user saved an old drvdata pointer and subsequently dereferences it, they may access already released driver data.

Device references do not retain unbound driver data Bound Unbound Unregistered Last put 2 references Private data valid 2 references Private data freed 1 reference Device survives 0 references Run release A held device reference cannot validate stale drvdata Counts only the registrant and one external holder, omitting temporary references.

Below, we only count references from the registrant and one external holder, omitting all temporary internal kernel references. The state model separates registration, binding, private resources, and object release, making it easier to observe which guarantees end with which step.

class Device:
    def __init__(self):
        self.refs = 1  # Reference obtained during initialization, to be returned by the registrant
        self.registered = False
        self.bound = False
        self.private_live = False
        self.releases = 0

    def get_from_live_reference(self):
        if self.refs == 0:
            raise RuntimeError("Cannot revive a released object")
        self.refs += 1

    def put(self):
        assert self.refs > 0
        self.refs -= 1
        if self.refs == 0:
            assert not self.registered and not self.bound
            self.releases += 1

    def add_and_bind(self):
        assert self.refs > 0 and not self.registered
        self.registered = self.bound = self.private_live = True

    def unbind(self):
        assert self.bound
        self.bound = False
        self.private_live = False

    def unregister(self):
        assert self.registered
        if self.bound:
            self.unbind()
        self.registered = False
        self.put()

    def io(self):
        if not self.bound or not self.private_live:
            raise RuntimeError("Object reference does not imply the driver can still provide service")
        return "ok"

dev = Device()
dev.add_and_bind()
dev.get_from_live_reference()
assert dev.refs == 2 and dev.io() == "ok"
dev.unbind()
assert dev.registered and dev.refs == 2 and not dev.private_live
try:
    dev.io()
except RuntimeError:
    pass
else:
    raise AssertionError("Cannot rely on references to access driver after unbinding")
dev.unregister()
assert dev.refs == 1 and dev.releases == 0
dev.put()  # Return the last external reference
assert dev.refs == 0 and dev.releases == 1
try:
    dev.get_from_live_reference()
except RuntimeError:
    pass
else:
    raise AssertionError("A finished lifecycle cannot be reopened by get")

failed_add = Device()  # Initialized, but addition step failed
failed_add.put()
assert failed_add.releases == 1

The real get_device() only performs reference operations; it does not safely retrieve an object from a potentially invalid raw pointer for the caller. Obtaining the initial reference requires relying on lock-protected lookups, existing references, or the acquisition protocol specified by the owning subsystem. An obtained reference protects the object's memory lifetime but does not replace unbinding synchronization or the reference rules for private state.

Local kernel implementations can see in drivers/base/core.c that device_unregister first calls device_del and then put_device; unbinding cleanup is contained in drivers/base/dd.c including devres_release_all and clearing drvdata. The responsibilities of these two paths differ, and driver design should clearly specify which layer of object the asynchronous user actually holds. Device Object Lifecycle, devres Release Semantics

devres manages resource release order

Managed APIs like devm_kzalloc() attach resources to the device, and they are typically cleaned up in reverse order upon probe failure or unbinding. This reduces error branches but does not automatically solve all asynchronous activities:

  1. Prevent new requests from entering.
  2. Stop DMA and disable interrupt sources according to hardware protocols.
  3. Wait for or cancel running work items, callbacks, and requests.
  4. Only then release resources that these paths might still access.

Waiting operations must not hold locks required by completion callbacks. Managed and unmanaged APIs can be used together, but you must clearly understand who releases each resource to avoid double-freeing after manual release. Refer to Device Lifecycle and the devres documentation.

Take a caller reference before unbinding or unregistering. A live device object and usable driver resources are separate conditions.

Preparing the visual
Does a live device object imply live managed resources?

A device object can outlive its driver binding. get_device retains object memory, not unbound devres or offline hardware.

4. Division of labor among sysfs, devtmpfs, and udev

/sys/devices displays the device hierarchy; /sys/bus/.../devices and /sys/class/... provide other lookup perspectives, often containing symbolic links. sysfs files are kernel-exported attribute interfaces, not ordinary disk files, and not every file corresponds to a device_attribute.

Device attribute show methods commonly use sysfs_emit() for output, and store methods can use kstrto*() to parse input. Attributes still require correct locking, permissions, and device state checks; once stably exported, user-space ABI considerations must be taken into account, and units and meanings cannot be changed arbitrarily.

For devices with device numbers registered using the appropriate mechanism, devtmpfs can create basic device nodes from within the kernel. udev receives uevents, reads attributes, and then sets permissions, creates stable symbolic links based on rules, and can trigger module loading based on modalias. Built-in drivers do not need to load modules, and many devices do not have /dev nodes at all; for example, network interfaces are typically used via sockets and netlink.

When the expected device is not visibleNext layer of evidence
Is it discovered?Bus enumeration and device object
Is it bound?Driver association, matching, and probe results
Can it provide functionality?Registration of subsystem functional objects
Is the name/access correct?User-space naming, permissions, and rules

The mere absence of a specific /dev name does not directly prove that probe was not executed.

5. Read-only view of a PCI device

Below, we only read sysfs without unbinding the device or modifying resources. Hot-plug events may cause files to disappear during traversal, so read exceptions are handled.

from pathlib import Path

for dev in sorted(Path('/sys/bus/pci/devices').glob('*'))[:8]:
    try:
        vendor = (dev / 'vendor').read_text().strip()
        product = (dev / 'device').read_text().strip()
        link = dev / 'driver'
        driver = link.resolve(strict=True).name if link.is_symlink() else '(unbound)'
        print(dev.name, vendor, product, driver)
    except OSError as exc:
        print(dev.name, type(exc).__name__)

For example, the name 0000:03:00.0 contains the PCI domain, bus, device, and function. The absence of a driver link only indicates that no driver is currently bound; the reason requires analysis combining match tables, modules, probe logs, and device status.

Continue Reading