summaryrefslogtreecommitdiff
path: root/os/pc/devfloppy.c
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
commit74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch)
treec6e220ba61db3a6ea4052e6841296d829654e664 /os/pc/devfloppy.c
parent46439007cf417cbd9ac8049bb4122c890097a0fa (diff)
20060303
Diffstat (limited to 'os/pc/devfloppy.c')
-rw-r--r--os/pc/devfloppy.c1082
1 files changed, 1082 insertions, 0 deletions
diff --git a/os/pc/devfloppy.c b/os/pc/devfloppy.c
new file mode 100644
index 00000000..2f29147c
--- /dev/null
+++ b/os/pc/devfloppy.c
@@ -0,0 +1,1082 @@
+#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;
+
+ for(t = floppytype; t < &floppytype[nelem(floppytype)]; t++)
+ if(dp->dt == t->dt){
+ dp->t = t;
+ floppydir[1+NFDIR*dp->dev].length = dp->t->cap;
+ break;
+ }
+}
+
+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;
+ }
+
+ dmainit(DMAchan, maxtsize);
+
+ /*
+ * allocate the drive storage
+ */
+ fl.d = xalloc(fl.ndrive*sizeof(FDrive));
+ fl.selected = fl.d;
+
+ /*
+ * 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, 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->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)
+ error(Eio);
+
+ DPRINT("changed: trying %s\n", dp->t->name);
+ fldump();
+ if(dp->t == start)
+ nexterror();
+ }
+
+ /* 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(Eio);
+}
+
+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);
+
+ rv = 0;
+ 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;
+
+ rv = 0;
+ 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);
+ 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, 0) < 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,
+};