# /* RL11-RL01/RL02 disk driver for V6 Unix * * Supports a single controller with up to 4 drives (maximum number * supported by a single controller); minor numbers 0-3 are RL01 drives * (5GB), numbers 4-7 are RL02 drives (10GB). RL01's and RL02's are almost * identical as far as the controller is concerned, except that RL01's * have only 256 cylinders, and RL02's have 512. * * Minor numbers > 7 are interleaved RL02 drives, with N-way interleave * across N drives, with N being (drive - 8). I.e. with drive (m + 8), * logical block b is found on drive (b % m), at physical block (b / m). * (Maximum interleave of 3, since the starting block number must fit * into a 16-bit integer; interleave of 0/1 of course makes no sense.) * NOTE: Multi-block operations on a raw, interleaved device don't work, * because the actual location of each logical block needs to be computed * separately (since they are scattered across multiple drives), but this * code does not do so (it does all long raw reads as multi-block reads). * For the moment, just returns an error if any such are attempted. * * * b_resid is used to hold the number of bytes completed so far. * av_back is used to hold the number of bytes in the current I/O * operation. * Since V6 C doesn't have casts, and as a result gets type clashes from * using that field, an ugly work-around has to be used anytime it's used. * (There is no other field in the buffer header we can 'borrow', since * b_resid is already used, although perhaps this data could be kept in a * driver static). * * This header said "Changes b_addr, b_xmem, and b_blkno for raw IO. This * is not the best way to do it." but it's not clear that mashing the raw * buffer header (which is presumably what this comment refers to, since for * a device which can only transfer a maximum of a single track in one * operation, a large raw request has to be broken up into multiple * transfers) is really that bad, as opposed to using a private data * area. * * * Doesn't do overlapped seeks (although in theory it's possible to do * so), because it's not clear this is really that doable anyway. (Not a * problem for simulated drives, of course.... :-) * * Although the "RL01/RL02 User Guide" says "The controller sends the Seek * command to the selected drive, causing the drive to start its Seek * operation. ... The controller is now ready to accept another command to * perform another operation on another drive while the Seek is occurring" * (pg. 4-16), it also says "it is possible to issue seeks to additional * drives while the first is seeking. However, no interrupt occurs when the * seeks are completed, so the transfer command should be issued to the * drive requiring the shortest seek" (pg. 4-18); and "The RK05 will provide * two interrupts as the result of a seek operation. The first interrupt * occurs as soon as the controller .. is free to handle another function. * The second interrupt occurs when the drive finishes the seek movement. * The RLOl/RL02 subsystem does not provide the second interrupt. Thus, * the software must perform the proper monitoring of the drive to determine * when the seek has been completed" (pg 4-22). * * This last seems to be a reference to the fact that the controller has * separate Controller and Drive Ready bits (with the interrupt being tied * to the former setting), and per the UNIBUS Peripheral Handbook "Drive Ready * ... is cleared when a seek operation is initiated and set when the seek * operation is completed"; i.e. it is possible to tell when a seek completes, * but only by selecting the drive (by writing the appropriate value into the * CSR) and examining the Drive Ready bit in the CSR. * * This all indicates that doing a driver which iniates seeks on multiple * drive, returns, and then starts a read operation as soon as the first * seek has actually completed would not be simple. Other optimizations * would be more feasible; e.g. if the controller is busy while requests for * several drives arrive, although the seeks on the other drives cannot be * requested until the current transfer completes, at that point multiple * seeks could be initiated, after which (as the manual recomends) a * transfer could be requested on the drive whose seek is expected to * complete first. * * Note that the busy loop in rlstart() after issuing the seek command, * before issuing the transfer command, is likely not very problematic; * it is just waiting for the seek to be started, and the controller to * become free (note that Drive Ready is not examined). Trying to use an * interrupt for the completion of the first command would probably be * more overhead than simply busy-looping momentarily. */ #include "../param.h" #include "../buf.h" #include "../conf.h" #include "../user.h" #define RLADDR 0174400 #define NRL 4 #define NRL1BLK 10240 /* RL01 readable space, includes bad sector table */ #define NRL2BLK 20480 /* RL02 readable space, includes bad sector table */ #define NBPT 20 /* blocks (2 sectors) per track */ struct { int rlcs; int rlba; int rlda; int rlmp; }; /* rlcs */ #define DE 040000 #define NXM 020000 #define DLT 010000 #define CRC 04000 #define OPI 02000 #define CTLRDY 0200 #define IENABLE 0100 #define NOP 0 /* no-op | reset | controller-test */ #define GETSTAT 04 #define SEEK 06 #define READHD 010 #define WCOM 012 #define RCOM 014 #define DRVRDY 01 /* unused */ /* rlda: get/reset status */ #define RESET 010 #define STS 02 #define MARK 01 /* also during seek */ /* rlda: IO */ #define CYL 0177600 #define SURF 0100 #define SECTOR 077 /* rlda: seek */ #define MVCENTER 04 /* rlmp: get status */ #define VC 01000 #define DT 0200 /* drive is an RL02 */ /* Driver stuff */ #define NODRIVE -1 #define INTMIN 010 /* Start of interleaved minor device numbers */ #define RL02 04 /* Bit in minor device number for RL02 */ #define RLDNO 03 /* Drive number in minor device number */ #define SSEEK 1 #define SIO 2 #define ioct av_back /* bytes in current IO */ #define bcount(wc) (-(wc) << 1) /* in v6, b_wcount is negative */ #define BSIZE 512 #define BMASK (BSIZE-1) #define BSHIFT 9 /* Convert bytes to blocks */ #define BSMASK 0177 /* Block number can be at most this big */ struct devtab rltab; struct buf rrlbuf; int rldrive NODRIVE; struct rl { char *rl_curr; /* current cylinder */ int rl_known; /* known position */ } rl[NRL]; /* Don't allow writes to bad block track. (The math does work to guarantee * this for interleaved disks; if IBNO < ((NRL2BLK - NBPT) * N), then * OBNO = IBNO/N is guaranteed to produce a number < (NRL2BLK - NBPT).) */ rlstrategy(abp) struct buf *abp; { register struct buf *bp; register int d; int maxrblk, maxwblk; bp = abp; if(bp->b_flags&B_PHYS) mapalloc(bp); d = bp->b_dev.d_minor - 7; if (d <= 0) { maxrblk = ((bp->b_dev.d_minor & RL02) ? NRL2BLK : NRL1BLK); maxwblk = (maxrblk - NBPT); } else { maxrblk = (NRL2BLK * d); maxwblk = (maxrblk - (d * NBPT)); } if ((bp->b_blkno >= maxrblk) || ((bp->b_flags&B_READ) == 0) && bp->b_blkno >= maxwblk) { bp->b_flags =| B_ERROR; iodone(bp); return; } bp->av_forw = NULL; spl5(); if (rltab.d_actf==NULL) rltab.d_actf = bp; else rltab.d_actl->av_forw = bp; rltab.d_actl = bp; bp->b_resid = 0; if (rltab.d_active==NULL) rlstart(); spl0(); } /* Notice hack where high bit of 'sector number' (actually sector number * *2, since there are two sectors per Unix block) gives the head number. */ rlstart() { register struct buf *bp; /* rc 5 6 */ register char **rlcp, *bc; /* rc 5 4 5 3 3 */ char *bno, *newst, *newcyl, *diff, *nblks; /* rc 4 4 4 4 */ int com, d, m, sector; if ((bp = rltab.d_actf) == NULL) { rldrive = NODRIVE; return; } rltab.d_active = SSEEK; bno = bp->b_blkno; m = bp->b_dev.d_minor - 7; if(m <= 0) d = (bp->b_dev.d_minor & RLDNO); else { d = bno % m; bno =/ m; } sector = bno % NBPT; newst = (((bno / NBPT) << 6) | (sector << 1)); while ((RLADDR->rlcs & CTLRDY) == 0) ; if (!rl[d].rl_known) rlreset(d); rlcp = &rl[d].rl_curr; newcyl = newst & CYL; if (newcyl > *rlcp) diff = (newcyl - *rlcp) | MVCENTER; else diff = *rlcp - newcyl; *rlcp = newcyl; RLADDR->rlda = ((diff + MARK) | ((newst & SURF) >> 2)); RLADDR->rlcs = (SEEK | (d << 8)); rltab.d_active = SIO; if ((bc = bcount(bp->b_wcount) - bp->b_resid) > BSIZE) { nblks = ((bc >> BSHIFT) & BSMASK); if (bc & BMASK) nblks++; if (sector + nblks > NBPT) bc = (NBPT - sector) << BSHIFT; } bp->ioct = bc; com = (((bp->b_xmem & 03) << 4) | IENABLE | ((d & RLDNO) << 8)); if (bp->b_flags & B_READ) com =| RCOM; else com =| WCOM; while ((RLADDR->rlcs & CTLRDY) == 0) ; RLADDR->rlda = newst; RLADDR->rlba = bp->b_addr; RLADDR->rlmp = (-(bc >> 1)); RLADDR->rlcs = com; rldrive = d; } /* Hack of using ">> BSHIFT" to convert bytes to blocks works here since * the maximum number of bytes in any one transfer is 256*40, or 10240, * so the high bit will always be clear. */ rlintr() { register struct buf *bp; register char *c, *a; int status; if (rltab.d_active == NULL || rldrive == NODRIVE) return; bp = rltab.d_actf; rltab.d_active = NULL; if (RLADDR->rlcs < 0) { /* error bit */ if (RLADDR->rlcs&DE) { RLADDR->rlda = STS|MARK; rlexec(GETSTAT, rldrive); status = RLADDR->rlmp; if (status&VC) --rltab.d_errcnt; else { printf("rlmp; "); deverror(bp, status, RLADDR->rlda); } } else if (RLADDR->rlcs&(NXM|DLT|CRC|OPI)) { printf("rlcs; "); deverror(bp, RLADDR->rlcs, RLADDR->rlda); } rlreset(rldrive); if (++rltab.d_errcnt <= 10) { rlstart(); return; } else bp->b_flags =| B_ERROR; } else { bp->b_resid =+ (c = bp->ioct); if (bcount(bp->b_wcount) != bp->b_resid) { bp->b_blkno =+ ((c = bp->ioct) >> BSHIFT); a = bp->b_addr; if ((bp->b_addr =+ (c = bp->ioct)) < a) /* overflow */ bp->b_xmem++; rlstart(); return; } } rltab.d_errcnt = 0; rltab.d_actf = bp->av_forw; bp->b_resid = 0; iodone(bp); rlstart(); } rlreset(adrive) { register int drive; drive = adrive; RLADDR->rlda = RESET|STS|MARK; rlexec(GETSTAT, drive); rlexec(READHD, drive); rl[drive].rl_curr = RLADDR->rlmp & CYL; rl[drive].rl_known = 1; } rlexec(c, d) { RLADDR->rlcs = (c | ((d & RLDNO) << 8)); while((RLADDR->rlcs & CTLRDY) == 0) ; } /* Multi-block transfers to raw interleaved devices not currently * supported. */ rlread(dev) { if ((dev.d_minor >= INTMIN) && (u.u_count > BSIZE)) { bp->b_flags =| B_ERROR; iodone(bp); } physio(rlstrategy, &rrlbuf, dev, B_READ); } rlwrite(dev) { if ((dev.d_minor >= INTMIN) && (u.u_count > BSIZE)) { bp->b_flags =| B_ERROR; iodone(bp); } physio(rlstrategy, &rrlbuf, dev, B_WRITE); }