date and time created 90/06/23 19:05:34 by donahn
[unix-history] / usr / src / sys / i386 / isa / fd.c
/*-
* Copyright (c) 1990 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Don Ahn.
*
* %sccs.include.386.c%
*
* @(#)fd.c 5.1 (Berkeley) %G%
*/
/****************************************************************************/
/* fd driver */
/****************************************************************************/
#include "param.h"
#include "dkbad.h"
#include "systm.h"
#include "conf.h"
#include "file.h"
#include "dir.h"
#include "user.h"
#include "ioctl.h"
#include "disk.h"
#include "buf.h"
#include "vm.h"
#include "uio.h"
#include "machine/pte.h"
#include "machine/device.h"
#include "icu.h"
#define NFD 2
#define FDUNIT(s) ((s)&1)
#define FDTYPE(s) (((s)>>1)&7)
#define b_cylin b_resid
#define FDBLK 512
#define NUMTYPES 4
struct fd_type {
int sectrac; /* sectors per track */
int secsize; /* size code for sectors */
int datalen; /* data len when secsize = 0 */
int gap; /* gap len between sectors */
int tracks; /* total num of tracks */
int size; /* size of disk in sectors */
int steptrac; /* steps per cylinder */
int trans; /* transfer speed code */
};
struct fd_type fd_types[NUMTYPES] = {
{ 18,2,0xFF,0x1B,80,2880,1,0 }, /* 1.44 meg HD 3.5in floppy */
{ 15,2,0xFF,0x1B,80,2400,1,0 }, /* 1.2 meg HD floppy */
{ 9,2,0xFF,0x23,40,720,2,1 }, /* 360k floppy in 1.2meg drive */
{ 9,2,0xFF,0x2A,40,720,1,1 }, /* 360k floppy in DD drive */
};
struct fd_u {
int type; /* Drive type (HD, DD */
int active; /* Drive activity boolean */
int motor; /* Motor on flag */
int opencnt; /* Num times open */
struct buf head; /* Head of buf chain */
struct buf rhead; /* Raw head of buf chain */
} fd_unit[NFD];
extern int hz;
/* state needed for current transfer */
static int fd_skip;
static int fd_state;
static int fd_retry;
static int fd_drive;
static int fd_status[7];
static char fdrawbuf[FDBLK];
/* stuff needed for virtual to physical calculations */
extern char Sysbase;
static unsigned long sbase = (unsigned long) &Sysbase;
/****************************************************************************/
/* autoconfiguration stuff */
/****************************************************************************/
int fdprobe(), fdattach(), fd_turnoff();
struct driver fddriver = {
fdprobe, fdattach, "fd",
};
fdprobe(dev)
struct device *dev;
{
return 1;
}
fdattach(dev)
struct device *dev;
{
INTREN(IRQ6);
}
/****************************************************************************/
/* fdstrategy */
/****************************************************************************/
fdstrategy(bp)
register struct buf *bp; /* IO operation to perform */
{
register struct buf *dp,*dp0,*dp1;
long nblocks,blknum;
int unit, type, s;
unit = FDUNIT(minor(bp->b_dev));
type = FDTYPE(minor(bp->b_dev));
#ifdef FDOTHER
printf("fdstrat: unit = %d, blkno = %d, bcount = %d\n",
unit, bp->b_blkno, bp->b_bcount);
#endif
if ((unit >= NFD) || (bp->b_blkno < 0)) {
printf("fdstrat: unit = %d, blkno = %d, bcount = %d\n",
unit, bp->b_blkno, bp->b_bcount);
pg("fd:error in fdstrategy");
bp->b_error = EINVAL;
goto bad;
}
/*
* Set up block calculations.
*/
blknum = (unsigned long) bp->b_blkno * DEV_BSIZE/FDBLK;
nblocks = fd_types[type].size;
if (blknum + (bp->b_bcount / FDBLK) > nblocks) {
if (blknum == nblocks) bp->b_resid = bp->b_bcount;
else bp->b_error = ENOSPC;
goto bad;
}
bp->b_cylin = blknum / (fd_types[type].sectrac * 2);
bp->b_cylin *= (fd_types[type].steptrac);
dp = &fd_unit[unit].head;
dp0 = &fd_unit[0].head;
dp1 = &fd_unit[1].head;
s = splbio();
disksort(dp, bp);
if ((dp0->b_active == 0)&&(dp1->b_active == 0)) {
dp->b_active = 1;
fd_drive = unit;
untimeout(fd_turnoff,unit);
fdstart(unit); /* start drive if idle */
}
splx(s);
return;
bad:
bp->b_flags |= B_ERROR;
biodone(bp);
}
/****************************************************************************/
/* motor control stuff */
/****************************************************************************/
set_motor(unit,reset)
int unit,reset;
{
int m0,m1;
m0 = fd_unit[0].motor;
m1 = fd_unit[1].motor;
outb(0x3f2,unit | (reset ? 0 : 0xC) | (m0 ? 16 : 0) | (m1 ? 32 : 0));
}
fd_turnoff(unit)
int unit;
{
fd_unit[unit].motor = 0;
if (unit) set_motor(0,0);
else set_motor(1,0);
}
fd_turnon(unit)
int unit;
{
fd_unit[unit].motor = 1;
set_motor(unit,0);
}
/****************************************************************************/
/* fdc in/out */
/****************************************************************************/
int
in_fdc()
{
int i;
while ((i = inb(0x3f4) & 192) != 192) if (i == 128) return -1;
return inb(0x3f5);
}
dump_stat()
{
int i;
for(i=0;i<7;i++) {
fd_status[i] = in_fdc();
if (fd_status[i] < 0) break;
}
printf("FD bad status :%X %X %X %X %X %X %X\n",
fd_status[0], fd_status[1], fd_status[2], fd_status[3],
fd_status[4], fd_status[5], fd_status[6] );
}
out_fdc(x)
int x;
{
int r,errcnt;
static int maxcnt = 0;
errcnt = 0;
do {
r = (inb(0x3f4) & 192);
if (r==128) break;
if (r==192) {
dump_stat(); /* error: direction. eat up output */
#ifdef FDOTHER
printf("%X\n",x);
#endif
}
/* printf("Error r = %d:",r); */
errcnt++;
} while (1);
if (errcnt > maxcnt) {
maxcnt = errcnt;
#ifdef FDOTHER
printf("New MAX = %d\n",maxcnt);
#endif
}
outb(0x3f5,x&0xFF);
}
/* see if fdc responding */
int
check_fdc()
{
int i;
for(i=0;i<100;i++) {
if (inb(0x3f4)&128) return 0;
}
return 1;
}
/****************************************************************************/
/* fdopen/fdclose */
/****************************************************************************/
fdopen(dev, flags)
dev_t dev;
int flags;
{
int unit = FDUNIT(minor(dev));
int type = FDTYPE(minor(dev));
int s;
/* check bounds */
if (unit >= NFD) return(ENXIO);
if (type >= NUMTYPES) return(ENXIO);
if (check_fdc()) return(EBUSY);
/* Set proper disk type, only allow one type */
s = splbio();
splx(s);
return 0;
}
fdclose(dev)
dev_t dev;
{
}
/****************************************************************************/
/* fdread/fdwrite */
/****************************************************************************/
/*
* Routines to do raw IO for a unit.
*/
fdread(dev, uio) /* character read routine */
dev_t dev;
struct uio *uio;
{
int unit = FDUNIT(minor(dev)) ;
if (unit >= NFD) return(ENXIO);
return(physio(fdstrategy,&fd_unit[unit].rhead,dev,B_READ,minphys,uio));
}
fdwrite(dev, uio) /* character write routine */
dev_t dev;
struct uio *uio;
{
int unit = FDUNIT(minor(dev));
if (unit >= NFD) return(ENXIO);
return(physio(fdstrategy,&fd_unit[unit].rhead,dev,B_WRITE,minphys,uio));
}
/****************************************************************************/
/* fdstart */
/****************************************************************************/
fdstart(unit)
int unit;
{
register struct buf *dp,*bp;
int s;
if (!fd_unit[unit].motor) {
fd_turnon(unit);
/* Wait for 1 sec */
timeout(fdstart,unit,hz);
} else {
s = splbio();
dp = &fd_unit[unit].head;
bp = dp->b_actf;
fd_retry = 0;
fd_state = 1;
fd_skip = 0;
/* Seek necessary, never quite sure where head is at! */
out_fdc(15); /* Seek function */
out_fdc(unit); /* Drive number */
out_fdc(bp->b_cylin);
splx(s);
}
}
/* XXX temporary */
kernel_space(x)
unsigned long x;
{
if ((x >= sbase) & (x < sbase + 0x800000)) return 1;
else return 0;
}
/****************************************************************************/
/* fd_dma */
/* set up DMA read/write operation and virtual address addr for nbytes */
/****************************************************************************/
fd_dma(read,addr,nbytes)
int read;
unsigned long addr;
int nbytes;
{
unsigned long phys;
int s,raw;
if (kernel_space(addr)) raw = 0;
else raw = 1;
/* copy bounce buffer on write */
if (raw && !read) bcopy(addr,fdrawbuf,FDBLK);
/* Set read/write bytes */
if (read) {
outb(0xC,0x46); outb(0xB,0x46);
} else {
outb(0xC,0x4A); outb(0xB,0x4A);
}
/* Send start address */
if (raw) phys = (unsigned long) &fdrawbuf[0];
else phys = addr;
/* translate to physical */
phys = phys - sbase;
outb(0x4,phys & 0xFF);
outb(0x4,(phys>>8) & 0xFF);
outb(0x81,(phys>>16) & 0xFF);
/* Send count */
nbytes--;
outb(0x5,nbytes & 0xFF);
outb(0x5,(nbytes>>8) & 0xFF);
/* set channel 2 */
outb(0x0A,2);
}
fd_timeout(x)
int x;
{
int i,j;
struct buf *dp,*bp;
dp = &fd_unit[fd_drive].head;
bp = dp->b_actf;
out_fdc(0x4);
out_fdc(fd_drive);
i = in_fdc();
printf("Timeout drive status %X\n",i);
out_fdc(0x8);
i = in_fdc();
j = in_fdc();
printf("ST0 = %X, PCN = %X\n",i,j);
if (bp) badtrans(dp,bp);
}
/****************************************************************************/
/* fdintr */
/****************************************************************************/
fdintr(vec)
int vec;
{
register struct buf *dp,*bp;
struct buf *dpother;
int read,head,trac,sec,i,s,sectrac;
unsigned long blknum;
struct fd_type *ft;
static int fd_track;
dp = &fd_unit[fd_drive].head;
bp = dp->b_actf;
read = bp->b_flags & B_READ;
ft = &fd_types[FDTYPE(bp->b_dev)];
switch (fd_state) {
case 1 : /* SEEK DONE, START DMA */
#ifdef FDOTHER
out_fdc(0x8);
i = in_fdc();
sec = in_fdc();
printf("ST0 = %X, PCN = %X:",i,sec);
#endif
fd_track = bp->b_cylin;
fd_dma(read,bp->b_un.b_addr+fd_skip,FDBLK);
blknum = (unsigned long)bp->b_blkno*DEV_BSIZE/FDBLK
+ fd_skip/FDBLK;
sectrac = ft->sectrac;
sec = blknum % (sectrac * 2);
head = sec / sectrac;
sec = sec % sectrac + 1;
if (read) out_fdc(0xE6); /* READ */
else out_fdc(0xC5); /* WRITE */
out_fdc(head << 2 | fd_drive); /* head & unit */
out_fdc(fd_track); /* track */
out_fdc(head);
out_fdc(sec); /* sector XXX +1? */
out_fdc(ft->secsize); /* sector size */
out_fdc(sectrac); /* sectors/track */
out_fdc(ft->gap); /* gap size */
out_fdc(ft->datalen); /* data length */
fd_state = 2;
/* XXX PARANOIA */
untimeout(fd_timeout,2);
timeout(fd_timeout,2,hz);
break;
case 2 : /* IO DONE, post-analyze */
untimeout(fd_timeout,2);
for(i=0;i<7;i++) {
fd_status[i] = in_fdc();
}
if (fd_status[0]&0xF8) {
#ifdef FDOTHER
printf("status0 err %d:",fd_status[0]);
#endif
goto retry;
}
if (fd_status[1]){
printf("status1 err %d:",fd_status[0]);
goto retry;
}
if (fd_status[2]){
printf("status2 err %d:",fd_status[0]);
goto retry;
}
/* All OK */
if (!kernel_space(bp->b_un.b_addr+fd_skip)) {
/* RAW transfer */
if (read) bcopy(fdrawbuf,bp->b_un.b_addr+fd_skip,
DEV_BSIZE);
}
fd_skip += FDBLK;
if (fd_skip >= bp->b_bcount) {
/* ALL DONE */
fd_skip = 0;
bp->b_resid = 0;
dp->b_actf = bp->av_forw;
biodone(bp);
nextstate(dp);
} else {
/* set up next transfer */
blknum = (unsigned long)bp->b_blkno*DEV_BSIZE/FDBLK
+ fd_skip/FDBLK;
fd_state = 1;
bp->b_cylin = (blknum / (ft->sectrac * 2));
bp->b_cylin *= ft->steptrac;
if (bp->b_cylin != fd_track) {
/* SEEK Necessary */
out_fdc(15); /* Seek function */
out_fdc(fd_drive);/* Drive number */
out_fdc(bp->b_cylin);
break;
} else fdintr();
}
break;
case 3:
/* Seek necessary */
out_fdc(15); /* Seek function */
out_fdc(fd_drive);/* Drive number */
out_fdc(bp->b_cylin);
fd_state = 1;
break;
case 4:
out_fdc(3); /* specify command */
out_fdc(0xDF);
out_fdc(2);
out_fdc(7); /* Recalibrate Function */
out_fdc(fd_drive);
fd_state = 3;
break;
default:
#ifdef FDDEBUG
printf("Unexpected FD int->");
out_fdc(0x8);
i = in_fdc();
sec = in_fdc();
printf("ST0 = %X, PCN = %X\n",i,sec);
out_fdc(0x4A);
out_fdc(fd_drive);
for(i=0;i<7;i++) {
fd_status[i] = in_fdc();
}
printf("intr status :%X %X %X %X %X %X %X ",
fd_status[0], fd_status[1], fd_status[2], fd_status[3],
fd_status[4], fd_status[5], fd_status[6] );
#endif
break;
}
return;
retry:
switch(fd_retry) {
case 0: case 1:
break;
case 2:
#ifdef FDDEBUG
printf("**RESET**\n");
#endif
/* Try a reset, keep motor on */
set_motor(fd_drive,1);
set_motor(fd_drive,0);
outb(0x3f7,ft->trans);
fd_retry++;
fd_state = 4;
return;
case 3: case 4:
case 5: case 6:
break;
default:
printf("FD err %X %X %X %X %X %X %X\n",
fd_status[0], fd_status[1], fd_status[2], fd_status[3],
fd_status[4], fd_status[5], fd_status[6] );
badtrans(dp,bp);
return;
}
fd_state = 1;
fd_retry++;
fdintr();
}
badtrans(dp,bp)
struct buf *dp,*bp;
{
bp->b_flags |= B_ERROR;
bp->b_error = EIO;
bp->b_resid = bp->b_bcount - fd_skip;
dp->b_actf = bp->av_forw;
fd_skip = 0;
biodone(bp);
nextstate(dp);
}
/*
nextstate : After a transfer is done, continue processing
requests on the current drive queue. If empty, go to
the other drives queue. If that is empty too, timeout
to turn off the current drive in 5 seconds, and go
to state 0 (not expecting any interrupts).
*/
nextstate(dp)
struct buf *dp;
{
struct buf *dpother;
dpother = &fd_unit[fd_drive ? 0 : 1].head;
if (dp->b_actf) fdstart(fd_drive);
else if (dpother->b_actf) {
dp->b_active = 0;
fdstart(fd_drive ? 0 : 1);
} else {
untimeout(fd_turnoff,fd_drive);
timeout(fd_turnoff,fd_drive,5*hz);
fd_state = 0;
dp->b_active = 0;
}
}