Before you start#
Required earlier lessons: C/C++ for source readers, Memory and system calls, Concurrency and object lifetime, and GKI and kernel builds. You need to know what a pointer, callback, process virtual address, kernel, and device lifetime mean. No device modification or driver loading is part of this lesson.
Learning goals#
You will distinguish a device-tree description from driver code; follow one pinned example from DT binding through match, probe, resource setup, and cleanup; explain common hardware terms without confusing bus/physical addresses with process virtual addresses; and explain what devm_* cleanup does and does not guarantee.
Practice#
Driver, subsystem, and device description
A device driver is kernel code that controls a hardware or virtual device. A driver should normally connect to the Linux subsystem for that device type: for example, input devices publish key/touch events through the input subsystem. Android userspace services/HALs can build higher-level features on top; they are not the kernel driver. A bus is a way devices are connected/discovered (platform, I2C, SPI, PCI, USB); it is also a parent context for resources and address interpretation.
A device tree (DT) is structured data describing hardware the kernel cannot discover by itself. Human-edited DTS is compiled to DTB; DTBO is an overlay that may be combined with a base tree (AOSP device-tree overlays). DT data describes a board; it is not executable driver code. The bootloader passes the selected tree to the kernel (Linux DT usage model). A binding defines which properties a compatible device may have and what they mean. The compatible string helps the kernel match a node to driver support (same page).
Plain-language hardware terms:
- A bus address is an address meaningful to a device/controller on a particular bus. A
regproperty commonly describes an address range, interpreted using the parent node's#address-cellsand#size-cells(DT specification, basics). It is not a userspace virtual pointer, and must not be copied across unrelated boards. - An interrupt (IRQ) is a notification that an event needs attention.
interruptsis interpreted through the relevant interrupt-controller binding; trigger type and numbering are controller-specific. - A clock provides a timing signal; a regulator supplies/manages electrical power; pinctrl selects/configures physical pins; DMA (direct memory access) lets a device transfer data to/from memory under system-controlled mappings. These are real hardware wiring/configuration contracts, not decorative properties.
- The CPU's virtual address is what a process pointer uses. A device register's physical/bus address is different; drivers use kernel resource APIs to map/access registers safely. Do not dereference a
regvalue as a C pointer.
A DTS can compile yet be wrong: syntax/schema validity cannot prove that a board's pin, voltage, clock, interrupt wiring, or address is correct. Never test guessed hardware values on a phone.
Selected verified example: GPIO keys
- gpio-keys.yaml binding
Describes compatible values and button properties. Device-tree data is not driver code.
- gpio_keys_of_match
The driver match table includes compatible = gpio-keys.
- gpio_keys_probe
Reads configuration, allocates state, and sets up GPIO/IRQ and deferred work.
- Input subsystem registration
Publishes the device interface after setup; cleanup must account for asynchronous users.
- Device active
Interrupts, timers, and delayed work may reference driver state.
- Quiesce asynchronous users
Cancel timers and synchronously cancel delayed work through the explicit cleanup action.
- Managed cleanup releases resources
devm helpers run registered cleanup; they do not invent rules for arbitrary callbacks or references.
Rather than “pick any driver,” use this fixed example from ACK commit 2f0d864181b9acf801e9b51c345e11b6156b767a:
- Binding:
Documentation/devicetree/bindings/input/gpio-keys.yamldeclares the compatible valuesgpio-keysandgpio-keys-polled. Its example has agpio-keysparent and child button nodes; properties describe GPIO/interrupt and Linux input key code. This is schema/example evidence, not a particular phone's wiring. - Match:
drivers/input/keyboard/gpio_keys.chasgpio_keys_of_match[]with.compatible = "gpio-keys"; the platform driver's.of_match_tablepoints at that table (around line 1056). - Probe:
gpio_keys_probe()(around line 802) reads platform data or DT child nodes, allocates driver/input state withdevm_*, sets input capabilities, sets up each key, then callsinput_register_device().probemeans “the driver matched a device; initialize it now.” A negative return is a normal initialization failure, not permission to leave half-initialized live callbacks. - Resources and async work: button setup requests GPIO/IRQ resources and initializes delayed work/timers. The interrupt path can defer debouncing/reporting to delayed work. The driver is therefore a useful concrete example of why an interrupt callback and slower work have different contexts.
- Cleanup and error unwind:
gpio_keys_quiesce_key()(around line 128) cancels timers and callscancel_delayed_work_sync()for work. Setup registers it throughdevm_add_action()(around line 658), so managed cleanup invokes this explicit quiesce action. Aprobeerror unwinds registered devres actions/resources; device detach after unregister also releases managed resources.platform_driver_unregister()appears in the module exit path (around line 1068).
This path is checked against the pinned source and binding, not inferred from a current upstream branch. In the driver, devm_kzalloc/devm_input_allocate_device tie allocations to device teardown (Linux devres describes managed resources released on driver detach); devm_add_action is used for the special timer/work shutdown. The driver still has to define ordering and synchronization: the managed helper does not invent a cancellation rule, stop arbitrary callbacks, cancel every async mechanism automatically, protect raw references held elsewhere, or make the hardware description correct. Managed lifetime is a cleanup mechanism, not a universal safety proof.
The important sequence is:
match → probe allocates/registers → interrupts/work may access state
→ prevent new work → synchronously cancel/finish pending work
→ release remaining resources/state
On any setup error, already-acquired resources must be released and no callback may continue using freed state. Driver authors arrange failure paths and devres ordering carefully; never assume “devm means cleanup is solved.”
Source-browser worksheet
Open only the pinned URLs above. Browser find (Ctrl/Cmd-F) locates symbols; verify the commit in the URL before recording a result. Do not build, load a module, edit a DTB, or access hardware.
| Trace | Exact thing to find | Record what it proves |
|---|---|---|
| DT → binding | In gpio-keys.yaml, the root compatible and one child example | Which schema describes the example; which properties belong to the parent vs child. |
| Binding → match | In gpio_keys.c, gpio_keys_of_match | The compatible string and the driver's match table. |
| Match → probe | .probe = gpio_keys_probe and gpio_keys_probe | Which function runs after a match and which subsystem it registers with. |
| Async → cleanup | INIT_DELAYED_WORK, gpio_keys_quiesce_key, cancel_delayed_work_sync, and devm_add_action | How async state is quiesced before managed state is released. |
| Driver unregister | gpio_keys_exit / platform_driver_unregister | The driver unregister path; distinguish it from per-device probe cleanup. |
Artifact: draw a six-node arrow chain (binding example → compatible → match table → probe → input subsystem + async worker → cleanup/unregister). Under each arrow cite a source path/symbol and write one “does not prove” note. Expected: the generic binding example does not prove any named board has that wiring; devm_* does not itself prove arbitrary callbacks have stopped. If source access fails, mark the artifact incomplete rather than substituting unpinned code.
Advanced reading: character device and user buffer
Some drivers expose a character device, a byte-stream/command-style interface using a struct file_operations table (.open, .read, .write, .ioctl, .release). It is not the right interface for every device; input, sensors, storage, networking, and graphics have established subsystem interfaces. A custom ioctl can become a permanent ABI. User-provided arguments are untrusted; kernel code must validate sizes/values and use user-copy helpers rather than dereference a userspace pointer directly.
Preserved source-reading example: below is an abbreviated excerpt from ACK drivers/char/mem.c at the pinned commit. The ... are omitted source, not compilable C; the excerpt shows the relevant loop/table shape.
static ssize_t read_zero(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
size_t cleared = 0;
while (count) {
size_t chunk = min_t(size_t, count, PAGE_SIZE);
size_t left;
left = clear_user(buf + cleared, chunk);
if (unlikely(left)) {
cleared += (chunk - left);
if (!cleared)
return -EFAULT;
break;
}
cleared += chunk;
...
}
...
}
static const struct file_operations zero_fops = {
.llseek = zero_lseek,
.write = write_zero,
.read_iter = read_iter_zero,
.read = read_zero,
.write_iter = write_iter_zero,
.mmap = mmap_zero,
...
};
char __user *buf is a userspace address, not a normal kernel pointer. clear_user is a fault-aware helper; it reports bytes it could not clear. If the first chunk fails the callback returns -EFAULT; after partial progress it can return the completed byte count. PAGE_SIZE bounds each chunk, not the entire read. zero_fops is a dispatch table, not evidence by itself of registration, permissions, or complete device lifetime. /dev/zero is a special core character device, not a platform-driver template.
The next chapter, Driver I/O, interrupts, and DMA, expands the hardware terms introduced above into a transfer lifecycle: fixed-width ioctl contracts, partial user-copy results, overflow-safe sizes, CPU versus DMA addresses, cache/order rules, interrupts, deferred completion, and teardown while a device can still write. The distinction matters because stopping a CPU callback does not itself stop a DMA engine.
Knowledge check#
Answer before reading the key.
- The
gpio-keysbinding compiles, but the driver never matches. Which link in the verified chain would you inspect first, and what exact source evidence is relevant? - GPIO-key work is queued when device removal starts. Why is freeing state before
cancel_delayed_work_sync()unsafe? - Does
devm_kzallocautomatically cancel a work item that contains a pointer to the allocation? - A DT
regvalue is0x1000. Is it safe to cast this to a userspace pointer? Why not? - The
/dev/zerocallback returns 20 for a 40-byte request. Must it be an error?
Answers#
- Compare the DT node's compatible with
gpio_keys_of_match[], then verify.of_match_tableis connected to the platform driver. The pinned binding and match-table source prove these links; they do not prove board wiring. - The callback may already be running or queued and could dereference freed state. Stop future work and synchronously wait for current/pending work before releasing state.
- No.
devm_kzallocreleases memory at device teardown; async users still need a correct cancellation/reference protocol. This driver registers an explicit quiesce action. - No.
regis a bus/device resource address interpreted in DT parent context; it is not a process virtual address. Drivers use resource APIs and correct mappings. - Not necessarily. A read can be short; a partial user-copy may report completed bytes. The caller must use the returned count and continue if appropriate.
Checkpoint#
- Ready: your chain identifies binding, match, probe, subsystem registration, and cleanup separately; you can state why teardown waits for callbacks.
- Revise: you describe DT as executable code, treat
regas a pointer, or saydevmcancels all asynchronous work; revisit the selected example and lifetime sequence. - Not yet evidenced: your worksheet lacks cited match/probe/cleanup symbols or conflates driver unregister with a per-device callback. Complete the table using the pinned source before proceeding.
Sources#
- Pinned ACK
gpio-keysbinding and driver — selected binding-to-probe-to-cleanup example. - Pinned ACK
drivers/char/mem.c— preserved/dev/zeroexcerpt. - Linux platform driver model, device-tree bindings, and device-tree usage model — general contracts; exact device interpretation is target-specific.
- Linux device resource management (devres), v6.1 —
devm_*resources are released automatically on driver detach; the page does not define cancellation of asynchronous work. - Devicetree specification, basics —
#address-cells/#size-cellsandreginterpretation (the stable spec, not a 6.1 snapshot). - AOSP device-tree overlays and Android HAL overview — Android integration context.