locks_up.c revision 1.2 1 /* $NetBSD: locks_up.c,v 1.2 2010/06/01 20:11:33 pooka Exp $ */
2
3 /*
4 * Copyright (c) 2010 Antti Kantee. All Rights Reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 /*
29 * Virtual uniprocessor rump kernel version of locks. Since the entire
30 * kernel is running on only one CPU in the system, there is no need
31 * to perform slow cache-coherent MP locking operations. This speeds
32 * up things quite dramatically and is a good example of that two
33 * disjoint kernels running simultaneously in an MP system can be
34 * massively faster than one with fine-grained locking.
35 */
36
37 #include <sys/cdefs.h>
38 __KERNEL_RCSID(0, "$NetBSD: locks_up.c,v 1.2 2010/06/01 20:11:33 pooka Exp $");
39
40 #include <sys/param.h>
41 #include <sys/kernel.h>
42 #include <sys/kmem.h>
43 #include <sys/mutex.h>
44 #include <sys/rwlock.h>
45
46 #include <rump/rumpuser.h>
47
48 #include "rump_private.h"
49
50 struct upmtx {
51 struct lwp *upm_owner;
52 int upm_wanted;
53 struct rumpuser_cv *upm_rucv;
54 };
55 #define UPMTX(mtx) struct upmtx *upm = *(struct upmtx **)mtx
56
57 static inline void
58 checkncpu(void)
59 {
60
61 if (__predict_false(ncpu != 1))
62 panic("UP lock implementation requires RUMP_NCPU == 1");
63 }
64
65 void
66 mutex_init(kmutex_t *mtx, kmutex_type_t type, int ipl)
67 {
68 struct upmtx *upm;
69
70 CTASSERT(sizeof(kmutex_t) >= sizeof(void *));
71 checkncpu();
72
73 /*
74 * XXX: pool_cache would be nice, but not easily possible,
75 * as pool cache init wants to call mutex_init() ...
76 */
77 upm = rumpuser_malloc(sizeof(*upm), 0);
78 memset(upm, 0, sizeof(*upm));
79 rumpuser_cv_init(&upm->upm_rucv);
80 memcpy(mtx, &upm, sizeof(void *));
81 }
82
83 void
84 mutex_destroy(kmutex_t *mtx)
85 {
86 UPMTX(mtx);
87
88 KASSERT(upm->upm_owner == NULL);
89 KASSERT(upm->upm_wanted == 0);
90 rumpuser_cv_destroy(upm->upm_rucv);
91 rumpuser_free(upm);
92 }
93
94 void
95 mutex_enter(kmutex_t *mtx)
96 {
97 UPMTX(mtx);
98
99 /* fastpath? */
100 if (mutex_tryenter(mtx))
101 return;
102
103 /*
104 * No? bummer, do it the slow and painful way then.
105 */
106 upm->upm_wanted++;
107 while (!mutex_tryenter(mtx)) {
108 rump_schedlock_cv_wait(upm->upm_rucv);
109 }
110 upm->upm_wanted--;
111
112 KASSERT(upm->upm_wanted >= 0);
113 }
114
115 void
116 mutex_spin_enter(kmutex_t *mtx)
117 {
118
119 mutex_enter(mtx);
120 }
121
122 int
123 mutex_tryenter(kmutex_t *mtx)
124 {
125 UPMTX(mtx);
126
127 if (upm->upm_owner)
128 return 0;
129
130 upm->upm_owner = curlwp;
131 return 1;
132 }
133
134 void
135 mutex_exit(kmutex_t *mtx)
136 {
137 UPMTX(mtx);
138
139 if (upm->upm_wanted) {
140 rumpuser_cv_signal(upm->upm_rucv); /* CPU is our interlock */
141 }
142 upm->upm_owner = NULL;
143 }
144
145 void
146 mutex_spin_exit(kmutex_t *mtx)
147 {
148
149 mutex_exit(mtx);
150 }
151
152 int
153 mutex_owned(kmutex_t *mtx)
154 {
155 UPMTX(mtx);
156
157 return upm->upm_owner == curlwp;
158 }
159
160 struct uprw {
161 struct lwp *uprw_owner;
162 int uprw_readers;
163 uint16_t uprw_rwant;
164 uint16_t uprw_wwant;
165 struct rumpuser_cv *uprw_rucv_reader;
166 struct rumpuser_cv *uprw_rucv_writer;
167 };
168
169 #define UPRW(rw) struct uprw *uprw = *(struct uprw **)rw
170
171 /* reader/writer locks */
172
173 void
174 rw_init(krwlock_t *rw)
175 {
176 struct uprw *uprw;
177
178 CTASSERT(sizeof(krwlock_t) >= sizeof(void *));
179 checkncpu();
180
181 uprw = rumpuser_malloc(sizeof(*uprw), 0);
182 memset(uprw, 0, sizeof(*uprw));
183 rumpuser_cv_init(&uprw->uprw_rucv_reader);
184 rumpuser_cv_init(&uprw->uprw_rucv_writer);
185 memcpy(rw, &uprw, sizeof(void *));
186 }
187
188 void
189 rw_destroy(krwlock_t *rw)
190 {
191 UPRW(rw);
192
193 rumpuser_cv_destroy(uprw->uprw_rucv_reader);
194 rumpuser_cv_destroy(uprw->uprw_rucv_writer);
195 rumpuser_free(uprw);
196 }
197
198 /* take rwlock. prefer writers over readers (see rw_tryenter and rw_exit) */
199 void
200 rw_enter(krwlock_t *rw, const krw_t op)
201 {
202 UPRW(rw);
203 struct rumpuser_cv *rucv;
204 uint16_t *wp;
205
206 if (rw_tryenter(rw, op))
207 return;
208
209 /* lagpath */
210 if (op == RW_READER) {
211 rucv = uprw->uprw_rucv_reader;
212 wp = &uprw->uprw_rwant;
213 } else {
214 rucv = uprw->uprw_rucv_writer;
215 wp = &uprw->uprw_wwant;
216 }
217
218 (*wp)++;
219 while (!rw_tryenter(rw, op)) {
220 rump_schedlock_cv_wait(rucv);
221 }
222 (*wp)--;
223 }
224
225 int
226 rw_tryenter(krwlock_t *rw, const krw_t op)
227 {
228 UPRW(rw);
229
230 switch (op) {
231 case RW_READER:
232 if (uprw->uprw_owner == NULL && uprw->uprw_wwant == 0) {
233 uprw->uprw_readers++;
234 return 1;
235 }
236 break;
237 case RW_WRITER:
238 if (uprw->uprw_owner == NULL && uprw->uprw_readers == 0) {
239 uprw->uprw_owner = curlwp;
240 return 1;
241 }
242 break;
243 }
244
245 return 0;
246 }
247
248 void
249 rw_exit(krwlock_t *rw)
250 {
251 UPRW(rw);
252
253 if (uprw->uprw_readers > 0) {
254 uprw->uprw_readers--;
255 } else {
256 KASSERT(uprw->uprw_owner == curlwp);
257 uprw->uprw_owner = NULL;
258 }
259
260 if (uprw->uprw_wwant) {
261 rumpuser_cv_signal(uprw->uprw_rucv_writer);
262 } else if (uprw->uprw_rwant) {
263 rumpuser_cv_signal(uprw->uprw_rucv_reader);
264 }
265 }
266
267 int
268 rw_tryupgrade(krwlock_t *rw)
269 {
270 UPRW(rw);
271
272 if (uprw->uprw_readers == 1 && uprw->uprw_owner == NULL) {
273 uprw->uprw_readers = 0;
274 uprw->uprw_owner = curlwp;
275 return 1;
276 } else {
277 return 0;
278 }
279 }
280
281 int
282 rw_write_held(krwlock_t *rw)
283 {
284 UPRW(rw);
285
286 return uprw->uprw_owner == curlwp;
287 }
288
289 int
290 rw_read_held(krwlock_t *rw)
291 {
292 UPRW(rw);
293
294 return uprw->uprw_readers > 0;
295 }
296
297 int
298 rw_lock_held(krwlock_t *rw)
299 {
300 UPRW(rw);
301
302 return uprw->uprw_owner || uprw->uprw_readers;
303 }
304
305
306 /*
307 * Condvars are almost the same as in the MP case except that we
308 * use the scheduler mutex as the pthread interlock instead of the
309 * mutex associated with the condvar.
310 */
311
312 #define RUMPCV(cv) (*(struct rumpuser_cv **)(cv))
313
314 void
315 cv_init(kcondvar_t *cv, const char *msg)
316 {
317
318 CTASSERT(sizeof(kcondvar_t) >= sizeof(void *));
319 checkncpu();
320
321 rumpuser_cv_init((struct rumpuser_cv **)cv);
322 }
323
324 void
325 cv_destroy(kcondvar_t *cv)
326 {
327
328 rumpuser_cv_destroy(RUMPCV(cv));
329 }
330
331 void
332 cv_wait(kcondvar_t *cv, kmutex_t *mtx)
333 {
334 #ifdef DIAGNOSTIC
335 UPMTX(mtx);
336 KASSERT(upm->upm_owner == curlwp);
337
338 if (rump_threads == 0)
339 panic("cv_wait without threads");
340 #endif
341
342 /*
343 * NOTE: we must atomically release the *CPU* here, i.e.
344 * nothing between mutex_exit and entering rumpuser condwait
345 * may preempt us from the virtual CPU.
346 */
347 mutex_exit(mtx);
348 rump_schedlock_cv_wait(RUMPCV(cv));
349 mutex_enter(mtx);
350 }
351
352 int
353 cv_wait_sig(kcondvar_t *cv, kmutex_t *mtx)
354 {
355
356 cv_wait(cv, mtx);
357 return 0;
358 }
359
360 int
361 cv_timedwait(kcondvar_t *cv, kmutex_t *mtx, int ticks)
362 {
363 struct timespec ts, tstick;
364
365 #ifdef DIAGNOSTIC
366 UPMTX(mtx);
367 KASSERT(upm->upm_owner == curlwp);
368 #endif
369
370 /*
371 * XXX: this fetches rump kernel time, but rumpuser_cv_timedwait
372 * uses host time.
373 */
374 nanotime(&ts);
375 tstick.tv_sec = ticks / hz;
376 tstick.tv_nsec = (ticks % hz) * (1000000000/hz);
377 timespecadd(&ts, &tstick, &ts);
378
379 if (ticks == 0) {
380 cv_wait(cv, mtx);
381 return 0;
382 } else {
383 int rv;
384 mutex_exit(mtx);
385 rv = rump_schedlock_cv_timedwait(RUMPCV(cv), &ts);
386 mutex_enter(mtx);
387 if (rv)
388 return EWOULDBLOCK;
389 else
390 return 0;
391 }
392 }
393
394 int
395 cv_timedwait_sig(kcondvar_t *cv, kmutex_t *mtx, int ticks)
396 {
397
398 return cv_timedwait(cv, mtx, ticks);
399 }
400
401 void
402 cv_signal(kcondvar_t *cv)
403 {
404
405 /* CPU == interlock */
406 rumpuser_cv_signal(RUMPCV(cv));
407 }
408
409 void
410 cv_broadcast(kcondvar_t *cv)
411 {
412
413 /* CPU == interlock */
414 rumpuser_cv_broadcast(RUMPCV(cv));
415 }
416
417 bool
418 cv_has_waiters(kcondvar_t *cv)
419 {
420
421 return rumpuser_cv_has_waiters(RUMPCV(cv));
422 }
423
424 /* this is not much of an attempt, but ... */
425 bool
426 cv_is_valid(kcondvar_t *cv)
427 {
428
429 return RUMPCV(cv) != NULL;
430 }
431