blob: 0702cbf167a9b98d99b0341f13e28574b55d59a8 [file] [log] [blame]
/* Copyright 2019 SiFive, Inc */
/* SPDX-License-Identifier: Apache-2.0 */
#ifndef METAL__LOCK_H
#define METAL__LOCK_H
#include <metal/machine.h>
#include <metal/memory.h>
#include <metal/compiler.h>
/*!
* @file lock.h
* @brief An API for creating and using a software lock/mutex
*/
/* TODO: How can we make the exception code platform-independant? */
#define _METAL_STORE_AMO_ACCESS_FAULT 7
#define METAL_LOCK_BACKOFF_CYCLES 32
#define METAL_LOCK_BACKOFF_EXPONENT 2
/*!
* @def METAL_LOCK_DECLARE
* @brief Declare a lock
*
* Locks must be declared with METAL_LOCK_DECLARE to ensure that the lock
* is linked into a memory region which supports atomic memory operations.
*/
#define METAL_LOCK_DECLARE(name) \
__attribute__((section(".data.locks"))) \
struct metal_lock name
/*!
* @brief A handle for a lock
*/
struct metal_lock {
int _state;
};
/*!
* @brief Initialize a lock
* @param lock The handle for a lock
* @return 0 if the lock is successfully initialized. A non-zero code indicates failure.
*
* If the lock cannot be initialized, attempts to take or give the lock
* will result in a Store/AMO access fault.
*/
__inline__ int metal_lock_init(struct metal_lock *lock) {
#ifdef __riscv_atomic
/* Get a handle for the memory which holds the lock state */
struct metal_memory *lock_mem = metal_get_memory_from_address((uintptr_t) &(lock->_state));
if(!lock_mem) {
return 1;
}
/* If the memory doesn't support atomics, report an error */
if(!metal_memory_supports_atomics(lock_mem)) {
return 2;
}
lock->_state = 0;
return 0;
#else
return 3;
#endif
}
/*!
* @brief Take a lock
* @param lock The handle for a lock
* @return 0 if the lock is successfully taken
*
* If the lock initialization failed, attempts to take a lock will result in
* a Store/AMO access fault.
*/
__inline__ int metal_lock_take(struct metal_lock *lock) {
#ifdef __riscv_atomic
int old = 1;
int new = 1;
int backoff = 1;
const int max_backoff = METAL_LOCK_BACKOFF_CYCLES * METAL_MAX_CORES;
while(1) {
__asm__ volatile("amoswap.w.aq %[old], %[new], (%[state])"
: [old] "=r" (old)
: [new] "r" (new), [state] "r" (&(lock->_state))
: "memory");
if (old == 0) {
break;
}
for (int i = 0; i < backoff; i++) {
__asm__ volatile("");
}
if (backoff < max_backoff) {
backoff *= METAL_LOCK_BACKOFF_EXPONENT;
}
}
return 0;
#else
/* Store the memory address in mtval like a normal store/amo access fault */
__asm__ ("csrw mtval, %[state]"
:: [state] "r" (&(lock->_state)));
/* Trigger a Store/AMO access fault */
_metal_trap(_METAL_STORE_AMO_ACCESS_FAULT);
/* If execution returns, indicate failure */
return 1;
#endif
}
/*!
* @brief Give back a held lock
* @param lock The handle for a lock
* @return 0 if the lock is successfully given
*
* If the lock initialization failed, attempts to give a lock will result in
* a Store/AMO access fault.
*/
__inline__ int metal_lock_give(struct metal_lock *lock) {
#ifdef __riscv_atomic
__asm__ volatile("amoswap.w.rl x0, x0, (%[state])"
:: [state] "r" (&(lock->_state))
: "memory");
return 0;
#else
/* Store the memory address in mtval like a normal store/amo access fault */
__asm__ ("csrw mtval, %[state]"
:: [state] "r" (&(lock->_state)));
/* Trigger a Store/AMO access fault */
_metal_trap(_METAL_STORE_AMO_ACCESS_FAULT);
/* If execution returns, indicate failure */
return 1;
#endif
}
#endif /* METAL__LOCK_H */