1 /* $NetBSD: locks_up.c,v 1.14 2023/11/02 10:31:55 martin 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.14 2023/11/02 10:31:55 martin 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-sys/kern.h> 47 48 #include <rump/rumpuser.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 * In uniprocessor locking we don't need to differentiate 75 * between spin mutexes and adaptive ones. We could 76 * replace mutex_enter() with a NOP for spin mutexes, but 77 * not bothering with that for now. 78 */ 79 80 /* 81 * XXX: pool_cache would be nice, but not easily possible, 82 * as pool cache init wants to call mutex_init() ... 83 */ 84 upm = rump_hypermalloc(sizeof(*upm), 0, true, "mutex_init"); 85 memset(upm, 0, sizeof(*upm)); 86 rumpuser_cv_init(&upm->upm_rucv); 87 memcpy(mtx, &upm, sizeof(void *)); 88 } 89 90 void 91 mutex_destroy(kmutex_t *mtx) 92 { 93 UPMTX(mtx); 94 95 KASSERT(upm->upm_owner == NULL); 96 KASSERT(upm->upm_wanted == 0); 97 rumpuser_cv_destroy(upm->upm_rucv); 98 rump_hyperfree(upm, sizeof(*upm)); 99 } 100 101 void 102 mutex_enter(kmutex_t *mtx) 103 { 104 UPMTX(mtx); 105 106 /* fastpath? */ 107 if (mutex_tryenter(mtx)) 108 return; 109 110 /* 111 * No? bummer, do it the slow and painful way then. 112 */ 113 upm->upm_wanted++; 114 while (!mutex_tryenter(mtx)) { 115 rump_schedlock_cv_wait(upm->upm_rucv); 116 } 117 upm->upm_wanted--; 118 119 KASSERT(upm->upm_wanted >= 0); 120 } 121 122 void 123 mutex_spin_enter(kmutex_t *mtx) 124 { 125 126 mutex_enter(mtx); 127 } 128 129 int 130 mutex_tryenter(kmutex_t *mtx) 131 { 132 UPMTX(mtx); 133 134 if (upm->upm_owner) 135 return 0; 136 137 upm->upm_owner = curlwp; 138 return 1; 139 } 140 141 void 142 mutex_exit(kmutex_t *mtx) 143 { 144 UPMTX(mtx); 145 146 if (upm->upm_wanted) { 147 rumpuser_cv_signal(upm->upm_rucv); /* CPU is our interlock */ 148 } 149 upm->upm_owner = NULL; 150 } 151 152 void 153 mutex_spin_exit(kmutex_t *mtx) 154 { 155 156 mutex_exit(mtx); 157 } 158 159 int 160 mutex_owned(kmutex_t *mtx) 161 { 162 UPMTX(mtx); 163 164 return upm->upm_owner == curlwp; 165 } 166 167 struct uprw { 168 struct lwp *uprw_owner; 169 int uprw_readers; 170 uint16_t uprw_rwant; 171 uint16_t uprw_wwant; 172 struct rumpuser_cv *uprw_rucv_reader; 173 struct rumpuser_cv *uprw_rucv_writer; 174 }; 175 176 #define UPRW(rw) struct uprw *uprw = *(struct uprw **)rw 177 178 /* reader/writer locks */ 179 180 void 181 rw_init(krwlock_t *rw) 182 { 183 struct uprw *uprw; 184 185 CTASSERT(sizeof(krwlock_t) >= sizeof(void *)); 186 checkncpu(); 187 188 uprw = rump_hypermalloc(sizeof(*uprw), 0, true, "rwinit"); 189 memset(uprw, 0, sizeof(*uprw)); 190 rumpuser_cv_init(&uprw->uprw_rucv_reader); 191 rumpuser_cv_init(&uprw->uprw_rucv_writer); 192 memcpy(rw, &uprw, sizeof(void *)); 193 } 194 195 void 196 rw_destroy(krwlock_t *rw) 197 { 198 UPRW(rw); 199 200 rumpuser_cv_destroy(uprw->uprw_rucv_reader); 201 rumpuser_cv_destroy(uprw->uprw_rucv_writer); 202 rump_hyperfree(uprw, sizeof(*uprw)); 203 } 204 205 /* take rwlock. prefer writers over readers (see rw_tryenter and rw_exit) */ 206 void 207 rw_enter(krwlock_t *rw, const krw_t op) 208 { 209 UPRW(rw); 210 struct rumpuser_cv *rucv; 211 uint16_t *wp; 212 213 if (rw_tryenter(rw, op)) 214 return; 215 216 /* lagpath */ 217 if (op == RW_READER) { 218 rucv = uprw->uprw_rucv_reader; 219 wp = &uprw->uprw_rwant; 220 } else { 221 rucv = uprw->uprw_rucv_writer; 222 wp = &uprw->uprw_wwant; 223 } 224 225 (*wp)++; 226 while (!rw_tryenter(rw, op)) { 227 rump_schedlock_cv_wait(rucv); 228 } 229 (*wp)--; 230 } 231 232 int 233 rw_tryenter(krwlock_t *rw, const krw_t op) 234 { 235 UPRW(rw); 236 237 switch (op) { 238 case RW_READER: 239 if (uprw->uprw_owner == NULL && uprw->uprw_wwant == 0) { 240 uprw->uprw_readers++; 241 return 1; 242 } 243 break; 244 case RW_WRITER: 245 if (uprw->uprw_owner == NULL && uprw->uprw_readers == 0) { 246 uprw->uprw_owner = curlwp; 247 return 1; 248 } 249 break; 250 } 251 252 return 0; 253 } 254 255 void 256 rw_exit(krwlock_t *rw) 257 { 258 UPRW(rw); 259 260 if (uprw->uprw_readers > 0) { 261 uprw->uprw_readers--; 262 } else { 263 KASSERT(uprw->uprw_owner == curlwp); 264 uprw->uprw_owner = NULL; 265 } 266 267 if (uprw->uprw_wwant) { 268 rumpuser_cv_signal(uprw->uprw_rucv_writer); 269 } else if (uprw->uprw_rwant) { 270 rumpuser_cv_signal(uprw->uprw_rucv_reader); 271 } 272 } 273 274 int 275 rw_tryupgrade(krwlock_t *rw) 276 { 277 UPRW(rw); 278 279 if (uprw->uprw_readers == 1 && uprw->uprw_owner == NULL) { 280 uprw->uprw_readers = 0; 281 uprw->uprw_owner = curlwp; 282 return 1; 283 } else { 284 return 0; 285 } 286 } 287 288 int 289 rw_write_held(krwlock_t *rw) 290 { 291 UPRW(rw); 292 293 return uprw->uprw_owner == curlwp; 294 } 295 296 int 297 rw_read_held(krwlock_t *rw) 298 { 299 UPRW(rw); 300 301 return uprw->uprw_readers > 0; 302 } 303 304 int 305 rw_lock_held(krwlock_t *rw) 306 { 307 UPRW(rw); 308 309 return uprw->uprw_owner || uprw->uprw_readers; 310 } 311 312 krw_t 313 rw_lock_op(krwlock_t *rw) 314 { 315 316 return rw_write_held(rw) ? RW_WRITER : RW_READER; 317 } 318 319 /* 320 * Condvars are almost the same as in the MP case except that we 321 * use the scheduler mutex as the pthread interlock instead of the 322 * mutex associated with the condvar. 323 */ 324 325 #define RUMPCV(cv) (*(struct rumpuser_cv **)(cv)) 326 327 void 328 cv_init(kcondvar_t *cv, const char *msg) 329 { 330 331 CTASSERT(sizeof(kcondvar_t) >= sizeof(void *)); 332 checkncpu(); 333 334 rumpuser_cv_init((struct rumpuser_cv **)cv); 335 } 336 337 void 338 cv_destroy(kcondvar_t *cv) 339 { 340 341 rumpuser_cv_destroy(RUMPCV(cv)); 342 } 343 344 void 345 cv_wait(kcondvar_t *cv, kmutex_t *mtx) 346 { 347 #ifdef DIAGNOSTIC 348 UPMTX(mtx); 349 KASSERT(upm->upm_owner == curlwp); 350 351 if (rump_threads == 0) 352 panic("cv_wait without threads"); 353 #endif 354 355 /* 356 * NOTE: we must atomically release the *CPU* here, i.e. 357 * nothing between mutex_exit and entering rumpuser condwait 358 * may preempt us from the virtual CPU. 359 */ 360 mutex_exit(mtx); 361 rump_schedlock_cv_wait(RUMPCV(cv)); 362 mutex_enter(mtx); 363 } 364 365 int 366 cv_wait_sig(kcondvar_t *cv, kmutex_t *mtx) 367 { 368 369 cv_wait(cv, mtx); 370 return 0; 371 } 372 373 int 374 cv_timedwait(kcondvar_t *cv, kmutex_t *mtx, int ticks) 375 { 376 struct timespec ts; 377 378 #ifdef DIAGNOSTIC 379 UPMTX(mtx); 380 KASSERT(upm->upm_owner == curlwp); 381 #endif 382 383 ts.tv_sec = ticks / hz; 384 ts.tv_nsec = (ticks % hz) * (1000000000/hz); 385 386 if (ticks == 0) { 387 cv_wait(cv, mtx); 388 return 0; 389 } else { 390 int rv; 391 mutex_exit(mtx); 392 rv = rump_schedlock_cv_timedwait(RUMPCV(cv), &ts); 393 mutex_enter(mtx); 394 if (rv) 395 return EWOULDBLOCK; 396 else 397 return 0; 398 } 399 } 400 401 int 402 cv_timedwait_sig(kcondvar_t *cv, kmutex_t *mtx, int ticks) 403 { 404 405 return cv_timedwait(cv, mtx, ticks); 406 } 407 408 void 409 cv_signal(kcondvar_t *cv) 410 { 411 412 /* CPU == interlock */ 413 rumpuser_cv_signal(RUMPCV(cv)); 414 } 415 416 void 417 cv_broadcast(kcondvar_t *cv) 418 { 419 420 /* CPU == interlock */ 421 rumpuser_cv_broadcast(RUMPCV(cv)); 422 } 423 424 bool 425 cv_has_waiters(kcondvar_t *cv) 426 { 427 int n; 428 429 rumpuser_cv_has_waiters(RUMPCV(cv), &n); 430 431 return n > 0; 432 } 433 434 /* this is not much of an attempt, but ... */ 435 bool 436 cv_is_valid(kcondvar_t *cv) 437 { 438 439 return RUMPCV(cv) != NULL; 440 } 441