What are RMW Atomic Operators for Device Registers in Linux-Linux device drivers course

RMW Atomic Operators for Device Registers
Safely modifying hardware registers under concurrency — Free Linux Device Drivers Course

← 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 raw register RMW is unsafe Bitwise set_bit/clear_bit/test_and_set_bit Locking a register update with spinlocks The modern regmap subsystem Real driver use case Performance and security pitfalls
Prerequisites
Basic C programming Memory-mapped I/O basics Atomic integer operators Concept of a spinlock

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.

Common RMW Bit Operators
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.

regmap_update_bits() at a Glance
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

Q1. What does RMW mean in Linux device drivers?

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.

Q2. Do set_bit() and clear_bit() work directly on hardware registers?

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.

Q3. Why use spin_lock_irqsave() instead of a plain spinlock for register access?

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.

Q4. What is regmap and why do modern drivers use it?

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.

Q5. Can an unsynchronized register update really cause a security issue?

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.

Q6. Is locking overhead a performance concern for register access?

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.

Q7. Is this part of a free course?

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 →

Continue the Free Linux Device Drivers Course

More lectures on device registers, concurrency, and driver internals — all free at EmbeddedPathashala.

Explore All Courses

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *