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