← Previous Lecture Next Lecture →
Every device driver author eventually runs into the same problem: a peripheral register needs some bits changed while the rest stay untouched, and more than one CPU (or interrupt context) might try to touch that same register at almost the same instant. This lecture in our free Linux device drivers course covers RMW atomic operators — the read-modify-write primitives the kernel gives you to make register updates safe, and how modern drivers actually apply them in practice on current kernels.
What You Will Learn
Why a Plain Register Update Is Not Safe
Setting a single bit in a hardware register looks harmless in isolation: read the register, OR in the bit you want, write it back. The trouble starts the moment more than one execution context can touch that register — a second CPU running the same driver’s code path, or an interrupt handler that fires in between your read and your write. If context A reads the register, then context B also reads the same old value before A has written back its change, B’s write can silently erase A’s update. This is a textbook lost-update race condition, and it is exactly the kind of bug that shows up rarely in testing and then intermittently in production.
RMW Atomic Bit Operators
The kernel provides a set of arch-independent bit-manipulation functions that perform the read-modify-write sequence as a single atomic step, so no other CPU can observe or interfere with it partway through. These operate directly on a memory location treated as a bitmap.
| Function | Effect |
|---|---|
| set_bit(nr, addr) | Atomically sets bit number nr |
| clear_bit(nr, addr) | Atomically clears bit number nr |
| test_and_set_bit(nr, addr) | Atomically sets a bit and returns its previous value |
| test_and_clear_bit(nr, addr) | Atomically clears a bit and returns its previous value |
| change_bit(nr, addr) | Atomically toggles a bit |
Important distinction: These bit operators work on a plain memory location (typically an unsigned long, in RAM), not directly on a memory-mapped hardware register. For actual peripheral registers accessed through ioread*()/iowrite*(), the kernel requires an explicit lock around the read-modify-write sequence instead, because the hardware bus access itself is not guaranteed atomic the way a CPU-local bit operation is.
Protecting a Real Register RMW With a Spinlock
For an actual memory-mapped device register, the standard modern approach is to wrap the read-modify-write sequence in a spinlock, so only one CPU can be inside that critical section at a time. Here is an original example for a hypothetical sensor control register:
#include <linux/spinlock.h>
#include <linux/io.h>
#define SENSOR_CTRL_REG (sensor_base + 0x04)
#define ENABLE_BIT BIT(7)
static DEFINE_SPINLOCK(sensor_lock);
static void __iomem *sensor_base;
void sensor_enable(void)
{
unsigned long flags;
u8 val;
spin_lock_irqsave(&sensor_lock, flags);
val = ioread8(SENSOR_CTRL_REG); /* read */
val |= ENABLE_BIT; /* modify */
iowrite8(val, SENSOR_CTRL_REG); /* write */
spin_unlock_irqrestore(&sensor_lock, flags);
}
void sensor_disable(void)
{
unsigned long flags;
u8 val;
spin_lock_irqsave(&sensor_lock, flags);
val = ioread8(SENSOR_CTRL_REG);
val &= ~ENABLE_BIT;
iowrite8(val, SENSOR_CTRL_REG);
spin_unlock_irqrestore(&sensor_lock, flags);
}
The spin_lock_irqsave() variant is used here rather than a plain spinlock because register updates like this are often also touched from interrupt handlers, so disabling local interrupts while holding the lock prevents a classic self-deadlock where an interrupt handler on the same CPU tries to take a lock that CPU is already holding.
The Modern Approach: regmap-Based RMW
Hand-rolled locking around every register access, as shown above, is still valid and widely used, but a large share of current mainline drivers — especially for I2C and SPI-connected peripherals — now delegate register access entirely to the kernel’s regmap subsystem. Regmap centralizes register I/O behind a single API and already implements safe, locked read-modify-write internally, so individual drivers don’t have to reinvent it.
| Driver Code | regmap Core | Hardware Bus |
|---|---|---|
| Calls regmap_update_bits(map, reg, mask, val) | Takes internal lock, performs read-modify-write | I2C/SPI transaction executes safely |
A driver author using regmap simply calls something equivalent to regmap_update_bits(map, SENSOR_CTRL_REG, ENABLE_BIT, ENABLE_BIT), and the locking, caching, and bus transaction details are handled centrally. This is now the preferred pattern for most new peripheral drivers rather than writing manual spinlock-protected RMW code for every single register.
Real-World Use Case: Enabling a Peripheral Without Disturbing Other Bits
Imagine a control register where bit 7 enables the device, but bits 0-3 hold an unrelated clock-divider setting configured earlier during probe. A naive write of a fixed value to that register would silently reset the clock divider back to zero. The RMW pattern — whether done manually with a spinlock or through regmap — is precisely what allows one driver function to flip a single bit without touching configuration that another part of the driver already set up.
Performance Considerations
Bus-connected registers (I2C, SPI) are already orders of magnitude slower than CPU-local memory, so the locking overhead around an RMW sequence is rarely the bottleneck — the bus transaction itself dominates the cost. For registers reachable through fast memory-mapped I/O on a local bus, keep the critical section as short as possible: do the read, modify, and write, and nothing else, while holding the lock.
Security Considerations
An unsynchronized register RMW isn’t just a correctness bug — on registers that gate security-relevant hardware state (locking a boot region, enabling a debug interface, gating a DMA engine) a lost update can leave a security-sensitive bit in the wrong state without any visible error. Always treat register RMW sequences that control security-relevant hardware features with the same rigor as any other concurrency-sensitive kernel code path.
Common Mistakes and Troubleshooting
- Forgetting the lock entirely: The three-line read-modify-write pattern looks so simple that it’s easy to skip locking “just this once” — that’s exactly how intermittent, hard-to-reproduce bugs get introduced.
- Using a plain spin_lock() when interrupts also touch the register: Use spin_lock_irqsave()/spin_unlock_irqrestore() whenever an interrupt handler can also access the same register.
- Holding the lock too long: Doing unrelated work while holding the register lock unnecessarily increases contention and can affect interrupt latency.
- Reinventing regmap: If your bus type is already supported by regmap, hand-written locking is usually unnecessary extra code to maintain.
Best Practices
- Always protect a genuine register RMW sequence with an appropriate lock.
- Prefer regmap for I2C/SPI-based peripherals instead of manual locking where possible.
- Keep the critical section as short as the read-modify-write itself.
- Use spin_lock_irqsave() when the same register can also be touched from interrupt context.
- Document which bits in a register are owned by which part of the driver to avoid accidental overwrites.
Summary and Key Takeaways
- A plain read-modify-write on a shared register is a race condition waiting to happen.
- set_bit/clear_bit-style functions atomically manipulate CPU-local bitmaps, not memory-mapped hardware registers directly.
- Real hardware registers need an explicit lock (commonly spin_lock_irqsave) around the RMW sequence.
- The regmap subsystem now provides this safety centrally for many I2C/SPI drivers.
- Register RMW races on security-relevant hardware state deserve extra scrutiny.
Frequently Asked Questions
RMW stands for Read-Modify-Write — the sequence of reading a register’s current value, changing only the bits you need, and writing the result back.
No, they operate atomically on a CPU-local memory location treated as a bitmap. Actual memory-mapped hardware registers need an explicit lock around the ioread/iowrite RMW sequence instead.
Because if an interrupt handler on the same CPU can also access the register, a plain spinlock can deadlock the CPU against itself. Disabling local interrupts while holding the lock avoids that.
regmap is a kernel subsystem that centralizes register access for buses like I2C and SPI, including safe locked read-modify-write, so individual drivers don’t need to implement their own locking for every register.
Yes. On registers that gate security-relevant features, a lost update from a race condition can leave the hardware in an unintended state without producing any visible error.
Usually not for bus-connected peripherals like I2C or SPI, since the bus transaction itself is far slower than the lock. It matters more for very fast, high-frequency memory-mapped registers.
Yes, this lecture is part of EmbeddedPathashala’s free Linux device drivers course, which also covers the free Linux kernel development course and free embedded systems course tracks.
← Previous Lecture Next Lecture →
More lectures on device registers, concurrency, and driver internals — all free at EmbeddedPathashala.
Explore All Courses
2 Comments