1 /* $NetBSD: ww_mutex.h,v 1.4 2014/07/26 21:36:40 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2014 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Taylor R. Campbell. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * Notes on porting: 34 * 35 * - We require a context for all locks, so ww_mutex_lock(m, NULL) is 36 * not kosher. Locking without a context is too painful to 37 * contemplate. 38 * 39 * - We require passing the context to trylock and unlock. Unlocking 40 * the wrong lock is too serious an error to pass up detection. 41 */ 42 43 #ifndef _ASM_WW_MUTEX_H_ 44 #define _ASM_WW_MUTEX_H_ 45 46 #include <sys/rbtree.h> 47 48 #include <linux/mutex.h> 49 50 struct ww_class { 51 volatile uint64_t wwc_ticket; 52 }; 53 54 #define DEFINE_WW_CLASS(CLASS) \ 55 struct ww_class CLASS = { \ 56 .wwc_ticket = 0, \ 57 } 58 59 struct ww_acquire_ctx { 60 struct ww_class *wwx_class __diagused; 61 uint64_t wwx_ticket; 62 unsigned wwx_acquired; 63 bool wwx_acquire_done; 64 struct rb_node wwx_rb_node; 65 }; 66 67 static inline int 68 ww_acquire_ctx_compare(void *cookie __unused, const void *va, const void *vb) 69 { 70 const struct ww_acquire_ctx *const ctx_a = va; 71 const struct ww_acquire_ctx *const ctx_b = vb; 72 73 if (ctx_a->wwx_ticket < ctx_b->wwx_ticket) 74 return -1; 75 if (ctx_a->wwx_ticket > ctx_b->wwx_ticket) 76 return -1; 77 return 0; 78 } 79 80 static inline int 81 ww_acquire_ctx_compare_key(void *cookie __unused, const void *vn, 82 const void *vk) 83 { 84 const struct ww_acquire_ctx *const ctx = vn; 85 const uint64_t *const ticketp = vk, ticket = *ticketp; 86 87 if (ctx->wwx_ticket < ticket) 88 return -1; 89 if (ctx->wwx_ticket > ticket) 90 return -1; 91 return 0; 92 } 93 94 static const rb_tree_ops_t ww_acquire_ctx_rb_ops = { 95 .rbto_compare_nodes = &ww_acquire_ctx_compare, 96 .rbto_compare_key = &ww_acquire_ctx_compare_key, 97 .rbto_node_offset = offsetof(struct ww_acquire_ctx, wwx_rb_node), 98 .rbto_context = NULL, 99 }; 100 101 static inline void 102 ww_acquire_init(struct ww_acquire_ctx *ctx, struct ww_class *class) 103 { 104 105 ctx->wwx_class = class; 106 ctx->wwx_ticket = atomic_inc_64_nv(&class->wwc_ticket); 107 ctx->wwx_acquired = 0; 108 ctx->wwx_acquire_done = false; 109 } 110 111 static inline void 112 ww_acquire_done(struct ww_acquire_ctx *ctx) 113 { 114 115 ctx->wwx_acquire_done = true; 116 } 117 118 static inline void 119 ww_acquire_fini(struct ww_acquire_ctx *ctx) 120 { 121 122 KASSERT(ctx->wwx_acquired == 0); 123 ctx->wwx_acquired = ~0U; /* Fail if called again. */ 124 } 125 126 struct ww_mutex { 127 kmutex_t wwm_lock; 128 enum ww_mutex_state { 129 WW_UNLOCKED, 130 WW_OWNED, 131 WW_CTX, 132 WW_WANTOWN, 133 } wwm_state; 134 union { 135 struct lwp *owner; 136 struct ww_acquire_ctx *ctx; 137 } wwm_u; 138 struct ww_class *wwm_class; 139 struct rb_tree wwm_waiters; 140 kcondvar_t wwm_cv; 141 }; 142 143 static inline void 144 ww_mutex_init(struct ww_mutex *mutex, struct ww_class *class) 145 { 146 147 /* 148 * XXX Apparently Linux takes these with spin locks held. That 149 * strikes me as a bad idea, but so it is... 150 */ 151 mutex_init(&mutex->wwm_lock, MUTEX_DEFAULT, IPL_VM); 152 mutex->wwm_state = WW_UNLOCKED; 153 mutex->wwm_class = class; 154 rb_tree_init(&mutex->wwm_waiters, &ww_acquire_ctx_rb_ops); 155 cv_init(&mutex->wwm_cv, "linuxwwm"); 156 } 157 158 static inline void 159 ww_mutex_destroy(struct ww_mutex *mutex) 160 { 161 162 cv_destroy(&mutex->wwm_cv); 163 #if 0 164 rb_tree_destroy(&mutex->wwm_waiters, &ww_acquire_ctx_rb_ops); 165 #endif 166 KASSERT(mutex->wwm_state == WW_UNLOCKED); 167 mutex_destroy(&mutex->wwm_lock); 168 } 169 170 /* 171 * XXX WARNING: This returns true if it is locked by ANYONE. Does not 172 * mean `Do I hold this lock?' (answering which really requires an 173 * acquire context). 174 */ 175 static inline bool 176 ww_mutex_is_locked(struct ww_mutex *mutex) 177 { 178 int locked; 179 180 mutex_enter(&mutex->wwm_lock); 181 switch (mutex->wwm_state) { 182 case WW_UNLOCKED: 183 locked = false; 184 break; 185 case WW_OWNED: 186 case WW_CTX: 187 case WW_WANTOWN: 188 locked = true; 189 break; 190 default: 191 panic("wait/wound mutex %p in bad state: %d", mutex, 192 (int)mutex->wwm_state); 193 } 194 mutex_exit(&mutex->wwm_lock); 195 196 return locked; 197 } 198 199 static inline void 200 ww_mutex_state_wait(struct ww_mutex *mutex, enum ww_mutex_state state) 201 { 202 203 KASSERT(mutex->wwm_state == state); 204 do cv_wait(&mutex->wwm_cv, &mutex->wwm_lock); 205 while (mutex->wwm_state == state); 206 } 207 208 static inline int 209 ww_mutex_state_wait_sig(struct ww_mutex *mutex, enum ww_mutex_state state) 210 { 211 int ret; 212 213 KASSERT(mutex->wwm_state == state); 214 do { 215 /* XXX errno NetBSD->Linux */ 216 ret = -cv_wait_sig(&mutex->wwm_cv, &mutex->wwm_lock); 217 if (ret) 218 break; 219 } while (mutex->wwm_state == state); 220 221 return ret; 222 } 223 224 static inline void 225 ww_mutex_lock_wait(struct ww_mutex *mutex, struct ww_acquire_ctx *ctx) 226 { 227 struct ww_acquire_ctx *collision __diagused; 228 229 KASSERT(mutex_owned(&mutex->wwm_lock)); 230 231 KASSERT(mutex->wwm_state == WW_CTX); 232 KASSERTMSG((ctx->wwx_class == mutex->wwm_u.ctx->wwx_class), 233 "ww mutex class mismatch: %p != %p", 234 ctx->wwx_class, mutex->wwm_u.ctx->wwx_class); 235 KASSERTMSG((mutex->wwm_u.ctx->wwx_ticket != ctx->wwx_ticket), 236 "ticket number reused: %"PRId64" (%p) %"PRId64" (%p)", 237 ctx->wwx_ticket, ctx, 238 mutex->wwm_u.ctx->wwx_ticket, mutex->wwm_u.ctx); 239 240 collision = rb_tree_insert_node(&mutex->wwm_waiters, ctx); 241 KASSERTMSG((collision == ctx), 242 "ticket number reused: %"PRId64" (%p) %"PRId64" (%p)", 243 ctx->wwx_ticket, ctx, collision->wwx_ticket, collision); 244 245 do cv_wait(&mutex->wwm_cv, &mutex->wwm_lock); 246 while (!((mutex->wwm_state == WW_CTX) && (mutex->wwm_u.ctx == ctx))); 247 248 rb_tree_remove_node(&mutex->wwm_waiters, ctx); 249 } 250 251 static inline int 252 ww_mutex_lock_wait_sig(struct ww_mutex *mutex, struct ww_acquire_ctx *ctx) 253 { 254 struct ww_acquire_ctx *collision __diagused; 255 int ret; 256 257 KASSERT(mutex_owned(&mutex->wwm_lock)); 258 259 KASSERT(mutex->wwm_state == WW_CTX); 260 KASSERTMSG((ctx->wwx_class == mutex->wwm_u.ctx->wwx_class), 261 "ww mutex class mismatch: %p != %p", 262 ctx->wwx_class, mutex->wwm_u.ctx->wwx_class); 263 KASSERTMSG((mutex->wwm_u.ctx->wwx_ticket != ctx->wwx_ticket), 264 "ticket number reused: %"PRId64" (%p) %"PRId64" (%p)", 265 ctx->wwx_ticket, ctx, 266 mutex->wwm_u.ctx->wwx_ticket, mutex->wwm_u.ctx); 267 268 collision = rb_tree_insert_node(&mutex->wwm_waiters, ctx); 269 KASSERTMSG((collision == ctx), 270 "ticket number reused: %"PRId64" (%p) %"PRId64" (%p)", 271 ctx->wwx_ticket, ctx, collision->wwx_ticket, collision); 272 273 do { 274 /* XXX errno NetBSD->Linux */ 275 ret = -cv_wait_sig(&mutex->wwm_cv, &mutex->wwm_lock); 276 if (ret) 277 goto out; 278 } while (!((mutex->wwm_state == WW_CTX) && (mutex->wwm_u.ctx == ctx))); 279 280 out: rb_tree_remove_node(&mutex->wwm_waiters, ctx); 281 return ret; 282 } 283 284 static inline void 285 ww_mutex_lock_noctx(struct ww_mutex *mutex) 286 { 287 288 mutex_enter(&mutex->wwm_lock); 289 retry: switch (mutex->wwm_state) { 290 case WW_UNLOCKED: 291 mutex->wwm_state = WW_OWNED; 292 mutex->wwm_u.owner = curlwp; 293 break; 294 case WW_OWNED: 295 KASSERTMSG((mutex->wwm_u.owner != curlwp), 296 "locking against myself: %p", curlwp); 297 ww_mutex_state_wait(mutex, WW_OWNED); 298 goto retry; 299 case WW_CTX: 300 KASSERT(mutex->wwm_u.ctx != NULL); 301 mutex->wwm_state = WW_WANTOWN; 302 case WW_WANTOWN: 303 ww_mutex_state_wait(mutex, WW_WANTOWN); 304 goto retry; 305 default: 306 panic("wait/wound mutex %p in bad state: %d", 307 mutex, (int)mutex->wwm_state); 308 } 309 KASSERT(mutex->wwm_state == WW_OWNED); 310 KASSERT(mutex->wwm_u.owner == curlwp); 311 mutex_exit(&mutex->wwm_lock); 312 } 313 314 static inline int 315 ww_mutex_lock_noctx_sig(struct ww_mutex *mutex) 316 { 317 int ret; 318 319 mutex_enter(&mutex->wwm_lock); 320 retry: switch (mutex->wwm_state) { 321 case WW_UNLOCKED: 322 mutex->wwm_state = WW_OWNED; 323 mutex->wwm_u.owner = curlwp; 324 break; 325 case WW_OWNED: 326 KASSERTMSG((mutex->wwm_u.owner != curlwp), 327 "locking against myself: %p", curlwp); 328 ret = ww_mutex_state_wait_sig(mutex, WW_OWNED); 329 if (ret) 330 goto out; 331 goto retry; 332 case WW_CTX: 333 KASSERT(mutex->wwm_u.ctx != NULL); 334 mutex->wwm_state = WW_WANTOWN; 335 case WW_WANTOWN: 336 ret = ww_mutex_state_wait_sig(mutex, WW_WANTOWN); 337 if (ret) 338 goto out; 339 goto retry; 340 default: 341 panic("wait/wound mutex %p in bad state: %d", 342 mutex, (int)mutex->wwm_state); 343 } 344 KASSERT(mutex->wwm_state == WW_OWNED); 345 KASSERT(mutex->wwm_u.owner == curlwp); 346 ret = 0; 347 out: mutex_exit(&mutex->wwm_lock); 348 return ret; 349 } 350 351 static inline int 352 ww_mutex_lock(struct ww_mutex *mutex, struct ww_acquire_ctx *ctx) 353 { 354 355 ASSERT_SLEEPABLE(); 356 357 if (ctx == NULL) { 358 ww_mutex_lock_noctx(mutex); 359 return 0; 360 } 361 362 KASSERT(!ctx->wwx_acquire_done); 363 364 mutex_enter(&mutex->wwm_lock); 365 retry: switch (mutex->wwm_state) { 366 case WW_UNLOCKED: 367 mutex->wwm_state = WW_CTX; 368 mutex->wwm_u.ctx = ctx; 369 goto locked; 370 case WW_OWNED: 371 KASSERTMSG((mutex->wwm_u.owner != curlwp), 372 "locking against myself: %p", curlwp); 373 ww_mutex_state_wait(mutex, WW_OWNED); 374 goto retry; 375 case WW_CTX: 376 break; 377 case WW_WANTOWN: 378 ww_mutex_state_wait(mutex, WW_WANTOWN); 379 goto retry; 380 default: 381 panic("wait/wound mutex %p in bad state: %d", 382 mutex, (int)mutex->wwm_state); 383 } 384 KASSERT(mutex->wwm_state == WW_CTX); 385 KASSERT(mutex->wwm_u.ctx != NULL); 386 if (mutex->wwm_u.ctx == ctx) { 387 /* 388 * We already own it. Yes, this can happen correctly 389 * for objects whose locking order is determined by 390 * userland. 391 */ 392 mutex_exit(&mutex->wwm_lock); 393 return -EALREADY; 394 } else if (mutex->wwm_u.ctx->wwx_ticket < ctx->wwx_ticket) { 395 /* 396 * Owned by a higher-priority party. Tell the caller 397 * to unlock everything and start over. 398 */ 399 KASSERTMSG((ctx->wwx_class == mutex->wwm_u.ctx->wwx_class), 400 "ww mutex class mismatch: %p != %p", 401 ctx->wwx_class, mutex->wwm_u.ctx->wwx_class); 402 mutex_exit(&mutex->wwm_lock); 403 return -EDEADLK; 404 } else { 405 /* 406 * Owned by a lower-priority party. Ask that party to 407 * wake us when it is done or it realizes it needs to 408 * back off. 409 */ 410 ww_mutex_lock_wait(mutex, ctx); 411 } 412 locked: ctx->wwx_acquired++; 413 KASSERT(mutex->wwm_state == WW_CTX); 414 KASSERT(mutex->wwm_u.ctx == ctx); 415 mutex_exit(&mutex->wwm_lock); 416 return 0; 417 } 418 419 static inline int 420 ww_mutex_lock_interruptible(struct ww_mutex *mutex, struct ww_acquire_ctx *ctx) 421 { 422 int ret; 423 424 ASSERT_SLEEPABLE(); 425 426 if (ctx == NULL) 427 return ww_mutex_lock_noctx_sig(mutex); 428 429 KASSERT(!ctx->wwx_acquire_done); 430 431 mutex_enter(&mutex->wwm_lock); 432 retry: switch (mutex->wwm_state) { 433 case WW_UNLOCKED: 434 mutex->wwm_state = WW_CTX; 435 mutex->wwm_u.ctx = ctx; 436 goto locked; 437 case WW_OWNED: 438 KASSERTMSG((mutex->wwm_u.owner != curlwp), 439 "locking against myself: %p", curlwp); 440 ret = ww_mutex_state_wait_sig(mutex, WW_OWNED); 441 if (ret) 442 goto out; 443 goto retry; 444 case WW_CTX: 445 break; 446 case WW_WANTOWN: 447 ret = ww_mutex_state_wait_sig(mutex, WW_WANTOWN); 448 if (ret) 449 goto out; 450 goto retry; 451 default: 452 panic("wait/wound mutex %p in bad state: %d", 453 mutex, (int)mutex->wwm_state); 454 } 455 KASSERT(mutex->wwm_state == WW_CTX); 456 KASSERT(mutex->wwm_u.ctx != NULL); 457 if (mutex->wwm_u.ctx == ctx) { 458 /* 459 * We already own it. Yes, this can happen correctly 460 * for objects whose locking order is determined by 461 * userland. 462 */ 463 mutex_exit(&mutex->wwm_lock); 464 return -EALREADY; 465 } else if (mutex->wwm_u.ctx->wwx_ticket < ctx->wwx_ticket) { 466 /* 467 * Owned by a higher-priority party. Tell the caller 468 * to unlock everything and start over. 469 */ 470 KASSERTMSG((ctx->wwx_class == mutex->wwm_u.ctx->wwx_class), 471 "ww mutex class mismatch: %p != %p", 472 ctx->wwx_class, mutex->wwm_u.ctx->wwx_class); 473 mutex_exit(&mutex->wwm_lock); 474 return -EDEADLK; 475 } else { 476 /* 477 * Owned by a lower-priority party. Ask that party to 478 * wake us when it is done or it realizes it needs to 479 * back off. 480 */ 481 ret = ww_mutex_lock_wait_sig(mutex, ctx); 482 if (ret) 483 goto out; 484 } 485 locked: KASSERT(mutex->wwm_state == WW_CTX); 486 KASSERT(mutex->wwm_u.ctx == ctx); 487 ctx->wwx_acquired++; 488 ret = 0; 489 out: mutex_exit(&mutex->wwm_lock); 490 return ret; 491 } 492 493 static inline void 494 ww_mutex_lock_slow(struct ww_mutex *mutex, struct ww_acquire_ctx *ctx) 495 { 496 497 ASSERT_SLEEPABLE(); 498 499 if (ctx == NULL) { 500 ww_mutex_lock_noctx(mutex); 501 return; 502 } 503 504 KASSERT(!ctx->wwx_acquire_done); 505 506 mutex_enter(&mutex->wwm_lock); 507 retry: switch (mutex->wwm_state) { 508 case WW_UNLOCKED: 509 mutex->wwm_state = WW_CTX; 510 mutex->wwm_u.ctx = ctx; 511 goto locked; 512 case WW_OWNED: 513 KASSERTMSG((mutex->wwm_u.owner != curlwp), 514 "locking against myself: %p", curlwp); 515 ww_mutex_state_wait(mutex, WW_OWNED); 516 goto retry; 517 case WW_CTX: 518 break; 519 case WW_WANTOWN: 520 ww_mutex_state_wait(mutex, WW_WANTOWN); 521 goto retry; 522 default: 523 panic("wait/wound mutex %p in bad state: %d", 524 mutex, (int)mutex->wwm_state); 525 } 526 KASSERT(mutex->wwm_state == WW_CTX); 527 KASSERT(mutex->wwm_u.ctx != NULL); 528 /* 529 * Owned by another party, of any priority. Ask that party to 530 * wake us when it's done. 531 */ 532 ww_mutex_lock_wait(mutex, ctx); 533 locked: KASSERT(mutex->wwm_state == WW_CTX); 534 KASSERT(mutex->wwm_u.ctx == ctx); 535 ctx->wwx_acquired++; 536 mutex_exit(&mutex->wwm_lock); 537 } 538 539 static inline int 540 ww_mutex_lock_slow_interruptible(struct ww_mutex *mutex, 541 struct ww_acquire_ctx *ctx) 542 { 543 int ret; 544 545 ASSERT_SLEEPABLE(); 546 547 if (ctx == NULL) 548 return ww_mutex_lock_noctx_sig(mutex); 549 550 KASSERT(!ctx->wwx_acquire_done); 551 552 mutex_enter(&mutex->wwm_lock); 553 retry: switch (mutex->wwm_state) { 554 case WW_UNLOCKED: 555 mutex->wwm_state = WW_CTX; 556 mutex->wwm_u.ctx = ctx; 557 goto locked; 558 case WW_OWNED: 559 KASSERTMSG((mutex->wwm_u.owner != curlwp), 560 "locking against myself: %p", curlwp); 561 ret = ww_mutex_state_wait_sig(mutex, WW_OWNED); 562 if (ret) 563 goto out; 564 goto retry; 565 case WW_CTX: 566 break; 567 case WW_WANTOWN: 568 ret = ww_mutex_state_wait_sig(mutex, WW_WANTOWN); 569 if (ret) 570 goto out; 571 goto retry; 572 default: 573 panic("wait/wound mutex %p in bad state: %d", 574 mutex, (int)mutex->wwm_state); 575 } 576 KASSERT(mutex->wwm_state == WW_CTX); 577 KASSERT(mutex->wwm_u.ctx != NULL); 578 /* 579 * Owned by another party, of any priority. Ask that party to 580 * wake us when it's done. 581 */ 582 ret = ww_mutex_lock_wait_sig(mutex, ctx); 583 if (ret) 584 goto out; 585 locked: KASSERT(mutex->wwm_state == WW_CTX); 586 KASSERT(mutex->wwm_u.ctx == ctx); 587 ctx->wwx_acquired++; 588 ret = 0; 589 out: mutex_exit(&mutex->wwm_lock); 590 return ret; 591 } 592 593 static inline int 594 ww_mutex_trylock(struct ww_mutex *mutex) 595 { 596 int ret; 597 598 mutex_enter(&mutex->wwm_lock); 599 if (mutex->wwm_state == WW_UNLOCKED) { 600 mutex->wwm_state = WW_OWNED; 601 mutex->wwm_u.owner = curlwp; 602 ret = 1; 603 } else { 604 ret = 0; 605 } 606 mutex_exit(&mutex->wwm_lock); 607 608 return ret; 609 } 610 611 static inline void 612 ww_mutex_unlock(struct ww_mutex *mutex) 613 { 614 struct ww_acquire_ctx *ctx; 615 616 mutex_enter(&mutex->wwm_lock); 617 KASSERT(mutex->wwm_state != WW_UNLOCKED); 618 switch (mutex->wwm_state) { 619 case WW_UNLOCKED: 620 panic("unlocking unlocked wait/wound mutex: %p", mutex); 621 case WW_OWNED: 622 /* Let the context lockers fight over it. */ 623 mutex->wwm_u.owner = NULL; 624 mutex->wwm_state = WW_UNLOCKED; 625 break; 626 case WW_CTX: 627 KASSERT(mutex->wwm_u.ctx != NULL); 628 mutex->wwm_u.ctx->wwx_acquired--; 629 mutex->wwm_u.ctx = NULL; 630 /* 631 * If there are any waiters with contexts, grant the 632 * lock to the highest-priority one. Otherwise, just 633 * unlock it. 634 */ 635 if ((ctx = RB_TREE_MIN(&mutex->wwm_waiters)) != NULL) { 636 mutex->wwm_state = WW_CTX; 637 mutex->wwm_u.ctx = ctx; 638 } else { 639 mutex->wwm_state = WW_UNLOCKED; 640 } 641 break; 642 case WW_WANTOWN: 643 /* Let the non-context lockers fight over it. */ 644 mutex->wwm_state = WW_UNLOCKED; 645 break; 646 } 647 cv_broadcast(&mutex->wwm_cv); 648 mutex_exit(&mutex->wwm_lock); 649 } 650 651 #endif /* _ASM_WW_MUTEX_H_ */ 652