Both Versal and Zynq UltraScale+ devices provide System Software Mutexes. This feature enables masters in a system to safely access shared resources in a cooperative manner. This article aims to increase awareness of this feature and shows how to use it.
Many designs involving multiple masters in an SoC will require coordinated access to one or more shared resources. Operating systems typically have mutex constructs for coordinating access within the OS among processes or threads. Another choice within a multithreaded application is to use atomics. The POSIX pthreads API contains methods for operating on mutexes; we contrast it with the built-in software mutex feature. The resource(s) that are protected by a mutex can be located anywhere relative to the accessors or the mutex objects, and can be accessed using other mechanisms (e.g. i2c bus protected by an axi-lite mutex).
The word “mutex” stands for ‘MUTual EXclusion’. It is a mechanism whereby multiple requestors can concurrently request access to a resource, with only one requestor being given access at a time. Resource control includes both a physical hardware resource and its associated software state; resources can also be purely software constructs. Example use cases are:
Peripheral sharing (e.g. access to a UART)
Emulation of atomic read-modify-write cycles to registers where the register interface does not support atomic access
Atomic reads or writes to multiple registers
Locking a Mutex
Locking refers to the act of obtaining exclusive access. A requestor uses an API specific method to lock the mutex. There are two outcomes:
If the lock request succeeds, the requestor gets access to the resource(s) being protected by the mutex
If the lock request fails, the requestor may get an error code to try again, or the requestor may automatically stall.
The requestor can perform unrelated activities before trying again, increasing application efficiency
Stalling is typically efficiently done by the OS, by devoting the CPU time to other processes/threads
The choice of API to use depends on the application context
Unlocking a Mutex
Once a requestor is done with accessing the shared resource(s), it should unlock the mutex in order to allow other requestors to use them.
If two or more mutexes have to be locked in order to operate on a set of resources, always lock them in the same order, in order to avoid system deadlock.
POSIX mutexes have many attributes that can be used for efficient multi-threaded operation
System Software Mutexes
The mutex objects are implemented in hardware. However they are called software mutexes as the requestors of the objects are typically processors running software applications. It is theoretically possible to implement a non-processor PL IP to interact with these mutexes. In this article we prefix them with ‘System’ because they are visible throughout the system.
System software mutexes are implemented as pre-defined registers in the system address space. The implementation assumes that:
Each requestor uses its own master ID that is unique system-wide
Provision is made in the system-wide master ID assignment for sub-agents within an AXI master
Requestors do not unlock a mutex that they do not own
The requestor writes its master ID to the register, possibly concurrently with other requestors. The ID of the requestor that was allocated the mutex is stored in the register. Each requestor then reads back the register; if it reads back its own ID, it knows that it obtained the lock; if not, it can try again (immediately or later).
A requestor writes a zero to the register to unlock the mutex.
When an MMU is present in the system, the mutex registers should be mapped as strongly ordered or device memory. This is normally done by the bare metal BSP.
The mutex register should be accessed through a volatile pointer to protect against compiler optimizations. Additional fencing is not required as the register is always read after a write, before using the underlying resources.
There is no re-entrancy - a master should lock a mutex exactly once before unlocking it.
There is no prioritization (and hence no priority inheritance) with system software mutexes.
Care should be taken that all writes to resources being controlled by a mutex complete in the system before releasing the mutex.
System designers should consider whether a single high-level mutex can be used instead of multiple lower-level mutexes, trading off application simplicity with higher resource usage efficiency.
They are available from power-on reset.
They do not consume PL resources.
They do not require additional reserved “normal” memory such as with “Atomics”
There are a finite number of these resources. If your application needs more than these, you can use the Mutex IP in the PL, which are supported by the bare metal library.
There is no native support in the bare metal or Linux libraries.
Versal has 32 mutexes in the PMC block named PMC_MUTEX_n at addresses 0x00F1111100 through 0x00F111117C, each register being 32-bits wide. The Technical Reference Manual (AM011) lists the predefined System Management IDs (SMID) that can be used for mutex management as well. Note that the mutex registers do not verify that the master ID written matches the requestor SMID.
Zynq UltraScale+ Specific Information
Zynq UltraScale+ MPSoC has 4 mutexes in the SLR block named Mutex0 through Mutex3. There is a “Master IDs List” section in the TRM (UG1085); again, the registers do not verify that the master ID written matches the requestor ID.