Before you start#
Read virtual networking and namespaces and locks and sleeping. Source references use upstream Linux v6.12 as a historical teaching baseline. Probe, removal and sysfs behavior depend on the bus, driver and configuration. The lab reads existing sysfs files; it does not bind, unbind or load anything.
Learning goals#
Explain device publication and final release, trace bus matching through probe and deferred probe, identify cleanup and concurrent removal obligations, and interpret a sysfs path without confusing it with a kernel reference.
Source walkthrough#
- Initialize and add
Initial device reference; publication can fail.
- Match and probe
Supplier, pinctrl, PM and driver callback conditions apply.
- Bind or unwind
driver_bound follows success; failures release acquired state.
- Remove and put
Unpublish and quiesce; final release waits for references.
A struct device represents a device known to the driver core. Its bus identifies a struct bus_type, whose operations can match and probe devices; struct device_driver describes a driver registered with a bus. A match means a candidate is suitable to try, not that initialization has succeeded. The bus's match callback applies bus-specific identity rules, and probe can still fail or defer. Physical devices, firmware-described platform devices and virtual devices can all use the model. See include/linux/device.h, include/linux/device/driver.h, and drivers/base/dd.c.
A device embeds a kobject, which participates in the object hierarchy and reference counting. A kset groups related kobjects and contributes to hierarchy and event handling; a kref is a reference-count mechanism, not a lock protecting every field. sysfs exposes selected object and attribute views using kernfs infrastructure. A pathname such as /sys/devices/... is a userspace name and access point, not a struct device * or a kernel reference that arbitrary driver code can safely retain. A uevent announces an object change to userspace; a userspace device manager may respond, but delivery and device-node creation are separate operations. See lib/kobject.c and fs/sysfs/file.c.
device_initialize initializes core state, including the embedded object's initial reference; device_add publishes a properly initialized device. device_register combines initialization and addition for its intended use. device_del removes a previously added device from the model; device_unregister combines deletion and putting a reference. put_device releases a reference, and the device's release callback runs only when its final reference is dropped. Therefore removal from sysfs and freeing the containing allocation are different events. A failed device_add does not authorize a direct kfree of an initialized device; the caller follows the documented put_device release path. Read the actual caller and release callback in drivers/base/core.c.
In really_probe, Linux v6.12, GPL-2.0, supplier readiness is checked before the driver callback:
link_ret = device_links_check_suppliers(dev);
if (link_ret == -EPROBE_DEFER)
return link_ret;
The core can defer before calling the driver when a required supplier is not ready. -EPROBE_DEFER means retry may become possible; it is not a successful binding or proof that every missing dependency will eventually appear. driver_probe_device tracks deferred attempts and triggers retries under the relevant rules. A missing permanently unsupported resource or malformed firmware can instead produce a non-deferral error. Retry policy, device links and the deferred-probe timeout are configuration and lifecycle dependent.
Match, probe, and unwind#
After a bus finds a candidate match, driver_probe_device calls __driver_probe_device. The latter rejects a dead or unregistered device and one that already has a driver. It obtains runtime-PM references for suppliers and parent where applicable, uses a PM barrier, calls really_probe, and later requests idle and releases those PM references. The device/parent locking contract is documented around this path; a caller cannot treat a raw pointer as proof that removal is impossible. drivers/base/dd.c is the source for these steps.
The early checks in __driver_probe_device come first. In drivers/base/dd.c, Linux v6.12, GPL-2.0 (lines omitted):
if (dev->p->dead || !device_is_registered(dev))
return -ENODEV;
if (dev->driver)
return -EBUSY;
A device that is dead or unregistered returns -ENODEV, and a device that already has a driver returns -EBUSY, both before any PM reference or really_probe call. The excerpt does not show those PM steps or the deferred-probe bookkeeping in the omitted lines.
really_probe checks supplier links and preexisting managed resources, sets dev->driver, binds pinctrl pins, invokes a bus DMA-configuration callback when present, creates driver sysfs links, activates an applicable PM domain, and calls the bus or driver probe through call_driver_probe. A successful callback is followed by driver groups, optional sync-state attribute work, pinctrl completion, PM-domain sync and driver_bound. The exact callback selected depends on the bus. Async probing can schedule eligible probes asynchronously under driver and configuration policy; deferred probing and asynchronous probing are different ideas. Neither guarantees a device is ready by a particular userspace timestamp.
The callback selection sits in call_driver_probe. In drivers/base/dd.c, Linux v6.12, GPL-2.0 (surrounding code omitted):
if (dev->bus->probe)
ret = dev->bus->probe(dev);
else if (drv->probe)
ret = drv->probe(dev);
A bus-supplied probe takes precedence over the driver's own probe in this function. The switch that follows the call, omitted here, logs -EPROBE_DEFER as a deferral request rather than as a probe failure, so the return value is classified before the caller decides what happens next.
- Bus match__driver_probe_device
matched candidate driver for the device
- __driver_probe_devicereally_probe
dead, registered and unbound checks pass
- really_probeSupplier links
device_links_check_suppliers
- Supplier linksreally_probe
Return. -EPROBE_DEFER: stop before the callback
- really_probeDriver callback
call_driver_probe: bus probe or driver probe
- Driver callbackreally_probe
Return. 0 binds; an error takes the probe_failed unwind
- really_probeBus match
Return. driver_bound on success
Time runs downward. Dashed arrows show returns.
Read the text version
- Bus match → __driver_probe_device: matched candidate driver for the device
- __driver_probe_device → really_probe: dead, registered and unbound checks pass
- really_probe → Supplier links: device_links_check_suppliers
- Supplier links → really_probe (return): -EPROBE_DEFER: stop before the callback
- really_probe → Driver callback: call_driver_probe: bus probe or driver probe
- Driver callback → really_probe (return): 0 binds; an error takes the probe_failed unwind
- really_probe → Bus match (return): driver_bound on success
The failure path after driver probing contains this excerpt from really_probe, Linux v6.12, GPL-2.0 (other labels and statements omitted):
probe_failed:
driver_sysfs_remove(dev);
sysfs_failed:
bus_notify(dev, BUS_NOTIFY_DRIVER_NOT_BOUND);
The first label removes driver sysfs associations when that stage had been reached. The next notifies the bus that the driver did not bind. The remaining cleanup can call bus DMA cleanup, update device links and run device_unbind_cleanup; earlier failure labels enter at the stage appropriate to their acquired resources. If the driver callback itself fails, this implementation changes the sign of its return for an internal distinction before returning to the caller; driver_probe_device explicitly handles either sign of EPROBE_DEFER. Do not copy a single label as a standalone universal cleanup recipe.
A device link models supplier/consumer dependencies. It can influence probe ordering, runtime PM and sync_state behavior. sync_state is a driver-core coordination callback for a supplier after relevant consumers have had an opportunity to initialize under the applicable rules; it is not a general "all hardware is permanently synchronized" guarantee. Firmware device links, best-effort behavior, timeout and device removal can change when or whether a callback happens. Pinctrl, clocks, regulators and PM domains can themselves be dependencies; a driver should propagate a legitimate -EPROBE_DEFER rather than converting every unavailable supplier into permanent success.
Managed resources are not object lifetime#
Devres associates resources with a device so release callbacks can unwind them on applicable probe failure or detach. devm_kzalloc, managed mappings and managed interrupt requests can simplify reverse-order cleanup, while devres groups can collect and release a subset transactionally. See drivers/base/devres.c. Managed allocation does not make outstanding DMA, timers, work items, file operations or userspace handles stop using a resource automatically. The driver must prevent new work and synchronize existing work before devres frees what those users need. A devm pointer is not safe after the resource's release merely because the containing struct device still has a reference.
Consider a hypothetical initialized device D with initial reference 1. A subsystem takes another reference, making 2. device_add(D) then fails before publication completes and unwinds its own partial registration. The caller calls put_device(D), reducing the count to 1; D must not be freed yet because the other subsystem still owns its reference. When that subsystem finishes and calls put_device, the count reaches 0 and D's release callback can free its containing allocation. The numbers are a teaching model; real reference counts need not be exposed or sampled, and concurrent users must follow actual APIs. Calling device_del for an object that was never successfully added is not a substitute for the correct failure contract.
Now consider unplug after a successful probe. The bus and driver core begin removal; the driver stops accepting work, disables the device's production of events and DMA as appropriate, synchronizes interrupt and worker activity, unregisters exposed interfaces, and releases resources in the order required by that device. An open userspace file can outlive a sysfs entry or the physical device. Its operations must check state and hold the right references rather than dereference hardware resources that devres has already released. A module reference protects code lifetime under its rules; it does not by itself keep a physical device present or serialize all callbacks.
Sysfs attributes and events#
A sysfs attribute callback is kernel code invoked for an exposed attribute. Kernfs coordinates active operations on the node during removal, but this is not equivalent to a reference on every object reachable from the callback. The driver must keep its private data valid and protect mutable state with the appropriate locking or references. Removing an attribute drains or blocks operations through that node according to kernfs rules; it does not stop unrelated file operations, workqueues, interrupts or DMA. Never infer safety from a pathname alone. See the sysfs documentation and the v6.12 fs/sysfs/file.c.
A device uevent can contain fields such as a subsystem and modalias, allowing a userspace manager to select a module or create policy-driven links. The kernel does not guarantee that such a manager is running or that a matching module is installed. A sysfs driver symlink indicates a binding at the instant it is observed; it can disappear or change concurrently. A device can exist without a driver, or a driver can be registered without a matching device. Reading modalias and uevent is observational; writing bind, unbind, uevent, or driver override files changes state and is outside this lesson.
Probe failure can happen after some managed allocations but before an interface is registered. The right question is not merely "did probe return an error?" but which stages acquired resources and which cleanup path owns them. If a driver registered an asynchronous callback before returning, it must ensure the callback cannot use already-released data. If a sysfs attribute read races unplug, the callback and removal path need a shared state/lifetime protocol. The core supplies infrastructure, but it cannot infer the device-specific sequence for disabling hardware or draining private work.
Publication, lookup, and final release#
The initialization and publication steps exist because other parts of the kernel and userspace can start finding a device once it is added. Before publication, the creator must establish the parent, bus, name, release callback and other fields required by that object's contract. Once device_add succeeds, sysfs, bus lists and notifications can expose it, and probe can occur according to the core's ordering. A driver should not publish a pointer to private state before that state is ready for every callback that can see it. Conversely, a failure during addition can occur after some internal steps succeeded; the device core unwinds its own partial work, while the creator still owns its initialized reference.
device_del removes an added device from the model and initiates the appropriate unbinding and visibility changes, but callers must still put references they hold. device_unregister is useful when the device was successfully registered and the caller is relinquishing its reference; its deletion and put operations do not mean that a release callback executes immediately. The release callback is the place where the owner of an enclosing allocation can free it when the last kobject reference is gone. A stack-allocated object cannot be safely freed or go out of scope early merely because its sysfs directory disappeared. Do not inspect or alter a reference count directly to decide whether an unprotected pointer is safe.
The unregister path shows the same split between removal and release. In drivers/base/core.c, Linux v6.12, GPL-2.0 (surrounding code omitted):
void device_unregister(struct device *dev)
{
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
device_del(dev);
put_device(dev);
}
device_unregister deletes the device from the model and then drops the caller's reference. Whether device_release runs depends on whether any other holder still has a reference, so the same call can leave the object alive.
- Initialize
device_initialize sets up the object and its first reference.
- Publish
device_add exposes the device; a failed add is unwound by the caller.
- Bind
Probe may succeed, defer or fail; success reaches driver_bound.
- Delete
device_del removes the device from the model; other references remain.
- Final put
The release callback runs only when the last reference is dropped.
The object hierarchy and sysfs hierarchy are related but not identical to physical topology in every case. A bus device, a class device and a parent can have links presenting useful views of the same underlying object relationships. A symlink target read from sysfs can change after hotplug or unbind; it is evidence of a relationship at observation time, not a locked snapshot of the entire system. A uevent can prompt userspace to enumerate again, but a listener can miss events or start after a device was registered. Robust userspace often combines an initial enumeration with event handling and rechecks actual state.
One probe retry and one removal race#
Suppose a device D depends on a regulator supplier S. D is discovered first, but the supplier is not ready. The supplier-link check or the driver's resource acquisition can return -EPROBE_DEFER; D is not successfully bound. After S registers and the appropriate deferred-probe trigger runs, the core can try D again. On retry, D must initialize from a clean state and cannot assume that private resources from the failed attempt remain usable. If the firmware describes a nonexistent supplier permanently, a retry loop does not create the hardware and an operator needs the actual error context.
Now suppose D is successfully bound and a sysfs attribute reader starts while unplug begins. A correct implementation relies on the sysfs/kernfs active-reference rules plus its own device state and locking to ensure that the callback does not dereference freed private memory. The unplug path must also deal with interrupt handlers and work that have nothing to do with that sysfs node. Removing the attribute is one step, not a global synchronization primitive. If an open file interface survives removal, its callbacks need a disconnected state and a lifetime strategy for the file's private data. The device object may persist because a reference remains even after the physical hardware is gone; the callback must not interpret that reference as proof that register access still works.
Devres groups are useful when a probe stage acquires several managed resources and wants to roll back that subset on a later failure. Group closure and release are explicit operations with defined scope. A group cannot infer that a hardware engine is still running or that a work item has a raw pointer to a managed buffer. For each resource, write down who stops its producer, who waits for in-flight users, and who invokes the release. That analysis is more reliable than classifying the entire driver as safe simply because most allocations begin with devm_.
Practice#
Source-only route:
rg -n -A135 'static int really_probe\(' "$KERNEL_SRC/drivers/base/dd.c"
rg -n -A65 'static int __driver_probe_device\(' "$KERNEL_SRC/drivers/base/dd.c"
rg -n -A25 '^void device_initialize\(' "$KERNEL_SRC/drivers/base/core.c"
rg -n -A20 '^void device_unregister\(' "$KERNEL_SRC/drivers/base/core.c"
Verify the checkout tag is v6.12. Mark supplier deferral, pinctrl and PM steps, callback, driver_bound, failure labels and final device put. Work through the two-reference failure example; identify why no device_del is assumed after failed addition.
Read-only Linux observation:
python3 - <<'PY'
from pathlib import Path
base = Path('/sys/bus/platform/devices')
try: devices = sorted(base.iterdir()) if base.is_dir() else []
except OSError as e:
print('platform inventory unavailable:', e.strerror); devices = []
print('platform entries:', len(devices))
if devices:
d = devices[0]
print('example:', d.name, 'driver:', (d / 'driver').resolve().name if (d / 'driver').exists() else 'unbound or unavailable')
for name in ('modalias', 'uevent'):
try: print(name + ':', (d / name).read_text().strip()[:300])
except OSError as e: print(name + ': unavailable:', e.strerror)
else: print('no visible platform devices')
PY
if command -v udevadm >/dev/null 2>&1; then udevadm info --query=property --path=/sys/devices/virtual/mem/null || echo 'udevadm query unavailable'; else echo 'udevadm unavailable'; fi
The count and chosen entry vary, and an unbound device or missing modalias is valid. The optional udevadm info query is read-only and can fail if that path or service data is unavailable; it does not trigger a uevent. A device can disappear between listing and reading, producing an error. Do not write sysfs attributes or trigger events. Deliver the observed binding/absence and a timeline of match, probe, failure cleanup and final put.
Knowledge check#
- Why does
device_addfailure not automatically free an initialized device? - What does
-EPROBE_DEFERsay about binding and future retries? - Which steps surround the driver callback in the cited
really_probepath? - Why is devres insufficient to stop DMA or an already-running worker?
- Why is a sysfs pathname not equivalent to a held
struct devicereference?
Answers#
- Initialization established a reference-counted object. The caller must put its reference, and final release waits for all other references.
- Binding has not succeeded; the core can retry when dependencies or triggers change, but eventual success is not guaranteed.
- The path checks suppliers, binds pinctrl, configures bus DMA and sysfs/PM state, calls the selected probe, then creates groups and calls
driver_boundon success; failure follows stage-specific cleanup. - Managed release frees registered resources at its lifecycle point; the driver must first quiesce hardware and synchronize asynchronous users that still depend on them.
- A pathname is a userspace view subject to removal and races; kernel code needs the actual object and its reference/locking contract.
Checkpoint#
Save the two-reference timeline, probe success/failure diagram and read-only observation. Pass when you can identify the owner of each reference, explain a deferred probe and describe an unplug race without treating devres as synchronization. Re-read really_probe and the device release contract if needed. This is practice, not certification.
Sources#
-
Device drivers infrastructure, Linux v6.12 — device initialization, addition, deletion, references and driver callbacks.
-
sysfs, Linux v6.12 — sysfs layout, attribute callbacks and ABI expectations.
-
Device links, Linux v6.12 — supplier/consumer ordering and sync-state semantics.