locks_up.c revision 1.6 1 /* $NetBSD: locks_up.c,v 1.6 2012/04/28 18:04:02 stacktic 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.6 2012/04/28 18:04:02 stacktic 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 = rump_hypermalloc(sizeof(*upm), 0, true, "mutex_init");
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 rump_hyperfree(upm, sizeof(*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 lwp *
161 mutex_owner(kmutex_t *mtx)
162 {
163 UPMTX(mtx);
164
165 return upm->upm_owner;
166 }
167
168 struct uprw {
169 struct lwp *uprw_owner;
170 int uprw_readers;
171 uint16_t uprw_rwant;
172 uint16_t uprw_wwant;
173 struct rumpuser_cv *uprw_rucv_reader;
174 struct rumpuser_cv *uprw_rucv_writer;
175 };
176
177 #define UPRW(rw) struct uprw *uprw = *(struct uprw **)rw
178
179 /* reader/writer locks */
180
181 void
182 rw_init(krwlock_t *rw)
183 {
184 struct uprw *uprw;
185
186 CTASSERT(sizeof(krwlock_t) >= sizeof(void *));
187 checkncpu();
188
189 uprw = rump_hypermalloc(sizeof(*uprw), 0, true, "rwinit");
190 memset(uprw, 0, sizeof(*uprw));
191 rumpuser_cv_init(&uprw->uprw_rucv_reader);
192 rumpuser_cv_init(&uprw->uprw_rucv_writer);
193 memcpy(rw, &uprw, sizeof(void *));
194 }
195
196 void
197 rw_destroy(krwlock_t *rw)
198 {
199 UPRW(rw);
200
201 rumpuser_cv_destroy(uprw->uprw_rucv_reader);
202 rumpuser_cv_destroy(uprw->uprw_rucv_writer);
203 rump_hyperfree(uprw, sizeof(*uprw));
204 }
205
206 /* take rwlock. prefer writers over readers (see rw_tryenter and rw_exit) */
207 void
208 rw_enter(krwlock_t *rw, const krw_t op)
209 {
210 UPRW(rw);
211 struct rumpuser_cv *rucv;
212 uint16_t *wp;
213
214 if (rw_tryenter(rw, op))
215 return;
216
217 /* lagpath */
218 if (op == RW_READER) {
219 rucv = uprw->uprw_rucv_reader;
220 wp = &uprw->uprw_rwant;
221 } else {
222 rucv = uprw->uprw_rucv_writer;
223 wp = &uprw->uprw_wwant;
224 }
225
226 (*wp)++;
227 while (!rw_tryenter(rw, op)) {
228 rump_schedlock_cv_wait(rucv);
229 }
230 (*wp)--;
231 }
232
233 int
234 rw_tryenter(krwlock_t *rw, const krw_t op)
235 {
236 UPRW(rw);
237
238 switch (op) {
239 case RW_READER:
240 if (uprw->uprw_owner == NULL && uprw->uprw_wwant == 0) {
241 uprw->uprw_readers++;
242 return 1;
243 }
244 break;
245 case RW_WRITER:
246 if (uprw->uprw_owner == NULL && uprw->uprw_readers == 0) {
247 uprw->uprw_owner = curlwp;
248 return 1;
249 }
250 break;
251 }
252
253 return 0;
254 }
255
256 void
257 rw_exit(krwlock_t *rw)
258 {
259 UPRW(rw);
260
261 if (uprw->uprw_readers > 0) {
262 uprw->uprw_readers--;
263 } else {
264 KASSERT(uprw->uprw_owner == curlwp);
265 uprw->uprw_owner = NULL;
266 }
267
268 if (uprw->uprw_wwant) {
269 rumpuser_cv_signal(uprw->uprw_rucv_writer);
270 } else if (uprw->uprw_rwant) {
271 rumpuser_cv_signal(uprw->uprw_rucv_reader);
272 }
273 }
274
275 int
276 rw_tryupgrade(krwlock_t *rw)
277 {
278 UPRW(rw);
279
280 if (uprw->uprw_readers == 1 && uprw->uprw_owner == NULL) {
281 uprw->uprw_readers = 0;
282 uprw->uprw_owner = curlwp;
283 return 1;
284 } else {
285 return 0;
286 }
287 }
288
289 int
290 rw_write_held(krwlock_t *rw)
291 {
292 UPRW(rw);
293
294 return uprw->uprw_owner == curlwp;
295 }
296
297 int
298 rw_read_held(krwlock_t *rw)
299 {
300 UPRW(rw);
301
302 return uprw->uprw_readers > 0;
303 }
304
305 int
306 rw_lock_held(krwlock_t *rw)
307 {
308 UPRW(rw);
309
310 return uprw->uprw_owner || uprw->uprw_readers;
311 }
312
313
314 /*
315 * Condvars are almost the same as in the MP case except that we
316 * use the scheduler mutex as the pthread interlock instead of the
317 * mutex associated with the condvar.
318 */
319
320 #define RUMPCV(cv) (*(struct rumpuser_cv **)(cv))
321
322 void
323 cv_init(kcondvar_t *cv, const char *msg)
324 {
325
326 CTASSERT(sizeof(kcondvar_t) >= sizeof(void *));
327 checkncpu();
328
329 rumpuser_cv_init((struct rumpuser_cv **)cv);
330 }
331
332 void
333 cv_destroy(kcondvar_t *cv)
334 {
335
336 rumpuser_cv_destroy(RUMPCV(cv));
337 }
338
339 void
340 cv_wait(kcondvar_t *cv, kmutex_t *mtx)
341 {
342 #ifdef DIAGNOSTIC
343 UPMTX(mtx);
344 KASSERT(upm->upm_owner == curlwp);
345
346 if (rump_threads == 0)
347 panic("cv_wait without threads");
348 #endif
349
350 /*
351 * NOTE: we must atomically release the *CPU* here, i.e.
352 * nothing between mutex_exit and entering rumpuser condwait
353 * may preempt us from the virtual CPU.
354 */
355 mutex_exit(mtx);
356 rump_schedlock_cv_wait(RUMPCV(cv));
357 mutex_enter(mtx);
358 }
359
360 int
361 cv_wait_sig(kcondvar_t *cv, kmutex_t *mtx)
362 {
363
364 cv_wait(cv, mtx);
365 return 0;
366 }
367
368 int
369 cv_timedwait(kcondvar_t *cv, kmutex_t *mtx, int ticks)
370 {
371 struct timespec ts, tstick;
372
373 #ifdef DIAGNOSTIC
374 UPMTX(mtx);
375 KASSERT(upm->upm_owner == curlwp);
376 #endif
377
378 /*
379 * XXX: this fetches rump kernel time, but rumpuser_cv_timedwait
380 * uses host time.
381 */
382 nanotime(&ts);
383 tstick.tv_sec = ticks / hz;
384 tstick.tv_nsec = (ticks % hz) * (1000000000/hz);
385 timespecadd(&ts, &tstick, &ts);
386
387 if (ticks == 0) {
388 cv_wait(cv, mtx);
389 return 0;
390 } else {
391 int rv;
392 mutex_exit(mtx);
393 rv = rump_schedlock_cv_timedwait(RUMPCV(cv), &ts);
394 mutex_enter(mtx);
395 if (rv)
396 return EWOULDBLOCK;
397 else
398 return 0;
399 }
400 }
401
402 int
403 cv_timedwait_sig(kcondvar_t *cv, kmutex_t *mtx, int ticks)
404 {
405
406 return cv_timedwait(cv, mtx, ticks);
407 }
408
409 void
410 cv_signal(kcondvar_t *cv)
411 {
412
413 /* CPU == interlock */
414 rumpuser_cv_signal(RUMPCV(cv));
415 }
416
417 void
418 cv_broadcast(kcondvar_t *cv)
419 {
420
421 /* CPU == interlock */
422 rumpuser_cv_broadcast(RUMPCV(cv));
423 }
424
425 bool
426 cv_has_waiters(kcondvar_t *cv)
427 {
428
429 return rumpuser_cv_has_waiters(RUMPCV(cv));
430 }
431
432 /* this is not much of an attempt, but ... */
433 bool
434 cv_is_valid(kcondvar_t *cv)
435 {
436
437 return RUMPCV(cv) != NULL;
438 }
439