summaryrefslogtreecommitdiff
path: root/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1-RevB_FreedomStudio/freedom-metal/metal/lock.h
blob: 0702cbf167a9b98d99b0341f13e28574b55d59a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* 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 */