1 /* $netBSD: iscsi_main.c,v 1.1.1.1 2011/05/02 07:01:11 agc Exp $ */ 2 3 /*- 4 * Copyright (c) 2004,2005,2006,2011 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Wasabi Systems, Inc. 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 #include "iscsi_globals.h" 32 33 #include <sys/systm.h> 34 #include <sys/buf.h> 35 #include <sys/file.h> 36 #include <sys/filedesc.h> 37 #include <sys/kmem.h> 38 #include <sys/socketvar.h> 39 #include <sys/sysctl.h> 40 41 #include "ioconf.h" 42 43 /*------------------------- Global Variables ------------------------*/ 44 45 extern struct cfdriver iscsi_cd; 46 47 #if defined(ISCSI_DEBUG) 48 int iscsi_debug_level = ISCSI_DEBUG; 49 #endif 50 51 bool iscsi_detaching; 52 53 /* the list of sessions */ 54 session_list_t iscsi_sessions = TAILQ_HEAD_INITIALIZER(iscsi_sessions); 55 56 /* the number of active send threads (for cleanup thread) */ 57 uint32_t iscsi_num_send_threads = 0; 58 59 /* Our node name, alias, and ISID */ 60 uint8_t iscsi_InitiatorName[ISCSI_STRING_LENGTH] = ""; 61 uint8_t iscsi_InitiatorAlias[ISCSI_STRING_LENGTH] = ""; 62 login_isid_t iscsi_InitiatorISID; 63 64 /******************************************************************************/ 65 66 /* 67 System interface: autoconf and device structures 68 */ 69 70 static void iscsi_attach(device_t parent, device_t self, void *aux); 71 static int iscsi_match(device_t, cfdata_t, void *); 72 static int iscsi_detach(device_t, int); 73 74 struct iscsi_softc { 75 device_t dev; 76 kmutex_t lock; 77 TAILQ_HEAD(, iscsifd) fds; 78 }; 79 80 CFATTACH_DECL_NEW(iscsi, sizeof(struct iscsi_softc), iscsi_match, iscsi_attach, 81 iscsi_detach, NULL); 82 83 84 static dev_type_open(iscsiopen); 85 static int iscsiclose(struct file *); 86 87 static const struct fileops iscsi_fileops = { 88 .fo_ioctl = iscsiioctl, 89 .fo_close = iscsiclose, 90 }; 91 92 struct cdevsw iscsi_cdevsw = { 93 .d_open = iscsiopen, 94 .d_close = noclose, 95 .d_read = noread, 96 .d_write = nowrite, 97 .d_ioctl = noioctl, 98 .d_stop = nostop, 99 .d_tty = notty, 100 .d_poll = nopoll, 101 .d_mmap = nommap, 102 .d_kqfilter = nokqfilter, 103 .d_discard = nodiscard, 104 .d_flag = D_OTHER 105 }; 106 107 /******************************************************************************/ 108 109 STATIC void iscsi_scsipi_request(struct scsipi_channel *, 110 scsipi_adapter_req_t, void *); 111 STATIC void iscsi_minphys(struct buf *); 112 113 /******************************************************************************/ 114 115 /******************************************************************************* 116 * Open and Close device interfaces. We don't really need them, because we don't 117 * have to keep track of device opens and closes from userland. But apps can't 118 * call ioctl without a handle to the device, and the kernel doesn't hand out 119 * handles without an open routine in the driver. So here they are in all their 120 * glory... 121 *******************************************************************************/ 122 123 int 124 iscsiopen(dev_t dev, int flag, int mode, struct lwp *l) 125 { 126 struct iscsifd *d; 127 struct iscsi_softc *sc; 128 struct file *fp; 129 int error, fd, unit; 130 131 unit = minor(dev); 132 133 DEB(99, ("ISCSI Open unit=%d\n",unit)); 134 135 sc = device_lookup_private(&iscsi_cd, unit); 136 if (sc == NULL) 137 return ENXIO; 138 139 if ((error = fd_allocfile(&fp, &fd)) != 0) 140 return error; 141 142 d = kmem_alloc(sizeof(*d), KM_SLEEP); 143 d->dev = sc->dev; 144 d->unit = unit; 145 146 mutex_enter(&sc->lock); 147 if (iscsi_detaching) { 148 mutex_exit(&sc->lock); 149 kmem_free(d, sizeof(*d)); 150 DEB(99, ("ISCSI Open aborting\n")); 151 fd_abort(curproc, fp, fd); 152 return ENXIO; 153 } 154 TAILQ_INSERT_TAIL(&sc->fds, d, link); 155 mutex_exit(&sc->lock); 156 157 return fd_clone(fp, fd, flag, &iscsi_fileops, d); 158 } 159 160 static int 161 iscsiclose(struct file *fp) 162 { 163 struct iscsifd *d = fp->f_iscsi; 164 struct iscsi_softc *sc; 165 166 sc = device_lookup_private(&iscsi_cd, d->unit); 167 if (sc == NULL) { 168 DEBOUT(("%s: Cannot find private data\n",__func__)); 169 return ENXIO; 170 } 171 172 mutex_enter(&sc->lock); 173 TAILQ_REMOVE(&sc->fds, d, link); 174 mutex_exit(&sc->lock); 175 176 kmem_free(d, sizeof(*d)); 177 fp->f_iscsi = NULL; 178 179 DEB(99, ("ISCSI Close\n")); 180 return 0; 181 } 182 183 /******************************************************************************/ 184 185 /* 186 * The config Match routine. 187 * Not much to do here, either - this is a pseudo-device. 188 */ 189 190 static int 191 iscsi_match(device_t self, cfdata_t cfdata, void *arg) 192 { 193 return 1; 194 } 195 196 /* 197 * iscsiattach: 198 * Only called when statically configured into a kernel 199 */ 200 void 201 iscsiattach(int n) 202 { 203 int err; 204 cfdata_t cf; 205 206 err = config_cfattach_attach(iscsi_cd.cd_name, &iscsi_ca); 207 if (err) { 208 aprint_error("%s: couldn't register cfattach: %d\n", 209 iscsi_cd.cd_name, err); 210 config_cfdriver_detach(&iscsi_cd); 211 return; 212 } 213 214 if (n > 1) 215 aprint_error("%s: only one device supported\n", 216 iscsi_cd.cd_name); 217 218 cf = kmem_alloc(sizeof(struct cfdata), KM_NOSLEEP); 219 if (cf == NULL) { 220 aprint_error("%s: couldn't allocate cfdata\n", 221 iscsi_cd.cd_name); 222 return; 223 } 224 cf->cf_name = iscsi_cd.cd_name; 225 cf->cf_atname = iscsi_cd.cd_name; 226 cf->cf_unit = 0; 227 cf->cf_fstate = FSTATE_NOTFOUND; 228 229 (void)config_attach_pseudo(cf); 230 return; 231 } 232 233 /* 234 * iscsi_attach: 235 * One-time inits go here. Not much for now, probably even less later. 236 */ 237 static void 238 iscsi_attach(device_t parent, device_t self, void *aux) 239 { 240 struct iscsi_softc *sc; 241 242 DEB(1, ("ISCSI: iscsi_attach, parent=%p, self=%p, aux=%p\n", parent, 243 self, aux)); 244 sc = (struct iscsi_softc *) device_private(self); 245 sc->dev = self; 246 247 TAILQ_INIT(&sc->fds); 248 mutex_init(&sc->lock, MUTEX_DEFAULT, IPL_NONE); 249 250 iscsi_detaching = false; 251 iscsi_init_cleanup(); 252 253 aprint_normal("%s: attached. major = %d\n", iscsi_cd.cd_name, 254 cdevsw_lookup_major(&iscsi_cdevsw)); 255 } 256 257 /* 258 * iscsi_detach: 259 * Cleanup. 260 */ 261 static int 262 iscsi_detach(device_t self, int flags) 263 { 264 struct iscsi_softc *sc; 265 int error; 266 267 DEB(1, ("ISCSI: detach\n")); 268 sc = (struct iscsi_softc *) device_private(self); 269 270 mutex_enter(&sc->lock); 271 if (!TAILQ_EMPTY(&sc->fds)) { 272 mutex_exit(&sc->lock); 273 return EBUSY; 274 } 275 iscsi_detaching = true; 276 mutex_exit(&sc->lock); 277 278 error = kill_all_sessions(); 279 if (error) 280 return error; 281 282 error = iscsi_destroy_cleanup(); 283 if (error) 284 return error; 285 286 mutex_destroy(&sc->lock); 287 288 return 0; 289 } 290 291 /******************************************************************************/ 292 293 typedef struct quirktab_t { 294 const char *tgt; 295 const char *iqn; 296 uint32_t quirks; 297 } quirktab_t; 298 299 static const quirktab_t quirktab[] = { 300 { "StarWind", "iqn.2008-08.com.starwindsoftware", PQUIRK_ONLYBIG }, 301 { "UNH", "iqn.2002-10.edu.unh.", 302 PQUIRK_NOBIGMODESENSE | 303 PQUIRK_NOMODESENSE | 304 PQUIRK_NOSYNCCACHE }, 305 { "NetBSD", "iqn.1994-04.org.netbsd.", 0 }, 306 { "Unknown", "unknown", 0 }, 307 { NULL, NULL, 0 } 308 }; 309 310 /* loop through the quirktab looking for a match on target name */ 311 static const quirktab_t * 312 getquirks(const char *iqn) 313 { 314 const quirktab_t *qp; 315 size_t iqnlen, quirklen; 316 317 if (iqn == NULL) 318 iqn = "unknown"; 319 iqnlen = strlen(iqn); 320 for (qp = quirktab ; qp->iqn ; qp++) { 321 quirklen = strlen(qp->iqn); 322 if (quirklen > iqnlen) 323 continue; 324 if (memcmp(qp->iqn, iqn, quirklen) == 0) 325 break; 326 } 327 return qp; 328 } 329 330 /******************************************************************************/ 331 332 /* 333 * map_session 334 * This (indirectly) maps the existing LUNs for a target to SCSI devices 335 * by going through config_found to tell any child drivers that there's 336 * a new adapter. 337 * Note that each session is equivalent to a SCSI adapter. 338 * 339 * Parameter: the session pointer 340 * 341 * Returns: 1 on success, 0 on failure 342 * 343 * ToDo: Figuring out how to handle more than one LUN. It appears that 344 * the NetBSD SCSI LUN discovery doesn't use "report LUNs", and instead 345 * goes through the LUNs sequentially, stopping somewhere on the way if it 346 * gets an error. We may have to do some LUN mapping in here if this is 347 * really how things work. 348 */ 349 350 int 351 map_session(session_t *session, device_t dev) 352 { 353 struct scsipi_adapter *adapt = &session->sc_adapter; 354 struct scsipi_channel *chan = &session->sc_channel; 355 const quirktab_t *tgt; 356 357 mutex_enter(&session->lock); 358 session->send_window = max(2, window_size(session, CCBS_FOR_SCSIPI)); 359 mutex_exit(&session->lock); 360 361 /* 362 * Fill in the scsipi_adapter. 363 */ 364 adapt->adapt_dev = dev; 365 adapt->adapt_nchannels = 1; 366 adapt->adapt_request = iscsi_scsipi_request; 367 adapt->adapt_minphys = iscsi_minphys; 368 adapt->adapt_openings = session->send_window; 369 adapt->adapt_max_periph = CCBS_FOR_SCSIPI; 370 adapt->adapt_flags = SCSIPI_ADAPT_MPSAFE; 371 372 /* 373 * Fill in the scsipi_channel. 374 */ 375 if ((tgt = getquirks(chan->chan_name)) == NULL) { 376 tgt = getquirks("unknown"); 377 } 378 chan->chan_name = tgt->tgt; 379 chan->chan_defquirks = tgt->quirks; 380 chan->chan_adapter = adapt; 381 chan->chan_bustype = &scsi_bustype; 382 chan->chan_channel = 0; 383 chan->chan_flags = SCSIPI_CHAN_NOSETTLE | SCSIPI_CHAN_CANGROW; 384 chan->chan_ntargets = 1; 385 chan->chan_nluns = 16; /* ToDo: ??? */ 386 chan->chan_id = session->id; 387 388 session->child_dev = config_found(dev, chan, scsiprint); 389 390 return session->child_dev != NULL; 391 } 392 393 394 /* 395 * unmap_session 396 * This (indirectly) unmaps the existing all LUNs for a target by 397 * telling the config system that the adapter has detached. 398 * 399 * Parameter: the session pointer 400 * 401 * Returns: 1 on success, 0 on failure 402 */ 403 404 int 405 unmap_session(session_t *session) 406 { 407 device_t dev; 408 int rv = 1; 409 410 if ((dev = session->child_dev) != NULL) { 411 session->child_dev = NULL; 412 if (config_detach(dev, 0)) 413 rv = 0; 414 } 415 416 return rv; 417 } 418 419 /* 420 * grow_resources 421 * Try to grow openings up to current window size 422 */ 423 static void 424 grow_resources(session_t *session) 425 { 426 struct scsipi_adapter *adapt = &session->sc_adapter; 427 int win; 428 429 mutex_enter(&session->lock); 430 if (session->refcount < CCBS_FOR_SCSIPI && 431 session->send_window < CCBS_FOR_SCSIPI) { 432 win = window_size(session, CCBS_FOR_SCSIPI - session->refcount); 433 if (win > session->send_window) { 434 session->send_window++; 435 adapt->adapt_openings++; 436 DEB(5, ("Grow send window to %d\n", session->send_window)); 437 } 438 } 439 mutex_exit(&session->lock); 440 } 441 442 /******************************************************************************/ 443 444 /***************************************************************************** 445 * SCSI interface routines 446 *****************************************************************************/ 447 448 /* 449 * iscsi_scsipi_request: 450 * Perform a request for the SCSIPI layer. 451 */ 452 453 void 454 iscsi_scsipi_request(struct scsipi_channel *chan, scsipi_adapter_req_t req, 455 void *arg) 456 { 457 struct scsipi_adapter *adapt = chan->chan_adapter; 458 struct scsipi_xfer *xs; 459 session_t *session; 460 int flags; 461 struct scsipi_xfer_mode *xm; 462 int error; 463 464 session = (session_t *) adapt; /* adapter is first field in session */ 465 466 error = ref_session(session); 467 468 switch (req) { 469 case ADAPTER_REQ_RUN_XFER: 470 DEB(9, ("ISCSI: scsipi_request RUN_XFER\n")); 471 xs = arg; 472 flags = xs->xs_control; 473 474 if (error) { 475 DEB(9, ("ISCSI: refcount too high: %d, winsize %d\n", 476 session->refcount, session->send_window)); 477 xs->error = XS_BUSY; 478 xs->status = XS_BUSY; 479 scsipi_done(xs); 480 return; 481 } 482 483 if ((flags & XS_CTL_POLL) != 0) { 484 xs->error = XS_DRIVER_STUFFUP; 485 DEBOUT(("Run Xfer request with polling\n")); 486 scsipi_done(xs); 487 break; 488 } 489 /* 490 * NOTE: It appears that XS_CTL_DATA_UIO is not actually used anywhere. 491 * Since it really would complicate matters to handle offsets 492 * into scatter-gather lists, and a number of other drivers don't 493 * handle uio-based data as well, XS_CTL_DATA_UIO isn't 494 * implemented in this driver (at least for now). 495 */ 496 if (flags & XS_CTL_DATA_UIO) { 497 xs->error = XS_DRIVER_STUFFUP; 498 DEBOUT(("Run Xfer with data in UIO\n")); 499 scsipi_done(xs); 500 break; 501 } 502 503 send_run_xfer(session, xs); 504 DEB(15, ("scsipi_req returns, refcount = %d\n", session->refcount)); 505 return; 506 507 case ADAPTER_REQ_GROW_RESOURCES: 508 DEB(5, ("ISCSI: scsipi_request GROW_RESOURCES\n")); 509 grow_resources(session); 510 break; 511 512 case ADAPTER_REQ_SET_XFER_MODE: 513 DEB(5, ("ISCSI: scsipi_request SET_XFER_MODE\n")); 514 xm = (struct scsipi_xfer_mode *)arg; 515 xm->xm_mode = PERIPH_CAP_TQING; 516 scsipi_async_event(chan, ASYNC_EVENT_XFER_MODE, xm); 517 break; 518 519 default: 520 DEBOUT(("ISCSI: scsipi_request with invalid REQ code %d\n", req)); 521 break; 522 } 523 524 if (!error) 525 unref_session(session); 526 } 527 528 /* cap the transfer at 64K */ 529 #define ISCSI_MAX_XFER 65536 530 531 /* 532 * iscsi_minphys: 533 * Limit a transfer to our maximum transfer size. 534 */ 535 536 void 537 iscsi_minphys(struct buf *bp) 538 { 539 if (bp->b_bcount > ISCSI_MAX_XFER) { 540 bp->b_bcount = ISCSI_MAX_XFER; 541 } 542 } 543 544 /***************************************************************************** 545 * SCSI job execution helper routines 546 *****************************************************************************/ 547 548 /* 549 * iscsi_done: 550 * 551 * A CCB has completed execution. Pass the status back to the 552 * upper layer. 553 */ 554 void 555 iscsi_done(ccb_t *ccb) 556 { 557 struct scsipi_xfer *xs = ccb->xs; 558 DEB(9, ("iscsi_done\n")); 559 560 if (xs != NULL) { 561 xs->resid = ccb->residual; 562 563 switch (ccb->status) { 564 case ISCSI_STATUS_SUCCESS: 565 xs->error = XS_NOERROR; 566 xs->status = SCSI_OK; 567 break; 568 569 case ISCSI_STATUS_CHECK_CONDITION: 570 xs->error = XS_SENSE; 571 xs->status = SCSI_CHECK; 572 break; 573 574 case ISCSI_STATUS_TARGET_BUSY: 575 case ISCSI_STATUS_NO_RESOURCES: 576 DEBC(ccb->connection, 5, ("target busy, ccb %p\n", ccb)); 577 xs->error = XS_BUSY; 578 xs->status = SCSI_BUSY; 579 break; 580 581 case ISCSI_STATUS_SOCKET_ERROR: 582 case ISCSI_STATUS_TIMEOUT: 583 xs->error = XS_SELTIMEOUT; 584 xs->status = SCSI_BUSY; 585 break; 586 587 case ISCSI_STATUS_QUEUE_FULL: 588 DEBC(ccb->connection, 5, ("queue full, ccb %p\n", ccb)); 589 xs->error = XS_BUSY; 590 xs->status = SCSI_QUEUE_FULL; 591 break; 592 593 default: 594 xs->error = XS_DRIVER_STUFFUP; 595 break; 596 } 597 598 DEB(99, ("Calling scsipi_done (%p), err = %d\n", xs, xs->error)); 599 scsipi_done(xs); 600 DEB(99, ("scsipi_done returned\n")); 601 } else { 602 DEBOUT(("ISCSI: iscsi_done CCB %p without XS\n", ccb)); 603 } 604 605 unref_session(ccb->session); 606 } 607 608 SYSCTL_SETUP(sysctl_iscsi_setup, "ISCSI subtree setup") 609 { 610 const struct sysctlnode *node = NULL; 611 612 sysctl_createv(clog, 0, NULL, &node, 613 CTLFLAG_PERMANENT, 614 CTLTYPE_NODE, "iscsi", 615 SYSCTL_DESCR("iscsi controls"), 616 NULL, 0, NULL, 0, 617 CTL_HW, CTL_CREATE, CTL_EOL); 618 619 #ifdef ISCSI_DEBUG 620 sysctl_createv(clog, 0, &node, NULL, 621 CTLFLAG_PERMANENT|CTLFLAG_READWRITE, 622 CTLTYPE_INT, "debug", 623 SYSCTL_DESCR("debug level"), 624 NULL, 0, &iscsi_debug_level, sizeof(iscsi_debug_level), 625 CTL_CREATE, CTL_EOL); 626 #endif 627 } 628 629 630 /* Kernel Module support */ 631 632 #include <sys/module.h> 633 634 MODULE(MODULE_CLASS_DRIVER, iscsi, NULL); /* Possibly a builtin module */ 635 636 #ifdef _MODULE 637 static const struct cfiattrdata ibescsi_info = { "scsi", 1, 638 {{"channel", "-1", -1},} 639 }; 640 641 static const struct cfiattrdata *const iscsi_attrs[] = { &ibescsi_info, NULL }; 642 643 CFDRIVER_DECL(iscsi, DV_DULL, iscsi_attrs); 644 645 static struct cfdata iscsi_cfdata[] = { 646 { 647 .cf_name = "iscsi", 648 .cf_atname = "iscsi", 649 .cf_unit = 0, /* Only unit 0 is ever used */ 650 .cf_fstate = FSTATE_NOTFOUND, 651 .cf_loc = NULL, 652 .cf_flags = 0, 653 .cf_pspec = NULL, 654 }, 655 { NULL, NULL, 0, 0, NULL, 0, NULL } 656 }; 657 #endif 658 659 static int 660 iscsi_modcmd(modcmd_t cmd, void *arg) 661 { 662 #ifdef _MODULE 663 devmajor_t cmajor = NODEVMAJOR, bmajor = NODEVMAJOR; 664 int error; 665 static struct sysctllog *clog; 666 #endif 667 668 switch (cmd) { 669 case MODULE_CMD_INIT: 670 #ifdef _MODULE 671 error = config_cfdriver_attach(&iscsi_cd); 672 if (error) { 673 return error; 674 } 675 676 error = config_cfattach_attach(iscsi_cd.cd_name, &iscsi_ca); 677 if (error) { 678 config_cfdriver_detach(&iscsi_cd); 679 aprint_error("%s: unable to register cfattach\n", 680 iscsi_cd.cd_name); 681 return error; 682 } 683 684 error = config_cfdata_attach(iscsi_cfdata, 1); 685 if (error) { 686 aprint_error("%s: unable to attach cfdata\n", 687 iscsi_cd.cd_name); 688 config_cfattach_detach(iscsi_cd.cd_name, &iscsi_ca); 689 config_cfdriver_detach(&iscsi_cd); 690 return error; 691 } 692 693 error = devsw_attach(iscsi_cd.cd_name, NULL, &bmajor, 694 &iscsi_cdevsw, &cmajor); 695 if (error) { 696 aprint_error("%s: unable to register devsw\n", 697 iscsi_cd.cd_name); 698 config_cfdata_detach(iscsi_cfdata); 699 config_cfattach_detach(iscsi_cd.cd_name, &iscsi_ca); 700 config_cfdriver_detach(&iscsi_cd); 701 return error; 702 } 703 704 if (config_attach_pseudo(iscsi_cfdata) == NULL) { 705 aprint_error("%s: config_attach_pseudo failed\n", 706 iscsi_cd.cd_name); 707 config_cfattach_detach(iscsi_cd.cd_name, &iscsi_ca); 708 config_cfdriver_detach(&iscsi_cd); 709 return ENXIO; 710 } 711 712 sysctl_iscsi_setup(&clog); 713 #endif 714 return 0; 715 break; 716 717 case MODULE_CMD_FINI: 718 #ifdef _MODULE 719 error = config_cfdata_detach(iscsi_cfdata); 720 if (error) 721 return error; 722 723 sysctl_teardown(&clog); 724 725 config_cfattach_detach(iscsi_cd.cd_name, &iscsi_ca); 726 config_cfdriver_detach(&iscsi_cd); 727 devsw_detach(NULL, &iscsi_cdevsw); 728 #endif 729 return 0; 730 break; 731 732 case MODULE_CMD_AUTOUNLOAD: 733 return EBUSY; 734 break; 735 736 default: 737 return ENOTTY; 738 break; 739 } 740 } 741