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 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * The tvhci driver can be used to exercise the mpxio framework together 30 * with tphci/tclient. 31 */ 32 33 #include <sys/conf.h> 34 #include <sys/file.h> 35 #include <sys/ddi.h> 36 #include <sys/sunddi.h> 37 #include <sys/scsi/scsi.h> 38 #include <sys/scsi/impl/scsi_reset_notify.h> 39 #include <sys/sunmdi.h> 40 #include <sys/mdi_impldefs.h> 41 #include <sys/disp.h> 42 43 /* cb_ops entry points */ 44 static int tvhci_open(dev_t *, int, int, cred_t *); 45 static int tvhci_close(dev_t, int, int, cred_t *); 46 static int tvhci_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); 47 static int tvhci_attach(dev_info_t *, ddi_attach_cmd_t); 48 static int tvhci_detach(dev_info_t *, ddi_detach_cmd_t); 49 static int tvhci_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); 50 51 /* bus_ops entry points */ 52 static int tvhci_ctl(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *, 53 void *); 54 static int tvhci_initchild(dev_info_t *, dev_info_t *); 55 static int tvhci_uninitchild(dev_info_t *, dev_info_t *); 56 static int tvhci_bus_config(dev_info_t *, uint_t, ddi_bus_config_op_t, void *, 57 dev_info_t **); 58 static int tvhci_bus_unconfig(dev_info_t *, uint_t, ddi_bus_config_op_t, 59 void *); 60 static int tvhci_intr_op(dev_info_t *dip, dev_info_t *rdip, 61 ddi_intr_op_t op, ddi_intr_handle_impl_t *hdlp, void *result); 62 63 /* vhci ops */ 64 static int tvhci_pi_init(dev_info_t *, mdi_pathinfo_t *, int); 65 static int tvhci_pi_uninit(dev_info_t *, mdi_pathinfo_t *, int); 66 static int tvhci_pi_state_change(dev_info_t *, mdi_pathinfo_t *, 67 mdi_pathinfo_state_t, uint32_t, int); 68 static int tvhci_failover(dev_info_t *, dev_info_t *, int); 69 70 static void *tvhci_state; 71 struct tvhci_state { 72 dev_info_t *dip; 73 }; 74 75 static mdi_vhci_ops_t tvhci_opinfo = { 76 MDI_VHCI_OPS_REV, 77 tvhci_pi_init, 78 tvhci_pi_uninit, 79 tvhci_pi_state_change, 80 tvhci_failover 81 }; 82 83 static struct cb_ops tvhci_cb_ops = { 84 tvhci_open, /* open */ 85 tvhci_close, /* close */ 86 nodev, /* strategy */ 87 nodev, /* print */ 88 nodev, /* dump */ 89 nodev, /* read */ 90 nodev, /* write */ 91 tvhci_ioctl, /* ioctl */ 92 nodev, /* devmap */ 93 nodev, /* mmap */ 94 nodev, /* segmap */ 95 nochpoll, /* chpoll */ 96 ddi_prop_op, /* cb_prop_op */ 97 0, /* streamtab */ 98 D_NEW | D_MP, /* cb_flag */ 99 CB_REV, /* rev */ 100 nodev, /* aread */ 101 nodev /* awrite */ 102 }; 103 104 static struct bus_ops tvhci_bus_ops = { 105 BUSO_REV, /* busops_rev */ 106 nullbusmap, /* bus_map */ 107 NULL, /* bus_get_intrspec */ 108 NULL, /* bus_add_interspec */ 109 NULL, /* bus_remove_interspec */ 110 i_ddi_map_fault, /* bus_map_fault */ 111 ddi_no_dma_map, /* bus_dma_map */ 112 ddi_no_dma_allochdl, /* bus_dma_allochdl */ 113 NULL, /* bus_dma_freehdl */ 114 NULL, /* bus_dma_bindhdl */ 115 NULL, /* bus_dma_unbindhdl */ 116 NULL, /* bus_dma_flush */ 117 NULL, /* bus_dma_win */ 118 NULL, /* bus_dma_ctl */ 119 tvhci_ctl, /* bus_ctl */ 120 ddi_bus_prop_op, /* bus_prop_op */ 121 NULL, /* bus_get_eventcookie */ 122 NULL, /* bus_add_eventcall */ 123 NULL, /* bus_remove_event */ 124 NULL, /* bus_post_event */ 125 NULL, /* bus_intr_ctl */ 126 tvhci_bus_config, /* bus_config */ 127 tvhci_bus_unconfig, /* bus_unconfig */ 128 NULL, /* bus_fm_init */ 129 NULL, /* bus_fm_fini */ 130 NULL, /* bus_fm_access_enter */ 131 NULL, /* bus_fm_access_exit */ 132 NULL, /* bus_power */ 133 tvhci_intr_op /* bus_intr_op */ 134 }; 135 136 static struct dev_ops tvhci_ops = { 137 DEVO_REV, 138 0, 139 tvhci_getinfo, 140 nulldev, /* identify */ 141 nulldev, /* probe */ 142 tvhci_attach, /* attach and detach are mandatory */ 143 tvhci_detach, 144 nodev, /* reset */ 145 &tvhci_cb_ops, /* cb_ops */ 146 &tvhci_bus_ops, /* bus_ops */ 147 NULL, /* power */ 148 }; 149 150 extern struct mod_ops mod_driverops; 151 152 static struct modldrv modldrv = { 153 &mod_driverops, 154 "test vhci driver %I%", 155 &tvhci_ops 156 }; 157 158 static struct modlinkage modlinkage = { 159 MODREV_1, 160 &modldrv, 161 NULL 162 }; 163 164 int 165 _init(void) 166 { 167 int rval; 168 169 if ((rval = ddi_soft_state_init(&tvhci_state, 170 sizeof (struct tvhci_state), 2)) != 0) { 171 return (rval); 172 } 173 174 if ((rval = mod_install(&modlinkage)) != 0) { 175 ddi_soft_state_fini(&tvhci_state); 176 } 177 return (rval); 178 } 179 180 181 int 182 _fini(void) 183 { 184 int rval; 185 186 /* 187 * don't start cleaning up until we know that the module remove 188 * has worked -- if this works, then we know that each instance 189 * has successfully been detached 190 */ 191 if ((rval = mod_remove(&modlinkage)) != 0) { 192 return (rval); 193 } 194 195 ddi_soft_state_fini(&tvhci_state); 196 197 return (rval); 198 } 199 200 int 201 _info(struct modinfo *modinfop) 202 { 203 return (mod_info(&modlinkage, modinfop)); 204 } 205 206 /* ARGSUSED */ 207 static int 208 tvhci_open(dev_t *devp, int flag, int otype, cred_t *credp) 209 { 210 struct tvhci_state *vhci; 211 212 if (otype != OTYP_CHR) { 213 return (EINVAL); 214 } 215 216 vhci = ddi_get_soft_state(tvhci_state, getminor(*devp)); 217 if (vhci == NULL) { 218 return (ENXIO); 219 } 220 221 return (0); 222 } 223 224 225 /* ARGSUSED */ 226 static int 227 tvhci_close(dev_t dev, int flag, int otype, cred_t *credp) 228 { 229 struct tvhci_state *vhci; 230 if (otype != OTYP_CHR) { 231 return (EINVAL); 232 } 233 234 vhci = ddi_get_soft_state(tvhci_state, getminor(dev)); 235 if (vhci == NULL) { 236 return (ENXIO); 237 } 238 239 return (0); 240 } 241 242 /* ARGSUSED */ 243 static int 244 tvhci_ioctl(dev_t dev, int cmd, intptr_t data, int mode, 245 cred_t *credp, int *rval) 246 { 247 return (0); 248 } 249 250 /* 251 * attach the module 252 */ 253 static int 254 tvhci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 255 { 256 char *vclass; 257 int instance, vhci_regis = 0; 258 struct tvhci_state *vhci = NULL; 259 dev_info_t *pdip; 260 261 instance = ddi_get_instance(dip); 262 263 switch (cmd) { 264 case DDI_ATTACH: 265 break; 266 267 case DDI_RESUME: 268 case DDI_PM_RESUME: 269 return (0); /* nothing to do */ 270 271 default: 272 return (DDI_FAILURE); 273 } 274 275 /* 276 * Allocate vhci data structure. 277 */ 278 if (ddi_soft_state_zalloc(tvhci_state, instance) != DDI_SUCCESS) { 279 return (DDI_FAILURE); 280 } 281 282 vhci = ddi_get_soft_state(tvhci_state, instance); 283 ASSERT(vhci != NULL); 284 vhci->dip = dip; 285 286 /* parent must be /pshot */ 287 pdip = ddi_get_parent(dip); 288 if (strcmp(ddi_driver_name(pdip), "pshot") != 0 || 289 ddi_get_parent(pdip) != ddi_root_node()) { 290 cmn_err(CE_NOTE, "tvhci must be under /pshot/"); 291 goto attach_fail; 292 } 293 294 /* 295 * XXX add mpxio-disable property. need to remove the check 296 * from the framework 297 */ 298 (void) ddi_prop_update_string(DDI_DEV_T_NONE, dip, 299 "mpxio-disable", "no"); 300 301 /* bus_addr is the <vhci_class> */ 302 vclass = ddi_get_name_addr(dip); 303 if (vclass == NULL || vclass[1] == '\0') { 304 cmn_err(CE_NOTE, "tvhci invalid vhci class"); 305 goto attach_fail; 306 } 307 308 /* 309 * Attach this instance with the mpxio framework 310 */ 311 if (mdi_vhci_register(vclass, dip, &tvhci_opinfo, 0) != MDI_SUCCESS) { 312 cmn_err(CE_WARN, "%s mdi_vhci_register failed", 313 ddi_node_name(dip)); 314 goto attach_fail; 315 } 316 vhci_regis++; 317 318 if (ddi_create_minor_node(dip, "devctl", S_IFCHR, 319 instance, DDI_NT_SCSI_NEXUS, 0) != DDI_SUCCESS) { 320 cmn_err(CE_NOTE, "%s ddi_create_minor_node failed", 321 ddi_node_name(dip)); 322 goto attach_fail; 323 } 324 325 (void) ddi_prop_update_int(DDI_DEV_T_NONE, dip, DDI_NO_AUTODETACH, 1); 326 ddi_report_dev(dip); 327 return (DDI_SUCCESS); 328 329 attach_fail: 330 if (vhci_regis) 331 (void) mdi_vhci_unregister(dip, 0); 332 333 ddi_soft_state_free(tvhci_state, instance); 334 return (DDI_FAILURE); 335 } 336 337 338 /*ARGSUSED*/ 339 static int 340 tvhci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 341 { 342 int instance = ddi_get_instance(dip); 343 344 switch (cmd) { 345 case DDI_DETACH: 346 break; 347 348 case DDI_SUSPEND: 349 case DDI_PM_SUSPEND: 350 return (0); /* nothing to do */ 351 352 default: 353 return (DDI_FAILURE); 354 } 355 356 if (mdi_vhci_unregister(dip, 0) != MDI_SUCCESS) 357 return (DDI_FAILURE); 358 359 ddi_remove_minor_node(dip, NULL); 360 ddi_soft_state_free(tvhci_state, instance); 361 362 return (DDI_SUCCESS); 363 } 364 365 /* 366 * tvhci_getinfo() 367 * Given the device number, return the devinfo pointer or the 368 * instance number. 369 * Note: always succeed DDI_INFO_DEVT2INSTANCE, even before attach. 370 */ 371 372 /*ARGSUSED*/ 373 static int 374 tvhci_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) 375 { 376 struct tvhci_state *vhci; 377 int instance = getminor((dev_t)arg); 378 379 switch (cmd) { 380 case DDI_INFO_DEVT2DEVINFO: 381 vhci = ddi_get_soft_state(tvhci_state, instance); 382 if (vhci != NULL) 383 *result = vhci->dip; 384 else { 385 *result = NULL; 386 return (DDI_FAILURE); 387 } 388 break; 389 390 case DDI_INFO_DEVT2INSTANCE: 391 *result = (void *)(uintptr_t)instance; 392 break; 393 394 default: 395 return (DDI_FAILURE); 396 } 397 398 return (DDI_SUCCESS); 399 } 400 401 /*ARGSUSED*/ 402 static int 403 tvhci_pi_init(dev_info_t *vdip, mdi_pathinfo_t *pip, int flags) 404 { 405 return (MDI_SUCCESS); 406 } 407 408 /*ARGSUSED*/ 409 static int 410 tvhci_pi_uninit(dev_info_t *vdip, mdi_pathinfo_t *pip, int flags) 411 { 412 return (MDI_SUCCESS); 413 } 414 415 /*ARGSUSED*/ 416 static int 417 tvhci_pi_state_change(dev_info_t *vdip, mdi_pathinfo_t *pip, 418 mdi_pathinfo_state_t state, uint32_t ext_state, int flags) 419 { 420 return (MDI_SUCCESS); 421 } 422 423 /*ARGSUSED*/ 424 static int 425 tvhci_failover(dev_info_t *vdip, dev_info_t *cdip, int flags) 426 { 427 return (MDI_SUCCESS); 428 } 429 430 /* 431 * Interrupt stuff. NO OP for pseudo drivers. 432 */ 433 /*ARGSUSED*/ 434 static int 435 tvhci_intr_op(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t op, 436 ddi_intr_handle_impl_t *hdlp, void *result) 437 { 438 return (DDI_FAILURE); 439 } 440 441 /*ARGSUSED*/ 442 static int 443 tvhci_ctl(dev_info_t *dip, dev_info_t *rdip, 444 ddi_ctl_enum_t ctlop, void *arg, void *result) 445 { 446 switch (ctlop) { 447 case DDI_CTLOPS_REPORTDEV: 448 if (rdip == (dev_info_t *)0) 449 return (DDI_FAILURE); 450 cmn_err(CE_CONT, "?tvhci-device: %s%d\n", 451 ddi_get_name(rdip), ddi_get_instance(rdip)); 452 return (DDI_SUCCESS); 453 454 case DDI_CTLOPS_INITCHILD: 455 { 456 dev_info_t *child = (dev_info_t *)arg; 457 return (tvhci_initchild(dip, child)); 458 } 459 460 case DDI_CTLOPS_UNINITCHILD: 461 { 462 dev_info_t *child = (dev_info_t *)arg; 463 return (tvhci_uninitchild(dip, child)); 464 } 465 466 case DDI_CTLOPS_DMAPMAPC: 467 case DDI_CTLOPS_REPORTINT: 468 case DDI_CTLOPS_REGSIZE: 469 case DDI_CTLOPS_NREGS: 470 case DDI_CTLOPS_SIDDEV: 471 case DDI_CTLOPS_SLAVEONLY: 472 case DDI_CTLOPS_AFFINITY: 473 case DDI_CTLOPS_POKE: 474 case DDI_CTLOPS_PEEK: 475 /* 476 * These ops correspond to functions that "shouldn't" be called 477 * by a pseudo driver. So we whine when we're called. 478 */ 479 cmn_err(CE_CONT, "%s%d: invalid op (%d) from %s%d\n", 480 ddi_get_name(dip), ddi_get_instance(dip), 481 ctlop, ddi_get_name(rdip), ddi_get_instance(rdip)); 482 return (DDI_FAILURE); 483 484 case DDI_CTLOPS_ATTACH: 485 case DDI_CTLOPS_BTOP: 486 case DDI_CTLOPS_BTOPR: 487 case DDI_CTLOPS_DETACH: 488 case DDI_CTLOPS_DVMAPAGESIZE: 489 case DDI_CTLOPS_IOMIN: 490 case DDI_CTLOPS_POWER: 491 case DDI_CTLOPS_PTOB: 492 default: 493 /* 494 * The ops that we pass up (default). We pass up memory 495 * allocation oriented ops that we receive - these may be 496 * associated with pseudo HBA drivers below us with target 497 * drivers below them that use ddi memory allocation 498 * interfaces like scsi_alloc_consistent_buf. 499 */ 500 return (ddi_ctlops(dip, rdip, ctlop, arg, result)); 501 } 502 } 503 504 /* set devi_addr to "g<guid>" */ 505 static int 506 tvhci_initchild(dev_info_t *dip, dev_info_t *child) 507 { 508 _NOTE(ARGUNUSED(dip)) 509 char *guid, *addr; 510 511 if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, 512 MDI_CLIENT_GUID_PROP, &guid) != DDI_SUCCESS) { 513 cmn_err(CE_NOTE, "tvhci_initchild - no guid property"); 514 return (DDI_FAILURE); 515 } 516 517 addr = kmem_alloc(MAXNAMELEN, KM_SLEEP); 518 (void) snprintf(addr, MAXNAMELEN, "g%s", guid); 519 ddi_set_name_addr(child, addr); 520 521 kmem_free(addr, MAXNAMELEN); 522 ddi_prop_free(guid); 523 return (DDI_SUCCESS); 524 } 525 526 /*ARGSUSED*/ 527 static int 528 tvhci_uninitchild(dev_info_t *dip, dev_info_t *child) 529 { 530 ddi_set_name_addr(child, NULL); 531 return (DDI_SUCCESS); 532 } 533 534 /* form paddr by cname@<phci_inst>,<guid> */ 535 static char * 536 tvh_get_phci_devname(char *cname, char *guid, 537 dev_info_t *pdip, char *pname, int len) 538 { 539 (void) snprintf(pname, len, "%s@%d,%s", 540 cname, ddi_get_instance(pdip), guid); 541 return (pname); 542 } 543 544 static int 545 tvh_enum_by_phci(dev_info_t *vdip, char *devnm, int flags) 546 { 547 mdi_phci_t *ph; 548 mdi_vhci_t *vh = (mdi_vhci_t *)DEVI(vdip)->devi_mdi_xhci; 549 dev_info_t *pdip, *cdip; 550 char *cname, *caddr, *guid, *pname; 551 int rval = DDI_FAILURE; 552 553 (void) i_ddi_parse_name(devnm, &cname, &caddr, NULL); 554 if (cname == NULL || caddr == NULL || caddr[0] != 'g') 555 return (rval); 556 557 guid = caddr + 1; 558 pname = kmem_alloc(MAXNAMELEN, KM_SLEEP); 559 560 /* mutex_enter(&mdi_mutex); XXX need lock access */ 561 ph = vh->vh_phci_head; 562 while (ph) { 563 pdip = ph->ph_dip; 564 /* mutex_exit(&mdi_mutex); */ 565 (void) tvh_get_phci_devname(cname, guid, pdip, pname, 566 MAXNAMELEN); 567 if (ndi_devi_config_one(pdip, pname, &cdip, flags) 568 == DDI_SUCCESS) { 569 ndi_rele_devi(cdip); 570 rval = DDI_SUCCESS; 571 } 572 /* mutex_enter(&mdi_mutex); */ 573 ph = ph->ph_next; 574 } 575 /* mutex_exit(&mdi_mutex); */ 576 577 *(caddr - 1) = '@'; /* undo damage from i_ddi_parse_name() */ 578 kmem_free(pname, MAXNAMELEN); 579 return (rval); 580 } 581 582 static int 583 tvh_remove_by_phci(dev_info_t *vdip, char *devnm, int flags) 584 { 585 int rval = DDI_SUCCESS; 586 mdi_phci_t *ph; 587 mdi_vhci_t *vh = (mdi_vhci_t *)DEVI(vdip)->devi_mdi_xhci; 588 dev_info_t *pdip; 589 char *cname, *caddr, *guid, *pname; 590 591 (void) i_ddi_parse_name(devnm, &cname, &caddr, NULL); 592 if (cname == NULL || caddr == NULL || caddr[0] != 'g') 593 return (rval); /* devnm can't exist */ 594 595 guid = caddr + 1; 596 pname = kmem_alloc(MAXNAMELEN, KM_SLEEP); 597 598 /* mutex_enter(&mdi_mutex); XXX need lock access */ 599 ph = vh->vh_phci_head; 600 while (ph) { 601 pdip = ph->ph_dip; 602 /* mutex_exit(&mdi_mutex); */ 603 (void) tvh_get_phci_devname(cname, guid, pdip, pname, 604 MAXNAMELEN); 605 rval = ndi_devi_unconfig_one(pdip, pname, NULL, flags); 606 /* mutex_enter(&mdi_mutex); */ 607 if (rval != NDI_SUCCESS) 608 break; 609 ph = ph->ph_next; 610 } 611 /* mutex_exit(&mdi_mutex); */ 612 613 kmem_free(pname, MAXNAMELEN); 614 return (rval); 615 } 616 617 static int 618 tvhci_bus_config(dev_info_t *parent, uint_t flags, 619 ddi_bus_config_op_t op, void *arg, dev_info_t **childp) 620 { 621 char *devnm; 622 dev_info_t *cdip; 623 int circ; 624 625 switch (op) { 626 case BUS_CONFIG_ONE: 627 break; 628 case BUS_CONFIG_ALL: 629 /* XXX call into phci's here? */ 630 case BUS_CONFIG_DRIVER: 631 return (ndi_busop_bus_config(parent, flags, op, arg, childp, 632 0)); 633 default: 634 return (DDI_FAILURE); 635 } 636 637 devnm = (char *)arg; 638 ndi_devi_enter(parent, &circ); 639 cdip = ndi_devi_findchild(parent, devnm); 640 ndi_devi_exit(parent, circ); 641 if (cdip == NULL) { 642 /* call into registered phci's */ 643 (void) tvh_enum_by_phci(parent, devnm, flags); 644 } 645 646 return (ndi_busop_bus_config(parent, flags, op, arg, childp, 0)); 647 } 648 649 static int 650 tvhci_bus_unconfig(dev_info_t *parent, uint_t flags, 651 ddi_bus_config_op_t op, void *arg) 652 { 653 char *devnm, *slashname; 654 int rval, circ; 655 dev_info_t *cdip, *ndip; 656 657 /* 658 * If we are not removing device nodes, pathinfo can be 659 * left as is. So no need to disturb the phci's. 660 */ 661 if ((flags & NDI_DEVI_REMOVE) == 0) { 662 return (ndi_busop_bus_unconfig(parent, flags, op, arg)); 663 } 664 665 switch (op) { 666 case BUS_UNCONFIG_ONE: 667 devnm = (char *)arg; 668 ndi_devi_enter(parent, &circ); 669 if (ndi_devi_findchild(parent, devnm) == NULL) { 670 ndi_devi_exit(parent, circ); 671 return (DDI_SUCCESS); 672 } 673 ndi_devi_exit(parent, circ); 674 return (tvh_remove_by_phci(parent, devnm, flags)); 675 676 case BUS_UNCONFIG_ALL: 677 /* this functionality is for developers only */ 678 rval = DDI_SUCCESS; 679 slashname = kmem_alloc(MAXNAMELEN, KM_SLEEP); 680 devnm = slashname + 1; 681 ndi_devi_enter(parent, &circ); 682 cdip = ddi_get_child(parent); 683 while (cdip != NULL) { 684 ndip = ddi_get_next_sibling(cdip); 685 (void) ddi_deviname(cdip, slashname); 686 ndi_devi_exit(parent, circ); 687 rval = tvh_remove_by_phci(parent, devnm, flags); 688 if (rval != DDI_SUCCESS) { 689 break; 690 } 691 ndi_devi_enter(parent, &circ); 692 cdip = ndip; 693 } 694 ndi_devi_exit(parent, circ); 695 return (rval); 696 697 case BUS_UNCONFIG_DRIVER: 698 /* unconfig driver never comes with NDI_DEVI_REMOVE */ 699 default: 700 return (DDI_FAILURE); 701 } 702 /*NOTREACHED*/ 703 } 704