]>
Commit | Line | Data |
---|---|---|
440d5faa DL |
1 | /* |
2 | * "Sequence" lock primitive | |
3 | * | |
4 | * Copyright (C) 2015 David Lamparter <equinox@diac24.net> | |
5 | * | |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License as published by the Free Software Foundation; either | |
9 | * version 2.1 of the License, or (at your option) any later version. | |
10 | * | |
11 | * This library is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Lesser General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Lesser General Public | |
17 | * License along with this library; if not, write to the | |
18 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
19 | * Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | #define _GNU_SOURCE | |
23 | ||
24 | #ifdef HAVE_CONFIG_H | |
25 | #include "config.h" | |
26 | #endif | |
27 | ||
28 | #include <unistd.h> | |
29 | #include <limits.h> | |
30 | #include <errno.h> | |
31 | #include <sys/types.h> | |
32 | #include <sys/time.h> | |
33 | #include <pthread.h> | |
34 | #include <assert.h> | |
35 | ||
36 | #include "seqlock.h" | |
37 | ||
38 | #ifdef HAVE_SYNC_LINUX_FUTEX | |
39 | /* Linux-specific - sys_futex() */ | |
40 | #include <sys/syscall.h> | |
41 | #include <linux/futex.h> | |
42 | ||
43 | static long sys_futex(void *addr1, int op, int val1, struct timespec *timeout, | |
44 | void *addr2, int val3) | |
45 | { | |
46 | return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3); | |
47 | } | |
48 | ||
49 | #define wait_once(sqlo, val) \ | |
50 | sys_futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0) | |
51 | #define wait_poke(sqlo) \ | |
52 | sys_futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0) | |
53 | ||
54 | #elif defined(HAVE_SYNC_OPENBSD_FUTEX) | |
55 | /* OpenBSD variant of the above. untested, not upstream in OpenBSD. */ | |
56 | #include <sys/syscall.h> | |
57 | #include <sys/futex.h> | |
58 | ||
59 | #define wait_once(sqlo, val) \ | |
60 | futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0) | |
61 | #define wait_poke(sqlo) \ | |
62 | futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0) | |
63 | ||
64 | #elif defined(HAVE_SYNC_UMTX_OP) | |
65 | /* FreeBSD-specific: umtx_op() */ | |
66 | #include <sys/umtx.h> | |
67 | ||
68 | #define wait_once(sqlo, val) \ | |
69 | _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val, NULL, NULL) | |
70 | #define wait_poke(sqlo) \ | |
71 | _umtx_op((void *)&sqlo->pos, UMTX_OP_WAKE, INT_MAX, NULL, NULL) | |
72 | ||
73 | #else | |
74 | /* generic version. used on *BSD, Solaris and OSX. | |
75 | */ | |
76 | ||
77 | #define wait_init(sqlo) do { \ | |
78 | pthread_mutex_init(&sqlo->lock, NULL); \ | |
79 | pthread_cond_init(&sqlo->wake, NULL); \ | |
80 | } while (0) | |
81 | #define wait_prep(sqlo) pthread_mutex_lock(&sqlo->lock) | |
82 | #define wait_once(sqlo, val) pthread_cond_wait(&sqlo->wake, &sqlo->lock) | |
83 | #define wait_done(sqlo) pthread_mutex_unlock(&sqlo->lock) | |
84 | #define wait_poke(sqlo) do { \ | |
85 | pthread_mutex_lock(&sqlo->lock); \ | |
86 | pthread_cond_broadcast(&sqlo->wake); \ | |
87 | pthread_mutex_unlock(&sqlo->lock); \ | |
88 | } while (0) | |
89 | ||
90 | #endif | |
91 | ||
92 | #ifndef wait_init | |
93 | #define wait_init(sqlo) /**/ | |
94 | #define wait_prep(sqlo) /**/ | |
95 | #define wait_done(sqlo) /**/ | |
96 | #endif /* wait_init */ | |
97 | ||
98 | ||
99 | void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val) | |
100 | { | |
101 | seqlock_val_t cur, cal; | |
102 | ||
103 | seqlock_assert_valid(val); | |
104 | ||
105 | wait_prep(sqlo); | |
106 | while (1) { | |
107 | cur = atomic_load_explicit(&sqlo->pos, memory_order_acquire); | |
108 | if (!(cur & 1)) | |
109 | break; | |
110 | cal = cur - val - 1; | |
111 | assert(cal < 0x40000000 || cal > 0xc0000000); | |
112 | if (cal < 0x80000000) | |
113 | break; | |
114 | ||
115 | wait_once(sqlo, cur); | |
116 | } | |
117 | wait_done(sqlo); | |
118 | } | |
119 | ||
120 | bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val) | |
121 | { | |
122 | seqlock_val_t cur; | |
123 | ||
124 | seqlock_assert_valid(val); | |
125 | ||
126 | cur = atomic_load_explicit(&sqlo->pos, memory_order_acquire); | |
127 | if (!(cur & 1)) | |
128 | return 1; | |
129 | cur -= val; | |
130 | assert(cur < 0x40000000 || cur > 0xc0000000); | |
131 | return cur < 0x80000000; | |
132 | } | |
133 | ||
134 | void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val) | |
135 | { | |
136 | seqlock_assert_valid(val); | |
137 | ||
138 | atomic_store_explicit(&sqlo->pos, val, memory_order_release); | |
139 | wait_poke(sqlo); | |
140 | } | |
141 | ||
142 | void seqlock_release(struct seqlock *sqlo) | |
143 | { | |
144 | atomic_store_explicit(&sqlo->pos, 0, memory_order_release); | |
145 | wait_poke(sqlo); | |
146 | } | |
147 | ||
148 | void seqlock_init(struct seqlock *sqlo) | |
149 | { | |
150 | sqlo->pos = 0; | |
151 | wait_init(sqlo); | |
152 | } | |
153 | ||
154 | ||
155 | seqlock_val_t seqlock_cur(struct seqlock *sqlo) | |
156 | { | |
157 | return atomic_load_explicit(&sqlo->pos, memory_order_acquire); | |
158 | } | |
159 | ||
160 | seqlock_val_t seqlock_bump(struct seqlock *sqlo) | |
161 | { | |
162 | seqlock_val_t val; | |
163 | ||
164 | val = atomic_fetch_add_explicit(&sqlo->pos, 2, memory_order_release); | |
165 | wait_poke(sqlo); | |
166 | return val; | |
167 | } |