ref: f88a55e79b5bf656e7f9578d1318a955b9a4963a
dir: /sys/src/9/pc/devfloppy.c/
#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/error.h" #include "floppy.h" /* Intel 82077A (8272A compatible) floppy controller */ /* This module expects the following functions to be defined * elsewhere: * * inb() * outb() * floppyexec() * floppyeject() * floppysetup0() * floppysetup1() * dmainit() * dmasetup() * dmaend() * * On DMA systems, floppyexec() should be an empty function; * on non-DMA systems, dmaend() should be an empty function; * dmasetup() may enforce maximum transfer sizes. */ enum { /* file types */ Qdir= 0, Qdata= (1<<2), Qctl= (2<<2), Qmask= (3<<2), DMAchan= 2, /* floppy dma channel */ }; #define DPRINT if(floppydebug)print int floppydebug = 0; /* * types of drive (from PC equipment byte) */ enum { Tnone= 0, T360kb= 1, T1200kb= 2, T720kb= 3, T1440kb= 4, }; FType floppytype[] = { { "3½HD", T1440kb, 512, 18, 2, 1, 80, 0x1B, 0x54, 0, }, { "3½DD", T1440kb, 512, 9, 2, 1, 80, 0x1B, 0x54, 2, }, { "3½DD", T720kb, 512, 9, 2, 1, 80, 0x1B, 0x54, 2, }, { "5¼HD", T1200kb, 512, 15, 2, 1, 80, 0x2A, 0x50, 0, }, { "5¼DD", T1200kb, 512, 9, 2, 2, 40, 0x2A, 0x50, 1, }, { "ATT3B1", T1200kb, 512, 8, 2, 2, 48, 0x2A, 0x50, 1, }, { "5¼DD", T360kb, 512, 9, 2, 1, 40, 0x2A, 0x50, 2, }, }; /* * bytes per sector encoding for the controller. * - index for b2c is is (bytes per sector/128). * - index for c2b is code from b2c */ static int b2c[] = { [1] 0, [2] 1, [4] 2, [8] 3, }; static int c2b[] = { 128, 256, 512, 1024, }; FController fl; #define MOTORBIT(i) (1<<((i)+4)) /* * predeclared */ static int cmddone(void*); static void floppyformat(FDrive*, Cmdbuf*); static void floppykproc(void*); static void floppypos(FDrive*,long); static int floppyrecal(FDrive*); static int floppyresult(void); static void floppyrevive(void); static long floppyseek(FDrive*, long); static int floppysense(void); static void floppywait(int); static long floppyxfer(FDrive*, int, void*, long, long); Dirtab floppydir[]={ ".", {Qdir, 0, QTDIR}, 0, 0550, "fd0disk", {Qdata + 0}, 0, 0660, "fd0ctl", {Qctl + 0}, 0, 0660, "fd1disk", {Qdata + 1}, 0, 0660, "fd1ctl", {Qctl + 1}, 0, 0660, "fd2disk", {Qdata + 2}, 0, 0660, "fd2ctl", {Qctl + 2}, 0, 0660, "fd3disk", {Qdata + 3}, 0, 0660, "fd3ctl", {Qctl + 3}, 0, 0660, }; #define NFDIR 2 /* directory entries/drive */ enum { CMdebug, CMnodebug, CMeject, CMformat, CMreset, }; static Cmdtab floppyctlmsg[] = { CMdebug, "debug", 1, CMnodebug, "nodebug", 1, CMeject, "eject", 1, CMformat, "format", 0, CMreset, "reset", 1, }; static void fldump(void) { DPRINT("sra %ux srb %ux dor %ux msr %ux dir %ux\n", inb(Psra), inb(Psrb), inb(Pdor), inb(Pmsr), inb(Pdir)); } /* * set floppy drive to its default type */ static void floppysetdef(FDrive *dp) { FType *t; dp->t = floppytype; for(t = floppytype; t < &floppytype[nelem(floppytype)]; t++) if(dp->dt == t->dt){ dp->t = t; break; } floppydir[1+NFDIR*dp->dev].length = dp->t->cap; dp->dt = dp->t->dt; } static void floppyreset(void) { FDrive *dp; FType *t; ulong maxtsize; floppysetup0(&fl); if(fl.ndrive == 0) return; /* * init dependent parameters */ maxtsize = 0; for(t = floppytype; t < &floppytype[nelem(floppytype)]; t++){ t->cap = t->bytes * t->heads * t->sectors * t->tracks; t->bcode = b2c[t->bytes/128]; t->tsize = t->bytes * t->sectors; if(maxtsize < t->tsize) maxtsize = t->tsize; } /* * Can fail if there is no space <= 16MB for the DMA * bounce buffer. */ if(dmainit(DMAchan, maxtsize)){ print("floppy: dmainit failed\n"); fl.ndrive = 0; return; } /* * allocate the drive storage */ fl.d = xalloc(fl.ndrive*sizeof(FDrive)); fl.selected = fl.d; if(fl.d == nil){ print("floppy: can't allocate memory\n"); fl.ndrive = 0; return; } /* * stop the motors */ fl.motor = 0; delay(10); outb(Pdor, fl.motor | Fintena | Fena); delay(10); /* * init drives */ for(dp = fl.d; dp < &fl.d[fl.ndrive]; dp++){ dp->dev = dp - fl.d; dp->dt = T1440kb; floppysetdef(dp); dp->cyl = -1; /* because we don't know */ dp->cache = (uchar*)xspanalloc(maxtsize, BY2PG, 64*1024); dp->ccyl = -1; dp->vers = 0; } /* * first operation will recalibrate */ fl.confused = 1; floppysetup1(&fl); } static Chan* floppyattach(char *spec) { static int kstarted; if(fl.ndrive == 0) error(Enodev); if(kstarted == 0){ /* * watchdog to turn off the motors */ kstarted = 1; kproc("floppy", floppykproc, 0); } return devattach('f', spec); } static Walkqid* floppywalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, floppydir, 1+fl.ndrive*NFDIR, devgen); } static int floppystat(Chan *c, uchar *dp, int n) { return devstat(c, dp, n, floppydir, 1+fl.ndrive*NFDIR, devgen); } static Chan* floppyopen(Chan *c, int omode) { return devopen(c, omode, floppydir, 1+fl.ndrive*NFDIR, devgen); } static void floppyclose(Chan *) { } static void islegal(ulong offset, long n, FDrive *dp) { if(offset % dp->t->bytes) error(Ebadarg); if(n % dp->t->bytes) error(Ebadarg); } /* * check if the floppy has been replaced under foot. cause * an error if it has. * * a seek and a read clears the condition. this was determined * experimentally, there has to be a better way. * * if the read fails, cycle through the possible floppy * density till one works or we've cycled through all * possibilities for this drive. */ static void changed(Chan *c, FDrive *dp) { ulong old; FType *start; /* * if floppy has changed or first time through */ if((inb(Pdir)&Fchange) || dp->vers == 0){ DPRINT("changed\n"); fldump(); dp->vers++; start = dp->t; dp->maxtries = 3; /* limit it when we're probing */ /* floppyon will fail if there's a controller but no drive */ dp->confused = 1; /* make floppyon recal */ if(floppyon(dp) < 0) error(Eio); /* seek to the first track */ floppyseek(dp, dp->t->heads*dp->t->tsize); while(waserror()){ /* * if first attempt doesn't reset changed bit, there's * no floppy there */ if(inb(Pdir)&Fchange) nexterror(); while(++dp->t){ if(dp->t >= &floppytype[nelem(floppytype)]) dp->t = floppytype; if(dp->t == start || dp->dt == dp->t->dt) break; } floppydir[1+NFDIR*dp->dev].length = dp->t->cap; /* floppyon will fail if there's a controller but no drive */ if(floppyon(dp) < 0) nexterror(); if(dp->t == start) nexterror(); DPRINT("changed: trying %s\n", dp->t->name); fldump(); } /* if the read succeeds, we've got the density right */ floppyxfer(dp, Fread, dp->cache, 0, dp->t->tsize); poperror(); dp->maxtries = 20; } old = c->qid.vers; c->qid.vers = dp->vers; if(old && old != dp->vers) error(Echange); } static int readtrack(FDrive *dp, int cyl, int head) { int i, nn, sofar; ulong pos; nn = dp->t->tsize; if(dp->ccyl==cyl && dp->chead==head) return nn; pos = (cyl*dp->t->heads+head) * nn; for(sofar = 0; sofar < nn; sofar += i){ dp->ccyl = -1; i = floppyxfer(dp, Fread, dp->cache + sofar, pos + sofar, nn - sofar); if(i <= 0) return -1; } dp->ccyl = cyl; dp->chead = head; return nn; } static long floppyread(Chan *c, void *a, long n, vlong off) { FDrive *dp; long rv; int sec, head, cyl; long len; uchar *aa; ulong offset = off; if(c->qid.type & QTDIR) return devdirread(c, a, n, floppydir, 1+fl.ndrive*NFDIR, devgen); dp = &fl.d[c->qid.path & ~Qmask]; switch ((int)(c->qid.path & Qmask)) { case Qdata: islegal(offset, n, dp); aa = a; qlock(&fl); if(waserror()){ qunlock(&fl); nexterror(); } floppyon(dp); changed(c, dp); for(rv = 0; rv < n; rv += len){ /* * all xfers come out of the track cache */ dp->len = n - rv; floppypos(dp, offset+rv); cyl = dp->tcyl; head = dp->thead; len = dp->len; sec = dp->tsec; if(readtrack(dp, cyl, head) < 0) break; memmove(aa+rv, dp->cache + (sec-1)*dp->t->bytes, len); } qunlock(&fl); poperror(); break; case Qctl: return readstr(offset, a, n, dp->t->name); default: panic("floppyread: bad qid"); } return rv; } static long floppywrite(Chan *c, void *a, long n, vlong off) { FDrive *dp; long rv, i; char *aa = a; Cmdbuf *cb; Cmdtab *ct; ulong offset = off; dp = &fl.d[c->qid.path & ~Qmask]; switch ((int)(c->qid.path & Qmask)) { case Qdata: islegal(offset, n, dp); qlock(&fl); if(waserror()){ qunlock(&fl); nexterror(); } floppyon(dp); changed(c, dp); for(rv = 0; rv < n; rv += i){ floppypos(dp, offset+rv); if(dp->tcyl == dp->ccyl) dp->ccyl = -1; i = floppyxfer(dp, Fwrite, aa+rv, offset+rv, n-rv); if(i < 0) break; if(i == 0) error(Eio); } qunlock(&fl); poperror(); break; case Qctl: rv = n; cb = parsecmd(a, n); if(waserror()){ free(cb); nexterror(); } qlock(&fl); if(waserror()){ qunlock(&fl); nexterror(); } ct = lookupcmd(cb, floppyctlmsg, nelem(floppyctlmsg)); switch(ct->index){ case CMeject: floppyeject(dp); break; case CMformat: floppyformat(dp, cb); break; case CMreset: fl.confused = 1; floppyon(dp); break; case CMdebug: floppydebug = 1; break; case CMnodebug: floppydebug = 0; break; } poperror(); qunlock(&fl); poperror(); free(cb); break; default: panic("floppywrite: bad qid"); } return rv; } static void floppykproc(void *) { FDrive *dp; while(waserror()) ; for(;;){ for(dp = fl.d; dp < &fl.d[fl.ndrive]; dp++){ if((fl.motor&MOTORBIT(dp->dev)) && TK2SEC(m->ticks - dp->lasttouched) > 5 && canqlock(&fl)){ if(TK2SEC(m->ticks - dp->lasttouched) > 5) floppyoff(dp); qunlock(&fl); } } tsleep(&up->sleep, return0, 0, 1000); } } /* * start a floppy drive's motor. */ static int floppyon(FDrive *dp) { int alreadyon; int tries; if(fl.confused) floppyrevive(); /* start motor and select drive */ alreadyon = fl.motor & MOTORBIT(dp->dev); fl.motor |= MOTORBIT(dp->dev); outb(Pdor, fl.motor | Fintena | Fena | dp->dev); if(!alreadyon){ /* wait for drive to spin up */ tsleep(&up->sleep, return0, 0, 750); /* clear any pending interrupts */ floppysense(); } /* set transfer rate */ if(fl.rate != dp->t->rate){ fl.rate = dp->t->rate; outb(Pdsr, fl.rate); } /* get drive to a known cylinder */ if(dp->confused) for(tries = 0; tries < 4; tries++) if(floppyrecal(dp) >= 0) break; dp->lasttouched = m->ticks; fl.selected = dp; /* return -1 if this didn't work */ if(dp->confused) return -1; return 0; } /* * stop the floppy if it hasn't been used in 5 seconds */ static void floppyoff(FDrive *dp) { fl.motor &= ~MOTORBIT(dp->dev); outb(Pdor, fl.motor | Fintena | Fena | dp->dev); } /* * send a command to the floppy */ static int floppycmd(void) { int i; int tries; fl.nstat = 0; for(i = 0; i < fl.ncmd; i++){ for(tries = 0; ; tries++){ if((inb(Pmsr)&(Ffrom|Fready)) == Fready) break; if(tries > 1000){ DPRINT("cmd %ux can't be sent (%d)\n", fl.cmd[0], i); fldump(); /* empty fifo, might have been a bad command */ floppyresult(); return -1; } microdelay(8); /* for machine independence */ } outb(Pfdata, fl.cmd[i]); } return 0; } /* * get a command result from the floppy * * when the controller goes ready waiting for a command * (instead of sending results), we're done * */ static int floppyresult(void) { int i, s; int tries; /* get the result of the operation */ for(i = 0; i < sizeof(fl.stat); i++){ /* wait for status byte */ for(tries = 0; ; tries++){ s = inb(Pmsr)&(Ffrom|Fready); if(s == Fready){ fl.nstat = i; return fl.nstat; } if(s == (Ffrom|Fready)) break; if(tries > 1000){ DPRINT("floppyresult: %d stats\n", i); fldump(); fl.confused = 1; return -1; } microdelay(8); /* for machine independence */ } fl.stat[i] = inb(Pfdata); } fl.nstat = sizeof(fl.stat); return fl.nstat; } /* * calculate physical address of a logical byte offset into the disk * * truncate dp->length if it crosses a track boundary */ static void floppypos(FDrive *dp, long off) { int lsec; int ltrack; int end; lsec = off/dp->t->bytes; ltrack = lsec/dp->t->sectors; dp->tcyl = ltrack/dp->t->heads; dp->tsec = (lsec % dp->t->sectors) + 1; dp->thead = (lsec/dp->t->sectors) % dp->t->heads; /* * can't read across track boundaries. * if so, decrement the bytes to be read. */ end = (ltrack+1)*dp->t->sectors*dp->t->bytes; if(off+dp->len > end) dp->len = end - off; } /* * get the interrupt cause from the floppy. */ static int floppysense(void) { fl.ncmd = 0; fl.cmd[fl.ncmd++] = Fsense; if(floppycmd() < 0) return -1; if(floppyresult() < 2){ DPRINT("can't read sense response\n"); fldump(); fl.confused = 1; return -1; } return 0; } static int cmddone(void *) { return fl.ncmd == 0; } /* * Wait for a floppy interrupt. If none occurs in 5 seconds, we * may have missed one. This only happens on some portables which * do power management behind our backs. Call the interrupt * routine to try to clear any conditions. */ static void floppywait(int slow) { tsleep(&fl.r, cmddone, 0, slow ? 5000 : 1000); if(!cmddone(0)){ floppyintr(0); fl.confused = 1; } } /* * we've lost the floppy position, go to cylinder 0. */ static int floppyrecal(FDrive *dp) { dp->ccyl = -1; dp->cyl = -1; fl.ncmd = 0; fl.cmd[fl.ncmd++] = Frecal; fl.cmd[fl.ncmd++] = dp->dev; if(floppycmd() < 0) return -1; floppywait(1); if(fl.nstat < 2){ DPRINT("recalibrate: confused %ux\n", inb(Pmsr)); fl.confused = 1; return -1; } if((fl.stat[0] & (Codemask|Seekend)) != Seekend){ DPRINT("recalibrate: failed\n"); dp->confused = 1; return -1; } dp->cyl = fl.stat[1]; if(dp->cyl != 0){ DPRINT("recalibrate: wrong cylinder %d\n", dp->cyl); dp->cyl = -1; dp->confused = 1; return -1; } dp->confused = 0; return 0; } /* * if the controller or a specific drive is in a confused state, * reset it and get back to a known state */ static void floppyrevive(void) { FDrive *dp; /* * reset the controller if it's confused */ if(fl.confused){ DPRINT("floppyrevive in\n"); fldump(); /* reset controller and turn all motors off */ splhi(); fl.ncmd = 1; fl.cmd[0] = 0; outb(Pdor, 0); delay(10); outb(Pdor, Fintena|Fena); delay(10); spllo(); fl.motor = 0; fl.confused = 0; floppywait(0); /* mark all drives in an unknown state */ for(dp = fl.d; dp < &fl.d[fl.ndrive]; dp++) dp->confused = 1; /* set rate to a known value */ outb(Pdsr, 0); fl.rate = 0; DPRINT("floppyrevive out\n"); fldump(); } } /* * seek to the target cylinder * * interrupt, no results */ static long floppyseek(FDrive *dp, long off) { floppypos(dp, off); if(dp->cyl == dp->tcyl) return dp->tcyl; dp->cyl = -1; fl.ncmd = 0; fl.cmd[fl.ncmd++] = Fseek; fl.cmd[fl.ncmd++] = (dp->thead<<2) | dp->dev; fl.cmd[fl.ncmd++] = dp->tcyl * dp->t->steps; if(floppycmd() < 0) return -1; floppywait(1); if(fl.nstat < 2){ DPRINT("seek: confused\n"); fl.confused = 1; return -1; } if((fl.stat[0] & (Codemask|Seekend)) != Seekend){ DPRINT("seek: failed\n"); dp->confused = 1; return -1; } dp->cyl = dp->tcyl; return dp->tcyl; } /* * read or write to floppy. try up to three times. */ static long floppyxfer(FDrive *dp, int cmd, void *a, long off, long n) { long offset; int tries; if(off >= dp->t->cap) return 0; if(off + n > dp->t->cap) n = dp->t->cap - off; /* retry on error (until it gets ridiculous) */ tries = 0; while(waserror()){ if(tries++ >= dp->maxtries) nexterror(); DPRINT("floppyxfer: retrying\n"); } dp->len = n; if(floppyseek(dp, off) < 0){ DPRINT("xfer: seek failed\n"); dp->confused = 1; error(Eio); } /* * set up the dma (dp->len may be trimmed) */ if(waserror()){ dmaend(DMAchan); nexterror(); } dp->len = dmasetup(DMAchan, a, dp->len, cmd==Fread ? DMAREAD : DMAWRITE); if(dp->len < 0) error(Eio); /* * start operation */ fl.ncmd = 0; fl.cmd[fl.ncmd++] = cmd | (dp->t->heads > 1 ? Fmulti : 0); fl.cmd[fl.ncmd++] = (dp->thead<<2) | dp->dev; fl.cmd[fl.ncmd++] = dp->tcyl; fl.cmd[fl.ncmd++] = dp->thead; fl.cmd[fl.ncmd++] = dp->tsec; fl.cmd[fl.ncmd++] = dp->t->bcode; fl.cmd[fl.ncmd++] = dp->t->sectors; fl.cmd[fl.ncmd++] = dp->t->gpl; fl.cmd[fl.ncmd++] = 0xFF; if(floppycmd() < 0) error(Eio); /* Poll ready bits and transfer data */ floppyexec((char*)a, dp->len, cmd==Fread); /* * give bus to DMA, floppyintr() will read result */ floppywait(0); dmaend(DMAchan); poperror(); /* * check for errors */ if(fl.nstat < 7){ DPRINT("xfer: confused\n"); fl.confused = 1; error(Eio); } if((fl.stat[0] & Codemask)!=0 || fl.stat[1] || fl.stat[2]){ DPRINT("xfer: failed %ux %ux %ux\n", fl.stat[0], fl.stat[1], fl.stat[2]); DPRINT("offset %lud len %ld\n", off, dp->len); if((fl.stat[0]&Codemask)==Cmdexec && fl.stat[1]==Overrun){ DPRINT("DMA overrun: retry\n"); } else dp->confused = 1; error(Eio); } /* * check for correct cylinder */ offset = fl.stat[3] * dp->t->heads + fl.stat[4]; offset = offset*dp->t->sectors + fl.stat[5] - 1; offset = offset * c2b[fl.stat[6]]; if(offset != off+dp->len){ DPRINT("xfer: ends on wrong cyl\n"); dp->confused = 1; error(Eio); } poperror(); dp->lasttouched = m->ticks; return dp->len; } /* * format a track */ static void floppyformat(FDrive *dp, Cmdbuf *cb) { int cyl, h, sec; ulong track; uchar *buf, *bp; FType *t; /* * set the type */ if(cb->nf == 2){ for(t = floppytype; t < &floppytype[nelem(floppytype)]; t++){ if(strcmp(cb->f[1], t->name)==0 && t->dt==dp->dt){ dp->t = t; floppydir[1+NFDIR*dp->dev].length = dp->t->cap; break; } } if(t >= &floppytype[nelem(floppytype)]) error(Ebadarg); } else if(cb->nf == 1){ floppysetdef(dp); t = dp->t; } else { cmderror(cb, "invalid floppy format command"); SET(t); } /* * buffer for per track info */ buf = smalloc(t->sectors*4); if(waserror()){ free(buf); nexterror(); } /* force a recalibrate to cylinder 0 */ dp->confused = 1; if(!waserror()){ floppyon(dp); poperror(); } /* * format a track at time */ for(track = 0; track < t->tracks*t->heads; track++){ cyl = track/t->heads; h = track % t->heads; /* * seek to track, ignore errors */ floppyseek(dp, track*t->tsize); dp->cyl = cyl; dp->confused = 0; /* * set up the dma (dp->len may be trimmed) */ bp = buf; for(sec = 1; sec <= t->sectors; sec++){ *bp++ = cyl; *bp++ = h; *bp++ = sec; *bp++ = t->bcode; } if(waserror()){ dmaend(DMAchan); nexterror(); } if(dmasetup(DMAchan, buf, bp-buf, DMAWRITE) < 0) error(Eio); /* * start operation */ fl.ncmd = 0; fl.cmd[fl.ncmd++] = Fformat; fl.cmd[fl.ncmd++] = (h<<2) | dp->dev; fl.cmd[fl.ncmd++] = t->bcode; fl.cmd[fl.ncmd++] = t->sectors; fl.cmd[fl.ncmd++] = t->fgpl; fl.cmd[fl.ncmd++] = 0x5a; if(floppycmd() < 0) error(Eio); /* Poll ready bits and transfer data */ floppyexec((char *)buf, bp-buf, 0); /* * give bus to DMA, floppyintr() will read result */ floppywait(1); dmaend(DMAchan); poperror(); /* * check for errors */ if(fl.nstat < 7){ DPRINT("format: confused\n"); fl.confused = 1; error(Eio); } if((fl.stat[0]&Codemask)!=0 || fl.stat[1]|| fl.stat[2]){ DPRINT("format: failed %ux %ux %ux\n", fl.stat[0], fl.stat[1], fl.stat[2]); dp->confused = 1; error(Eio); } } free(buf); dp->confused = 1; poperror(); } static void floppyintr(Ureg *) { switch(fl.cmd[0]&~Fmulti){ case Fread: case Fwrite: case Fformat: case Fdumpreg: floppyresult(); break; case Fseek: case Frecal: default: floppysense(); /* to clear interrupt */ break; } fl.ncmd = 0; wakeup(&fl.r); } Dev floppydevtab = { 'f', "floppy", floppyreset, devinit, devshutdown, floppyattach, floppywalk, floppystat, floppyopen, devcreate, floppyclose, floppyread, devbread, floppywrite, devbwrite, devremove, devwstat, };