1098 lines
20 KiB
C
1098 lines
20 KiB
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);
|
|
|
|
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 ? 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,
|
|
};
|