#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"io.h"
#include	"../port/error.h"

/* Intel 82077A (8272A compatible) floppy controller */

typedef	struct Drive		Drive;
typedef	struct Controller	Controller;
typedef struct Type		Type;
typedef struct Device		Device;

/* registers on the 82077 */
struct Device
{
	uchar		sra;	/* (R/O) status register A */		
	uchar		srb;	/* (R/O) status register B */
	uchar		dor;	/* (R/W) digital output register */
	uchar		rsvd3;	/* reserved */
	uchar		msr;	/* (R/O) main status register */
#define dsr	msr
	uchar		fifo;	/* (R/W) data FIFO */
	uchar		rsvd6;	/* reserved */
	uchar		dir;	/* (R/O) digital input register */
#define ccr	dir
	uchar		flpctl;	/* (W/O) control (not in 82077) */
};

/* bits in the registers */
enum
{
	/* status register A */
	Fintpend=	0x80,	/* interrupt pending */

	/* digital output register */
	Fintena=	0x8,	/* enable floppy interrupt */
	Fena=		0x4,	/* 0 == reset controller */

	/* main status register */
	Fready=		0x80,	/* ready to be touched */
	Ffrom=		0x40,	/* data from controller */
	Fbusy=		0x10,	/* operation not over */

	/* data register */
	Frecal=		0x07,	/* recalibrate cmd */
	Fseek=		0x0f,	/* seek cmd */
	Fsense=		0x08,	/* sense cmd */
	Fread=		0x66,	/* read cmd */
	Freadid=	0x4a,	/* read track id */
	Fspec=		0x03,	/* set hold times */
	Fwrite=		0x45,	/* write cmd */
	Fmulti=		0x80,	/* or'd with Fread or Fwrite for multi-head */
	Fdumpreg=	0x0e,	/* dump internal registers */

	/* disk changed register */
	Fchange=	0x80,	/* disk has changed */

	/* floppy control register (not part of 82077) */
	Eject_disc	= 0x80,		/* Eject the floppy */
	Sel_82077	= 0x40,		/* Scsi/82077 select */
	Drv_id		= 0x04,		/* Drive present flag */
	Mid		= 0x03,		/* Media id bits */

	/* status 0 byte */
	Drivemask=	3<<0,
	Seekend=	1<<5,
	Codemask=	(3<<6)|(3<<3),

	/* file types */
	Qdir=		0,
	Qdata=		(1<<2),
	Qctl=		(2<<2),
	Qmask=		(3<<2),
};

/*
 *  floppy types
 */
struct Type
{
	char	*name;
	int	bytes;		/* bytes/sector */
	int	sectors;	/* sectors/track */
	int	heads;		/* number of heads */
	int	steps;		/* steps per cylinder */
	int	tracks;		/* tracks/disk */
	int	gpl;		/* intersector gap length for read/write */	
	int	fgpl;		/* intersector gap length for format */

	/*
	 *  these depend on previous entries and are set filled in
	 *  by floppyinit
	 */
	int	bcode;		/* coded version of bytes for the controller */
	long	cap;		/* drive capacity in bytes */
	long	tsize;		/* track size in bytes */
};
Type floppytype[] =
{
	{ "MF2HD",	512,	18,	2,	1,	80,	0x1B,	0x54, },
	{ "MF1DD",	512,	9,	2,	1,	80,	0x1B,	0x54, },
	{ "MF4HD",	1024,	18,	2,	1,	80,	0x1B,	0x54, },
	{ "F2HD",	512,	15,	2,	1,	80,	0x2A,	0x50, },
	{ "F2DD",	512,	8,	2,	2,	40,	0x2A,	0x50, },
	{ "F1DD",	512,	8,	1,	2,	40,	0x2A,	0x50, },
};
#define NTYPES (sizeof(floppytype)/sizeof(Type))

/*
 *  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,
};

/*
 *  a floppy drive
 */
struct Drive
{
	Type	*t;
	int	dev;

	ulong	lasttouched;	/* time last touched */
	int	cyl;		/* current cylinder */
	int	confused;	/* needs to be recalibrated */
	int	vers;

	int	tcyl;		/* target cylinder */
	int	thead;		/* target head */
	int	tsec;		/* target sector */
	long	len;		/* size of xfer */

	uchar	*virtual;	/* track cache */
	uchar	*physical;	/* ... */
	int	ccyl;
	int	chead;

	Rendez	r;		/* waiting here for motor to spin up */
};

/*
 *  controller for 4 floppys
 */
struct Controller
{
	QLock;			/* exclusive access to the contoller */

	Drive	*d;		/* the floppy drives */
	uchar	cmd[14];	/* command */
	int	ncmd;		  /* # command bytes */
	uchar	stat[14];	/* command status */
	int	nstat;		  /* # status bytes */
	int	confused;	/* controler needs to be reset */
	Rendez	r;		/* wait here for command termination */
	int	motor;		/* bit mask of spinning disks */

	uchar	*virtual;	/* kernel buffer for DMA (output) */
	uchar	*physical;	/* ... */

	Rendez	kr;		/* for motor watcher */
};

Controller	fl;

#define MOTORBIT(i)	(1<<((i)+4))

/*
 *  predeclared
 */
static int	floppycmd(void);
static void	floppyeject(Drive*);
static void	floppykproc(void*);
static void	floppyon(Drive*);
static void	floppyoff(Drive*);
static void	floppypos(Drive*,long);
static int	floppyrecal(Drive*);
static int	floppyresult(void);
static void	floppyrevive(void);
static long	floppyseek(Drive*, long);
static int	floppysense(void);
static void	floppywait(void);
static long	floppyxfer(Drive*, int, void*, long, long);
static long	floppythrice(Drive*, int, void*, long, long);
static int	cmddone(void*);

Dirtab floppydir[]={
	"fd0disk",		{Qdata + 0},	0,	0666,
	"fd0ctl",		{Qctl + 0},	0,	0666,
	"fd1disk",		{Qdata + 1},	0,	0666,
	"fd1ctl",		{Qctl + 1},	0,	0666,
	"fd2disk",		{Qdata + 2},	0,	0666,
	"fd2ctl",		{Qctl + 2},	0,	0666,
	"fd3disk",		{Qdata + 3},	0,	0666,
	"fd3ctl",		{Qctl + 3},	0,	0666,
};
#define NFDIR	2	/* directory entries/drive */

/*
 *  grab DMA from SCSI
 */
#define GETDMA\
{\
	qlock(&dmalock);\
	dmaowner = Ofloppy;\
	((Device*)(FDCTLRL))->flpctl = Sel_82077;\
	if(waserror()){\
		((Device*)(FDCTLRL))->flpctl = 0;\
		qunlock(&dmalock);\
		nexterror();\
	}\
}

/*
 *  let SCSI have DMA
 */
#define PUTDMA\
{\
	((Device*)(FDCTLRL))->flpctl = 0;\
	dmaowner = Oscsi;\
	qunlock(&dmalock);\
	poperror();\
}

void
nextfloppyreset(void)
{
	Device *devp = (Device*)(FDCTLRL);
	Drive *dp;
	Type *t;
	ulong p;
	long l;

	/*
	 *  init dependent parameters
	 */
	for(t = floppytype; t < &floppytype[NTYPES]; t++){
		t->cap = t->bytes * t->heads * t->sectors * t->tracks;
		t->bcode = b2c[t->bytes/128];
		t->tsize = t->bytes * t->sectors;
	}

	/*
	 *  allocate the drive storage
	 */
	fl.d = xalloc(conf.nfloppy*sizeof(Drive));

	/*
	 *  allocate a page for dma
	 */
	p = (ulong)xspanalloc(BY2PG, BY2PG, 0);
	fl.virtual = (uchar *)kmappa(p);
	fl.physical = (uchar*)p;

	/*
	 *  reset the chip, leave it off
	 */
	fl.motor = 0;
	devp->flpctl = Sel_82077;
	devp->dor = 0;
	devp->flpctl = 0;

	/*
	 *  init drives
	 */
	for(dp = fl.d; dp < &fl.d[conf.nfloppy]; dp++){
		dp->dev = dp - fl.d;
		dp->t = &floppytype[0];		/* default type */
		floppydir[NFDIR*dp->dev].length = dp->t->cap;
		dp->cyl = -1;		/* because we don't know */
		p = (ulong)xspanalloc(dp->t->tsize, BY2PG, 0);
		dp->virtual = (uchar *)kmappa(p);
		for(l = BY2PG; l < dp->t->tsize; l += BY2PG)
			kmappa(p+l);
		dp->physical = (uchar*)p;
		dp->ccyl = -1;
		dp->vers = 1;
	}

	/*
	 *  first operation will recalibrate
	 */
	fl.confused = 1;
}

void
nextfloppyinit(void)
{
}

Chan*
nextfloppyattach(char *spec)
{
	static int kstarted;

	if(kstarted == 0){
		/*
		 *  watchdog to turn off the motors
		 */
		kstarted = 1;
		kproc("floppy", floppykproc, 0);
	}
	return devattach('f', spec);
}

Chan*
nextfloppyclone(Chan *c, Chan *nc)
{
	return devclone(c, nc);
}

int
nextfloppywalk(Chan *c, char *name)
{
	return devwalk(c, name, floppydir, conf.nfloppy*NFDIR, devgen);
}

void
nextfloppystat(Chan *c, char *dp)
{
	devstat(c, dp, floppydir, conf.nfloppy*NFDIR, devgen);
}

Chan*
nextfloppyopen(Chan *c, int omode)
{
	return devopen(c, omode, floppydir, conf.nfloppy*NFDIR, devgen);
}

void
nextfloppycreate(Chan *c, char *name, int omode, ulong perm)
{
	USED(c, name, omode, perm);
	error(Eperm);
}

void
nextfloppyclose(Chan *c)
{
	USED(c);
}

void
nextfloppyremove(Chan *c)
{
	USED(c);
	error(Eperm);
}

void
nextfloppywstat(Chan *c, char *dp)
{
	USED(c, dp);
	error(Eperm);
}

static void
islegal(Chan *c, long n, Drive *dp)
{
	if(c->offset % dp->t->bytes)
		error(Ebadarg);
	if(n % dp->t->bytes)
		error(Ebadarg);
	if(c->qid.vers!=0 && c->qid.vers!=dp->vers){
		c->qid.vers = dp->vers;
		error(Eio);
	} else
		c->qid.vers = dp->vers;
}

static void
changed(Chan *c, Drive *dp)
{
	ulong old;

	old = c->qid.vers;
	c->qid.vers = dp->vers;
	if(old && old!=dp->vers)
		error(Eio);
}

long
nextfloppyread(Chan *c, void *a, long n)
{
	Drive *dp;
	long rv, i;
	int nn, sec, head, cyl;
	long len;
	uchar *aa;

	if(c->qid.path == CHDIR)
		return devdirread(c, a, n, floppydir, conf.nfloppy*NFDIR, devgen);

	rv = 0;
	dp = &fl.d[c->qid.path & ~Qmask];

	switch ((int)(c->qid.path & Qmask)) {
	case Qdata:
		islegal(c, n, dp);
		aa = a;
		nn = dp->t->tsize;

		qlock(&fl);
		if(waserror()){
			qunlock(&fl);
			fl.confused = 1;
			CLRCTL(Cpu_dma);
			nexterror();
		}
		GETDMA;
		floppyon(dp);
		changed(c, dp);
		for(rv = 0; rv < n; rv += len){
			/*
			 *  truncate xfer at track boundary
			 */
			dp->len = n - rv;
			floppypos(dp, c->offset+rv);
			cyl = dp->tcyl;
			head = dp->thead;
			len = dp->len;
			sec = dp->tsec;

			/*
			 *  read the track
			 */
			if(dp->ccyl!=cyl || dp->chead!=head){
				dp->ccyl = -1;
				i = floppythrice(dp, Fread,0,(cyl*dp->t->heads+head)*nn,nn);
				if(i != nn){
					if(i == 0)
						break;
					error(Eio);
				}
				dp->ccyl = cyl;
				dp->chead = head;
			}
			memmove(aa+rv, dp->virtual + (sec-1)*dp->t->bytes, len);
		}
		PUTDMA;
		qunlock(&fl);
		poperror();
		break;
	case Qctl:
		break;
	default:
		panic("floppyread: bad qid");
	}

	return rv;
}

#define SNCMP(a, b) strncmp(a, b, sizeof(b)-1)
long
nextfloppywrite(Chan *c, void *a, long n)
{
	Drive *dp;
	long rv, i;
	char *aa = a;

	rv = 0;
	dp = &fl.d[c->qid.path & ~Qmask];

	switch ((int)(c->qid.path & Qmask)) {
	case Qdata:
		islegal(c, n, dp);
		qlock(&fl);
		if(waserror()){
			qunlock(&fl);
			fl.confused = 1;
			CLRCTL(Cpu_dma);
			nexterror();
		}
		GETDMA;
		floppyon(dp);
		changed(c, dp);
		for(rv = 0; rv < n; rv += i){
			floppypos(dp, c->offset+rv);
			if(dp->tcyl == dp->ccyl)
				dp->ccyl = -1;
			i = floppythrice(dp, Fwrite, aa+rv, c->offset+rv,
				n-rv);
			if(i <= 0)
				break;
		}
		PUTDMA;
		qunlock(&fl);
		poperror();
		break;
	case Qctl:
		if(SNCMP(aa, "eject") == 0){
			qlock(&fl);
			GETDMA;
			floppyeject(dp);
			PUTDMA;
			qunlock(&fl);
		} else if(SNCMP(aa, "reset") == 0){
			qlock(&fl);
			GETDMA;
			floppyon(dp);
			PUTDMA;
			qunlock(&fl);
		}
		break;
	default:
		panic("floppywrite: bad qid");
	}

	return rv;
}

static void
floppykproc(void *a)
{
	Drive *dp;

	USED(a);

	while(waserror())
		;
	for(;;){
		for(dp = fl.d; dp < &fl.d[conf.nfloppy]; dp++){
			if((fl.motor&MOTORBIT(dp->dev))
			&& TK2SEC(m->ticks - dp->lasttouched) > 5
			&& canqlock(&fl)){
				GETDMA;
				if(TK2SEC(m->ticks - dp->lasttouched) > 5)
					floppyoff(dp);
				qunlock(&fl);
				PUTDMA;
			}
		}
		tsleep(&fl.kr, return0, 0, 5*1000);
	}
}

/*
 *  start a floppy drive's motor.
 */
static void
floppyon(Drive *dp)
{
	Device *devp = (Device*)(FDCTLRL);
	int alreadyon;
	int tries;

	for(tries = 0; tries < 4; tries++){
		if(fl.confused)
			floppyrevive();
	
		/* start motor and select drive */
		alreadyon = fl.motor & MOTORBIT(dp->dev);
		fl.motor |= MOTORBIT(dp->dev);
		devp->dor = fl.motor | Fintena | Fena | dp->dev;
		if(!alreadyon)
			tsleep(&dp->r, return0, 0, 750);
	
		/* get drive to a known cylinder */
		if(dp->confused)
			if(floppyrecal(dp) >= 0)
				break;
		else
			break;
	}
	dp->lasttouched = m->ticks;
}

/*
 *  stop the floppy if it hasn't been used in 5 seconds
 */
static void
floppyoff(Drive *dp)
{
	Device *devp = (Device*)(FDCTLRL);

	fl.motor &= ~MOTORBIT(dp->dev);
	devp->dor = fl.motor | Fintena | Fena | dp->dev;
}

/*
 *  eject disk
 */
static void
floppyeject(Drive *dp)
{
	Device *devp = (Device*)(FDCTLRL);

	dp->vers++;
	floppyon(dp);
	delay(1);
	devp->flpctl = Eject_disc|Sel_82077;
	delay(1);
	devp->flpctl = Sel_82077;
	delay(1);
	floppyoff(dp);
}

/*
 *  send a command to the floppy
 */
static int
floppycmd(void)
{
	Device *devp = (Device*)(FDCTLRL);
	int i;
	int tries;

	for(i = 0; i < fl.ncmd; i++){
		for(tries = 0; ; tries++){
			if(tries > 1000){
print("cmd %ux can't be sent (%d %ux)\n", fl.cmd[0], i, devp->msr);
				fl.confused = 1;
				return -1;
			}
			if((devp->msr&(Ffrom|Fready)) == Fready)
				break;
		}
		devp->fifo = 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)
{
	Device *devp = (Device*)(FDCTLRL);
	int i, s;
	int tries;

	for(i = 0; i < sizeof(fl.stat); i++){
		for(tries = 0; ; tries++){
			if(tries > 1000){
				fl.confused = 1;
				return -1;
			}
			s = devp->msr&(Ffrom|Fready);
			if(s == Fready){
				fl.nstat = i;
				return i;
			}
			if(s == (Ffrom|Fready))
				break;
		}
		fl.stat[i] = devp->fifo;
	}
	fl.nstat = i;
	return i;
}

/*
 *  calculate physical address of a logical byte offset into the disk
 *
 *  truncate dp->length if it crosses a track boundary
 */
static void
floppypos(Drive *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){
print("can't read sense response\n");
		fl.confused = 1;
		return -1;
	}
	return 0;
}

static int
cmddone(void *a)
{
	USED(a);
	return fl.ncmd == 0;
}

/*
 *  wait for a floppy interrupt
 */
static void
floppywait(void)
{
	tsleep(&fl.r, cmddone, 0, 2000);
}

/*
 *  we've lost the floppy position, go to cylinder 0.
 */
static int
floppyrecal(Drive *dp)
{
	Device *devp = (Device*)(FDCTLRL);
	int type;

	dp->ccyl = -1;

	type = devp->flpctl & Mid;
	switch(type){
	case 0:
		print("no diskette %d\n", dp->dev);
		return -1;
	case 1:
		devp->dsr = 3;
		devp->ccr = 3;
		dp->t = &floppytype[2];
		return -1;
	case 2:
		dp->t = &floppytype[0];
		break;
	case 3:
		devp->dsr = 0;
		devp->ccr = 0;
		dp->t = &floppytype[1];
		break;
	}
	
	fl.ncmd = 0;
	fl.cmd[fl.ncmd++] = Frecal;
	fl.cmd[fl.ncmd++] = dp->dev;
	if(floppycmd() < 0)
		return -1;
	floppywait();
	if(fl.nstat < 2){
		fl.confused = 1;
		return -1;
	}
	if((fl.stat[0] & (Codemask|Seekend)) != Seekend){
		dp->confused = 1;
		return -1;
	}
	dp->cyl = fl.stat[1]/dp->t->steps;
	if(dp->cyl != 0){
		print("recalibrate went to wrong cylinder %d\n", dp->cyl);
		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 kown state
 */
void
floppyrevive(void)
{
	Device *devp = (Device*)(FDCTLRL);
	Drive *dp;

	/*
	 *  reset the controller if it's confused
	 */
	if(fl.confused){
		/* reset controller and turn all motors off */
		splhi();
		fl.cmd[0] = 0;
		devp->dor = 0;
		delay(1);
		devp->dor = Fintena|Fena;
		spllo();
		for(dp = fl.d; dp < &fl.d[conf.nfloppy]; dp++)
			dp->confused = 1;
		fl.motor = 0;
		floppywait();
		fl.confused = 0;
		devp->dsr = 0;
		devp->ccr = 0;
	}
}

/*
 *  seek to the target cylinder
 *
 *	interrupt, no results
 */
static long
floppyseek(Drive *dp, long off)
{
	floppypos(dp, off);
	if(dp->cyl == dp->tcyl)
		return dp->cyl;

/* print("seeking tcyl %d, thead %d\n", dp->tcyl, dp->thead); /**/
	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){
		print("seek cmd failed\n");
		return -1;
	}
	floppywait();
	if(fl.nstat < 2){
		fl.confused = 1;
		return -1;
	}
	if((fl.stat[0] & (Codemask|Seekend)) != Seekend){
		print("seek failed\n");
		dp->confused = 1;
		return -1;
	}

	dp->cyl = dp->tcyl;
	return dp->cyl;
}

/*
 *  since floppies are so flakey, try 3 times before giving up
 */
static long
floppythrice(Drive *dp, int cmd, void *a, long off, long n)
{
	int tries;
	long rv;

	for(tries = 0; ; tries++){
		if(waserror()){
			if(strcmp(u->error, Eintr)==0 || tries > 3)
				nexterror();
		} else {
			rv = floppyxfer(dp, cmd, a, off, n);
			poperror();
			return rv;
		}
	}
}

static long
floppyxfer(Drive *dp, int cmd, void *a, long off, long n)
{
	Device *devp = (Device*)(FDCTLRL);
	SCSIdma *dma = (SCSIdma *)SCSIDMA;
	long offset;
	ulong up;

	if(off >= dp->t->cap)
		return 0;
	if(off + n > dp->t->cap)
		n = dp->t->cap - off;

	/*
	 *  calculate new position and seek to it (dp->len may be trimmed)
	 */
	dp->len = n;
	if(floppyseek(dp, off) < 0)
		error(Eio);
	
/*print("tcyl %d, thead %d, tsec %d, addr %lux, buf %lux, n %d\n",
	dp->tcyl, dp->thead, dp->tsec, a, fl.physical, dp->len);/**/

	/*
	 *  turn off any previous dma, set up new one
	 */
	dma->csr = Dinit | Dcreset;
	if(cmd == Fread){
		up = (ulong)dp->physical;
	} else {
		memmove(fl.virtual, a, dp->len);
		up = (ulong)fl.physical;
	}
	dma->base = up;
	if(cmd == Fread){
		dma->limit = up + dp->len;
		SETCTL(Dmadir);
		dma->csr = Dseten | Dsetread | Dinit;
	} else {
		dma->limit = up + dp->len + 16;
		CLRCTL(Dmadir);
		dma->csr = Dseten | Dinit;
	}

	/*
	 *  start operation
	 */
/*	cmd = cmd | (dp->t->heads > 1 ? Fmulti : 0);/**/
	fl.ncmd = 0;
	fl.cmd[fl.ncmd++] = cmd;
	fl.cmd[fl.ncmd++] = (dp->thead<<2) | dp->dev;
	fl.cmd[fl.ncmd++] = dp->tcyl * dp->t->steps;
	fl.cmd[fl.ncmd++] = dp->thead;
	fl.cmd[fl.ncmd++] = dp->tsec;
	fl.cmd[fl.ncmd++] = dp->t->bcode;
	fl.cmd[fl.ncmd++] = dp->tsec + dp->len/dp->t->bytes - 1;
	fl.cmd[fl.ncmd++] = dp->t->gpl;
	fl.cmd[fl.ncmd++] = 0xFF;
	splhi();
	if(floppycmd() < 0){
		spllo();
		print("xfer cmd failed\n");
		error(Eio);
	}

	/*
	 *  give bus to DMA, floppyintr() will read result
	 */
	SETCTL(Cpu_dma);
	spllo();
	floppywait();

	/*
	 *  check for errors
	 */
	if(fl.nstat < 7){
		print("xfer result failed %lux\n", devp->msr);
		fl.confused = 1;
		error(Eio);
	}
	if((fl.stat[0] & Codemask)!=0 || fl.stat[1] || fl.stat[2]){
		if(fl.stat[1] != 0x80){
			print("xfer failed %lux %lux %lux\n", fl.stat[0],
				fl.stat[1], fl.stat[2]);
			print("offset %lud len %d\n", off, dp->len);
			dp->confused = 1;
			error(Eio);
		} else
			fl.stat[5]++;
	}

	/*
	 *  check for correct cylinder
	 */
	offset = (fl.stat[3]/dp->t->steps) * 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){
		print("new offset %d instead of %d\n", offset, off+dp->len);
		dp->confused = 1;
		error(Eio);
	}

	dp->lasttouched = m->ticks;
	return dp->len;
}

void
floppyintr(void)
{
	SCSIdma *dma = (SCSIdma *)SCSIDMA;
	Device *devp = (Device*)(FDCTLRL);

	switch(fl.cmd[0]&~Fmulti){
	case Fread:
	case Fwrite:
		if(dma->base != dma->limit)
			crankfifo(dma->limit - dma->base);
		CLRCTL(Cpu_dma);
		floppyresult();
		break;
	case Freadid:
		floppyresult();
		break;
	case Fseek:
	case Frecal:
	default:
		if(dmaowner != Ofloppy)
			devp->flpctl = Sel_82077;
		floppysense();	/* to clear interrupt */
		if(dmaowner != Ofloppy)
			devp->flpctl = 0;
		break;
	}
	fl.ncmd = 0;
	wakeup(&fl.r);
}
