|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1988 Regents of the University of California. ! 3: * All rights reserved. ! 4: * ! 5: * This code is derived from software contributed to Berkeley by ! 6: * Computer Consoles Inc. ! 7: * ! 8: * Redistribution and use in source and binary forms, with or without ! 9: * modification, are permitted provided that the following conditions ! 10: * are met: ! 11: * 1. Redistributions of source code must retain the above copyright ! 12: * notice, this list of conditions and the following disclaimer. ! 13: * 2. Redistributions in binary form must reproduce the above copyright ! 14: * notice, this list of conditions and the following disclaimer in the ! 15: * documentation and/or other materials provided with the distribution. ! 16: * 3. All advertising materials mentioning features or use of this software ! 17: * must display the following acknowledgement: ! 18: * This product includes software developed by the University of ! 19: * California, Berkeley and its contributors. ! 20: * 4. Neither the name of the University nor the names of its contributors ! 21: * may be used to endorse or promote products derived from this software ! 22: * without specific prior written permission. ! 23: * ! 24: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ! 25: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ! 26: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ! 27: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ! 28: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ! 29: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ! 30: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ! 31: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ! 32: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ! 33: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ! 34: * SUCH DAMAGE. ! 35: * ! 36: * @(#)vd.c 7.14 (Berkeley) 4/1/91 ! 37: */ ! 38: ! 39: #include "dk.h" ! 40: #if NVD > 0 ! 41: /* ! 42: * Versabus VDDC/SMDE driver. ! 43: */ ! 44: #include "sys/param.h" ! 45: #include "sys/buf.h" ! 46: #include "sys/cmap.h" ! 47: #include "sys/conf.h" ! 48: #include "sys/dkstat.h" ! 49: #include "sys/disklabel.h" ! 50: #include "sys/map.h" ! 51: #include "sys/file.h" ! 52: #include "sys/systm.h" ! 53: #include "sys/user.h" ! 54: #include "sys/vmmac.h" ! 55: #include "sys/proc.h" ! 56: #include "sys/syslog.h" ! 57: #include "sys/kernel.h" ! 58: #include "sys/ioctl.h" ! 59: #include "sys/stat.h" ! 60: ! 61: #include "../include/cpu.h" ! 62: #include "../include/mtpr.h" ! 63: #include "../include/pte.h" ! 64: ! 65: #include "../vba/vbavar.h" ! 66: #include "../vba/vdreg.h" ! 67: ! 68: #ifndef COMPAT_42 ! 69: #define COMPAT_42 ! 70: #endif ! 71: #define B_FORMAT B_XXX /* XXX */ ! 72: ! 73: #define vdunit(dev) (minor(dev) >> 3) ! 74: #define vdpart(dev) (minor(dev) & 0x07) ! 75: #define vdminor(unit,part) (((unit) << 3) | (part)) ! 76: ! 77: struct vba_ctlr *vdminfo[NVD]; ! 78: struct vba_device *vddinfo[NDK]; ! 79: int vdprobe(), vdslave(), vdattach(), vddgo(), vdstrategy(); ! 80: long vdstd[] = { 0xffff2000, 0xffff2100, 0xffff2200, 0xffff2300, 0 }; ! 81: struct vba_driver vddriver = ! 82: { vdprobe, vdslave, vdattach, vddgo, vdstd, "dk", vddinfo, "vd", vdminfo }; ! 83: ! 84: /* ! 85: * Per-controller state. ! 86: */ ! 87: struct vdsoftc { ! 88: u_short vd_flags; ! 89: #define VD_PRINT 0x1 /* controller info printed */ ! 90: #define VD_STARTED 0x2 /* start command issued */ ! 91: #define VD_DOSEEKS 0x4 /* should overlap seeks */ ! 92: #define VD_SCATGATH 0x8 /* can do scatter-gather commands (correctly) */ ! 93: #define VD_LOCKED 0x10 /* locked for direct controller access */ ! 94: #define VD_WAIT 0x20 /* someone needs direct controller access */ ! 95: u_short vd_type; /* controller type */ ! 96: u_short vd_wticks; /* timeout */ ! 97: u_short vd_secsize; /* sector size for controller */ ! 98: struct mdcb vd_mdcb; /* master command block */ ! 99: u_long vd_mdcbphys; /* physical address of vd_mdcb */ ! 100: struct dcb vd_dcb; /* i/o command block */ ! 101: u_long vd_dcbphys; /* physical address of vd_dcb */ ! 102: struct vb_buf vd_rbuf; /* vba resources */ ! 103: } vdsoftc[NVD]; ! 104: ! 105: #define VDMAXTIME 20 /* max time for operation, sec. */ ! 106: ! 107: /* ! 108: * Per-drive state. ! 109: */ ! 110: struct dksoftc { ! 111: int dk_state; /* open fsm */ ! 112: #ifndef SECSIZE ! 113: u_short dk_bshift; /* shift for * (DEV_BSIZE / sectorsize) XXX */ ! 114: #endif SECSIZE ! 115: int dk_wlabel; /* label sector is currently writable */ ! 116: u_long dk_copenpart; /* character units open on this drive */ ! 117: u_long dk_bopenpart; /* block units open on this drive */ ! 118: u_long dk_openpart; /* all units open on this drive */ ! 119: u_int dk_curcyl; /* last selected cylinder */ ! 120: struct skdcb dk_dcb; /* seek command block */ ! 121: u_long dk_dcbphys; /* physical address of dk_dcb */ ! 122: int df_reg[3]; /* for formatting, in-out parameters */ ! 123: } dksoftc[NDK]; ! 124: ! 125: /* ! 126: * Drive states. Used during steps of open/initialization. ! 127: * States < OPEN (> 0) are transient, during an open operation. ! 128: * OPENRAW is used for unlabeled disks, to allow format operations. ! 129: */ ! 130: #define CLOSED 0 /* disk is closed */ ! 131: #define WANTOPEN 1 /* open requested, not started */ ! 132: #define WANTOPENRAW 2 /* open requested, no label */ ! 133: #define RDLABEL 3 /* reading pack label */ ! 134: #define OPEN 4 /* intialized and ready */ ! 135: #define OPENRAW 5 /* open, no label */ ! 136: ! 137: struct buf dkutab[NDK]; /* i/o queue headers */ ! 138: struct disklabel dklabel[NDK]; /* pack labels */ ! 139: ! 140: #define b_cylin b_resid ! 141: #define b_track b_error /* used for seek commands */ ! 142: #define b_seekf b_forw /* second queue on um_tab */ ! 143: #define b_seekl b_back /* second queue on um_tab */ ! 144: ! 145: int vdwstart, vdwatch(); ! 146: ! 147: /* ! 148: * See if the controller is really there; if so, initialize it. ! 149: */ ! 150: vdprobe(reg, vm) ! 151: caddr_t reg; ! 152: struct vba_ctlr *vm; ! 153: { ! 154: register br, cvec; /* must be r12, r11 */ ! 155: register struct vddevice *vdaddr = (struct vddevice *)reg; ! 156: struct vdsoftc *vd; ! 157: int s; ! 158: ! 159: #ifdef lint ! 160: br = 0; cvec = br; br = cvec; ! 161: vdintr(0); ! 162: #endif ! 163: if (badaddr((caddr_t)reg, 2)) ! 164: return (0); ! 165: vd = &vdsoftc[vm->um_ctlr]; ! 166: vdaddr->vdreset = 0xffffffff; ! 167: DELAY(1000000); ! 168: if (vdaddr->vdreset != (unsigned)0xffffffff) { ! 169: vd->vd_type = VDTYPE_VDDC; ! 170: vd->vd_flags &= ~VD_DOSEEKS; ! 171: DELAY(1000000); ! 172: } else { ! 173: vd->vd_type = VDTYPE_SMDE; ! 174: vd->vd_flags |= VD_DOSEEKS; ! 175: vdaddr->vdrstclr = 0; ! 176: DELAY(3000000); ! 177: } ! 178: vd->vd_mdcbphys = vtoph((struct proc *)0, (unsigned)&vd->vd_mdcb); ! 179: vd->vd_dcbphys = vtoph((struct proc *)0, (unsigned)&vd->vd_dcb); ! 180: vm->um_addr = reg; /* XXX */ ! 181: s = spl7(); ! 182: if (vdinit_ctlr(vm, vd) == 0) { ! 183: splx(s); ! 184: return (0); ! 185: } ! 186: if (vd->vd_type == VDTYPE_SMDE) { ! 187: #ifdef notdef ! 188: /* ! 189: * Attempt PROBE to get all drive status; ! 190: * we take advantage of this in vdreset_drive ! 191: * to try to avoid guessing games. ! 192: */ ! 193: (void) vdcmd(vm, VDOP_PROBE, 5, 0); ! 194: #endif ! 195: /* ! 196: * Check for scatter-gather by checking firmware date ! 197: * with IDENT command. The date is printed when ! 198: * vdslave is first called, thus this must be ! 199: * the last controller operation in vdprobe. ! 200: */ ! 201: vd->vd_dcb.trail.idtrail.date = 0; ! 202: if (vdcmd(vm, VDOP_IDENT, 10, 0)) { ! 203: uncache(&vd->vd_dcb.trail.idtrail.date); ! 204: if (vd->vd_dcb.trail.idtrail.date != 0) ! 205: vd->vd_flags |= VD_SCATGATH; ! 206: } ! 207: } ! 208: splx(s); ! 209: /* ! 210: * Allocate page tables and i/o buffer. ! 211: */ ! 212: if (vbainit(&vd->vd_rbuf, MAXPHYS, ! 213: vd->vd_type == VDTYPE_VDDC ? VB_24BIT : VB_32BIT) == 0) { ! 214: printf("vd%d: vbainit failed\n", vm->um_ctlr); ! 215: return (0); ! 216: } ! 217: br = 0x17, cvec = 0xe0 + vm->um_ctlr; /* XXX */ ! 218: return (sizeof (struct vddevice)); ! 219: } ! 220: ! 221: /* ! 222: * See if a drive is really there. ! 223: * ! 224: * Can't read pack label here as various data structures ! 225: * aren't setup for doing a read in a straightforward ! 226: * manner. Instead just probe for the drive and leave ! 227: * the pack label stuff to the attach routine. ! 228: */ ! 229: /* ARGSUSED */ ! 230: vdslave(vi, vdaddr) ! 231: register struct vba_device *vi; ! 232: struct vddevice *vdaddr; ! 233: { ! 234: register struct disklabel *lp = &dklabel[vi->ui_unit]; ! 235: register struct dksoftc *dk = &dksoftc[vi->ui_unit]; ! 236: struct vdsoftc *vd = &vdsoftc[vi->ui_ctlr]; ! 237: int bcd(); ! 238: ! 239: if ((vd->vd_flags&VD_PRINT) == 0) { ! 240: printf("vd%d: %s controller", vi->ui_ctlr, ! 241: vd->vd_type == VDTYPE_VDDC ? "VDDC" : "SMDE"); ! 242: if (vd->vd_flags & VD_SCATGATH) { ! 243: char rev[5]; ! 244: ! 245: bcopy((caddr_t)&vd->vd_dcb.trail.idtrail.rev, rev, ! 246: sizeof(vd->vd_dcb.trail.idtrail.rev)); ! 247: printf(" firmware rev %s (%d-%d-%d)", rev, ! 248: bcd((vd->vd_dcb.trail.idtrail.date >> 8) & 0xff), ! 249: bcd(vd->vd_dcb.trail.idtrail.date & 0xff), ! 250: bcd((vd->vd_dcb.trail.idtrail.date >> 16)&0xffff)); ! 251: } ! 252: printf("\n"); ! 253: vd->vd_flags |= VD_PRINT; ! 254: } ! 255: ! 256: /* ! 257: * Initialize label enough to do a reset on ! 258: * the drive. The remainder of the default ! 259: * label values will be filled in in vdinit ! 260: * at attach time. ! 261: */ ! 262: if (vd->vd_type == VDTYPE_SMDE) ! 263: lp->d_secsize = VD_MAXSECSIZE; ! 264: else ! 265: lp->d_secsize = VDDC_SECSIZE; ! 266: lp->d_nsectors = 66; /* only used on smd-e */ ! 267: lp->d_ntracks = 23; ! 268: lp->d_ncylinders = 850; ! 269: lp->d_secpercyl = 66*23; ! 270: lp->d_rpm = 3600; ! 271: lp->d_npartitions = 1; ! 272: lp->d_partitions[0].p_offset = 0; ! 273: lp->d_partitions[0].p_size = LABELSECTOR + 1; ! 274: ! 275: /* ! 276: * Initialize invariant portion of ! 277: * dcb used for overlapped seeks. ! 278: */ ! 279: dk->dk_dcb.opcode = VDOP_SEEK; ! 280: dk->dk_dcb.intflg = DCBINT_NONE | DCBINT_PBA; ! 281: dk->dk_dcb.devselect = vi->ui_slave; ! 282: dk->dk_dcb.trailcnt = sizeof (struct trseek) / sizeof (long); ! 283: dk->dk_dcb.trail.sktrail.skaddr.sector = 0; ! 284: dk->dk_dcbphys = vtoph((struct proc *)0, (unsigned)&dk->dk_dcb); ! 285: #ifndef SECSIZE ! 286: vd_setsecsize(dk, lp); ! 287: #endif ! 288: return (vdreset_drive(vi)); ! 289: } ! 290: ! 291: static int ! 292: bcd(n) ! 293: register u_int n; ! 294: { ! 295: register int bin = 0; ! 296: register int mul = 1; ! 297: ! 298: while (n) { ! 299: bin += (n & 0xf) * mul; ! 300: n >>= 4; ! 301: mul *= 10; ! 302: } ! 303: return (bin); ! 304: } ! 305: ! 306: vdattach(vi) ! 307: register struct vba_device *vi; ! 308: { ! 309: register int unit = vi->ui_unit; ! 310: register struct disklabel *lp = &dklabel[unit]; ! 311: ! 312: /* ! 313: * Try to initialize device and read pack label. ! 314: */ ! 315: if (vdinit(vdminor(unit, 0), 0) != 0) { ! 316: printf(": unknown drive type"); ! 317: return; ! 318: } ! 319: if (dksoftc[unit].dk_state == OPEN) ! 320: printf(": %s <secsize %d, ntrak %d, ncyl %d, nsec %d>", ! 321: lp->d_typename, lp->d_secsize, ! 322: lp->d_ntracks, lp->d_ncylinders, lp->d_nsectors); ! 323: /* ! 324: * (60 / rpm) / (sectors per track * (bytes per sector / 2)) ! 325: */ ! 326: if (vi->ui_dk >= 0) ! 327: dk_wpms[vi->ui_dk] = ! 328: (lp->d_rpm * lp->d_nsectors * lp->d_secsize) / 120; ! 329: #ifdef notyet ! 330: addswap(makedev(VDMAJOR, vdminor(unit, 0)), lp); ! 331: #endif ! 332: } ! 333: ! 334: vdopen(dev, flags, fmt) ! 335: dev_t dev; ! 336: int flags, fmt; ! 337: { ! 338: register unit = vdunit(dev); ! 339: register struct disklabel *lp; ! 340: register struct dksoftc *dk; ! 341: register struct partition *pp; ! 342: struct vba_device *vi; ! 343: int s, error = 0, part = vdpart(dev), mask = 1 << part; ! 344: daddr_t start, end; ! 345: ! 346: if (unit >= NDK || (vi = vddinfo[unit]) == 0 || vi->ui_alive == 0) ! 347: return (ENXIO); ! 348: lp = &dklabel[unit]; ! 349: dk = &dksoftc[unit]; ! 350: ! 351: s = spl7(); ! 352: while (dk->dk_state != OPEN && dk->dk_state != OPENRAW && ! 353: dk->dk_state != CLOSED) ! 354: if (error = tsleep((caddr_t)dk, (PZERO+1) | PCATCH, devopn, 0)) ! 355: break; ! 356: splx(s); ! 357: if (error) ! 358: return (error); ! 359: if (dk->dk_state != OPEN && dk->dk_state != OPENRAW) ! 360: if (error = vdinit(dev, flags)) ! 361: return (error); ! 362: ! 363: if (vdwstart == 0) { ! 364: timeout(vdwatch, (caddr_t)0, hz); ! 365: vdwstart++; ! 366: } ! 367: /* ! 368: * Warn if a partion is opened ! 369: * that overlaps another partition which is open ! 370: * unless one is the "raw" partition (whole disk). ! 371: */ ! 372: #define RAWPART 8 /* 'x' partition */ /* XXX */ ! 373: if ((dk->dk_openpart & mask) == 0 && part != RAWPART) { ! 374: pp = &lp->d_partitions[part]; ! 375: start = pp->p_offset; ! 376: end = pp->p_offset + pp->p_size; ! 377: for (pp = lp->d_partitions; ! 378: pp < &lp->d_partitions[lp->d_npartitions]; pp++) { ! 379: if (pp->p_offset + pp->p_size <= start || ! 380: pp->p_offset >= end) ! 381: continue; ! 382: if (pp - lp->d_partitions == RAWPART) ! 383: continue; ! 384: if (dk->dk_openpart & (1 << (pp - lp->d_partitions))) ! 385: log(LOG_WARNING, ! 386: "dk%d%c: overlaps open partition (%c)\n", ! 387: unit, part + 'a', ! 388: pp - lp->d_partitions + 'a'); ! 389: } ! 390: } ! 391: if (part >= lp->d_npartitions) ! 392: return (ENXIO); ! 393: dk->dk_openpart |= mask; ! 394: switch (fmt) { ! 395: case S_IFCHR: ! 396: dk->dk_copenpart |= mask; ! 397: break; ! 398: case S_IFBLK: ! 399: dk->dk_bopenpart |= mask; ! 400: break; ! 401: } ! 402: return (0); ! 403: } ! 404: ! 405: /* ARGSUSED */ ! 406: vdclose(dev, flags, fmt) ! 407: dev_t dev; ! 408: int flags, fmt; ! 409: { ! 410: register int unit = vdunit(dev); ! 411: register struct dksoftc *dk = &dksoftc[unit]; ! 412: int part = vdpart(dev), mask = 1 << part; ! 413: ! 414: switch (fmt) { ! 415: case S_IFCHR: ! 416: dk->dk_copenpart &= ~mask; ! 417: break; ! 418: case S_IFBLK: ! 419: dk->dk_bopenpart &= ~mask; ! 420: break; ! 421: } ! 422: if (((dk->dk_copenpart | dk->dk_bopenpart) & mask) == 0) ! 423: dk->dk_openpart &= ~mask; ! 424: /* ! 425: * Should wait for i/o to complete on this partition ! 426: * even if others are open, but wait for work on blkflush(). ! 427: */ ! 428: if (dk->dk_openpart == 0) { ! 429: int s = spl7(); ! 430: while (dkutab[unit].b_actf) ! 431: sleep((caddr_t)dk, PZERO-1); ! 432: splx(s); ! 433: dk->dk_state = CLOSED; ! 434: dk->dk_wlabel = 0; ! 435: } ! 436: return (0); ! 437: } ! 438: ! 439: vdinit(dev, flags) ! 440: dev_t dev; ! 441: int flags; ! 442: { ! 443: register struct disklabel *lp; ! 444: register struct dksoftc *dk; ! 445: struct vba_device *vi; ! 446: int unit = vdunit(dev), error = 0; ! 447: char *msg, *readdisklabel(); ! 448: extern int cold; ! 449: ! 450: dk = &dksoftc[unit]; ! 451: if (flags & O_NDELAY) { ! 452: dk->dk_state = OPENRAW; ! 453: return (0); ! 454: } ! 455: dk->dk_state = RDLABEL; ! 456: lp = &dklabel[unit]; ! 457: vi = vddinfo[unit]; ! 458: if (msg = readdisklabel(dev, vdstrategy, lp)) { ! 459: if (cold) { ! 460: printf(": %s", msg); ! 461: dk->dk_state = CLOSED; ! 462: } else { ! 463: log(LOG_ERR, "dk%d: %s\n", unit, msg); ! 464: dk->dk_state = OPENRAW; ! 465: } ! 466: #ifdef COMPAT_42 ! 467: vdlock(vi->ui_ctlr); ! 468: if (vdmaptype(vi, lp)) ! 469: dk->dk_state = OPEN; ! 470: vdunlock(vi->ui_ctlr); ! 471: #endif ! 472: } else { ! 473: /* ! 474: * Now that we have the label, configure ! 475: * the correct drive parameters. ! 476: */ ! 477: vdlock(vi->ui_ctlr); ! 478: if (vdreset_drive(vi)) ! 479: dk->dk_state = OPEN; ! 480: else { ! 481: dk->dk_state = CLOSED; ! 482: error = ENXIO; ! 483: } ! 484: vdunlock(vi->ui_ctlr); ! 485: } ! 486: #ifndef SECSIZE ! 487: vd_setsecsize(dk, lp); ! 488: #endif ! 489: wakeup((caddr_t)dk); ! 490: return (error); ! 491: } ! 492: ! 493: #ifndef SECSIZE ! 494: vd_setsecsize(dk, lp) ! 495: register struct dksoftc *dk; ! 496: register struct disklabel *lp; ! 497: { ! 498: int mul; ! 499: ! 500: /* ! 501: * Calculate scaling shift for mapping ! 502: * DEV_BSIZE blocks to drive sectors. ! 503: */ ! 504: mul = DEV_BSIZE / lp->d_secsize; ! 505: dk->dk_bshift = 0; ! 506: while ((mul >>= 1) > 0) ! 507: dk->dk_bshift++; ! 508: } ! 509: #endif SECSIZE ! 510: ! 511: /*ARGSUSED*/ ! 512: vddgo(vm) ! 513: struct vba_device *vm; ! 514: { ! 515: ! 516: } ! 517: ! 518: vdstrategy(bp) ! 519: register struct buf *bp; ! 520: { ! 521: register struct vba_device *vi; ! 522: register struct disklabel *lp; ! 523: register struct dksoftc *dk; ! 524: register int unit; ! 525: register daddr_t sn; ! 526: struct buf *dp; ! 527: daddr_t sz, maxsz; ! 528: int part, s; ! 529: ! 530: unit = vdunit(bp->b_dev); ! 531: if (unit >= NDK) { ! 532: bp->b_error = ENXIO; ! 533: goto bad; ! 534: } ! 535: vi = vddinfo[unit]; ! 536: lp = &dklabel[unit]; ! 537: if (vi == 0 || vi->ui_alive == 0) { ! 538: bp->b_error = ENXIO; ! 539: goto bad; ! 540: } ! 541: dk = &dksoftc[unit]; ! 542: if (dk->dk_state < OPEN) { ! 543: if (dk->dk_state == CLOSED) { ! 544: bp->b_error = EIO; ! 545: goto bad; ! 546: } ! 547: goto q; ! 548: } ! 549: if (dk->dk_state != OPEN && (bp->b_flags & B_READ) == 0) { ! 550: bp->b_error = EROFS; ! 551: goto bad; ! 552: } ! 553: part = vdpart(bp->b_dev); ! 554: if ((dk->dk_openpart & (1 << part)) == 0) { ! 555: bp->b_error = ENODEV; ! 556: goto bad; ! 557: } ! 558: sz = (bp->b_bcount + lp->d_secsize - 1) / lp->d_secsize; ! 559: maxsz = lp->d_partitions[part].p_size; ! 560: #ifndef SECSIZE ! 561: sn = bp->b_blkno << dk->dk_bshift; ! 562: #else SECSIZE ! 563: sn = bp->b_blkno; ! 564: #endif SECSIZE ! 565: if (sn + lp->d_partitions[part].p_offset <= LABELSECTOR && ! 566: #if LABELSECTOR != 0 ! 567: sn + lp->d_partitions[part].p_offset + sz > LABELSECTOR && ! 568: #endif ! 569: (bp->b_flags & B_READ) == 0 && dk->dk_wlabel == 0) { ! 570: bp->b_error = EROFS; ! 571: goto bad; ! 572: } ! 573: if (sn < 0 || sn + sz > maxsz) { ! 574: if (sn == maxsz) { ! 575: bp->b_resid = bp->b_bcount; ! 576: goto done; ! 577: } ! 578: sz = maxsz - sn; ! 579: if (sz <= 0) { ! 580: bp->b_error = EINVAL; ! 581: goto bad; ! 582: } ! 583: bp->b_bcount = sz * lp->d_secsize; ! 584: } ! 585: bp->b_cylin = (sn + lp->d_partitions[part].p_offset) / lp->d_secpercyl; ! 586: #ifdef SECSIZE ! 587: if (bp->b_blksize != lp->d_secsize && (bp->b_flags & B_PGIN) == 0) ! 588: panic("vdstrat blksize"); ! 589: #endif SECSIZE ! 590: q: ! 591: s = spl7(); ! 592: dp = &dkutab[vi->ui_unit]; ! 593: disksort(dp, bp); ! 594: if (!dp->b_active) { ! 595: (void) vdustart(vi); ! 596: if (!vi->ui_mi->um_tab.b_active) ! 597: vdstart(vi->ui_mi); ! 598: } ! 599: splx(s); ! 600: return; ! 601: bad: ! 602: bp->b_flags |= B_ERROR; ! 603: done: ! 604: biodone(bp); ! 605: return; ! 606: } ! 607: ! 608: vdustart(vi) ! 609: register struct vba_device *vi; ! 610: { ! 611: register struct buf *bp, *dp; ! 612: register struct vba_ctlr *vm; ! 613: register int unit = vi->ui_unit; ! 614: register struct dksoftc *dk; ! 615: register struct vdsoftc *vd; ! 616: struct disklabel *lp; ! 617: ! 618: dp = &dkutab[unit]; ! 619: /* ! 620: * If queue empty, nothing to do. ! 621: */ ! 622: if ((bp = dp->b_actf) == NULL) ! 623: return; ! 624: /* ! 625: * If drive is off-cylinder and controller supports seeks, ! 626: * place drive on seek queue for controller. ! 627: * Otherwise, place on transfer queue. ! 628: */ ! 629: vd = &vdsoftc[vi->ui_ctlr]; ! 630: dk = &dksoftc[unit]; ! 631: vm = vi->ui_mi; ! 632: if (bp->b_cylin != dk->dk_curcyl && vd->vd_flags&VD_DOSEEKS) { ! 633: lp = &dklabel[unit]; ! 634: bp->b_track = (bp->b_blkno % lp->d_secpercyl) / lp->d_nsectors; ! 635: if (vm->um_tab.b_seekf == NULL) ! 636: vm->um_tab.b_seekf = dp; ! 637: else ! 638: vm->um_tab.b_seekl->b_forw = dp; ! 639: vm->um_tab.b_seekl = dp; ! 640: } else { ! 641: if (vm->um_tab.b_actf == NULL) ! 642: vm->um_tab.b_actf = dp; ! 643: else ! 644: vm->um_tab.b_actl->b_forw = dp; ! 645: vm->um_tab.b_actl = dp; ! 646: } ! 647: dp->b_forw = NULL; ! 648: dp->b_active++; ! 649: } ! 650: ! 651: /* ! 652: * Start next transfer on a controller. ! 653: * There are two queues of drives, the first on-cylinder ! 654: * and the second off-cylinder from their next transfers. ! 655: * Perform the first transfer for the first drive on the on-cylinder ! 656: * queue, if any, otherwise the first transfer for the first drive ! 657: * on the second queue. Initiate seeks on remaining drives on the ! 658: * off-cylinder queue, then move them all to the on-cylinder queue. ! 659: */ ! 660: vdstart(vm) ! 661: register struct vba_ctlr *vm; ! 662: { ! 663: register struct buf *bp; ! 664: register struct vba_device *vi; ! 665: register struct vdsoftc *vd; ! 666: register struct dksoftc *dk; ! 667: register struct disklabel *lp; ! 668: register struct dcb **dcbp; ! 669: struct buf *dp; ! 670: int sn, tn; ! 671: ! 672: loop: ! 673: /* ! 674: * Pull a request off the controller queue. ! 675: */ ! 676: if ((dp = vm->um_tab.b_actf) == NULL && ! 677: (dp = vm->um_tab.b_seekf) == NULL) ! 678: return; ! 679: if ((bp = dp->b_actf) == NULL) { ! 680: if (dp == vm->um_tab.b_actf) ! 681: vm->um_tab.b_actf = dp->b_forw; ! 682: else ! 683: vm->um_tab.b_seekf = dp->b_forw; ! 684: goto loop; ! 685: } ! 686: ! 687: /* ! 688: * Mark controller busy, and determine ! 689: * destination of this request. ! 690: */ ! 691: vm->um_tab.b_active++; ! 692: vi = vddinfo[vdunit(bp->b_dev)]; ! 693: dk = &dksoftc[vi->ui_unit]; ! 694: #ifndef SECSIZE ! 695: sn = bp->b_blkno << dk->dk_bshift; ! 696: #else SECSIZE ! 697: sn = bp->b_blkno; ! 698: #endif SECSIZE ! 699: lp = &dklabel[vi->ui_unit]; ! 700: sn %= lp->d_secpercyl; ! 701: tn = sn / lp->d_nsectors; ! 702: sn %= lp->d_nsectors; ! 703: ! 704: /* ! 705: * Construct dcb for read/write command. ! 706: */ ! 707: vd = &vdsoftc[vm->um_ctlr]; ! 708: vd->vd_dcb.intflg = DCBINT_DONE; ! 709: vd->vd_dcb.devselect = dk->dk_dcb.devselect; ! 710: vd->vd_dcb.operrsta = 0; ! 711: vd->vd_dcb.nxtdcb = (struct dcb *)0; /* end of chain */ ! 712: vd->vd_dcb.trail.rwtrail.disk.cylinder = bp->b_cylin; ! 713: vd->vd_dcb.trail.rwtrail.disk.track = tn; ! 714: vd->vd_dcb.trail.rwtrail.disk.sector = sn; ! 715: dk->dk_curcyl = bp->b_cylin; ! 716: bp->b_track = 0; /* init overloaded field */ ! 717: vd->vd_dcb.trailcnt = sizeof (struct trrw) / sizeof (long); ! 718: if (bp->b_flags & B_FORMAT) ! 719: vd->vd_dcb.opcode = dk->dk_op; ! 720: else if (vd->vd_flags & VD_SCATGATH && ! 721: ((int)bp->b_un.b_addr & (sizeof(long) - 1)) == 0) ! 722: vd->vd_dcb.opcode = (bp->b_flags & B_READ)? VDOP_RAS : VDOP_GAW; ! 723: else ! 724: vd->vd_dcb.opcode = (bp->b_flags & B_READ)? VDOP_RD : VDOP_WD; ! 725: ! 726: switch (vd->vd_dcb.opcode) { ! 727: case VDOP_FSECT: ! 728: vd->vd_dcb.trailcnt = sizeof (struct trfmt) / sizeof (long); ! 729: vd->vd_dcb.trail.fmtrail.nsectors = bp->b_bcount / ! 730: lp->d_secsize; ! 731: vd->vd_dcb.trail.fmtrail.hdr = *(dskadr *)&dk->dk_althdr; ! 732: vd->vd_dcb.trail.rwtrail.disk.cylinder |= dk->dk_fmtflags; ! 733: goto setupaddr; ! 734: ! 735: case VDOP_RDRAW: ! 736: case VDOP_RD: ! 737: case VDOP_RHDE: ! 738: case VDOP_WD: ! 739: vd->vd_dcb.trail.rwtrail.wcount = (bp->b_bcount+1) >> 1; ! 740: setupaddr: ! 741: vd->vd_dcb.trail.rwtrail.memadr = ! 742: vbasetup(bp, &vd->vd_rbuf, (int)lp->d_secsize); ! 743: break; ! 744: ! 745: case VDOP_RAS: ! 746: case VDOP_GAW: ! 747: vd->vd_dcb.trailcnt += vd_sgsetup(bp, &vd->vd_rbuf, ! 748: &vd->vd_dcb.trail.sgtrail); ! 749: break; ! 750: } ! 751: if (vi->ui_dk >= 0) { ! 752: dk_busy |= 1<<vi->ui_dk; ! 753: dk_xfer[vi->ui_dk]++; ! 754: dk_wds[vi->ui_dk] += bp->b_bcount>>6; ! 755: } ! 756: ! 757: /* ! 758: * Look for any seeks to be performed on other drives on this ! 759: * controller. If overlapped seeks exist, insert seek commands ! 760: * on the controller's command queue before the transfer. ! 761: */ ! 762: dcbp = &vd->vd_mdcb.mdcb_head; ! 763: ! 764: if (dp == vm->um_tab.b_seekf) ! 765: dp = dp->b_forw; ! 766: else ! 767: dp = vm->um_tab.b_seekf; ! 768: for (; dp != NULL; dp = dp->b_forw) { ! 769: if ((bp = dp->b_actf) == NULL) ! 770: continue; ! 771: vi = vddinfo[vdunit(bp->b_dev)]; ! 772: dk = &dksoftc[vi->ui_unit]; ! 773: dk->dk_curcyl = bp->b_cylin; ! 774: if (vi->ui_dk >= 0) ! 775: dk_seek[vi->ui_dk]++; ! 776: dk->dk_dcb.operrsta = 0; ! 777: dk->dk_dcb.trail.sktrail.skaddr.cylinder = bp->b_cylin; ! 778: dk->dk_dcb.trail.sktrail.skaddr.track = bp->b_track; ! 779: *dcbp = (struct dcb *)dk->dk_dcbphys; ! 780: dcbp = &dk->dk_dcb.nxtdcb; ! 781: } ! 782: *dcbp = (struct dcb *)vd->vd_dcbphys; ! 783: if (vm->um_tab.b_actf) ! 784: vm->um_tab.b_actl->b_forw = vm->um_tab.b_seekf; ! 785: else ! 786: vm->um_tab.b_actf = vm->um_tab.b_seekf; ! 787: if (vm->um_tab.b_seekf) ! 788: vm->um_tab.b_actl = vm->um_tab.b_seekl; ! 789: vm->um_tab.b_seekf = 0; ! 790: ! 791: /* ! 792: * Initiate operation. ! 793: */ ! 794: vd->vd_mdcb.mdcb_status = 0; ! 795: VDGO(vm->um_addr, vd->vd_mdcbphys, vd->vd_type); ! 796: } ! 797: ! 798: /* ! 799: * Wait for controller to finish current operation ! 800: * so that direct controller accesses can be done. ! 801: */ ! 802: vdlock(ctlr) ! 803: { ! 804: register struct vba_ctlr *vm = vdminfo[ctlr]; ! 805: register struct vdsoftc *vd = &vdsoftc[ctlr]; ! 806: int s; ! 807: ! 808: s = spl7(); ! 809: while (vm->um_tab.b_active || vd->vd_flags & VD_LOCKED) { ! 810: vd->vd_flags |= VD_WAIT; ! 811: sleep((caddr_t)vd, PRIBIO); ! 812: } ! 813: vd->vd_flags |= VD_LOCKED; ! 814: splx(s); ! 815: } ! 816: ! 817: /* ! 818: * Continue normal operations after pausing for ! 819: * munging the controller directly. ! 820: */ ! 821: vdunlock(ctlr) ! 822: { ! 823: register struct vba_ctlr *vm = vdminfo[ctlr]; ! 824: register struct vdsoftc *vd = &vdsoftc[ctlr]; ! 825: ! 826: vd->vd_flags &= ~VD_LOCKED; ! 827: if (vd->vd_flags & VD_WAIT) { ! 828: vd->vd_flags &= ~VD_WAIT; ! 829: wakeup((caddr_t)vd); ! 830: } else if (vm->um_tab.b_actf || vm->um_tab.b_seekf) ! 831: vdstart(vm); ! 832: } ! 833: ! 834: #define DONTCARE (DCBS_DSE|DCBS_DSL|DCBS_TOP|DCBS_TOM|DCBS_FAIL|DCBS_DONE) ! 835: /* ! 836: * Handle a disk interrupt. ! 837: */ ! 838: vdintr(ctlr) ! 839: register ctlr; ! 840: { ! 841: register struct buf *bp, *dp; ! 842: register struct vba_ctlr *vm = vdminfo[ctlr]; ! 843: register struct vba_device *vi; ! 844: register struct vdsoftc *vd = &vdsoftc[ctlr]; ! 845: register status; ! 846: int timedout; ! 847: struct dksoftc *dk; ! 848: ! 849: if (!vm->um_tab.b_active) { ! 850: printf("vd%d: stray interrupt\n", ctlr); ! 851: return; ! 852: } ! 853: /* ! 854: * Get device and block structures, and a pointer ! 855: * to the vba_device for the drive. ! 856: */ ! 857: dp = vm->um_tab.b_actf; ! 858: bp = dp->b_actf; ! 859: vi = vddinfo[vdunit(bp->b_dev)]; ! 860: dk = &dksoftc[vi->ui_unit]; ! 861: if (vi->ui_dk >= 0) ! 862: dk_busy &= ~(1<<vi->ui_dk); ! 863: timedout = (vd->vd_wticks >= VDMAXTIME); ! 864: /* ! 865: * Check for and process errors on ! 866: * either the drive or the controller. ! 867: */ ! 868: uncache(&vd->vd_dcb.operrsta); ! 869: status = vd->vd_dcb.operrsta; ! 870: if (bp->b_flags & B_FORMAT) { ! 871: dk->dk_operrsta = status; ! 872: uncache(&vd->vd_dcb.err_code); ! 873: /* ecodecnt gets err_code + err_wcnt from the same longword */ ! 874: dk->dk_ecodecnt = *(long *)&vd->vd_dcb.err_code; ! 875: uncache(&vd->vd_dcb.err_trk); ! 876: /* erraddr gets error trk/sec/cyl from the same longword */ ! 877: dk->dk_erraddr = *(long *)&vd->vd_dcb.err_trk; ! 878: } else if (status & VDERR_HARD || timedout) { ! 879: if (vd->vd_type == VDTYPE_SMDE) ! 880: uncache(&vd->vd_dcb.err_code); ! 881: if (status & DCBS_WPT) { ! 882: /* ! 883: * Give up on write locked devices immediately. ! 884: */ ! 885: printf("dk%d: write locked\n", vi->ui_unit); ! 886: bp->b_flags |= B_ERROR; ! 887: } else if (status & VDERR_RETRY || timedout) { ! 888: if (status & VDERR_CTLR || timedout) { ! 889: vdharderr(timedout ? ! 890: "controller timeout" : "controller err", ! 891: vd, bp, &vd->vd_dcb); ! 892: printf("; resetting controller..."); ! 893: vdreset_ctlr(vm); ! 894: } else if (status & VDERR_DRIVE) { ! 895: vdharderr("drive err", vd, bp, &vd->vd_dcb); ! 896: printf("; resetting drive..."); ! 897: if (!vdreset_drive(vi)) ! 898: dk->dk_state = CLOSED; ! 899: } else ! 900: vdharderr("data err", vd, bp, &vd->vd_dcb); ! 901: /* ! 902: * Retry transfer once, unless reset failed. ! 903: */ ! 904: if (!vi->ui_alive || dp->b_errcnt++ >= 1) { ! 905: printf("\n"); ! 906: goto hard; ! 907: } ! 908: ! 909: printf(" retrying\n"); ! 910: vm->um_tab.b_active = 0; /* force retry */ ! 911: } else { ! 912: vdharderr("hard error", vd, bp, &vd->vd_dcb); ! 913: printf("\n"); ! 914: hard: ! 915: bp->b_flags |= B_ERROR; ! 916: } ! 917: } else if (status & DCBS_SOFT) ! 918: vdsofterr(bp, &vd->vd_dcb); ! 919: if (vd->vd_wticks > 3) { ! 920: vd->vd_dcb.err_code = vd->vd_wticks; ! 921: vdharderr("slow transfer (ecode is sec.)", vd, bp, &vd->vd_dcb); ! 922: printf("\n"); ! 923: } ! 924: vd->vd_wticks = 0; ! 925: if (vm->um_tab.b_active) { ! 926: vm->um_tab.b_active = 0; ! 927: vm->um_tab.b_actf = dp->b_forw; ! 928: dp->b_active = 0; ! 929: dp->b_errcnt = 0; ! 930: dp->b_actf = bp->av_forw; ! 931: bp->b_resid = 0; ! 932: vbadone(bp, &vd->vd_rbuf); ! 933: biodone(bp); ! 934: /* ! 935: * If this unit has more work to do, ! 936: * then start it up right away. ! 937: */ ! 938: if (dp->b_actf) ! 939: vdustart(vi); ! 940: else if (dk->dk_openpart == 0) ! 941: wakeup((caddr_t)dk); ! 942: } ! 943: /* ! 944: * If there are devices ready to ! 945: * transfer, start the controller. ! 946: */ ! 947: if (vd->vd_flags & VD_WAIT) { ! 948: vd->vd_flags &= ~VD_WAIT; ! 949: wakeup((caddr_t)vd); ! 950: } else if (vm->um_tab.b_actf || vm->um_tab.b_seekf) ! 951: vdstart(vm); ! 952: } ! 953: ! 954: vdharderr(what, vd, bp, dcb) ! 955: char *what; ! 956: struct vdsoftc *vd; ! 957: register struct buf *bp; ! 958: register struct dcb *dcb; ! 959: { ! 960: int unit = vdunit(bp->b_dev), status = dcb->operrsta; ! 961: register struct disklabel *lp = &dklabel[unit]; ! 962: int blkdone; ! 963: ! 964: if (vd->vd_wticks < VDMAXTIME) ! 965: status &= ~DONTCARE; ! 966: blkdone = ((((dcb->err_cyl & 0xfff) * lp->d_ntracks + dcb->err_trk) * ! 967: lp->d_nsectors + dcb->err_sec - ! 968: lp->d_partitions[vdpart(bp->b_dev)].p_offset) >> ! 969: dksoftc[unit].dk_bshift) - bp->b_blkno; ! 970: diskerr(bp, "dk", what, LOG_PRINTF, blkdone, lp); ! 971: printf(", status %b", status, VDERRBITS); ! 972: if (vd->vd_type == VDTYPE_SMDE) ! 973: printf(" ecode %x", dcb->err_code); ! 974: } ! 975: ! 976: vdsofterr(bp, dcb) ! 977: register struct buf *bp; ! 978: register struct dcb *dcb; ! 979: { ! 980: int unit = vdunit(bp->b_dev); ! 981: struct disklabel *lp = &dklabel[unit]; ! 982: int status = dcb->operrsta; ! 983: int blkdone; ! 984: ! 985: blkdone = ((((dcb->err_cyl & 0xfff) * lp->d_ntracks + dcb->err_trk) * ! 986: lp->d_nsectors + dcb->err_sec - ! 987: lp->d_partitions[vdpart(bp->b_dev)].p_offset) >> ! 988: dksoftc[unit].dk_bshift) - bp->b_blkno; ! 989: ! 990: if (status != (DCBS_CCD|DCBS_SOFT|DCBS_ERR|DCBS_DONE)) { ! 991: diskerr(bp, "dk", "soft error", LOG_WARNING, blkdone, lp); ! 992: addlog(", status %b ecode %x\n", status, VDERRBITS, ! 993: dcb->err_code); ! 994: } else { ! 995: diskerr(bp, "dk", "soft ecc", LOG_WARNING, blkdone, lp); ! 996: addlog("\n"); ! 997: } ! 998: } ! 999: ! 1000: vdioctl(dev, cmd, data, flag) ! 1001: dev_t dev; ! 1002: int cmd; ! 1003: caddr_t data; ! 1004: int flag; ! 1005: { ! 1006: register int unit = vdunit(dev); ! 1007: register struct disklabel *lp = &dklabel[unit]; ! 1008: register struct dksoftc *dk = &dksoftc[unit]; ! 1009: int error = 0, vdformat(); ! 1010: ! 1011: switch (cmd) { ! 1012: ! 1013: case DIOCGDINFO: ! 1014: *(struct disklabel *)data = *lp; ! 1015: break; ! 1016: ! 1017: case DIOCGPART: ! 1018: ((struct partinfo *)data)->disklab = lp; ! 1019: ((struct partinfo *)data)->part = ! 1020: &lp->d_partitions[vdpart(dev)]; ! 1021: break; ! 1022: ! 1023: case DIOCSDINFO: ! 1024: if ((flag & FWRITE) == 0) ! 1025: error = EBADF; ! 1026: else ! 1027: error = setdisklabel(lp, (struct disklabel *)data, ! 1028: (dk->dk_state == OPENRAW) ? 0 : dk->dk_openpart); ! 1029: if (error == 0 && dk->dk_state == OPENRAW && ! 1030: vdreset_drive(vddinfo[unit])) ! 1031: dk->dk_state = OPEN; ! 1032: break; ! 1033: ! 1034: case DIOCWLABEL: ! 1035: if ((flag & FWRITE) == 0) ! 1036: error = EBADF; ! 1037: else ! 1038: dk->dk_wlabel = *(int *)data; ! 1039: break; ! 1040: ! 1041: case DIOCWDINFO: ! 1042: if ((flag & FWRITE) == 0) ! 1043: error = EBADF; ! 1044: else if ((error = setdisklabel(lp, (struct disklabel *)data, ! 1045: (dk->dk_state == OPENRAW) ? 0 : dk->dk_openpart)) == 0) { ! 1046: int wlab; ! 1047: ! 1048: if (error == 0 && dk->dk_state == OPENRAW && ! 1049: vdreset_drive(vddinfo[unit])) ! 1050: dk->dk_state = OPEN; ! 1051: /* simulate opening partition 0 so write succeeds */ ! 1052: dk->dk_openpart |= (1 << 0); /* XXX */ ! 1053: wlab = dk->dk_wlabel; ! 1054: dk->dk_wlabel = 1; ! 1055: error = writedisklabel(dev, vdstrategy, lp); ! 1056: dk->dk_openpart = dk->dk_copenpart | dk->dk_bopenpart; ! 1057: dk->dk_wlabel = wlab; ! 1058: } ! 1059: break; ! 1060: ! 1061: case DIOCWFORMAT: ! 1062: { ! 1063: register struct format_op *fop; ! 1064: struct uio auio; ! 1065: struct iovec aiov; ! 1066: ! 1067: if ((flag & FWRITE) == 0) { ! 1068: error = EBADF; ! 1069: break; ! 1070: } ! 1071: fop = (struct format_op *)data; ! 1072: aiov.iov_base = fop->df_buf; ! 1073: aiov.iov_len = fop->df_count; ! 1074: auio.uio_iov = &aiov; ! 1075: auio.uio_iovcnt = 1; ! 1076: auio.uio_resid = fop->df_count; ! 1077: auio.uio_segflg = UIO_USERSPACE; ! 1078: auio.uio_offset = fop->df_startblk * lp->d_secsize; ! 1079: /* This assumes one active format operation per disk... */ ! 1080: dk->dk_op = fop->dk_op; ! 1081: dk->dk_althdr = fop->dk_althdr; ! 1082: dk->dk_fmtflags = fop->dk_fmtflags; ! 1083: /* ! 1084: * Don't return errors, as the format op won't get copied ! 1085: * out if we return nonzero. Callers must check the returned ! 1086: * registers and count. ! 1087: */ ! 1088: error = physio(vdformat, (struct buf *)NULL, dev, ! 1089: B_WRITE, minphys, &auio); ! 1090: if (error == EIO) ! 1091: error = 0; ! 1092: fop->df_count -= auio.uio_resid; ! 1093: /* This assumes one active format operation per disk... */ ! 1094: fop->dk_operrsta = dk->dk_operrsta; ! 1095: fop->dk_ecodecnt = dk->dk_ecodecnt; ! 1096: fop->dk_erraddr = dk->dk_erraddr; ! 1097: break; ! 1098: } ! 1099: ! 1100: default: ! 1101: error = ENOTTY; ! 1102: break; ! 1103: } ! 1104: return (error); ! 1105: } ! 1106: ! 1107: vdformat(bp) ! 1108: struct buf *bp; ! 1109: { ! 1110: bp->b_flags |= B_FORMAT; ! 1111: vdstrategy(bp); ! 1112: } ! 1113: ! 1114: /* ! 1115: * Watch for lost interrupts. ! 1116: */ ! 1117: vdwatch() ! 1118: { ! 1119: register struct vdsoftc *vd; ! 1120: register struct vba_ctlr *vm; ! 1121: register int ctlr; ! 1122: int s; ! 1123: ! 1124: timeout(vdwatch, (caddr_t)0, hz); ! 1125: for (ctlr = 0; ctlr < NVD; ctlr++) { ! 1126: vm = vdminfo[ctlr]; ! 1127: if (vm == 0 || vm->um_alive == 0) ! 1128: continue; ! 1129: vd = &vdsoftc[ctlr]; ! 1130: s = spl7(); ! 1131: if (vm->um_tab.b_active && vd->vd_wticks++ >= VDMAXTIME) { ! 1132: printf("vd%d: lost interrupt\n", ctlr); ! 1133: #ifdef maybe ! 1134: VDABORT((struct vddevice *)vm->um_addr, vd->vd_type); ! 1135: #endif ! 1136: vdintr(ctlr); ! 1137: } ! 1138: splx(s); ! 1139: } ! 1140: } ! 1141: ! 1142: #define DBSIZE 64 /* controller limit with 1K sectors */ ! 1143: /* ! 1144: * Crash dump. ! 1145: */ ! 1146: vddump(dev) ! 1147: dev_t dev; ! 1148: { ! 1149: register struct vba_device *vi; ! 1150: register struct vba_ctlr *vm; ! 1151: register struct disklabel *lp; ! 1152: register struct vdsoftc *vd; ! 1153: struct dksoftc *dk; ! 1154: int part, unit, num; ! 1155: u_long start; ! 1156: ! 1157: start = 0; ! 1158: unit = vdunit(dev); ! 1159: if (unit > NDK || (vi = vddinfo[unit]) == 0 || vi->ui_alive == 0) ! 1160: return (ENXIO); ! 1161: dk = &dksoftc[unit]; ! 1162: if (dk->dk_state != OPEN && dk->dk_state != OPENRAW && ! 1163: vdinit(vdminor(unit, 0), 0) != 0) ! 1164: return (ENXIO); ! 1165: lp = &dklabel[unit]; ! 1166: part = vdpart(dev); ! 1167: if (part >= lp->d_npartitions) ! 1168: return (ENXIO); ! 1169: vm = vi->ui_mi; ! 1170: vdreset_ctlr(vm); ! 1171: if (dumplo < 0) ! 1172: return (EINVAL); ! 1173: /* ! 1174: * Maxfree is in pages, dumplo is in DEV_BSIZE units. ! 1175: */ ! 1176: num = maxfree * (NBPG / lp->d_secsize); ! 1177: dumplo *= DEV_BSIZE / lp->d_secsize; ! 1178: if (dumplo + num >= lp->d_partitions[vdpart(dev)].p_size) ! 1179: num = lp->d_partitions[vdpart(dev)].p_size - dumplo; ! 1180: vd = &vdsoftc[vm->um_ctlr]; ! 1181: vd->vd_dcb.intflg = DCBINT_NONE; ! 1182: vd->vd_dcb.opcode = VDOP_WD; ! 1183: vd->vd_dcb.devselect = dk->dk_dcb.devselect; ! 1184: vd->vd_dcb.trailcnt = sizeof (struct trrw) / sizeof (long); ! 1185: while (num > 0) { ! 1186: int nsec, cn, sn, tn; ! 1187: ! 1188: nsec = MIN(num, DBSIZE); ! 1189: sn = dumplo + start / lp->d_secsize; ! 1190: cn = (sn + lp->d_partitions[vdpart(dev)].p_offset) / ! 1191: lp->d_secpercyl; ! 1192: sn %= lp->d_secpercyl; ! 1193: tn = sn / lp->d_nsectors; ! 1194: sn %= lp->d_nsectors; ! 1195: vd->vd_mdcb.mdcb_head = (struct dcb *)vd->vd_dcbphys; ! 1196: vd->vd_dcb.trail.rwtrail.memadr = start; ! 1197: vd->vd_dcb.trail.rwtrail.wcount = (nsec * lp->d_secsize) >> 1; ! 1198: vd->vd_dcb.trail.rwtrail.disk.cylinder = cn; ! 1199: vd->vd_dcb.trail.rwtrail.disk.track = tn; ! 1200: vd->vd_dcb.trail.rwtrail.disk.sector = sn; ! 1201: vd->vd_dcb.operrsta = 0; ! 1202: VDGO(vm->um_addr, vd->vd_mdcbphys, vd->vd_type); ! 1203: if (!vdpoll(vm, 5)) { ! 1204: printf(" during dump\n"); ! 1205: return (EIO); ! 1206: } ! 1207: if (vd->vd_dcb.operrsta & VDERR_HARD) { ! 1208: printf("dk%d: hard error, status=%b\n", unit, ! 1209: vd->vd_dcb.operrsta, VDERRBITS); ! 1210: return (EIO); ! 1211: } ! 1212: start += nsec * lp->d_secsize; ! 1213: num -= nsec; ! 1214: } ! 1215: return (0); ! 1216: } ! 1217: ! 1218: vdsize(dev) ! 1219: dev_t dev; ! 1220: { ! 1221: register int unit = vdunit(dev); ! 1222: register struct dksoftc *dk; ! 1223: struct vba_device *vi; ! 1224: struct disklabel *lp; ! 1225: ! 1226: if (unit >= NDK || (vi = vddinfo[unit]) == 0 || vi->ui_alive == 0 || ! 1227: (dk = &dksoftc[unit])->dk_state != OPEN) ! 1228: return (-1); ! 1229: lp = &dklabel[unit]; ! 1230: #ifdef SECSIZE ! 1231: return ((int)lp->d_partitions[vdpart(dev)].p_size); ! 1232: #else SECSIZE ! 1233: return ((int)lp->d_partitions[vdpart(dev)].p_size >> dk->dk_bshift); ! 1234: #endif SECSIZE ! 1235: } ! 1236: ! 1237: /* ! 1238: * Initialize controller. ! 1239: */ ! 1240: vdinit_ctlr(vm, vd) ! 1241: struct vba_ctlr *vm; ! 1242: struct vdsoftc *vd; ! 1243: { ! 1244: register struct vddevice *vdaddr = (struct vddevice *)vm->um_addr; ! 1245: ! 1246: if (vd->vd_type == VDTYPE_SMDE) { ! 1247: vdaddr->vdcsr = 0; ! 1248: vdaddr->vdtcf_mdcb = AM_ENPDA; ! 1249: vdaddr->vdtcf_dcb = AM_ENPDA; ! 1250: vdaddr->vdtcf_trail = AM_ENPDA; ! 1251: vdaddr->vdtcf_data = AM_ENPDA; ! 1252: vdaddr->vdccf = CCF_SEN | CCF_DIU | CCF_STS | CCF_RFE | ! 1253: XMD_32BIT | BSZ_16WRD | ! 1254: CCF_ENP | CCF_EPE | CCF_EDE | CCF_ECE | CCF_ERR; ! 1255: } ! 1256: if (!vdcmd(vm, VDOP_INIT, 10, 0) || !vdcmd(vm, VDOP_DIAG, 10, 0)) { ! 1257: printf("vd%d: %s cmd failed\n", vm->um_ctlr, ! 1258: vd->vd_dcb.opcode == VDOP_INIT ? "init" : "diag"); ! 1259: return (0); ! 1260: } ! 1261: vd->vd_secsize = vdaddr->vdsecsize << 1; ! 1262: return (1); ! 1263: } ! 1264: ! 1265: /* ! 1266: * Perform a controller reset. ! 1267: */ ! 1268: vdreset_ctlr(vm) ! 1269: register struct vba_ctlr *vm; ! 1270: { ! 1271: register struct vddevice *vdaddr = (struct vddevice *)vm->um_addr; ! 1272: register struct vdsoftc *vd = &vdsoftc[vm->um_ctlr]; ! 1273: register int unit; ! 1274: struct vba_device *vi; ! 1275: ! 1276: VDRESET(vdaddr, vd->vd_type); ! 1277: if (vdinit_ctlr(vm, vd) == 0) ! 1278: return; ! 1279: for (unit = 0; unit < NDK; unit++) ! 1280: if (vi = vddinfo[unit] && vi->ui_mi == vm && vi->ui_alive) ! 1281: (void) vdreset_drive(vi); ! 1282: } ! 1283: ! 1284: vdreset_drive(vi) ! 1285: register struct vba_device *vi; ! 1286: { ! 1287: register struct disklabel *lp = &dklabel[vi->ui_unit]; ! 1288: struct vba_ctlr *vm = vdminfo[vi->ui_ctlr]; ! 1289: struct vddevice *vdaddr = (struct vddevice *)vm->um_addr; ! 1290: register struct vdsoftc *vd = &vdsoftc[vi->ui_ctlr]; ! 1291: register struct dksoftc *dk = &dksoftc[vi->ui_unit]; ! 1292: int config_status, config_ecode, saw_drive = 0; ! 1293: ! 1294: #ifdef notdef ! 1295: /* ! 1296: * check for ESDI distribution panel already configured, ! 1297: * e.g. on boot drive, or if PROBE on controller actually ! 1298: * worked. Status will be zero if drive hasn't ! 1299: * been probed yet. ! 1300: */ ! 1301: #if STA_ESDI != 0 ! 1302: if ((vdaddr->vdstatus[vi->ui_slave] & STA_TYPE) == STA_ESDI) ! 1303: lp->d_devflags |= VD_ESDI; ! 1304: #endif ! 1305: #endif ! 1306: top: ! 1307: vd->vd_dcb.opcode = VDOP_CONFIG; /* command */ ! 1308: vd->vd_dcb.intflg = DCBINT_NONE; ! 1309: vd->vd_dcb.nxtdcb = (struct dcb *)0; /* end of chain */ ! 1310: vd->vd_dcb.operrsta = 0; ! 1311: vd->vd_dcb.devselect = vi->ui_slave | lp->d_devflags; ! 1312: vd->vd_dcb.trail.rstrail.ncyl = lp->d_ncylinders; ! 1313: vd->vd_dcb.trail.rstrail.nsurfaces = lp->d_ntracks; ! 1314: if (vd->vd_type == VDTYPE_SMDE) { ! 1315: vd->vd_dcb.trailcnt = sizeof (struct treset) / sizeof (long); ! 1316: vd->vd_dcb.trail.rstrail.nsectors = lp->d_nsectors; ! 1317: vd->vd_dcb.trail.rstrail.slip_sec = lp->d_sparespertrack; ! 1318: vd->vd_dcb.trail.rstrail.recovery = ! 1319: (lp->d_flags & D_REMOVABLE) ? VDRF_NORMAL : ! 1320: (VDRF_NORMAL &~ (VDRF_OSP|VDRF_OSM)); ! 1321: } else ! 1322: vd->vd_dcb.trailcnt = 2; /* XXX */ ! 1323: vd->vd_mdcb.mdcb_head = (struct dcb *)vd->vd_dcbphys; ! 1324: vd->vd_mdcb.mdcb_status = 0; ! 1325: VDGO(vdaddr, vd->vd_mdcbphys, vd->vd_type); ! 1326: if (!vdpoll(vm, 5)) { ! 1327: printf(" during config\n"); ! 1328: return (0); ! 1329: } ! 1330: config_status = vd->vd_dcb.operrsta; ! 1331: config_ecode = (u_char)vd->vd_dcb.err_code; ! 1332: if (config_status & VDERR_HARD) { ! 1333: if (vd->vd_type == VDTYPE_SMDE) { ! 1334: /* ! 1335: * If drive status was updated successfully, ! 1336: * STA_US (unit selected) should be set ! 1337: * if the drive is attached and powered up. ! 1338: * (But only if we've guessed right on SMD ! 1339: * vs. ESDI; if that flag is wrong, we won't ! 1340: * see the drive.) If we don't see STA_US ! 1341: * with either SMD or ESDI set for the unit, ! 1342: * we assume that the drive doesn't exist, ! 1343: * and don't wait for it to spin up. ! 1344: */ ! 1345: (void) vdcmd(vm, VDOP_STATUS, 5, vi->ui_slave); ! 1346: uncache(&vdaddr->vdstatus[vi->ui_slave]); ! 1347: if (vdaddr->vdstatus[vi->ui_slave] & STA_US) ! 1348: saw_drive = 1; ! 1349: else if (lp->d_devflags == 0) { ! 1350: lp->d_devflags = VD_ESDI; ! 1351: goto top; ! 1352: } ! 1353: } else ! 1354: saw_drive = 1; ! 1355: if ((config_status & (DCBS_OCYL|DCBS_NRDY)) == 0) ! 1356: printf("dk%d: config error %b ecode %x\n", vi->ui_unit, ! 1357: config_status, VDERRBITS, config_ecode); ! 1358: else if ((vd->vd_flags & VD_STARTED) == 0 && saw_drive) { ! 1359: int started; ! 1360: ! 1361: printf(" starting drives, wait ... "); ! 1362: vd->vd_flags |= VD_STARTED; ! 1363: started = (vdcmd(vm, VDOP_START, 10) == 1); ! 1364: DELAY(62000000); ! 1365: printf("done\n"); ! 1366: lp->d_devflags = 0; ! 1367: if (started) ! 1368: goto top; ! 1369: } ! 1370: return (0); ! 1371: } ! 1372: dk->dk_dcb.devselect |= lp->d_devflags; ! 1373: return (1); ! 1374: } ! 1375: ! 1376: /* ! 1377: * Perform a command w/o trailer. ! 1378: */ ! 1379: vdcmd(vm, cmd, t, slave) ! 1380: register struct vba_ctlr *vm; ! 1381: { ! 1382: register struct vdsoftc *vd = &vdsoftc[vm->um_ctlr]; ! 1383: ! 1384: vd->vd_dcb.opcode = cmd; /* command */ ! 1385: vd->vd_dcb.intflg = DCBINT_NONE; ! 1386: vd->vd_dcb.nxtdcb = (struct dcb *)0; /* end of chain */ ! 1387: vd->vd_dcb.operrsta = 0; ! 1388: vd->vd_dcb.devselect = slave; ! 1389: vd->vd_dcb.trailcnt = 0; ! 1390: vd->vd_mdcb.mdcb_head = (struct dcb *)vd->vd_dcbphys; ! 1391: vd->vd_mdcb.mdcb_status = 0; ! 1392: VDGO(vm->um_addr, vd->vd_mdcbphys, vd->vd_type); ! 1393: if (!vdpoll(vm, t)) { ! 1394: printf(" during init\n"); ! 1395: return (0); ! 1396: } ! 1397: return ((vd->vd_dcb.operrsta&VDERR_HARD) == 0); ! 1398: } ! 1399: ! 1400: /* ! 1401: * Poll controller until operation ! 1402: * completes or timeout expires. ! 1403: */ ! 1404: vdpoll(vm, t) ! 1405: register struct vba_ctlr *vm; ! 1406: register int t; ! 1407: { ! 1408: register struct vdsoftc *vd = &vdsoftc[vm->um_ctlr]; ! 1409: register struct vddevice *vdaddr = (struct vddevice *)vm->um_addr; ! 1410: ! 1411: t *= 1000; ! 1412: for (;;) { ! 1413: uncache(&vd->vd_dcb.operrsta); ! 1414: if (vd->vd_dcb.operrsta & (DCBS_DONE|DCBS_ABORT)) ! 1415: break; ! 1416: if (--t <= 0) { ! 1417: printf("vd%d: controller timeout", vm->um_ctlr); ! 1418: VDABORT(vdaddr, vd->vd_type); ! 1419: return (0); ! 1420: } ! 1421: DELAY(1000); ! 1422: } ! 1423: if (vd->vd_type == VDTYPE_SMDE) { ! 1424: do { ! 1425: DELAY(50); ! 1426: uncache(&vdaddr->vdcsr); ! 1427: } while (vdaddr->vdcsr & CS_GO); ! 1428: DELAY(300); ! 1429: uncache(&vd->vd_dcb.err_code); ! 1430: } ! 1431: DELAY(200); ! 1432: uncache(&vd->vd_dcb.operrsta); ! 1433: return (1); ! 1434: } ! 1435: ! 1436: #ifdef COMPAT_42 ! 1437: struct vdst { ! 1438: int nsec; /* sectors/track */ ! 1439: int ntrack; /* tracks/cylinder */ ! 1440: int ncyl; /* cylinders */ ! 1441: int secsize; /* sector size */ ! 1442: char *name; /* type name */ ! 1443: struct { ! 1444: int off; /* partition offset in sectors */ ! 1445: int size; /* partition size in sectors */ ! 1446: } parts[8]; ! 1447: } vdst[] = { ! 1448: { 66, 23, 850, 512, "NEC 800", ! 1449: {0, 1290300}, /* a cyl 0 - 849 */ ! 1450: }, ! 1451: { 64, 20, 842, 512, "2361a", ! 1452: {0, 61440}, /* a cyl 0 - 47 */ ! 1453: {61440, 67840}, /* b cyl 48 - 100 */ ! 1454: {129280, 942080}, /* c cyl 101 - 836 */ ! 1455: {0, 1071360}, /* d cyl 0 - 836 */ ! 1456: {449280, 311040}, /* e cyl 351 - 593 */ ! 1457: {760320, 311040}, /* f cyl 594 - 836 */ ! 1458: {449280, 622080}, /* g cyl 351 - 836 */ ! 1459: {129280, 320000} /* h cyl 101 - 350 */ ! 1460: }, ! 1461: { 48, 24, 711, 512, "xsd", ! 1462: {0, 61056}, /* a cyl 0 - 52 */ ! 1463: {61056, 61056}, /* b cyl 53 - 105 */ ! 1464: {122112, 691200}, /* c cyl 106 - 705 */ ! 1465: {237312, 576000}, /* d cyl 206 - 705 */ ! 1466: {352512, 460800}, /* e cyl 306 - 705 */ ! 1467: {467712, 345600}, /* f cyl 406 - 705 */ ! 1468: {582912, 230400}, /* g cyl 506 - 705 */ ! 1469: {698112, 115200} /* h cyl 606 - 705 */ ! 1470: }, ! 1471: { 44, 20, 842, 512, "eagle", ! 1472: {0, 52800}, /* egl0a cyl 0 - 59 */ ! 1473: {52800, 66000}, /* egl0b cyl 60 - 134 */ ! 1474: {118800, 617760}, /* egl0c cyl 135 - 836 */ ! 1475: {736560, 4400}, /* egl0d cyl 837 - 841 */ ! 1476: {0, 736560}, /* egl0e cyl 0 - 836 */ ! 1477: {0, 740960}, /* egl0f cyl 0 - 841 */ ! 1478: {118800, 310640}, /* egl0g cyl 135 - 487 */ ! 1479: {429440, 307120} /* egl0h cyl 488 - 836 */ ! 1480: }, ! 1481: { 64, 10, 823, 512, "fuj", ! 1482: {0, 38400}, /* fuj0a cyl 0 - 59 */ ! 1483: {38400, 48000}, /* fuj0b cyl 60 - 134 */ ! 1484: {86400, 437120}, /* fuj0c cyl 135 - 817 */ ! 1485: {159360, 364160}, /* fuj0d cyl 249 - 817 */ ! 1486: {232320, 291200}, /* fuj0e cyl 363 - 817 */ ! 1487: {305280, 218240}, /* fuj0f cyl 477 - 817 */ ! 1488: {378240, 145280}, /* fuj0g cyl 591 - 817 */ ! 1489: {451200, 72320} /* fug0h cyl 705 - 817 */ ! 1490: }, ! 1491: { 32, 24, 711, 512, "xfd", ! 1492: { 0, 40704 }, /* a cyl 0 - 52 */ ! 1493: { 40704, 40704 }, /* b cyl 53 - 105 */ ! 1494: { 81408, 460800 }, /* c cyl 106 - 705 */ ! 1495: { 0, 81408 }, /* d cyl 709 - 710 (a & b) */ ! 1496: { 0, 542208 }, /* e cyl 0 - 705 */ ! 1497: { 40704, 501504 }, /* f cyl 53 - 705 (b & c) */ ! 1498: { 81408, 230400 }, /* g cyl 106 - 405 (1/2 of c) */ ! 1499: { 311808,230400 } /* h cyl 406 - 705 (1/2 of c) */ ! 1500: }, ! 1501: { 32, 19, 823, 512, "smd", ! 1502: {0, 40128}, /* a cyl 0-65 */ ! 1503: {40128, 27360}, /* b cyl 66-110 */ ! 1504: {67488, 429856}, /* c cyl 111-817 */ ! 1505: {139232, 358112}, /* d cyl 229 - 817 */ ! 1506: {210976, 286368}, /* e cyl 347 - 817 */ ! 1507: {282720, 214624}, /* f cyl 465 - 817 */ ! 1508: {354464, 142880}, /* g cyl 583 - 817 */ ! 1509: {426208, 71136} /* h cyl 701 - 817 */ ! 1510: }, ! 1511: { 18, 15, 1224, 1024, "mxd", ! 1512: {0, 21600}, /* a cyl 0-79 */ ! 1513: {21600, 22410}, /* b cyl 80-162 */ ! 1514: {44010, 285120}, /* c cyl 163-1217 */ ! 1515: #ifdef notyet ! 1516: {x, 237600}, /* d cyl y - 1217 */ ! 1517: {x, 190080}, /* e cyl y - 1217 */ ! 1518: {x, 142560}, /* f cyl y - 1217 */ ! 1519: {x, 95040}, /* g cyl y - 1217 */ ! 1520: {x, 47520} /* h cyl 701 - 817 */ ! 1521: #endif ! 1522: }, ! 1523: { 32, 10, 823, 512, "fsd", ! 1524: {0, 19200}, /* a cyl 0 - 59 */ ! 1525: {19200, 24000}, /* b cyl 60 - 134 */ ! 1526: {43200, 218560}, /* c cyl 135 - 817 */ ! 1527: } ! 1528: }; ! 1529: #define NVDST (sizeof (vdst) / sizeof (vdst[0])) ! 1530: ! 1531: /* ! 1532: * Construct a label for an unlabeled pack. We ! 1533: * deduce the drive type by reading from the last ! 1534: * track on successively smaller drives until we ! 1535: * don't get an error. ! 1536: */ ! 1537: vdmaptype(vi, lp) ! 1538: register struct vba_device *vi; ! 1539: register struct disklabel *lp; ! 1540: { ! 1541: register struct vdsoftc *vd; ! 1542: register struct vdst *p; ! 1543: struct vba_ctlr *vm = vi->ui_mi; ! 1544: int i; ! 1545: ! 1546: vd = &vdsoftc[vi->ui_ctlr]; ! 1547: for (p = vdst; p < &vdst[NVDST]; p++) { ! 1548: if (vd->vd_type == VDTYPE_VDDC && p->nsec != 32) ! 1549: continue; ! 1550: lp->d_nsectors = p->nsec; ! 1551: lp->d_ntracks = p->ntrack; ! 1552: lp->d_ncylinders = p->ncyl; ! 1553: lp->d_secsize = p->secsize; ! 1554: DELAY(100000); ! 1555: if (!vdreset_drive(vi)) ! 1556: return (0); ! 1557: DELAY(100000); ! 1558: vd->vd_dcb.opcode = VDOP_RD; ! 1559: vd->vd_dcb.intflg = DCBINT_NONE; ! 1560: vd->vd_dcb.nxtdcb = (struct dcb *)0; /* end of chain */ ! 1561: vd->vd_dcb.devselect = dksoftc[vi->ui_unit].dk_dcb.devselect; ! 1562: vd->vd_dcb.trailcnt = sizeof (struct trrw) / sizeof (long); ! 1563: vd->vd_dcb.trail.rwtrail.memadr = ! 1564: vtoph((struct proc *)0, (unsigned)vd->vd_rbuf.vb_rawbuf); ! 1565: vd->vd_dcb.trail.rwtrail.wcount = lp->d_secsize / sizeof(short); ! 1566: vd->vd_dcb.operrsta = 0; ! 1567: vd->vd_dcb.trail.rwtrail.disk.cylinder = p->ncyl - 2; ! 1568: vd->vd_dcb.trail.rwtrail.disk.track = p->ntrack - 1; ! 1569: vd->vd_dcb.trail.rwtrail.disk.sector = p->nsec - 1; ! 1570: vd->vd_mdcb.mdcb_head = (struct dcb *)vd->vd_dcbphys; ! 1571: vd->vd_mdcb.mdcb_status = 0; ! 1572: VDGO(vm->um_addr, vd->vd_mdcbphys, vd->vd_type); ! 1573: if (!vdpoll(vm, 60)) ! 1574: printf(" during probe\n"); ! 1575: if ((vd->vd_dcb.operrsta & VDERR_HARD) == 0) ! 1576: break; ! 1577: } ! 1578: if (p >= &vdst[NVDST]) ! 1579: return (0); ! 1580: ! 1581: for (i = 0; i < 8; i++) { ! 1582: lp->d_partitions[i].p_offset = p->parts[i].off; ! 1583: lp->d_partitions[i].p_size = p->parts[i].size; ! 1584: } ! 1585: lp->d_npartitions = 8; ! 1586: lp->d_secpercyl = lp->d_nsectors * lp->d_ntracks; ! 1587: bcopy(p->name, lp->d_typename, 4); ! 1588: return (1); ! 1589: } ! 1590: #endif COMPAT_42 ! 1591: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.