/* * DEC RX02 dual-density floppy disk drive */ #include "sys/param.h" #include "sys/dir.h" #include "sys/user.h" #include "sys/buf.h" #include "sys/elog.h" #include "sys/iobuf.h" #include "sys/systm.h" #include "sys/ioctl.h" struct device { unsigned short cs; /* Control/status register */ unsigned short db; /* Date buffer register */ }; struct device *rx_addr; /* * state codes, which tell rxintr * what is going on * these live in rxtab.b_state * synonymous with the RX02 function * currently being executed * already shifted for rx->cs */ #define FILL 00 /* Fill buffer command as part of a write */ #define EMPTY 02 /* Empty buffer command as part of a read */ #define WRITE 04 /* Write sector command */ #define READ 06 /* Read sector command */ /* * additional functions * (never in rxtab.b_state) * probably should be accessible * via ioctl() */ #define DENSITY 010 /* Set density command */ #define STATUS 012 /* Read status command */ #define WDDS 014 /* Write deleted data sector command */ #define RERROR 016 /* Read error code command */ /* Bits in the CS register */ #define GO 1 #define UNIT (1<<4) #define DONE (1<<5) #define IE (1<<6) #define TR (1<<7) #define DENS (1<<8) #define INIT (1<<14) #define ERROR (1<<15) /* * splitup of the minor device number */ #define DUNIT 01 /* unit, 0 or 1 */ #define DHIGH 02 /* high density */ #define DRRAW 04 /* really raw mode (no mapping) */ #define DSHARE 010 /* (not yet) non-exclusive open */ /* Some useful macros */ #define tsize(X) (density(X) ? 256 : 128 ) #define density(X) ( minor( (X)->b_dev ) & DHIGH ) #define unit(X) ( minor( (X)->b_dev ) & DUNIT ) #define unmapped(X) (minor((X)->b_dev) & DRRAW) #define track(bp) (lobyte((bp)->av_back)) #define sector(bp) (hibyte((bp)->av_back)) #define b_curdp io_s1 #define b_state io_s2 #define b_open io_s1 #define b_cmd b_resid /* Some constants used by the driver */ #define NSECT 26 /* sectors per track */ #define NTRACK 77 /* tracks per disk */ #define NBLOCKS (NSECT*NTRACK) /* RX blocks per disk */ #define MAXCOUNT (512*NSECT) /* max xfer size, bytes */ #define TIMELOOP 6 /* time to wait for TR */ #define RETRY 3 #define NUNITS 2 int rxmap[NUNITS]; /* UNIBUS mapping registers */ paddr_t rxuniaddr[NUNITS]; /* UNIBUS address of next transfer */ int rxlblock[NUNITS]; /* logical block of next transfer */ int rxstrategy(); struct buf rxbuf; struct buf crxbuf; struct iostat rxstat[NUNITS]; struct iobuf rxtab = tabinit(RX0,0); struct iobuf rxutab[NUNITS] = {tabinit(RX0,&rxstat[0]), tabinit(RX0,&rxstat[1])}; rxopen(dev) register dev_t dev; { register struct iobuf *dp; if ( (rxtab.b_flags & B_ONCE) == 0 ) { rxmap[0] = ubmalloc( MAXCOUNT , 0 ); rxmap[1] = ubmalloc( MAXCOUNT , 0 ); rxtab.b_flags |= B_ONCE; } dp = &rxutab[dev & DUNIT]; dp->io_addr = (physadr) rx_addr; dp->io_nreg = NDEVREG; if (dp->b_open++) u.u_error = EIO; } rxclose(dev) register dev_t dev; { rxutab[dev & DUNIT].b_open = 0; } rxread(dev) { physio(rxstrategy,&rxbuf,dev,B_READ); } rxwrite(dev) { physio(rxstrategy,&rxbuf,dev,B_WRITE); } rxioctl(dev, cmd, arg, mode) { switch (cmd) { case IOCTYPE: u.u_rval1 = RIOC; break; case RIOCINI: rxcommand(dev, DENSITY); break; default: u.u_error = EINVAL; break; } } rxcommand(dev, cmd) dev_t dev; int cmd; { register struct buf *bp; bp = &crxbuf; spl5(); while (bp->b_flags & B_BUSY) { bp->b_flags |= B_WANTED; sleep(bp, PRIBIO); } spl0(); bp->b_dev = dev; bp->b_cmd = cmd; bp->b_flags = B_BUSY; rxstrategy(bp); iowait(bp); if (bp->b_flags & B_WANTED) wakeup(bp); geterror(bp); bp->b_flags = 0; } rxstrategy(bp) register struct buf *bp; { register struct iobuf *dp; register int max; if (bp != &crxbuf) { if ( bp->b_bcount > MAXCOUNT ) { bp->b_flags |= B_ERROR; iodone(bp); return; } max = NBLOCKS; if ((minor(bp->b_dev) & DRRAW) == 0) max -= NSECT; /* DEC doesn't use track 0 */ max <<= density(bp) ? 2 : 1; if (bp->b_blkno >= max) { if (bp->b_blkno == max) bp->b_resid = bp->b_bcount; else bp->b_flags |= B_ERROR; iodone(bp); return; } if ( bp->b_bcount < tsize(bp) ) { bp->b_resid = bp->b_bcount; iodone(bp); return; } } dp = &rxutab[unit(bp)]; spl5(); bp->av_forw = NULL; if ( dp->b_actf != NULL ) dp->b_actl->av_forw = bp; else dp->b_actf = bp; dp->b_actl = bp; if (rxtab.b_active == 0) rxstart( unit(bp) ); spl0(); } /* * start a transfer * preferably on the named drive */ rxstart( drive ) int drive; { register struct device *rx; register struct iobuf *dp; register struct buf *bp; ushort tcs; register int i; rx = rx_addr; loop: /* * pick a drive */ dp = &rxutab[drive]; if ((bp = dp->b_actf) == NULL) { drive = (drive == 0) ? 1 : 0; dp = &rxutab[drive]; if ((bp = dp->b_actf) == NULL) return; } if ((rx->cs & DONE) == 0) { tcs = rx->cs; rx->cs = INIT; i = 10; /* stall while it resets */ while (--i > 0) ; if ((rx->cs & DONE) == 0) { printf("rx: not ready, cs %o\n", tcs); bp->b_flags |= B_ERROR; } } else if (bp == &crxbuf) { rxcio(bp); rxtab.b_curdp = (int)dp; rxtab.b_active++; } else { if (dp->b_active++ == 0) { #if vax rxuniaddr[drive] = ubmaddr(bp, rxmap[drive]); #else rxuniaddr[drive] = paddr(bp); #endif rxlblock[drive] = bp->b_blkno << (density(bp) ? 1 : 2); bp->b_resid = bp->b_bcount; } if (rxblock(bp)) bp->b_flags |= B_ERROR; else { rxtab.b_curdp = (int)dp; rxtab.b_active++; if ( bp->b_flags & B_READ ) rxsio(bp); else rxbio(bp); } } if (bp->b_flags & B_ERROR) { dp->b_actf = bp->av_forw; rxtab.b_active = 0; dp->b_active = 0; iodone(bp); goto loop; /* ugh */ } blkacty |= (1 << RX0); } rxintr() { register struct device *rx; register struct buf *bp; register struct iobuf *dp; rx = rx_addr; dp = (struct iobuf *)rxtab.b_curdp; if (dp == NULL || (bp = dp->b_actf) == NULL ) { printf("rx: stray\n"); return; } if ( rx->cs & ERROR ) { if (rxtab.b_errcnt > 2) #if vax deverr(dp, (ushort)rx->cs, (ushort)rx->db); #else deverror(bp, (ushort)rx->cs, (ushort)rx->db); #endif if (rxtab.b_errcnt++ > RETRY) bp->b_flags |= B_ERROR; else if (rxtab.b_state == READ || rxtab.b_state == WRITE) rxsio(bp); else bp->b_flags |= B_ERROR; fmtberr(dp, 0); } else { switch (rxtab.b_state) { case READ: rxbio(bp); break; case FILL: rxsio(bp); break; case WRITE: case EMPTY: bp->b_resid -= tsize(bp); rxuniaddr[unit(bp)] += tsize(bp); rxlblock[unit(bp)]++; rxtab.b_active = 0; break; case DENSITY: bp->b_resid = 0; rxtab.b_active = 0; break; default: printf("rx: bozo state %o\n", rxtab.b_state); bp->b_flags |= B_ERROR; break; } } if (bp->b_flags & B_ERROR || bp->b_resid < tsize(bp)) { blkacty &=~ (1 << RX0); if (dp->io_erec) logberr(dp, bp->b_flags & B_ERROR); dp->b_actf = bp->av_forw; dp->b_active = 0; rxtab.b_active = 0; rxtab.b_errcnt = 0; iodone(bp); } if (rxtab.b_active == 0) rxstart(unit(bp) ? 0 : 1); } /* * the following pair of routines * echo the bozo way in which the RX02 works * to read a sector, * you tell the controller to read that sector * into its internal buffer, * then give another command * to make it dump the buffer into memory * similarly for write * hence: * * rxbio(bp) does the appropriate buffer<>memory command * rxsio(bp) does the proper disk<>buffer command * * eg, for a read, the sequence is * * rxsio(bp); (read sector into buffer) * wait for intr * rxbio(bp); (dump buffer into our memory) * wait for intr * * both routines set B_ERROR if something goes wrong */ rxbio(bp) register struct buf *bp; { register struct device *rx; register ushort tcs; rx = rx_addr; tcs = hiword(rxuniaddr[unit(bp)]) << 12; tcs |= IE | GO; if (unit(bp)) tcs |= UNIT; if (density(bp)) tcs |= DENS; if (bp->b_flags & B_READ) rxtab.b_state = EMPTY; else rxtab.b_state = FILL; tcs |= rxtab.b_state; rx->cs = tcs; if (rxpdb(tsize(bp) >> 1) || rxpdb(loword(rxuniaddr[unit(bp)]))) bp->b_flags |= B_ERROR; } rxsio(bp) register struct buf *bp; { register struct device *rx; register ushort tcs; rx = rx_addr; tcs = IE | GO; if (unit(bp)) tcs |= UNIT; if (density(bp)) tcs |= DENS; if (bp->b_flags & B_READ) rxtab.b_state = READ; else rxtab.b_state = WRITE; tcs |= rxtab.b_state; rx->cs = tcs; if (rxpdb(sector(bp)) || rxpdb(track(bp))) bp->b_flags |= B_ERROR; } /* * start routine for non-I/O commands * such as initializing a diskette */ rxcio(bp) register struct buf *bp; { register struct device *rx; register ushort cs; rx = rx_addr; cs = bp->b_cmd | IE | GO; if (unit(bp)) cs |= UNIT; if (density(bp)) cs |= DENS; rxtab.b_state = bp->b_cmd; switch (bp->b_cmd) { case DENSITY: rx->cs = cs; if (rxpdb('I')) bp->b_flags |= B_ERROR; break; default: printf("rx: bozo command %o\n", bp->b_cmd); bp->b_flags |= B_ERROR; break; } } /* * wait for TR to come on, then * load the data buffer register * returns nonzero if it didn't * which generally means the device is unhappy */ rxpdb(val) unsigned short val; { register struct device *rx; register int tim; rx = rx_addr; if ( rx->cs & DONE ) return(1); tim = TIMELOOP; while ((rx->cs & TR) == 0) if (--tim <= 0) return (1); rx->db = val; return (0); } /* * turn a logical block number * (from rxlblock) * into a track & sector * if RRAW, just map directly * otherwise apply DEC standard mapping * cribbed from VAX/VMS I/O User's Guide * sec 3.2.4 (page 3-5) */ rxblock(bp) register struct buf *bp; { register int lbno; lbno = rxlblock[unit(bp)]; if (minor(bp->b_dev) & DRRAW) { track(bp) = lbno / NSECT; sector(bp) = lbno % NSECT + 1; } else { register int sect; track(bp) = lbno / NSECT; sect = lbno % NSECT; sect *= 2; if (sect > 24) sect++; sect += track(bp) * 6; sect %= NSECT; sect++; sector(bp) = sect; track(bp)++; } return (track(bp) >= NTRACK); }