GPIO Expanders in Interrupt Paths: Respect Accessors That May Sleep
Moving a signal from a memory-mapped SoC GPIO to an I2C expander can change the legal execution context of the same-looking GPIO operation. Audit the provider and use a sleepable path when required. Adding the _cansleep suffix does not make a hard-interrupt handler sleepable; the operation must move to a suitable thread or other permitted context.
Applicability: the references are Linux v6.12 GPIO descriptor consumers, generic IRQ handling, and the gpio-pca953x implementation. The example migration is hypothetical. This is a Linux provider/context review, not a FreeRTOS interrupt-priority rule or a guarantee of real-time response from an expander.

1. Trace the descriptor to its real provider
Start from the consumer’s GPIO property and resolve the controller that supplies it on each board variant. Record the provider driver, transport, descriptor function, polarity, and direction. The same consumer driver can bind to a local controller on one board and a message-based provider on another.
The v6.12 consumer interface distinguishes controllers that can be accessed without sleeping from those requiring operations over buses such as I2C or SPI. gpiod_cansleep identifies that property for the descriptor. Accessors with the _cansleep suffix require a context where sleeping is allowed.
Check this during integration, before the first event arrives. A driver that fundamentally needs immediate atomic access can reject an incompatible provider with a clear diagnostic. A portable driver may instead use a threaded design for all supported providers, accepting and measuring the resulting latency.
2. Audit the complete caller context
For every read or write, record the entry point and all constraints still held at the call: hard IRQ, threaded IRQ, work item, timer callback, spinlock, interrupt-disabled region, or ordinary task context. A function called from a thread can still be in a non-sleepable region because its caller holds a spinlock.
Follow callbacks and wrappers. A helper called read_ready may hide a descriptor access several layers down. Review error and recovery paths too; a rare fault callback can perform the same illegal expander transaction as the normal path it was designed to repair.
The v6.12 pca953x driver marks its GPIO chip as sleepable and performs value access through locked register operations. That concrete implementation explains why a local-SoC assumption cannot simply follow the signal to this provider.
3. Choose the handoff before changing the accessor
For a hypothetical device-ready signal, suppose the original hard handler immediately reads a local GPIO and starts a bus transaction. After the signal moves to an expander, both the line read and the downstream action may need a thread. Keep the hard path limited to operations whose provider and context contracts permit them.
A threaded interrupt can perform the expander access in sleepable context. The generic IRQ documentation distinguishes the primary handler from thread_fn and documents IRQF_ONESHOT as keeping the interrupt line masked while the thread runs. Select flags and trigger behavior for the actual interrupt topology.
This is not permission to copy a dedicated-line design onto a shared IRQ. Attribution, masking scope, and provider interrupt demultiplexing need their own contract. Likewise, workqueue deferral requires an explicit event-storage policy: one queued work item may represent pending work, not one retained record per edge.
Document what the hard path preserves, what the thread reads later, and what can change in between. A delayed GPIO read observes a later level. It does not recreate the exact level or timestamp at the original event unless the hardware supplies a suitable latch.
4. Remove lock dependencies that defeat the thread
Do not hold a spinlock across a sleepable GPIO access. Instead, capture or update the minimal shared software state under the appropriate lock, release it, perform the permitted bus operation, then reconcile the result with the current device state.
That reconciliation matters when shutdown or reconfiguration can occur while the thread sleeps. Associate the pending operation with a state or generation and check whether its result is still applicable. Do not let an old completion re-enable a signal after the device has entered a disabled state.
Review the provider’s bus dependencies as well. If the handler needs an I2C adapter that another path holds while waiting for this handler, moving the operation to a thread has not removed the dependency cycle. Draw the wait graph and eliminate the circular ownership rather than merely increasing a timeout.
5. Handle access failure and latency honestly
Check read return values before interpreting them as active/inactive. The v6.12 GPIO implementation propagates the value-read result through the logical accessor. A negative error must not become Boolean true in a generic if-value test.
The descriptor setter returns no per-call status to the consumer. Do not invent a successful physical transition acknowledgement from that void return. Where the device protocol requires confirmation, obtain separate supported evidence, such as a meaningful readable state or the device’s response, and document its limits.
Include queueing for the expander bus, transfer time, thread scheduling, and downstream processing in the event-response budget. A legal sleepable operation can still miss a deadline. If the requirement cannot tolerate that path, reconsider hardware routing or the required function rather than suppressing context warnings.
6. Test the provider change as an interface change
- Provider matrix: exercise local and expander-backed descriptors through the same intended consumer contract.
- Context diagnostics: use the relevant kernel debug configuration to expose sleeping-in-atomic or lock misuse on every callback path.
- Bus delay: introduce controlled competing traffic and observe event-to-action latency.
- Read failure: inject a transport error in a safe fixture and require an error state rather than a false asserted input.
- Removal or shutdown: verify that pending deferred work is drained or invalidated before its resources disappear.
Use a controllable input pulse or retained status source to establish which events survive the handoff. State the pulse-duration and hardware-latch assumptions; a software queue cannot recover an edge the expander never reported.
No GPIO or interrupt test was run for this article. The deliverable is a provider/context table, a permitted handoff design, a lock-dependency review, and a measured response budget when hardware becomes available. The fix is complete when the call is both legal for its provider and useful within the application’s timing and event-retention requirements.