1 /* $NetBSD: dksubr.c,v 1.51 2014/06/14 07:39:00 hannken Exp $ */ 2 3 /*- 4 * Copyright (c) 1996, 1997, 1998, 1999, 2002, 2008 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jason R. Thorpe and Roland C. Dowdeswell. 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 #include <sys/cdefs.h> 33 __KERNEL_RCSID(0, "$NetBSD: dksubr.c,v 1.51 2014/06/14 07:39:00 hannken Exp $"); 34 35 #include <sys/param.h> 36 #include <sys/systm.h> 37 #include <sys/stat.h> 38 #include <sys/proc.h> 39 #include <sys/ioctl.h> 40 #include <sys/device.h> 41 #include <sys/disk.h> 42 #include <sys/disklabel.h> 43 #include <sys/buf.h> 44 #include <sys/bufq.h> 45 #include <sys/vnode.h> 46 #include <sys/fcntl.h> 47 #include <sys/namei.h> 48 #include <sys/module.h> 49 50 #include <dev/dkvar.h> 51 #include <miscfs/specfs/specdev.h> /* for v_rdev */ 52 53 int dkdebug = 0; 54 55 #ifdef DEBUG 56 #define DKDB_FOLLOW 0x1 57 #define DKDB_INIT 0x2 58 #define DKDB_VNODE 0x4 59 60 #define IFDEBUG(x,y) if (dkdebug & (x)) y 61 #define DPRINTF(x,y) IFDEBUG(x, printf y) 62 #define DPRINTF_FOLLOW(y) DPRINTF(DKDB_FOLLOW, y) 63 #else 64 #define IFDEBUG(x,y) 65 #define DPRINTF(x,y) 66 #define DPRINTF_FOLLOW(y) 67 #endif 68 69 static int dk_subr_modcmd(modcmd_t, void *); 70 71 #define DKLABELDEV(dev) \ 72 (MAKEDISKDEV(major((dev)), DISKUNIT((dev)), RAW_PART)) 73 74 static void dk_makedisklabel(struct dk_intf *, struct dk_softc *); 75 76 void 77 dk_sc_init(struct dk_softc *dksc, const char *xname) 78 { 79 80 memset(dksc, 0x0, sizeof(*dksc)); 81 strncpy(dksc->sc_xname, xname, DK_XNAME_SIZE); 82 dksc->sc_dkdev.dk_name = dksc->sc_xname; 83 } 84 85 /* ARGSUSED */ 86 int 87 dk_open(struct dk_intf *di, struct dk_softc *dksc, dev_t dev, 88 int flags, int fmt, struct lwp *l) 89 { 90 struct disklabel *lp = dksc->sc_dkdev.dk_label; 91 int part = DISKPART(dev); 92 int pmask = 1 << part; 93 int ret = 0; 94 struct disk *dk = &dksc->sc_dkdev; 95 96 DPRINTF_FOLLOW(("dk_open(%s, %p, 0x%"PRIx64", 0x%x)\n", 97 di->di_dkname, dksc, dev, flags)); 98 99 mutex_enter(&dk->dk_openlock); 100 part = DISKPART(dev); 101 102 /* 103 * If there are wedges, and this is not RAW_PART, then we 104 * need to fail. 105 */ 106 if (dk->dk_nwedges != 0 && part != RAW_PART) { 107 ret = EBUSY; 108 goto done; 109 } 110 111 pmask = 1 << part; 112 113 /* 114 * If we're init'ed and there are no other open partitions then 115 * update the in-core disklabel. 116 */ 117 if ((dksc->sc_flags & DKF_INITED)) { 118 if (dk->dk_openmask == 0) { 119 dk_getdisklabel(di, dksc, dev); 120 } 121 /* XXX re-discover wedges? */ 122 } 123 124 /* Fail if we can't find the partition. */ 125 if ((part != RAW_PART) && 126 (((dksc->sc_flags & DKF_INITED) == 0) || 127 ((part >= lp->d_npartitions) || 128 (lp->d_partitions[part].p_fstype == FS_UNUSED)))) { 129 ret = ENXIO; 130 goto done; 131 } 132 133 /* Mark our unit as open. */ 134 switch (fmt) { 135 case S_IFCHR: 136 dk->dk_copenmask |= pmask; 137 break; 138 case S_IFBLK: 139 dk->dk_bopenmask |= pmask; 140 break; 141 } 142 143 dk->dk_openmask = dk->dk_copenmask | dk->dk_bopenmask; 144 145 done: 146 mutex_exit(&dk->dk_openlock); 147 return ret; 148 } 149 150 /* ARGSUSED */ 151 int 152 dk_close(struct dk_intf *di, struct dk_softc *dksc, dev_t dev, 153 int flags, int fmt, struct lwp *l) 154 { 155 int part = DISKPART(dev); 156 int pmask = 1 << part; 157 struct disk *dk = &dksc->sc_dkdev; 158 159 DPRINTF_FOLLOW(("dk_close(%s, %p, 0x%"PRIx64", 0x%x)\n", 160 di->di_dkname, dksc, dev, flags)); 161 162 mutex_enter(&dk->dk_openlock); 163 164 switch (fmt) { 165 case S_IFCHR: 166 dk->dk_copenmask &= ~pmask; 167 break; 168 case S_IFBLK: 169 dk->dk_bopenmask &= ~pmask; 170 break; 171 } 172 dk->dk_openmask = dk->dk_copenmask | dk->dk_bopenmask; 173 174 mutex_exit(&dk->dk_openlock); 175 return 0; 176 } 177 178 void 179 dk_strategy(struct dk_intf *di, struct dk_softc *dksc, struct buf *bp) 180 { 181 int s; 182 int wlabel; 183 daddr_t blkno; 184 185 DPRINTF_FOLLOW(("dk_strategy(%s, %p, %p)\n", 186 di->di_dkname, dksc, bp)); 187 188 if (!(dksc->sc_flags & DKF_INITED)) { 189 DPRINTF_FOLLOW(("dk_strategy: not inited\n")); 190 bp->b_error = ENXIO; 191 biodone(bp); 192 return; 193 } 194 195 /* XXX look for some more errors, c.f. ld.c */ 196 197 bp->b_resid = bp->b_bcount; 198 199 /* If there is nothing to do, then we are done */ 200 if (bp->b_bcount == 0) { 201 biodone(bp); 202 return; 203 } 204 205 wlabel = dksc->sc_flags & (DKF_WLABEL|DKF_LABELLING); 206 if (DISKPART(bp->b_dev) != RAW_PART && 207 bounds_check_with_label(&dksc->sc_dkdev, bp, wlabel) <= 0) { 208 biodone(bp); 209 return; 210 } 211 212 blkno = bp->b_blkno; 213 if (DISKPART(bp->b_dev) != RAW_PART) { 214 struct partition *pp; 215 216 pp = 217 &dksc->sc_dkdev.dk_label->d_partitions[DISKPART(bp->b_dev)]; 218 blkno += pp->p_offset; 219 } 220 bp->b_rawblkno = blkno; 221 222 /* 223 * Start the unit by calling the start routine 224 * provided by the individual driver. 225 */ 226 s = splbio(); 227 bufq_put(dksc->sc_bufq, bp); 228 di->di_diskstart(dksc); 229 splx(s); 230 return; 231 } 232 233 int 234 dk_size(struct dk_intf *di, struct dk_softc *dksc, dev_t dev) 235 { 236 struct disklabel *lp; 237 int is_open; 238 int part; 239 int size; 240 241 if ((dksc->sc_flags & DKF_INITED) == 0) 242 return -1; 243 244 part = DISKPART(dev); 245 is_open = dksc->sc_dkdev.dk_openmask & (1 << part); 246 247 if (!is_open && di->di_open(dev, 0, S_IFBLK, curlwp)) 248 return -1; 249 250 lp = dksc->sc_dkdev.dk_label; 251 if (lp->d_partitions[part].p_fstype != FS_SWAP) 252 size = -1; 253 else 254 size = lp->d_partitions[part].p_size * 255 (lp->d_secsize / DEV_BSIZE); 256 257 if (!is_open && di->di_close(dev, 0, S_IFBLK, curlwp)) 258 return 1; 259 260 return size; 261 } 262 263 int 264 dk_ioctl(struct dk_intf *di, struct dk_softc *dksc, dev_t dev, 265 u_long cmd, void *data, int flag, struct lwp *l) 266 { 267 struct disklabel *lp; 268 struct disk *dk; 269 #ifdef __HAVE_OLD_DISKLABEL 270 struct disklabel newlabel; 271 #endif 272 int error = 0; 273 274 DPRINTF_FOLLOW(("dk_ioctl(%s, %p, 0x%"PRIx64", 0x%lx)\n", 275 di->di_dkname, dksc, dev, cmd)); 276 277 /* ensure that the pseudo disk is open for writes for these commands */ 278 switch (cmd) { 279 case DIOCSDINFO: 280 case DIOCWDINFO: 281 #ifdef __HAVE_OLD_DISKLABEL 282 case ODIOCSDINFO: 283 case ODIOCWDINFO: 284 #endif 285 case DIOCWLABEL: 286 case DIOCAWEDGE: 287 case DIOCDWEDGE: 288 if ((flag & FWRITE) == 0) 289 return EBADF; 290 } 291 292 /* ensure that the pseudo-disk is initialized for these */ 293 switch (cmd) { 294 #ifdef DIOCGSECTORSIZE 295 case DIOCGSECTORSIZE: 296 case DIOCGMEDIASIZE: 297 #endif 298 case DIOCGDINFO: 299 case DIOCSDINFO: 300 case DIOCWDINFO: 301 case DIOCGPART: 302 case DIOCWLABEL: 303 case DIOCGDEFLABEL: 304 case DIOCAWEDGE: 305 case DIOCDWEDGE: 306 case DIOCLWEDGES: 307 case DIOCCACHESYNC: 308 #ifdef __HAVE_OLD_DISKLABEL 309 case ODIOCGDINFO: 310 case ODIOCSDINFO: 311 case ODIOCWDINFO: 312 case ODIOCGDEFLABEL: 313 #endif 314 if ((dksc->sc_flags & DKF_INITED) == 0) 315 return ENXIO; 316 } 317 318 switch (cmd) { 319 #ifdef DIOCGSECTORSIZE 320 case DIOCGSECTORSIZE: 321 *(u_int *)data = dksc->sc_dkdev.dk_geom.dg_secsize; 322 return 0; 323 case DIOCGMEDIASIZE: 324 *(off_t *)data = 325 (off_t)dksc->sc_dkdev.dk_geom.dg_secsize * 326 dksc->sc_dkdev.dk_geom.dg_nsectors; 327 return 0; 328 #endif 329 330 case DIOCGDINFO: 331 *(struct disklabel *)data = *(dksc->sc_dkdev.dk_label); 332 break; 333 334 #ifdef __HAVE_OLD_DISKLABEL 335 case ODIOCGDINFO: 336 newlabel = *(dksc->sc_dkdev.dk_label); 337 if (newlabel.d_npartitions > OLDMAXPARTITIONS) 338 return ENOTTY; 339 memcpy(data, &newlabel, sizeof (struct olddisklabel)); 340 break; 341 #endif 342 343 case DIOCGPART: 344 ((struct partinfo *)data)->disklab = dksc->sc_dkdev.dk_label; 345 ((struct partinfo *)data)->part = 346 &dksc->sc_dkdev.dk_label->d_partitions[DISKPART(dev)]; 347 break; 348 349 case DIOCWDINFO: 350 case DIOCSDINFO: 351 #ifdef __HAVE_OLD_DISKLABEL 352 case ODIOCWDINFO: 353 case ODIOCSDINFO: 354 #endif 355 #ifdef __HAVE_OLD_DISKLABEL 356 if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) { 357 memset(&newlabel, 0, sizeof newlabel); 358 memcpy(&newlabel, data, sizeof (struct olddisklabel)); 359 lp = &newlabel; 360 } else 361 #endif 362 lp = (struct disklabel *)data; 363 364 dk = &dksc->sc_dkdev; 365 mutex_enter(&dk->dk_openlock); 366 dksc->sc_flags |= DKF_LABELLING; 367 368 error = setdisklabel(dksc->sc_dkdev.dk_label, 369 lp, 0, dksc->sc_dkdev.dk_cpulabel); 370 if (error == 0) { 371 if (cmd == DIOCWDINFO 372 #ifdef __HAVE_OLD_DISKLABEL 373 || cmd == ODIOCWDINFO 374 #endif 375 ) 376 error = writedisklabel(DKLABELDEV(dev), 377 di->di_strategy, dksc->sc_dkdev.dk_label, 378 dksc->sc_dkdev.dk_cpulabel); 379 } 380 381 dksc->sc_flags &= ~DKF_LABELLING; 382 mutex_exit(&dk->dk_openlock); 383 break; 384 385 case DIOCWLABEL: 386 if (*(int *)data != 0) 387 dksc->sc_flags |= DKF_WLABEL; 388 else 389 dksc->sc_flags &= ~DKF_WLABEL; 390 break; 391 392 case DIOCGDEFLABEL: 393 dk_getdefaultlabel(di, dksc, (struct disklabel *)data); 394 break; 395 396 #ifdef __HAVE_OLD_DISKLABEL 397 case ODIOCGDEFLABEL: 398 dk_getdefaultlabel(di, dksc, &newlabel); 399 if (newlabel.d_npartitions > OLDMAXPARTITIONS) 400 return ENOTTY; 401 memcpy(data, &newlabel, sizeof (struct olddisklabel)); 402 break; 403 #endif 404 405 case DIOCAWEDGE: 406 { 407 struct dkwedge_info *dkw = (void *)data; 408 409 if ((flag & FWRITE) == 0) 410 return (EBADF); 411 412 /* If the ioctl happens here, the parent is us. */ 413 strcpy(dkw->dkw_parent, dksc->sc_dkdev.dk_name); 414 return (dkwedge_add(dkw)); 415 } 416 417 case DIOCDWEDGE: 418 { 419 struct dkwedge_info *dkw = (void *)data; 420 421 if ((flag & FWRITE) == 0) 422 return (EBADF); 423 424 /* If the ioctl happens here, the parent is us. */ 425 strcpy(dkw->dkw_parent, dksc->sc_dkdev.dk_name); 426 return (dkwedge_del(dkw)); 427 } 428 429 case DIOCLWEDGES: 430 { 431 struct dkwedge_list *dkwl = (void *)data; 432 433 return (dkwedge_list(&dksc->sc_dkdev, dkwl, l)); 434 } 435 436 case DIOCGSTRATEGY: 437 { 438 struct disk_strategy *dks = (void *)data; 439 int s; 440 441 s = splbio(); 442 strlcpy(dks->dks_name, bufq_getstrategyname(dksc->sc_bufq), 443 sizeof(dks->dks_name)); 444 splx(s); 445 dks->dks_paramlen = 0; 446 447 return 0; 448 } 449 450 case DIOCSSTRATEGY: 451 { 452 struct disk_strategy *dks = (void *)data; 453 struct bufq_state *new; 454 struct bufq_state *old; 455 int s; 456 457 if ((flag & FWRITE) == 0) { 458 return EBADF; 459 } 460 if (dks->dks_param != NULL) { 461 return EINVAL; 462 } 463 dks->dks_name[sizeof(dks->dks_name) - 1] = 0; /* ensure term */ 464 error = bufq_alloc(&new, dks->dks_name, 465 BUFQ_EXACT|BUFQ_SORT_RAWBLOCK); 466 if (error) { 467 return error; 468 } 469 s = splbio(); 470 old = dksc->sc_bufq; 471 bufq_move(new, old); 472 dksc->sc_bufq = new; 473 splx(s); 474 bufq_free(old); 475 476 return 0; 477 } 478 479 default: 480 error = ENOTTY; 481 } 482 483 return error; 484 } 485 486 /* 487 * dk_dump dumps all of physical memory into the partition specified. 488 * This requires substantially more framework than {s,w}ddump, and hence 489 * is probably much more fragile. 490 * 491 * XXX: we currently do not implement this. 492 */ 493 494 #define DKF_READYFORDUMP (DKF_INITED|DKF_TAKEDUMP) 495 #define DKFF_READYFORDUMP(x) (((x) & DKF_READYFORDUMP) == DKF_READYFORDUMP) 496 static volatile int dk_dumping = 0; 497 498 /* ARGSUSED */ 499 int 500 dk_dump(struct dk_intf *di, struct dk_softc *dksc, dev_t dev, 501 daddr_t blkno, void *va, size_t size) 502 { 503 504 /* 505 * ensure that we consider this device to be safe for dumping, 506 * and that the device is configured. 507 */ 508 if (!DKFF_READYFORDUMP(dksc->sc_flags)) 509 return ENXIO; 510 511 /* ensure that we are not already dumping */ 512 if (dk_dumping) 513 return EFAULT; 514 dk_dumping = 1; 515 516 /* XXX: unimplemented */ 517 518 dk_dumping = 0; 519 520 /* XXX: actually for now, we are going to leave this alone */ 521 return ENXIO; 522 } 523 524 /* ARGSUSED */ 525 void 526 dk_getdefaultlabel(struct dk_intf *di, struct dk_softc *dksc, 527 struct disklabel *lp) 528 { 529 struct disk_geom *dg = &dksc->sc_dkdev.dk_geom; 530 531 memset(lp, 0, sizeof(*lp)); 532 533 lp->d_secperunit = dg->dg_secperunit; 534 lp->d_secsize = dg->dg_secsize; 535 lp->d_nsectors = dg->dg_nsectors; 536 lp->d_ntracks = dg->dg_ntracks; 537 lp->d_ncylinders = dg->dg_ncylinders; 538 lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors; 539 540 strncpy(lp->d_typename, di->di_dkname, sizeof(lp->d_typename)); 541 lp->d_type = di->di_dtype; 542 strncpy(lp->d_packname, "fictitious", sizeof(lp->d_packname)); 543 lp->d_rpm = 3600; 544 lp->d_interleave = 1; 545 lp->d_flags = 0; 546 547 lp->d_partitions[RAW_PART].p_offset = 0; 548 lp->d_partitions[RAW_PART].p_size = dg->dg_secperunit; 549 lp->d_partitions[RAW_PART].p_fstype = FS_UNUSED; 550 lp->d_npartitions = RAW_PART + 1; 551 552 lp->d_magic = DISKMAGIC; 553 lp->d_magic2 = DISKMAGIC; 554 lp->d_checksum = dkcksum(dksc->sc_dkdev.dk_label); 555 } 556 557 /* ARGSUSED */ 558 void 559 dk_getdisklabel(struct dk_intf *di, struct dk_softc *dksc, dev_t dev) 560 { 561 struct disklabel *lp = dksc->sc_dkdev.dk_label; 562 struct cpu_disklabel *clp = dksc->sc_dkdev.dk_cpulabel; 563 struct disk_geom *dg = &dksc->sc_dkdev.dk_geom; 564 struct partition *pp; 565 int i; 566 const char *errstring; 567 568 memset(clp, 0x0, sizeof(*clp)); 569 dk_getdefaultlabel(di, dksc, lp); 570 errstring = readdisklabel(DKLABELDEV(dev), di->di_strategy, 571 dksc->sc_dkdev.dk_label, dksc->sc_dkdev.dk_cpulabel); 572 if (errstring) { 573 dk_makedisklabel(di, dksc); 574 if (dksc->sc_flags & DKF_WARNLABEL) 575 printf("%s: %s\n", dksc->sc_xname, errstring); 576 return; 577 } 578 579 if ((dksc->sc_flags & DKF_LABELSANITY) == 0) 580 return; 581 582 /* Sanity check */ 583 if (lp->d_secperunit != dg->dg_secperunit) 584 printf("WARNING: %s: total sector size in disklabel (%d) " 585 "!= the size of %s (%" PRId64 ")\n", dksc->sc_xname, 586 lp->d_secperunit, di->di_dkname, dg->dg_secperunit); 587 588 for (i=0; i < lp->d_npartitions; i++) { 589 pp = &lp->d_partitions[i]; 590 if (pp->p_offset + pp->p_size > dg->dg_secperunit) 591 printf("WARNING: %s: end of partition `%c' exceeds " 592 "the size of %s (%" PRId64 ")\n", dksc->sc_xname, 593 'a' + i, di->di_dkname, dg->dg_secperunit); 594 } 595 } 596 597 /* ARGSUSED */ 598 static void 599 dk_makedisklabel(struct dk_intf *di, struct dk_softc *dksc) 600 { 601 struct disklabel *lp = dksc->sc_dkdev.dk_label; 602 603 lp->d_partitions[RAW_PART].p_fstype = FS_BSDFFS; 604 strncpy(lp->d_packname, "default label", sizeof(lp->d_packname)); 605 lp->d_checksum = dkcksum(lp); 606 } 607 608 /* This function is taken from ccd.c:1.76 --rcd */ 609 610 /* 611 * XXX this function looks too generic for dksubr.c, shouldn't we 612 * put it somewhere better? 613 */ 614 615 /* 616 * Lookup the provided name in the filesystem. If the file exists, 617 * is a valid block device, and isn't being used by anyone else, 618 * set *vpp to the file's vnode. 619 */ 620 int 621 dk_lookup(struct pathbuf *pb, struct lwp *l, struct vnode **vpp) 622 { 623 struct nameidata nd; 624 struct vnode *vp; 625 int error; 626 627 if (l == NULL) 628 return ESRCH; /* Is ESRCH the best choice? */ 629 630 NDINIT(&nd, LOOKUP, FOLLOW, pb); 631 if ((error = vn_open(&nd, FREAD | FWRITE, 0)) != 0) { 632 DPRINTF((DKDB_FOLLOW|DKDB_INIT), 633 ("dk_lookup: vn_open error = %d\n", error)); 634 return error; 635 } 636 637 vp = nd.ni_vp; 638 if (vp->v_type != VBLK) { 639 error = ENOTBLK; 640 goto out; 641 } 642 643 /* Reopen as anonymous vnode to protect against forced unmount. */ 644 if ((error = bdevvp(vp->v_rdev, vpp)) != 0) 645 goto out; 646 VOP_UNLOCK(vp); 647 if ((error = vn_close(vp, FREAD | FWRITE, l->l_cred)) != 0) { 648 vrele(*vpp); 649 return error; 650 } 651 if ((error = VOP_OPEN(*vpp, FREAD | FWRITE, l->l_cred)) != 0) { 652 vrele(*vpp); 653 return error; 654 } 655 mutex_enter((*vpp)->v_interlock); 656 (*vpp)->v_writecount++; 657 mutex_exit((*vpp)->v_interlock); 658 659 IFDEBUG(DKDB_VNODE, vprint("dk_lookup: vnode info", *vpp)); 660 661 return 0; 662 out: 663 VOP_UNLOCK(vp); 664 (void) vn_close(vp, FREAD | FWRITE, l->l_cred); 665 return error; 666 } 667 668 MODULE(MODULE_CLASS_MISC, dk_subr, NULL); 669 670 static int 671 dk_subr_modcmd(modcmd_t cmd, void *arg) 672 { 673 switch (cmd) { 674 case MODULE_CMD_INIT: 675 case MODULE_CMD_FINI: 676 return 0; 677 case MODULE_CMD_STAT: 678 case MODULE_CMD_AUTOUNLOAD: 679 default: 680 return ENOTTY; 681 } 682 } 683