Loading...
Searching...
No Matches
spin_lock.h
1/*
2 * Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7#ifndef _HARDWARE_SYNC_SPIN_LOCK_H
8#define _HARDWARE_SYNC_SPIN_LOCK_H
9
10#include "pico.h"
11#include "hardware/sync.h"
12
13// PICO_CONFIG: PICO_USE_SW_SPIN_LOCKS, Use software implementation for spin locks, type=bool, default=1 on RP2350 due to errata E2, group=hardware_sync
14#ifndef PICO_USE_SW_SPIN_LOCKS
15#if PICO_RP2350
16#define PICO_USE_SW_SPIN_LOCKS (!PICO_XIP_RAM)
17#endif
18#endif
19
20// Default spinlock assignments
21//
22// On RP2350, writes to SIO register offsets +0x180..0x1fc spuriously release the spinlock addressed
23// 0x80 bytes lower (erratum RP2350-E2). By default we use software spinlocks to work around this.
24// Otherwise (PICO_USE_SW_SPIN_LOCKS=0) we change the default numbering to avoid locks which alias
25// with writable registers: 5-7, 10-11 and 26-31 have no register at that offset, and 18-25 alias
26// the read-only TMDS PEEK/POP registers. The rest alias a writable register: 0-4
27// DOORBELL_*/PERI_NONSEC, 8-9 RISCV_SOFTIRQ/MTIME_CTRL, 12-15 MTIME*, 16-17 TMDS_CTRL/WDATA.
28//
29// On RP2040 we preserve the original default lock numbers existing since SDK v1.0, since they are a
30// compatibility detail that out-of-tree code might rely on.
31
32// PICO_CONFIG: PICO_SPINLOCK_ID_IRQ, Spinlock ID for IRQ protection, min=0, max=31, default=9 (5 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
33#ifndef PICO_SPINLOCK_ID_IRQ
34#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
35#define PICO_SPINLOCK_ID_IRQ 5
36#else
37#define PICO_SPINLOCK_ID_IRQ 9
38#endif
39#endif
40
41// PICO_CONFIG: PICO_SPINLOCK_ID_TIMER, Spinlock ID for Timer protection, min=0, max=31, default=10 (6 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
42#ifndef PICO_SPINLOCK_ID_TIMER
43#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
44#define PICO_SPINLOCK_ID_TIMER 6
45#else
46#define PICO_SPINLOCK_ID_TIMER 10
47#endif
48#endif
49
50// PICO_CONFIG: PICO_SPINLOCK_ID_HARDWARE_CLAIM, Spinlock ID for Hardware claim protection, min=0, max=31, default=11 (7 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
51#ifndef PICO_SPINLOCK_ID_HARDWARE_CLAIM
52#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
53#define PICO_SPINLOCK_ID_HARDWARE_CLAIM 7
54#else
55#define PICO_SPINLOCK_ID_HARDWARE_CLAIM 11
56#endif
57#endif
58
59// PICO_CONFIG: PICO_SPINLOCK_ID_RAND, Spinlock ID for Random Number Generator, min=0, max=31, default=12 (10 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
60#ifndef PICO_SPINLOCK_ID_RAND
61#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
62#define PICO_SPINLOCK_ID_RAND 10
63#else
64#define PICO_SPINLOCK_ID_RAND 12
65#endif
66#endif
67
68// PICO_CONFIG: PICO_SPINLOCK_ID_ATOMIC, Spinlock ID for atomics, min=0, max=31, default=13 (11 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
69#ifndef PICO_SPINLOCK_ID_ATOMIC
70#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
71#define PICO_SPINLOCK_ID_ATOMIC 11
72#else
73#define PICO_SPINLOCK_ID_ATOMIC 13
74#endif
75#endif
76
77// PICO_CONFIG: PICO_SPINLOCK_ID_OS1, First Spinlock ID reserved for use by low level OS style software, min=0, max=31, default=14 (18 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
78#ifndef PICO_SPINLOCK_ID_OS1
79#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
80#define PICO_SPINLOCK_ID_OS1 18
81#else
82#define PICO_SPINLOCK_ID_OS1 14
83#endif
84#endif
85
86// PICO_CONFIG: PICO_SPINLOCK_ID_OS2, Second Spinlock ID reserved for use by low level OS style software, min=0, max=31, default=15 (19 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
87#ifndef PICO_SPINLOCK_ID_OS2
88#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
89#define PICO_SPINLOCK_ID_OS2 19
90#else
91#define PICO_SPINLOCK_ID_OS2 15
92#endif
93#endif
94
95// PICO_CONFIG: PICO_SPINLOCK_ID_STRIPED_FIRST, Lowest Spinlock ID in the 'striped' range, min=0, max=31, default=16 (20 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
96#ifndef PICO_SPINLOCK_ID_STRIPED_FIRST
97#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
98#define PICO_SPINLOCK_ID_STRIPED_FIRST 20
99#else
100#define PICO_SPINLOCK_ID_STRIPED_FIRST 16
101#endif
102#endif
103
104// PICO_CONFIG: PICO_SPINLOCK_ID_STRIPED_LAST, Highest Spinlock ID in the 'striped' range, min=0, max=31, default=23 (25 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
105#ifndef PICO_SPINLOCK_ID_STRIPED_LAST
106#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
107#define PICO_SPINLOCK_ID_STRIPED_LAST 25
108#else
109#define PICO_SPINLOCK_ID_STRIPED_LAST 23
110#endif
111#endif
112
113// PICO_CONFIG: PICO_SPINLOCK_ID_CLAIM_FREE_FIRST, Lowest Spinlock ID in the 'claim free' range, min=0, max=31, default=24 (26 on RP2350 when PICO_USE_SW_SPIN_LOCKS is 0), group=hardware_sync
114#ifndef PICO_SPINLOCK_ID_CLAIM_FREE_FIRST
115#if PICO_RP2350 && !PICO_USE_SW_SPIN_LOCKS
116#define PICO_SPINLOCK_ID_CLAIM_FREE_FIRST 26
117#else
118#define PICO_SPINLOCK_ID_CLAIM_FREE_FIRST 24
119#endif
120#endif
121
122#ifdef PICO_SPINLOCK_ID_CLAIM_FREE_END
123#warning PICO_SPINLOCK_ID_CLAIM_FREE_END has been renamed to PICO_SPINLOCK_ID_CLAIM_FREE_LAST
124#endif
125
126// PICO_CONFIG: PICO_SPINLOCK_ID_CLAIM_FREE_LAST, Highest Spinlock ID in the 'claim free' range, min=0, max=31, default=31, group=hardware_sync
127#ifndef PICO_SPINLOCK_ID_CLAIM_FREE_LAST
128#define PICO_SPINLOCK_ID_CLAIM_FREE_LAST 31
129#endif
130
134#if !PICO_USE_SW_SPIN_LOCKS
135// Hardware lock flag in SIO:
136typedef io_rw_32 spin_lock_t;
137#else
138#ifndef SW_SPIN_LOCK_TYPE
139// Byte flag in memory:
140#define SW_SPIN_LOCK_TYPE volatile uint8_t
141#endif
142typedef SW_SPIN_LOCK_TYPE spin_lock_t;
143#endif
144
145#if PICO_USE_SW_SPIN_LOCKS
146
147#ifndef SW_SPIN_LOCK_INSTANCE
148#define SW_SPIN_LOCK_INSTANCE(lock_num) ({ \
149 extern spin_lock_t _sw_spin_locks[NUM_SPIN_LOCKS]; \
150 &_sw_spin_locks[lock_num]; \
151 })
152#endif
153
154#ifndef SW_SPIN_LOCK_NUM
155#define SW_SPIN_LOCK_NUM(lock) ({ \
156 extern spin_lock_t _sw_spin_locks[NUM_SPIN_LOCKS]; \
157 (lock) - _sw_spin_locks; \
158 })
159#endif
160
161#ifndef SW_SPIN_LOCK_IS_LOCKED
162#define SW_SPIN_LOCK_IS_LOCKED(lock) ((bool) *(lock))
163#endif
164
165#ifndef SW_SPIN_LOCK_LOCK
166#if __ARM_ARCH_8M_MAIN__
167#define SW_SPIN_LOCK_LOCK(lock) ({ \
168 uint32_t _tmp0, _tmp1; \
169 pico_default_asm_volatile ( \
170 "1:\n" \
171 "ldaexb %1, [%2]\n" \
172 "movs %0, #1\n" /* fill dependency slot */ \
173 "cmp %1, #0\n" \
174 /* Immediately retry if lock is seen to be taken */ \
175 "bne 1b\n" \
176 /* Attempt to claim */ \
177 "strexb %1, %0, [%2]\n" \
178 "cmp %1, #0\n" \
179 /* Claim failed due to intervening write, so retry */ \
180 "bne 1b\n" \
181 : "=&r" (_tmp0), "=&r" (_tmp1) : "r" (lock) \
182 ); \
183 __mem_fence_acquire(); \
184 })
185#elif __riscv && (defined(__riscv_a) || defined(__riscv_zaamo))
186#define SW_SPIN_LOCK_LOCK(lock) ({ \
187 uint32_t _tmp0, _tmp1; \
188 pico_default_asm_volatile ( \
189 /* Get word address, and bit mask for LSB of the */ \
190 /* correct byte within that word -- note shamt is modulo xlen: */ \
191 "slli %1, %0, 3\n" \
192 "bset %1, zero, %1\n" \
193 "andi %0, %0, -4\n" \
194 /* Repeatedly set the bit until we see that it was clear at the */ \
195 /* point we set it. A set from 0 -> 1 is a successful lock take. */ \
196 "1:" \
197 "amoor.w.aq %2, %1, (%0)\n" \
198 "and %2, %2, %1\n" \
199 "bnez %2, 1b\n" \
200 : "+r" (lock), "=r" (_tmp0), "=r" (_tmp1) \
201 ); \
202 __mem_fence_acquire(); \
203 })
204#else
205#error no SW_SPIN_LOCK_LOCK available for PICO_USE_SW_SPIN_LOCK on this platform
206#endif
207#endif
208
209#ifndef SW_SPIN_TRY_LOCK
210#if __ARM_ARCH_8M_MAIN__
211#define SW_SPIN_TRY_LOCK(lock) ({ \
212 uint32_t _tmp0, _tmp1; \
213 pico_default_asm_volatile ( \
214 "ldaexb %1, [%2]\n" \
215 "movs %0, #1\n" /* fill dependency slot */ \
216 "cmp %1, #0\n" \
217 /* Immediately give up if lock is seen to be taken */ \
218 "bne 1f\n" \
219 /* Otherwise attempt to claim, once. */ \
220 "strexb %1, %0, [%2]\n" \
221 "1:\n" \
222 : "=&r" (_tmp0), "=&r" (_tmp1) : "r" (lock) \
223 ); \
224 __mem_fence_acquire(); \
225 !_tmp1; \
226 })
227#elif __riscv && (defined(__riscv_a) || defined(__riscv_zaamo))
228#define SW_SPIN_TRY_LOCK(lock) ({ \
229 uint32_t _tmp0; \
230 pico_default_asm_volatile ( \
231 /* Get word address, and bit mask for LSB of the */ \
232 /* correct byte within that word -- note shamt is modulo xlen: */ \
233 "slli %1, %0, 3\n" \
234 "bset %1, zero, %1\n" \
235 "andi %0, %0, -4\n" \
236 /* Set the bit. If it was clear at the point we set it, then we took */ \
237 /* the lock. Otherwise the lock was already held, and we give up. */ \
238 "amoor.w.aq %0, %1, (%0)\n" \
239 "and %1, %1, %0\n" \
240 : "+r" (lock), "=r" (_tmp0) \
241 ); \
242 __mem_fence_acquire(); \
243 !_tmp0; \
244 })
245#else
246#error no SW_SPIN_TRY_LOCK available for PICO_USE_SW_SPIN_LOCK on this platform
247#endif
248#endif
249
250#ifndef SW_SPIN_LOCK_UNLOCK
251#if __ARM_ARCH_8M_MAIN__
252#define SW_SPIN_LOCK_UNLOCK(lock) ({ \
253 /* Release-ordered store is available: use instead of separate fence */ \
254 uint32_t zero = 0; \
255 pico_default_asm_volatile( \
256 "stlb %0, [%1]\n" \
257 : : "r" (zero), "r" (lock) \
258 ); \
259 })
260#elif __riscv
261#define SW_SPIN_LOCK_UNLOCK(lock) ({ \
262 __mem_fence_release(); \
263 *(lock) = 0; /* write to spinlock register (release lock) */ \
264 })
265#else
266#error no SW_SPIN_LOCK_UNLOCK available for PICO_USE_SW_SPIN_LOCK on this platform
267#endif
268#endif
269
270#endif
271
278__force_inline static spin_lock_t *spin_lock_instance(uint lock_num) {
279 invalid_params_if(HARDWARE_SYNC, lock_num >= NUM_SPIN_LOCKS);
280#if PICO_USE_SW_SPIN_LOCKS
281 return SW_SPIN_LOCK_INSTANCE(lock_num);
282#else
283 return (spin_lock_t *) (SIO_BASE + SIO_SPINLOCK0_OFFSET + lock_num * 4);
284#endif
285}
286
293__force_inline static uint spin_lock_get_num(spin_lock_t *lock) {
294#if PICO_USE_SW_SPIN_LOCKS
295 uint lock_num = SW_SPIN_LOCK_NUM(lock);
296 invalid_params_if(HARDWARE_SYNC, lock_num >= (uint)NUM_SPIN_LOCKS);
297 return lock_num;
298#else
299 invalid_params_if(HARDWARE_SYNC, (uint) lock < SIO_BASE + SIO_SPINLOCK0_OFFSET ||
300 (uint) lock >= NUM_SPIN_LOCKS * sizeof(spin_lock_t) + SIO_BASE + SIO_SPINLOCK0_OFFSET ||
301 ((uint) lock - SIO_BASE + SIO_SPINLOCK0_OFFSET) % sizeof(spin_lock_t) != 0);
302 return (uint) (lock - (spin_lock_t *) (SIO_BASE + SIO_SPINLOCK0_OFFSET));
303#endif
304}
305
311__force_inline static void spin_lock_unsafe_blocking(spin_lock_t *lock) {
312 // Note we don't do a wfe or anything, because by convention these spin_locks are VERY SHORT LIVED and NEVER BLOCK and run
313 // with INTERRUPTS disabled (to ensure that)... therefore nothing on our core could be blocking us, so we just need to wait on another core
314 // anyway which should be finished soon
315#if PICO_USE_SW_SPIN_LOCKS
316 SW_SPIN_LOCK_LOCK(lock);
317#else
318 while (__builtin_expect(!*lock, 0)) { // read from spinlock register (tries to acquire the lock)
320 }
322#endif
323}
324
325__force_inline static bool spin_try_lock_unsafe(spin_lock_t *lock) {
326#if PICO_USE_SW_SPIN_LOCKS
327 return SW_SPIN_TRY_LOCK(lock);
328#else
329 return *lock;
330#endif
331}
337__force_inline static void spin_unlock_unsafe(spin_lock_t *lock) {
338#if PICO_USE_SW_SPIN_LOCKS
339 SW_SPIN_LOCK_UNLOCK(lock);
340#else
342 *lock = 0; // write to spinlock register (release lock)
343#endif
344}
345
354__force_inline static uint32_t spin_lock_blocking(spin_lock_t *lock) {
355 uint32_t save = save_and_disable_interrupts();
357 return save;
358}
359
365inline static bool is_spin_locked(spin_lock_t *lock) {
366#if PICO_USE_SW_SPIN_LOCKS
367 return SW_SPIN_LOCK_IS_LOCKED(lock);
368#else
369 check_hw_size(spin_lock_t, 4);
370 uint lock_num = spin_lock_get_num(lock);
371 return 0 != (*(io_ro_32 *) (SIO_BASE + SIO_SPINLOCK_ST_OFFSET) & (1u << lock_num));
372#endif
373}
374
385__force_inline static void spin_unlock(spin_lock_t *lock, uint32_t saved_irq) {
386 spin_unlock_unsafe(lock);
388}
389
398spin_lock_t *spin_lock_init(uint lock_num);
399
403void spin_locks_reset(void);
404
405#endif
static __force_inline uint32_t spin_lock_blocking(spin_lock_t *lock)
Acquire a spin lock safely.
Definition spin_lock.h:354
static __force_inline uint32_t save_and_disable_interrupts(void)
Disable interrupts on the calling core, returning the previous interrupt state.
Definition sync.h:252
static __force_inline void __mem_fence_release(void)
Release a memory fence.
Definition sync.h:204
static __force_inline void spin_unlock_unsafe(spin_lock_t *lock)
Release a spin lock without re-enabling interrupts.
Definition spin_lock.h:337
static __force_inline void spin_unlock(spin_lock_t *lock, uint32_t saved_irq)
Release a spin lock safely.
Definition spin_lock.h:385
static __force_inline void restore_interrupts_from_disabled(uint32_t status)
Restore interrupts to a specified state on the calling core with restricted transitions.
Definition sync.h:295
void spin_locks_reset(void)
Release all spin locks.
Definition sync_spin_lock.c:8
static __force_inline spin_lock_t * spin_lock_instance(uint lock_num)
Get HW Spinlock instance from number.
Definition spin_lock.h:278
static __force_inline void __mem_fence_acquire(void)
Acquire a memory fence.
Definition sync.h:188
static __force_inline void spin_lock_unsafe_blocking(spin_lock_t *lock)
Acquire a spin lock without disabling interrupts (hence unsafe).
Definition spin_lock.h:311
spin_lock_t * spin_lock_init(uint lock_num)
Initialise a spin lock.
Definition sync_spin_lock.c:14
static __force_inline uint spin_lock_get_num(spin_lock_t *lock)
Get HW Spinlock number from instance.
Definition spin_lock.h:293
static bool is_spin_locked(spin_lock_t *lock)
Check to see if a spinlock is currently acquired elsewhere.
Definition spin_lock.h:365
#define SW_SPIN_LOCK_TYPE
A spin lock identifier.
Definition spin_lock.h:140
#define __force_inline
Attribute to force inlining of a function regardless of optimization level.
Definition compiler.h:128
static __force_inline void tight_loop_contents(void)
No-op function for the body of tight loops.
Definition common.h:79