Interdata_v6/usr/sys/dsk.c
#
/*
* 10-MB cartridge disk driver: Multi-disc version
* - overlapped seeks
* - seeks ordered by 'FSCAN' algorithm
*/
#include "param.h"
#include "buf.h"
#include "conf.h"
#include "selch.h"
/* Configuration data */
#define NSELCH 0 /* attached to first selector channel */
#define NDRV 1 /* number of disk drives */
#define NDSK 2 /* number of disks */
#define NDSKBLK 9792 /* number of blocks on disk */
#define NDSKERR 10 /* number of error retries */
int cntladdr 0xb6; /* disk controller address */
char dskaddr[NDSK] { /* disk file addresses */
0xc6,
0xc7
};
#define drivemap(dev) &dsk[dev.d_minor>>1] /* map minor dev => drive */
#define devmap(addr) &dsk[(addr-0xc6)>>4] /* map phys addr => drive */
/* Data areas */
int dskstart(), dskscintr(), dsknrdy();
struct buf rdskbuf; /* raw I/O buffer */
struct devtab dsktab; /* major device table (per controller)
* d_active = controller busy flag
* d_actf = ptr to dsk table for last
* accessed drive
*/
struct dsk { /* disk table (per drive) */
char d_active; /* drive busy flag */
char d_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 */
} dsk[NDRV];
#define DSEEK 1
#define DIO 2
#define DNRDY 3
struct selchq dskscq { /* channel queue entry */
&dskstart,
&dskscintr,
0
};
/* Redefined buffer fields */
#define b_cyl av_back.integ
#define b_sector b_resid.integ
/* 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 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;
trace(1<<16, "dstrategy", bp);
if (bp->b_dev.d_minor >= NDSK) {
bp->b_flags |= B_ERROR;
bp->b_resid = bp->b_bcount;
iodone(bp);
return;
}
/*
* Block no. too high -- looks like EOF for raw read, error otherwise
*/
if (bp->b_blkno >= NDSKBLK) {
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;
}
/*
* Calculate cylinder/head/sector
*/
bp->b_cyl = bp->b_blkno / 24;
if ((bp->b_sector = (bp->b_blkno<<1) % 48) >= 24)
bp->b_sector += 32-24;
/*
* Put buffer on queue for drive
*/
dp = drivemap(bp->b_dev);
spl(5);
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->d_active || dsktab.d_active))
selchreq(NSELCH, &dskscq);
spl(0);
}
/*
* Disk startup routine
* - start overlapped seeks on all drives
* - check status of finished seeks
* - start I/O transfer on 'next' drive (round robin)
*/
dskstart()
{
register struct dsk *dp, *idp;
register struct buf *bp;
register int file, stat;
idp = 0;
if ((dp = dsktab.d_actf) == 0)
dsktab.d_actf = dp = dsk;
trace(1<<16, "dstart", dp);
do { /* for each drive */
/*
* Start overlapped seeks on all drives
*/
if (++dp >= &dsk[NDRV])
dp = dsk;
while (!dp->d_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 = dskaddr[bp->b_dev.d_minor];
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->d_active = DNRDY;
timeout(&dsknrdy, dp, 1000);
break;
}
if (stat & UNRCV) {
dp->d_errcnt = NDSKERR; /* no retry */
dskerror(dp, stat, file);
continue;
}
dp->d_active = DSEEK;
wh(file, bp->b_cyl);
oc(file, SEEK);
trace(2<<16, "seek", file);
trace(2<<16, "cyl", bp->b_cyl);
break;
}
} while (dp != dsktab.d_actf);
/*
* Start next I/O transfer
*/
if (!idp) /* no I/O to start */
selchfree(NSELCH);
else {
dsktab.d_actf = idp;
bp = idp->dk_actf;
file = dskaddr[bp->b_dev.d_minor];
while (ss(file) & ADDR_INTLK)
;
oc(selchaddr[NSELCH], STOP);
wdh(selchaddr[NSELCH], bp->b_addr);
wdh(selchaddr[NSELCH], bp->b_addr+bp->b_bcount-1);
wh(file, bp->b_cyl);
wd(cntladdr, bp->b_sector);
if (bp->b_flags & B_READ) {
oc(cntladdr, READ);
oc(selchaddr[NSELCH], READ_GO);
} else {
oc(cntladdr, WRITE);
oc(selchaddr[NSELCH], GO);
}
trace(2<<16, "dio", file);
trace(2<<16, "sector", bp->b_sector);
idp->d_active = DIO;
dsktab.d_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->d_active = 0;
if (dsktab.d_active == 0)
selchreq(NSELCH, &dskscq);
}
/*
* 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", dev);
trace(4<<16, "status", stat);
dp = devmap(dev);
if (dp->d_active != DSEEK)
return;
dp->d_active = 0;
dp->dk_cyl = dp->dk_actf->b_cyl;
if (!dsktab.d_active)
selchreq(NSELCH, &dskscq);
}
/*
* Selch interrupt routine
* - selch interrupt will be followed by a controller interrupt
* (unless OVERRUN status is set)
*/
dskscintr(dev, stat)
{
register struct dsk *dp;
if (!(dp = dsktab.d_actf))
return;
oc(selchaddr[NSELCH], STOP);
if ((stat = ss(cntladdr)) & OVERRUN) {
dskerror(dp, stat, cntladdr);
dsktab.d_active = dp->d_active = 0;
dskstart();
}
}
/*
* 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", dev);
trace(4<<16, "status", stat);
dsktab.d_active = 0;
if (!(dp = dsktab.d_actf) || dp->d_active != DIO)
return;
dp->d_active = 0;
bp = dp->dk_actf;
if (stat & CNTL_UNRCV) {
dskerror(dp, stat, cntladdr);
dp->dk_cyl = -1; /* force seek */
dskstart();
return;
}
/* cylinder overflow */
if ((stat & CYL_OV) == 0)
bp->b_resid = 0;
else {
oc(selchaddr[NSELCH], STOP);
resid = (rdh(selchaddr[NSELCH])-bp->b_addr+2) & ~0xff;
bp->b_addr += resid;
bp->b_bcount -= resid;
bp->b_sector = 0;
trace(1<<16, "cylov", bp->b_cyl);
if (++bp->b_cyl < NDSKBLK/24) {
dskstart();
return;
} else {
if ((bp->b_flags&(B_PHYS|B_READ)) != (B_PHYS|B_READ))
bp->b_flags |= B_ERROR;
bp->b_resid = resid;
}
}
dp->d_errcnt = 0;
dp->dk_actf = bp->av_forw;
iodone(bp);
dskstart();
}
/*
* 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->d_errcnt > NDSKERR) {
dp->d_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);
}