|
| 1 | +#include <assert.h> |
| 2 | +#include <stdbool.h> |
| 3 | +#include <stdint.h> |
| 4 | +#include <stdio.h> |
| 5 | +#include <string.h> |
| 6 | + |
| 7 | +#include "seqlock.h" |
| 8 | + |
| 9 | +#define SEQLOCK_WRITER 1U |
| 10 | + |
| 11 | +#if defined(__i386__) || defined(__x86_64__) |
| 12 | +#define spin_wait() __builtin_ia32_pause() |
| 13 | +#elif defined(__aarch64__) |
| 14 | +#define spin_wait() __asm__ __volatile__("isb\n") |
| 15 | +#else |
| 16 | +#define spin_wait() ((void) 0) |
| 17 | +#endif |
| 18 | + |
| 19 | +#if defined(__aarch64__) |
| 20 | +#define SEVL() sevl() |
| 21 | +static inline void sevl(void) |
| 22 | +{ |
| 23 | + __asm__ volatile("sevl" : : :); |
| 24 | +} |
| 25 | +#define WFE() wfe() |
| 26 | +static inline int wfe(void) |
| 27 | +{ |
| 28 | + __asm__ volatile("wfe" : : : "memory"); |
| 29 | + return 1; |
| 30 | +} |
| 31 | +#define LDX(a, b) ldx((a), (b)) |
| 32 | +static inline uint32_t ldx(const uint8_t *var, int mm) |
| 33 | +{ |
| 34 | + uint32_t old; |
| 35 | + if (mm == __ATOMIC_ACQUIRE) |
| 36 | + __asm volatile("ldaxrb %w0, [%1]" : "=&r"(old) : "r"(var) : "memory"); |
| 37 | + else if (mm == __ATOMIC_RELAXED) |
| 38 | + __asm volatile("ldxrb %w0, [%1]" : "=&r"(old) : "r"(var) : "memory"); |
| 39 | + else |
| 40 | + abort(); |
| 41 | + return old; |
| 42 | +} |
| 43 | +#else /* generic */ |
| 44 | +#define SEVL() (void) 0 |
| 45 | +#define WFE() 1 |
| 46 | +#define LDX(a, b) __atomic_load_n((a), (b)) |
| 47 | +#endif |
| 48 | + |
| 49 | +#define UNLIKELY(x) __builtin_expect(!!(x), 0) |
| 50 | + |
| 51 | +void seqlock_init(seqlock_t *sync) |
| 52 | +{ |
| 53 | + *sync = 0; |
| 54 | +} |
| 55 | + |
| 56 | +static inline seqlock_t wait_for_no_writer(const seqlock_t *sync, int mo) |
| 57 | +{ |
| 58 | + seqlock_t l; |
| 59 | + SEVL(); /* Do SEVL early to avoid excessive loop alignment (NOPs) */ |
| 60 | + if (UNLIKELY(((l = __atomic_load_n(sync, mo)) & SEQLOCK_WRITER) != 0)) { |
| 61 | + while (WFE() && ((l = LDX(sync, mo)) & SEQLOCK_WRITER) != 0) |
| 62 | + spin_wait(); |
| 63 | + } |
| 64 | + assert((l & SEQLOCK_WRITER) == 0); /* No writer in progress */ |
| 65 | + return l; |
| 66 | +} |
| 67 | + |
| 68 | +seqlock_t seqlock_acquire_rd(const seqlock_t *sync) |
| 69 | +{ |
| 70 | + /* Wait for any present writer to go away */ |
| 71 | + /* B: Synchronize with A */ |
| 72 | + return wait_for_no_writer(sync, __ATOMIC_ACQUIRE); |
| 73 | +} |
| 74 | + |
| 75 | +bool seqlock_release_rd(const seqlock_t *sync, seqlock_t prv) |
| 76 | +{ |
| 77 | + /* Enforce Load/Load order as if synchronizing with a store-release or |
| 78 | + * fence-release in another thread. |
| 79 | + */ |
| 80 | + __atomic_thread_fence(__ATOMIC_ACQUIRE); |
| 81 | + /* Test if sync remains unchanged => success */ |
| 82 | + return __atomic_load_n(sync, __ATOMIC_RELAXED) == prv; |
| 83 | +} |
| 84 | + |
| 85 | +void seqlock_acquire_wr(seqlock_t *sync) |
| 86 | +{ |
| 87 | + seqlock_t l; |
| 88 | + do { |
| 89 | + /* Wait for any present writer to go away */ |
| 90 | + l = wait_for_no_writer(sync, __ATOMIC_RELAXED); |
| 91 | + /* Attempt to increment, setting writer flag */ |
| 92 | + } while ( |
| 93 | + /* C: Synchronize with A */ |
| 94 | + !__atomic_compare_exchange_n(sync, &l, l + SEQLOCK_WRITER, |
| 95 | + /*weak=*/true, __ATOMIC_ACQUIRE, |
| 96 | + __ATOMIC_RELAXED)); |
| 97 | + /* Enforce Store/Store order as if synchronizing with a load-acquire or |
| 98 | + * fence-acquire in another thread. |
| 99 | + */ |
| 100 | + __atomic_thread_fence(__ATOMIC_RELEASE); |
| 101 | +} |
| 102 | + |
| 103 | +void seqlock_release_wr(seqlock_t *sync) |
| 104 | +{ |
| 105 | + seqlock_t cur = *sync; |
| 106 | + if (UNLIKELY(cur & SEQLOCK_WRITER) == 0) { |
| 107 | + perror("seqlock: invalid write release"); |
| 108 | + return; |
| 109 | + } |
| 110 | + |
| 111 | + /* Increment, clearing writer flag */ |
| 112 | + /* A: Synchronize with B and C */ |
| 113 | + __atomic_store_n(sync, cur + 1, __ATOMIC_RELEASE); |
| 114 | +} |
| 115 | + |
| 116 | +#define ATOMIC_COPY(_d, _s, _sz, _type) \ |
| 117 | + ({ \ |
| 118 | + _type val = __atomic_load_n((const _type *) (_s), __ATOMIC_RELAXED); \ |
| 119 | + _s += sizeof(_type); \ |
| 120 | + __atomic_store_n((_type *) (_d), val, __ATOMIC_RELAXED); \ |
| 121 | + _d += sizeof(_type); \ |
| 122 | + _sz -= sizeof(_type); \ |
| 123 | + }) |
| 124 | + |
| 125 | +static inline void atomic_memcpy(char *dst, const char *src, size_t sz) |
| 126 | +{ |
| 127 | +#if __SIZEOF_POINTER__ == 8 |
| 128 | + while (sz >= sizeof(uint64_t)) |
| 129 | + ATOMIC_COPY(dst, src, sz, uint64_t); |
| 130 | + if (sz >= sizeof(uint32_t)) |
| 131 | + ATOMIC_COPY(dst, src, sz, uint32_t); |
| 132 | +#else //__SIZEOF_POINTER__ == 4 |
| 133 | + while (sz >= sizeof(uint32_t)) |
| 134 | + ATOMIC_COPY(dst, src, sz, uint32_t); |
| 135 | +#endif |
| 136 | + if (sz >= sizeof(uint16_t)) |
| 137 | + ATOMIC_COPY(dst, src, sz, uint16_t); |
| 138 | + if (sz >= sizeof(uint8_t)) |
| 139 | + ATOMIC_COPY(dst, src, sz, uint8_t); |
| 140 | +} |
| 141 | + |
| 142 | +void seqlock_read(seqlock_t *sync, void *dst, const void *data, size_t len) |
| 143 | +{ |
| 144 | + seqlock_t prv; |
| 145 | + do { |
| 146 | + prv = seqlock_acquire_rd(sync); |
| 147 | + atomic_memcpy(dst, data, len); |
| 148 | + } while (!seqlock_release_rd(sync, prv)); |
| 149 | +} |
| 150 | + |
| 151 | +void seqlock_write(seqlock_t *sync, const void *src, void *data, size_t len) |
| 152 | +{ |
| 153 | + seqlock_acquire_wr(sync); |
| 154 | + atomic_memcpy(data, src, len); |
| 155 | + seqlock_release_wr(sync); |
| 156 | +} |
0 commit comments