/* * 2.5-MB and 10-MB cartridge disk driver: Multi-disc version * - overlapped seeks * - seeks ordered by 'FSCAN' algorithm * - dual-platter logical disks */ #include "../h/local.h" #ifdef SCCS_ID static char SCCS_ID [] = "@(#)dsk.c 3.3 14:58:45 - 82/09/10 "; #endif SCCS_ID #include "../h/param.h" #include "../h/buf.h" #include "../h/conf.h" #include "../h/selch.h" #include "../h/systm.h" #define NBPC 24 /* number of blocks per cylinder */ #define NDSKCYL (dsksize? 408 : 203) /* no of cylinders per disk */ #define NDSKERR 10 /* number of error retries */ extern int ndsk; /* number of disks */ extern int dskcntl; /* disk controller address */ extern char dskaddr[]; /* disk file addresses */ extern int dskselch; /* channel address for disk controller */ extern int dsksize; /* 0 => 2.5M, 1 => 10M */ int dskstart(), dskscintr(), dsknrdy(); struct buf rdskbuf; /* raw I/O buffer */ struct buf dsktab; /* major device table (per controller) * b_active = controller busy flag * b_actf = ptr to dsk table for last * accessed drive */ struct dsk { /* disk table (per drive) */ char dk_active; /* drive busy flag */ char dk_errcnt; /* error count (for recovery) */ int dk_cyl; /* current cylinder position */ int dk_dir; /* current direction of arm motion */ struct buf *av_forw; /* head of I/O queue for next pass */ struct buf *dk_actf; /* head of I/O queue for this pass */ int dk_junk[3]; /* kludge padding to power of two */ } dsk[4] = { { 0, 0, -1 }, { 0, 0, -1 }, { 0, 0, -1 }, { 0, 0, -1 } }; #define DSEEK 1 #define DIO 2 #define DNRDY 3 struct selchq dskscq { /* channel queue entry */ &dskstart, &dskscintr, 0, 0 }; /* Redefined buffer fields */ struct { /* kludge to be cleaned later */ int integ; }; struct { /* as above */ char pad[2]; char hibyte, lobyte; }; #define b_cyl av_back.integ #define b_sector b_resid.hibyte #define b_file b_resid.lobyte /* Disk file status & commands */ #define UNRCV 0x62 /* wrt chk|ill addr|seek inc */ #define ADDR_INTLK 0x10 #define WRT_PROT 0x80 #define NOT_READY 0x01 #define SEEK 0x42 /* Disk controller status & commands */ #define CNTL_UNRCV 0xe1 /* overrun|addr comp fail|def trk|data err */ #define OVERRUN 0x80 #define CYL_OV 0x10 #define IDLE 0x02 #define READ 0x01 #define WRITE 0x02 /* * Disk strategy routine * - check block no. & translate to cylinder/head/sector * - put buffer on queue for corresponding drive * - if controller free, call dskstart() to start I/O */ dskstrategy(bp) register struct buf *bp; { register struct dsk *dp; register struct buf *p1, *p2; register int dev; register int dn; trace(1<<16, "dstrategy", bp); if ((minor(bp->b_dev)&07) >= ndsk) { bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; iodone(bp); return; } /* * Calculate file/cylinder/head/sector */ bp->b_cyl = bp->b_blkno / NBPC; if ((bp->b_sector = (bp->b_blkno<<1) % (2*NBPC)) >= NBPC) bp->b_sector += 32-NBPC; /* * Minor device n>=8 is interleaved cylinders of devices [n]-8, [n]-7 * (where [n] is n rounded down to an even number) */ if ((dev = minor(bp->b_dev)) < 8) bp->b_file = dskaddr[dev]; else { bp->b_file = (dskaddr[dev&06]) | (bp->b_cyl&01); bp->b_cyl >>= 1; } /* * Block no. too high -- looks like EOF for raw read, error otherwise */ if (bp->b_cyl >= NDSKCYL) { if ((bp->b_flags&(B_PHYS|B_READ)) != (B_PHYS|B_READ)) bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; iodone(bp); return; } /* * Put buffer on queue for drive */ dp = &dsk[(bp->b_dev&07)>>dsksize]; dn = dp - dsk; dk_numb[dn]++; dk_wds[dn] += (bp->b_bcount>>6); spl5(); for (p1 = dp; (p2 = p1->av_forw) && (dp->dk_dir? (p2->b_cyl <= bp->b_cyl) : (p2->b_cyl >= bp->b_cyl) ); ) p1 = p2; bp->av_forw = p2; p1->av_forw = bp; /* * Start I/O */ if (!(dp->dk_active || dsktab.b_active)) selchreq(dskselch, &dskscq, 0); spl0(); } /* * Disk startup routine * - start overlapped seeks on all drives * - check status of finished seeks * - start I/O transfer on 'next' drive (round robin) */ dskstart(unit) { register struct dsk *dp, *idp; register struct buf *bp; register int file, stat; idp = 0; if ((dp = dsktab.b_actf) == 0) dsktab.b_actf = dp = dsk; trace(1<<16, "dstart", dp); do { /* for each drive */ /* * Start overlapped seeks on all drives */ if (++dp >= &dsk[4]) dp = dsk; while (!dp->dk_active) { if (!(bp = dp->dk_actf)) { if (!(bp = dp->av_forw)) break; else { dp->dk_actf = bp; dp->av_forw = 0; dp->dk_dir = ~dp->dk_dir; } } file = bp->b_file; while ((stat = ss(file)) & ADDR_INTLK) ; if (bp->b_cyl == dp->dk_cyl) { /* seek done */ if (idp) break; /* check status from seek */ if (stat & ~WRT_PROT) { dskerror(dp, stat, file); dp->dk_cyl = -1; /* force seek */ continue; } idp = dp; break; } /* seek required */ if (stat & NOT_READY) { dp->dk_active = DNRDY; timeout(&dsknrdy, dp, 1000); break; } if (stat & UNRCV) { dp->dk_errcnt = NDSKERR; /* no retry */ dskerror(dp, stat, file); continue; } dp->dk_active = DSEEK; dk_busy |= 1<<(dp-dsk); wh(file, bp->b_cyl); oc(file, SEEK); trace(2<<16, "seek", file); trace(2<<16, "cyl", bp->b_cyl); break; } } while (dp != dsktab.b_actf); /* * Start next I/O transfer */ if (!idp) /* no I/O to start */ selchfree(dskselch); else { dsktab.b_actf = idp; bp = idp->dk_actf; file = bp->b_file; while (ss(file) & ADDR_INTLK) ; oc(dskselch, STOP); wdh(dskselch, bp->b_un.b_addr); wdh(dskselch, bp->b_un.b_addr+bp->b_bcount-1); wh(file, bp->b_cyl); wd(dskcntl, bp->b_sector); if (bp->b_flags & B_READ) { oc(dskcntl, READ); oc(dskselch, READ_GO); } else { oc(dskcntl, WRITE); oc(dskselch, GO); } trace(2<<16, "dio", file); trace(2<<16, "sector", bp->b_sector); idp->dk_active = DIO; dk_busy |= 1<<(idp-dsk); dsktab.b_active++; } } /* * Disk not ready routine: * - restart disk every 10 seconds until it comes ready again * - (this should not be necessary: according the Interdata manual there * should be an interrupt when NRSRW drops) * * NOTE: * This will not work when multi-level interrupts are implemented. As * this routine is called from the clock interrupt, it may be out of sync with * dskstart(), which will operate at a lower interrupt level. */ dsknrdy(dp) struct dsk *dp; { dp->dk_active = 0; if (dsktab.b_active == 0) selchreq(dskselch, &dskscq, 0); } /* * Disk interrupt routine * - disk interrupts only after a seek * - status will be checked by dskstart() when controller is free */ dskintr(dev, stat) { register struct dsk *dp; trace(4<<16, "interrupt", dskaddr[dev]); trace(4<<16, "status", stat); dp = &dsk[dev >> dsksize]; if (dp->dk_active != DSEEK) return; dp->dk_active = 0; dk_busy &= ~(1<<(dp-dsk)); dp->dk_cyl = dp->dk_actf->b_cyl; if (!dsktab.b_active) selchreq(dskselch, &dskscq, 0); } /* * Selch interrupt routine * - selch interrupt will be followed by a controller interrupt * (unless OVERRUN status is set) */ dskscintr(dev, stat, unit) { register struct dsk *dp; if (!(dp = dsktab.b_actf)) return; oc(dskselch, STOP); if ((stat = ss(dskcntl)) & OVERRUN) { dskerror(dp, stat, dskcntl); dsktab.b_active = dp->dk_active = 0; dk_busy &= ~(1<<(dp-dsk)); dskstart(0); } } /* * Disk controller interrupt routine * - check transfer status * - on cylinder overflow, restart I/O on next cylinder * - signal I/O done */ cntlintr(dev, stat) register int stat; { register struct dsk *dp; register struct buf *bp; register int resid; trace(4<<16, "interrupt", dskcntl); trace(4<<16, "status", stat); dsktab.b_active = 0; if (!(dp = dsktab.b_actf) || dp->dk_active != DIO) return; dp->dk_active = 0; dk_busy &= ~(1<<(dp-dsk)); bp = dp->dk_actf; if (stat & CNTL_UNRCV) { dskerror(dp, stat, dskcntl); dp->dk_cyl = -1; /* force seek */ dskstart(0); return; } /* cylinder overflow */ if ((stat & CYL_OV) == 0) bp->b_resid = 0; else { oc(dskselch, STOP); resid = (rdh(dskselch)-bp->b_un.b_addr+2) & ~0xff; bp->b_un.b_addr += resid; bp->b_bcount -= resid; bp->b_sector = 0; trace(1<<16, "cylov", bp->b_cyl); if (minor(bp->b_dev) < 8) bp->b_cyl++; else { /* * Dual-platter logical disk: switch platters */ if (bp->b_file&01) bp->b_cyl++; bp->b_file ^= 01; } if (bp->b_cyl < NDSKCYL) { dskstart(0); return; } else { if ((bp->b_flags&(B_PHYS|B_READ)) != (B_PHYS|B_READ)) bp->b_flags |= B_ERROR; bp->b_resid = resid; } } dp->dk_errcnt = 0; dp->dk_actf = bp->av_forw; iodone(bp); dskstart(0); } /* * Common error routine */ dskerror(dp, stat, dev) register struct dsk *dp; { register struct buf *bp; bp = dp->dk_actf; deverror(bp, stat, dev); if (++dp->dk_errcnt > NDSKERR) { dp->dk_errcnt = 0; bp->b_flags |= B_ERROR; dp->dk_actf = bp->av_forw; iodone(bp); } } /* * 'Raw' disk interface */ dskread(dev) { physio(dskstrategy, &rdskbuf, dev, B_READ); } dskwrite(dev) { physio(dskstrategy, &rdskbuf, dev, B_WRITE); } /* * Dump all of memory to disk at end of swap area */ dskdump() { register selch, cntl, file; register cyl, len; register char *memp; int dcyl; /* Make sure this is the right driver */ if (bdevsw[major(swapdev)].d_strategy != dskstrategy) { printf("Wrong dump\n"); return; } /* Set up device addresses */ selch = dskselch; cntl = dskcntl; file = dskaddr[minor(swapdev)&07]; /* Dump from memory address 0 */ memp = (char *)0; dcyl = cyl = (swplo + 1 + nswap - ((memtop+BSIZE)>>BSHIFT)) / NBPC; while ((len = memtop-memp) > 0) { /* Dump a cylinder at a time */ if (len > NBPC*BSIZE) len = NBPC*BSIZE; oc(selch, STOP); wh(file, cyl); oc(file, SEEK); while (ss(file) != 0) ; wdh(selch, memp); wdh(selch, memp + len - 1); wh(file, cyl); wd(cntl, 0); oc(cntl, WRITE); oc(selch, GO); while (ss(selch)&SELCHBSY) ; oc(selch, STOP); while (ss(cntl) != IDLE) ; memp += len; cyl++; } printf("Dumped to cyl %d\n", dcyl); }