1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * DR control module for LDoms 29 */ 30 31 #include <sys/sysmacros.h> 32 #include <sys/modctl.h> 33 #include <sys/conf.h> 34 #include <sys/ddi.h> 35 #include <sys/sunddi.h> 36 #include <sys/ddi_impldefs.h> 37 #include <sys/stat.h> 38 #include <sys/door.h> 39 #include <sys/open.h> 40 #include <sys/note.h> 41 #include <sys/ldoms.h> 42 #include <sys/dr_util.h> 43 #include <sys/drctl.h> 44 #include <sys/drctl_impl.h> 45 46 47 static int drctl_attach(dev_info_t *, ddi_attach_cmd_t); 48 static int drctl_detach(dev_info_t *, ddi_detach_cmd_t); 49 static int drctl_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); 50 51 static int drctl_open(dev_t *, int, int, cred_t *); 52 static int drctl_close(dev_t, int, int, cred_t *); 53 static int drctl_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); 54 55 static void *pack_message(int, int, int, void *, size_t *, size_t *); 56 static int send_message(void *, size_t, drctl_resp_t **, size_t *); 57 58 59 /* 60 * Configuration data structures 61 */ 62 static struct cb_ops drctl_cb_ops = { 63 drctl_open, /* open */ 64 drctl_close, /* close */ 65 nodev, /* strategy */ 66 nodev, /* print */ 67 nodev, /* dump */ 68 nodev, /* read */ 69 nodev, /* write */ 70 drctl_ioctl, /* ioctl */ 71 nodev, /* devmap */ 72 nodev, /* mmap */ 73 nodev, /* segmap */ 74 nochpoll, /* poll */ 75 ddi_prop_op, /* prop_op */ 76 NULL, /* streamtab */ 77 D_MP | D_NEW, /* driver compatibility flag */ 78 CB_REV, /* cb_ops revision */ 79 nodev, /* async read */ 80 nodev /* async write */ 81 }; 82 83 84 static struct dev_ops drctl_ops = { 85 DEVO_REV, /* devo_rev */ 86 0, /* refcnt */ 87 drctl_getinfo, /* info */ 88 nulldev, /* identify */ 89 nulldev, /* probe */ 90 drctl_attach, /* attach */ 91 drctl_detach, /* detach */ 92 nodev, /* reset */ 93 &drctl_cb_ops, /* driver operations */ 94 NULL, /* bus operations */ 95 NULL, /* power */ 96 ddi_quiesce_not_needed, /* quiesce */ 97 }; 98 99 static struct modldrv modldrv = { 100 &mod_driverops, /* type of module - driver */ 101 "DR Control pseudo driver", 102 &drctl_ops 103 }; 104 105 static struct modlinkage modlinkage = { 106 MODREV_1, 107 &modldrv, 108 NULL 109 }; 110 111 112 /* 113 * Locking strategy 114 * 115 * One of the reasons for this module's existence is to serialize 116 * DR requests which might be coming from different sources. Only 117 * one operation is allowed to be in progress at any given time. 118 * 119 * A single lock word (the 'drc_busy' element below) is NULL 120 * when there is no operation in progress. When a client of this 121 * module initiates an operation it grabs the mutex 'drc_lock' in 122 * order to examine the lock word ('drc_busy'). If no other 123 * operation is in progress, the lock word will be NULL. If so, 124 * a cookie which uniquely identifies the requestor is stored in 125 * the lock word, and the mutex is released. Attempts by other 126 * clients to initiate an operation will fail. 127 * 128 * When the lock-holding client's operation is completed, the 129 * client will call a "finalize" function in this module, providing 130 * the cookie passed with the original request. Since the cookie 131 * matches, the operation will succeed and the lock word will be 132 * cleared. At this point, an new operation may be initiated. 133 */ 134 135 /* 136 * Driver private data 137 */ 138 static struct drctl_unit { 139 kmutex_t drc_lock; /* global driver lock */ 140 dev_info_t *drc_dip; /* dev_info pointer */ 141 kcondvar_t drc_busy_cv; /* block for !busy */ 142 drctl_cookie_t drc_busy; /* NULL if free else a unique */ 143 /* identifier for caller */ 144 int drc_cmd; /* the cmd underway (or -1) */ 145 int drc_flags; /* saved flag from above cmd */ 146 int drc_inst; /* our single instance */ 147 uint_t drc_state; /* driver state */ 148 } drctl_state; 149 150 static struct drctl_unit *drctlp = &drctl_state; 151 152 int 153 _init(void) 154 { 155 int rv; 156 157 drctlp->drc_inst = -1; 158 mutex_init(&drctlp->drc_lock, NULL, MUTEX_DRIVER, NULL); 159 cv_init(&drctlp->drc_busy_cv, NULL, CV_DRIVER, NULL); 160 161 if ((rv = mod_install(&modlinkage)) != 0) 162 mutex_destroy(&drctlp->drc_lock); 163 164 return (rv); 165 } 166 167 168 int 169 _fini(void) 170 { 171 int rv; 172 173 if ((rv = mod_remove(&modlinkage)) != 0) 174 return (rv); 175 cv_destroy(&drctlp->drc_busy_cv); 176 mutex_destroy(&drctlp->drc_lock); 177 return (0); 178 } 179 180 181 int 182 _info(struct modinfo *modinfop) 183 { 184 return (mod_info(&modlinkage, modinfop)); 185 } 186 187 188 /* 189 * Do the attach work 190 */ 191 static int 192 drctl_do_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 193 { 194 _NOTE(ARGUNUSED(cmd)) 195 196 char *str = "drctl_do_attach"; 197 int retval = DDI_SUCCESS; 198 199 if (drctlp->drc_inst != -1) { 200 cmn_err(CE_WARN, "%s: an instance is already attached!", str); 201 return (DDI_FAILURE); 202 } 203 drctlp->drc_inst = ddi_get_instance(dip); 204 205 retval = ddi_create_minor_node(dip, "drctl", S_IFCHR, 206 drctlp->drc_inst, DDI_PSEUDO, 0); 207 if (retval != DDI_SUCCESS) { 208 cmn_err(CE_WARN, "%s: can't create minor node", str); 209 drctlp->drc_inst = -1; 210 return (retval); 211 } 212 213 drctlp->drc_dip = dip; 214 ddi_report_dev(dip); 215 216 return (retval); 217 } 218 219 220 static int 221 drctl_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 222 { 223 switch (cmd) { 224 case DDI_ATTACH: 225 return (drctl_do_attach(dip, cmd)); 226 227 default: 228 return (DDI_FAILURE); 229 } 230 } 231 232 233 /* ARGSUSED */ 234 static int 235 drctl_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 236 { 237 switch (cmd) { 238 case DDI_DETACH: 239 drctlp->drc_inst = -1; 240 ddi_remove_minor_node(dip, "drctl"); 241 return (DDI_SUCCESS); 242 243 default: 244 return (DDI_FAILURE); 245 } 246 } 247 248 static int 249 drctl_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) 250 { 251 _NOTE(ARGUNUSED(dip, cmd, arg, resultp)) 252 253 return (0); 254 } 255 256 static int 257 drctl_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) 258 { 259 _NOTE(ARGUNUSED(devp, flag, cred_p)) 260 261 if (otyp != OTYP_CHR) 262 return (EINVAL); 263 264 return (0); 265 } 266 267 static int 268 drctl_close(dev_t dev, int flag, int otyp, cred_t *cred_p) 269 { 270 _NOTE(ARGUNUSED(dev, flag, otyp, cred_p)) 271 272 return (0); 273 } 274 275 /* 276 * Create a reponse structure which includes an array of drctl_rsrc_t 277 * structures in which each status element is set to the 'status' 278 * arg. There is no error text, so set the 'offset' elements to 0. 279 */ 280 static drctl_resp_t * 281 drctl_generate_resp(drctl_rsrc_t *res, 282 int count, size_t *rsize, drctl_status_t status) 283 { 284 int i; 285 size_t size; 286 drctl_rsrc_t *rsrc; 287 drctl_resp_t *resp; 288 289 size = offsetof(drctl_resp_t, resp_resources) + (count * sizeof (*res)); 290 resp = kmem_alloc(size, KM_SLEEP); 291 DR_DBG_KMEM("%s: alloc addr %p size %ld\n", 292 __func__, (void *)resp, size); 293 294 resp->resp_type = DRCTL_RESP_OK; 295 rsrc = resp->resp_resources; 296 297 bcopy(res, rsrc, count * sizeof (*res)); 298 299 for (i = 0; i < count; i++) { 300 rsrc[i].status = status; 301 rsrc[i].offset = 0; 302 } 303 304 *rsize = size; 305 306 return (resp); 307 } 308 309 /* 310 * Generate an error response message. 311 */ 312 static drctl_resp_t * 313 drctl_generate_err_resp(char *msg, size_t *size) 314 { 315 drctl_resp_t *resp; 316 317 ASSERT(msg != NULL); 318 ASSERT(size != NULL); 319 320 *size = offsetof(drctl_resp_t, resp_err_msg) + strlen(msg) + 1; 321 resp = kmem_alloc(*size, KM_SLEEP); 322 DR_DBG_KMEM("%s: alloc addr %p size %ld\n", 323 __func__, (void *)resp, *size); 324 325 resp->resp_type = DRCTL_RESP_ERR; 326 (void) strcpy(resp->resp_err_msg, msg); 327 328 return (resp); 329 } 330 331 /* 332 * Since response comes from userland, verify that it is at least the 333 * minimum size based on the size of the original request. Verify 334 * that any offsets to error strings are within the string area of 335 * the response and, force the string area to be null-terminated. 336 */ 337 static int 338 verify_response(int cmd, 339 int count, drctl_resp_t *resp, size_t sent_len, size_t resp_len) 340 { 341 drctl_rsrc_t *rsrc = resp->resp_resources; 342 size_t rcvd_len = resp_len - (offsetof(drctl_resp_t, resp_resources)); 343 int is_cpu = 0; 344 int i; 345 346 switch (cmd) { 347 case DRCTL_CPU_CONFIG_REQUEST: 348 case DRCTL_CPU_UNCONFIG_REQUEST: 349 if (rcvd_len < sent_len) 350 return (EIO); 351 is_cpu = 1; 352 break; 353 case DRCTL_IO_UNCONFIG_REQUEST: 354 case DRCTL_IO_CONFIG_REQUEST: 355 if (count != 1) 356 return (EIO); 357 break; 358 case DRCTL_MEM_CONFIG_REQUEST: 359 case DRCTL_MEM_UNCONFIG_REQUEST: 360 break; 361 default: 362 return (EIO); 363 } 364 365 for (i = 0; i < count; i++) 366 if ((rsrc[i].offset > 0) && 367 /* string can't be inside the bounds of original request */ 368 (((rsrc[i].offset < sent_len) && is_cpu) || 369 /* string must start inside the message */ 370 (rsrc[i].offset >= rcvd_len))) 371 return (EIO); 372 373 /* If there are any strings, terminate the string area. */ 374 if (rcvd_len > sent_len) 375 *((char *)rsrc + rcvd_len - 1) = '\0'; 376 377 return (0); 378 } 379 380 static int 381 drctl_config_common(int cmd, int flags, drctl_rsrc_t *res, 382 int count, drctl_resp_t **rbuf, size_t *rsize, size_t *rq_size) 383 { 384 int rv = 0; 385 size_t size; 386 char *bufp; 387 388 switch (cmd) { 389 case DRCTL_CPU_CONFIG_REQUEST: 390 case DRCTL_CPU_CONFIG_NOTIFY: 391 case DRCTL_CPU_UNCONFIG_REQUEST: 392 case DRCTL_CPU_UNCONFIG_NOTIFY: 393 case DRCTL_IO_UNCONFIG_REQUEST: 394 case DRCTL_IO_UNCONFIG_NOTIFY: 395 case DRCTL_IO_CONFIG_REQUEST: 396 case DRCTL_IO_CONFIG_NOTIFY: 397 case DRCTL_MEM_CONFIG_REQUEST: 398 case DRCTL_MEM_CONFIG_NOTIFY: 399 case DRCTL_MEM_UNCONFIG_REQUEST: 400 case DRCTL_MEM_UNCONFIG_NOTIFY: 401 rv = 0; 402 break; 403 default: 404 rv = ENOTSUP; 405 break; 406 } 407 408 if (rv != 0) { 409 DR_DBG_CTL("%s: invalid cmd %d\n", __func__, cmd); 410 return (rv); 411 } 412 413 /* 414 * If the operation is a FORCE, we don't send a message to 415 * the daemon. But, the upstream clients still expect a 416 * response, so generate a response with all ops 'allowed'. 417 */ 418 if (flags == DRCTL_FLAG_FORCE) { 419 if (rbuf != NULL) 420 *rbuf = drctl_generate_resp(res, 421 count, rsize, DRCTL_STATUS_ALLOW); 422 return (0); 423 } 424 425 bufp = pack_message(cmd, flags, count, (void *)res, &size, rq_size); 426 DR_DBG_CTL("%s: from pack_message, bufp = %p size %ld\n", 427 __func__, (void *)bufp, size); 428 429 if (bufp == NULL || size == 0) 430 return (EINVAL); 431 432 return (send_message(bufp, size, rbuf, rsize)); 433 } 434 435 /* 436 * Prepare for a reconfig operation. 437 */ 438 int 439 drctl_config_init(int cmd, int flags, drctl_rsrc_t *res, 440 int count, drctl_resp_t **rbuf, size_t *rsize, drctl_cookie_t ck) 441 { 442 static char inval_msg[] = "Invalid command format received.\n"; 443 static char unsup_msg[] = "Unsuppported command received.\n"; 444 static char unk_msg [] = "Failure reason unknown.\n"; 445 static char rsp_msg [] = "Invalid response from " 446 "reconfiguration daemon.\n"; 447 static char drd_msg [] = "Cannot communicate with reconfiguration " 448 "daemon (drd) in target domain.\n" 449 "drd(1M) SMF service may not be enabled.\n"; 450 static char busy_msg [] = "Busy executing earlier command; " 451 "please try again later.\n"; 452 size_t rq_size; 453 char *ermsg; 454 int rv; 455 456 if (ck == 0) { 457 *rbuf = drctl_generate_err_resp(inval_msg, rsize); 458 459 return (EINVAL); 460 } 461 462 mutex_enter(&drctlp->drc_lock); 463 if (drctlp->drc_busy != NULL) { 464 mutex_exit(&drctlp->drc_lock); 465 *rbuf = drctl_generate_err_resp(busy_msg, rsize); 466 467 return (EBUSY); 468 } 469 470 DR_DBG_CTL("%s: cmd %d flags %d res %p count %d\n", 471 __func__, cmd, flags, (void *)res, count); 472 473 /* Mark the link busy. Below we will fill in the actual cookie. */ 474 drctlp->drc_busy = (drctl_cookie_t)-1; 475 mutex_exit(&drctlp->drc_lock); 476 477 rv = drctl_config_common(cmd, flags, res, count, rbuf, rsize, &rq_size); 478 if (rv == 0) { 479 /* 480 * If the upcall to the daemon returned successfully, we 481 * still need to validate the format of the returned msg. 482 */ 483 if ((rv = verify_response(cmd, 484 count, *rbuf, rq_size, *rsize)) != 0) { 485 DR_DBG_KMEM("%s: free addr %p size %ld\n", 486 __func__, (void *)*rbuf, *rsize); 487 kmem_free(*rbuf, *rsize); 488 *rbuf = drctl_generate_err_resp(rsp_msg, rsize); 489 drctlp->drc_busy = NULL; 490 cv_broadcast(&drctlp->drc_busy_cv); 491 } else { /* message format is valid */ 492 drctlp->drc_busy = ck; 493 drctlp->drc_cmd = cmd; 494 drctlp->drc_flags = flags; 495 } 496 } else { 497 switch (rv) { 498 case ENOTSUP: 499 ermsg = unsup_msg; 500 break; 501 case EIO: 502 ermsg = drd_msg; 503 break; 504 default: 505 ermsg = unk_msg; 506 break; 507 } 508 509 *rbuf = drctl_generate_err_resp(ermsg, rsize); 510 511 drctlp->drc_cmd = -1; 512 drctlp->drc_flags = 0; 513 drctlp->drc_busy = NULL; 514 cv_broadcast(&drctlp->drc_busy_cv); 515 } 516 return (rv); 517 } 518 519 /* 520 * Complete a reconfig operation. 521 */ 522 int 523 drctl_config_fini(drctl_cookie_t ck, drctl_rsrc_t *res, int count) 524 { 525 int rv; 526 int notify_cmd; 527 int flags; 528 size_t rq_size; 529 530 mutex_enter(&drctlp->drc_lock); 531 if (drctlp->drc_busy != ck) { 532 mutex_exit(&drctlp->drc_lock); 533 return (EBUSY); 534 } 535 mutex_exit(&drctlp->drc_lock); 536 537 flags = drctlp->drc_flags; 538 /* 539 * Flip the saved _REQUEST command to its corresponding 540 * _NOTIFY command. 541 */ 542 switch (drctlp->drc_cmd) { 543 case DRCTL_CPU_CONFIG_REQUEST: 544 notify_cmd = DRCTL_CPU_CONFIG_NOTIFY; 545 break; 546 547 case DRCTL_CPU_UNCONFIG_REQUEST: 548 notify_cmd = DRCTL_CPU_UNCONFIG_NOTIFY; 549 break; 550 551 case DRCTL_IO_UNCONFIG_REQUEST: 552 notify_cmd = DRCTL_IO_UNCONFIG_NOTIFY; 553 break; 554 555 case DRCTL_IO_CONFIG_REQUEST: 556 notify_cmd = DRCTL_IO_CONFIG_NOTIFY; 557 break; 558 559 case DRCTL_MEM_CONFIG_REQUEST: 560 notify_cmd = DRCTL_MEM_CONFIG_NOTIFY; 561 break; 562 563 case DRCTL_MEM_UNCONFIG_REQUEST: 564 notify_cmd = DRCTL_MEM_UNCONFIG_NOTIFY; 565 break; 566 567 default: 568 /* none of the above should have been accepted in _init */ 569 ASSERT(0); 570 cmn_err(CE_CONT, 571 "drctl_config_fini: bad cmd %d\n", drctlp->drc_cmd); 572 rv = EINVAL; 573 goto done; 574 } 575 576 rv = drctl_config_common(notify_cmd, 577 flags, res, count, NULL, 0, &rq_size); 578 579 done: 580 drctlp->drc_cmd = -1; 581 drctlp->drc_flags = 0; 582 drctlp->drc_busy = NULL; 583 cv_broadcast(&drctlp->drc_busy_cv); 584 return (rv); 585 } 586 587 static int 588 drctl_ioctl(dev_t dev, 589 int cmd, intptr_t arg, int mode, cred_t *cred_p, int *rval_p) 590 { 591 _NOTE(ARGUNUSED(dev, mode, cred_p, rval_p)) 592 593 int rv; 594 595 switch (cmd) { 596 case DRCTL_IOCTL_CONNECT_SERVER: 597 rv = i_drctl_ioctl(cmd, arg); 598 break; 599 default: 600 rv = ENOTSUP; 601 } 602 603 *rval_p = (rv == 0) ? 0 : -1; 604 605 return (rv); 606 } 607 608 /* 609 * Accept a preformatted request from caller and send a message to 610 * the daemon. A pointer to the daemon's response buffer is passed 611 * back in obufp, its size in osize. 612 */ 613 static int 614 send_message(void *msg, size_t size, drctl_resp_t **obufp, size_t *osize) 615 { 616 drctl_resp_t *bufp; 617 drctl_rsrc_t *rsrcs; 618 size_t rsrcs_size; 619 int rv; 620 621 rv = i_drctl_send(msg, size, (void **)&rsrcs, &rsrcs_size); 622 623 if ((rv == 0) && ((rsrcs == NULL) ||(rsrcs_size == 0))) 624 rv = EINVAL; 625 626 if (rv == 0) { 627 if (obufp != NULL) { 628 ASSERT(osize != NULL); 629 630 *osize = 631 offsetof(drctl_resp_t, resp_resources) + rsrcs_size; 632 bufp = 633 kmem_alloc(*osize, KM_SLEEP); 634 DR_DBG_KMEM("%s: alloc addr %p size %ld\n", 635 __func__, (void *)bufp, *osize); 636 bufp->resp_type = DRCTL_RESP_OK; 637 bcopy(rsrcs, bufp->resp_resources, rsrcs_size); 638 *obufp = bufp; 639 } 640 641 DR_DBG_KMEM("%s: free addr %p size %ld\n", 642 __func__, (void *)rsrcs, rsrcs_size); 643 kmem_free(rsrcs, rsrcs_size); 644 } 645 646 DR_DBG_KMEM("%s:free addr %p size %ld\n", __func__, msg, size); 647 kmem_free(msg, size); 648 649 return (rv); 650 } 651 652 static void * 653 pack_message(int cmd, 654 int flags, int count, void *data, size_t *osize, size_t *data_size) 655 { 656 drd_msg_t *msgp = NULL; 657 size_t hdr_size = offsetof(drd_msg_t, data); 658 659 switch (cmd) { 660 case DRCTL_CPU_CONFIG_REQUEST: 661 case DRCTL_CPU_CONFIG_NOTIFY: 662 case DRCTL_CPU_UNCONFIG_REQUEST: 663 case DRCTL_CPU_UNCONFIG_NOTIFY: 664 *data_size = count * sizeof (drctl_rsrc_t); 665 break; 666 case DRCTL_MEM_CONFIG_REQUEST: 667 case DRCTL_MEM_CONFIG_NOTIFY: 668 case DRCTL_MEM_UNCONFIG_REQUEST: 669 case DRCTL_MEM_UNCONFIG_NOTIFY: 670 *data_size = count * sizeof (drctl_rsrc_t); 671 break; 672 case DRCTL_IO_CONFIG_REQUEST: 673 case DRCTL_IO_CONFIG_NOTIFY: 674 case DRCTL_IO_UNCONFIG_REQUEST: 675 case DRCTL_IO_UNCONFIG_NOTIFY: 676 *data_size = sizeof (drctl_rsrc_t) + 677 strlen(((drctl_rsrc_t *)data)->res_dev_path); 678 break; 679 default: 680 cmn_err(CE_WARN, 681 "drctl: pack_message received invalid cmd %d", cmd); 682 break; 683 } 684 685 if (data_size) { 686 *osize = hdr_size + *data_size; 687 msgp = kmem_alloc(*osize, KM_SLEEP); 688 DR_DBG_KMEM("%s: alloc addr %p size %ld\n", 689 __func__, (void *)msgp, *osize); 690 msgp->cmd = cmd; 691 msgp->count = count; 692 msgp->flags = flags; 693 bcopy(data, msgp->data, *data_size); 694 } 695 696 return (msgp); 697 } 698 699 /* 700 * Block DR operations 701 */ 702 void 703 drctl_block(void) 704 { 705 /* Wait for any in progress DR operation to complete */ 706 mutex_enter(&drctlp->drc_lock); 707 while (drctlp->drc_busy != NULL) 708 (void) cv_wait_sig(&drctlp->drc_busy_cv, &drctlp->drc_lock); 709 /* Mark the link busy */ 710 drctlp->drc_busy = (drctl_cookie_t)-1; 711 drctlp->drc_cmd = DRCTL_DRC_BLOCK; 712 drctlp->drc_flags = 0; 713 mutex_exit(&drctlp->drc_lock); 714 } 715 716 /* 717 * Unblock DR operations 718 */ 719 void 720 drctl_unblock(void) 721 { 722 /* Mark the link free */ 723 mutex_enter(&drctlp->drc_lock); 724 drctlp->drc_cmd = -1; 725 drctlp->drc_flags = 0; 726 drctlp->drc_busy = NULL; 727 cv_broadcast(&drctlp->drc_busy_cv); 728 mutex_exit(&drctlp->drc_lock); 729 } 730