Another Synchronization Primitive for Rust Drivers: SRCU

My Rust abstraction for SRCU (sleepable read-copy update) has now been merged into Linus's tree as part of the latest RCU updates.

SRCU is useful when readers need the scalability of RCU but must also be allowed to sleep inside read-side critical sections. This makes it a good fit for drivers with shared state accessed from paths that may sleep.

The primary motivation for this work was the GPU reset support I have been implementing for the Arm Mali Tyr driver. A reset must block new hardware access, wait until all ongoing accesses have finished and then give the reset worker exclusive access to the GPU's MMIO registers.

We do this through an HwGate that combines a mutex with SRCU. The mutex controls admission while SRCU tracks active hardware users. This lets hardware access paths run concurrently and sleep without holding the mutex for their entire lifetime.

The gate owns the MMIO mapping together with the mutex and SRCU domain:

#[pin_data]
pub(crate) struct HwGate<'hw> {
    iomem: IoMem<'hw>,
    #[pin]
    gate_lock: Mutex<()>,
    #[pin]
    srcu: Srcu,
}

Hardware users enter through access(). The reset worker uses close() to stop new users and wait for existing ones:

impl<'hw> HwGate<'hw> {
    pub(crate) fn access(&self) -> HwAccessGuard<'_, 'hw> {
        let gate_lock = self.gate_lock.lock();
        let srcu = self.srcu.read_lock();
        drop(gate_lock);

        HwAccessGuard {
            gate: self,
            _srcu: srcu,
        }
    }

    pub(super) fn close(&self) -> HwClosedGuard<'_, 'hw> {
        let gate_lock = self.gate_lock.lock();
        self.srcu.synchronize();

        HwClosedGuard {
            gate: self,
            _gate_lock: gate_lock,
        }
    }
}

A hardware user gets the MMIO mapping only after acquiring the read-side guard:

let gpu_info = {
    let hw_guard = hw.access();
    let gpu_info = GpuInfo::new(hw_guard.iomem());
    gpu_info.log(pdev.as_ref());
    gpu_info
};

The reset path uses the gate directly:

pub(super) fn run_reset(dev: &Device<Bound>, hw: &HwGate<'_>) -> Result {
    let hw_guard = hw.close();
    let iomem = hw_guard.iomem();

    issue_soft_reset(dev, iomem)?;
    gpu::l2_power_on(dev, iomem)?;
    Ok(())
}

The guard keeps the gate closed for the whole reset and reopens it automatically when the function returns.

The new Rust API is backed by the kernel's existing C srcu_struct. It is not a separate implementation. It provides a guard-based read-side API, so leaving the scope automatically unlocks the critical section.

The teardown path is pinned and checks for leaked read guards and drains pending callbacks before cleaning up the underlying SRCU structure. If a guard has been leaked, it waits instead of risking a use-after-free.

This is another incremental but important step for Rust drivers. Useful abstractions do more than expose existing kernel facilities to Rust. They also make their safety rules much more difficult to violate. Another good example is the wound-wait mutex synchronization primitive. The LockSet tracks multiple locks and safely handles deadlock retries with help from Rust's type system. It is expected to be used by GPUVM soon, but that is another topic for another day.

Relevant links:

#linux #rust #open-source