* Copyright (c) 1990 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* @(#)fd.c 7.4 (Berkeley) 5/25/91
* PATCHES MAGIC LEVEL PATCH THAT GOT US HERE
* -------------------- ----- ----------------------
* CURRENT PATCH LEVEL: 1 00153
* -------------------- ----- ----------------------
* 20 Apr 93 Julian Elischer Heavily re worked, see notes below
* Largely rewritten to handle multiple controllers and drives
* By Julian Elischer, Sun Apr 4 16:34:33 WST 1993
char rev
[] = "$Revision: 1.2 $";
* $Header: /freefall/a/cvs/386BSD/src/sys/i386/isa/fd.c,v 1.2 1993/07/15 17:53:04 davidg Exp $
* Revision 1.2 1993/07/15 17:53:04 davidg
* Modified attach printf's so that the output is compatible with the "new"
* way of doing things. There still remain several drivers that need to
* be updated. Also added a compile-time option to pccons to switch the
* control and caps-lock keys (REVERSE_CAPS_CTRL) - added for my personal
* Revision 1.1.1.1 1993/06/12 14:58:02 rgrimes
* Initial import, 0.1 + pk 0.2.4-B1
* Revision 1.10 93/04/13 16:53:29 root
* make sure turning off a drive motor doesn't deselect another
* drive active at the time.
* Also added a pointer from the fd_data to it's fd_type.
* Revision 1.9 93/04/13 15:31:02 root
* make all seeks go through DOSEEK state so are sure of being done right.
* Revision 1.8 93/04/12 21:20:13 root
* only check if old fd is the one we are working on if there IS
* an old fd pointer. (in fdstate())
* Revision 1.7 93/04/11 17:05:35 root
* also fix bug to select teh correct drive when running > 1 drive
* Revision 1.6 93/04/05 00:48:45 root
* change a timeout and add version to banner message
* Revision 1.5 93/04/04 16:39:08 root
* first working version.. some floppy controllers don't seem to
* like 2 int. status inquiries in a row.
#include "i386/isa/isa.h"
#include "i386/isa/isa_device.h"
#include "i386/isa/fdreg.h"
#include "i386/isa/icu.h"
#include "i386/isa/rtc.h"
#define FDUNIT(s) ((s>>3)&1)
#define FDTYPE(s) ((s)&7)
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 */
int heads
; /* number of heads */
struct fd_type fd_types
[NUMTYPES
] =
{ 18,2,0xFF,0x1B,80,2880,1,0,2 }, /* 1.44 meg HD 3.5in floppy */
{ 15,2,0xFF,0x1B,80,2400,1,0,2 }, /* 1.2 meg HD floppy */
{ 9,2,0xFF,0x23,40,720,2,1,2 }, /* 360k floppy in 1.2meg drive */
{ 9,2,0xFF,0x2A,40,720,1,1,2 }, /* 360k floppy in DD drive */
/***********************************************************************\
* Per controller structure. *
\***********************************************************************/
int fdcu
; /* our unit number */
#define FDC_ATTACHED 0x01
int fdu
; /* the active drive */
struct buf head
; /* Head of buf chain */
struct buf rhead
; /* Raw head of buf chain */
int status
[7]; /* copy of the registers */
}fdc_data
[(NFD
+1)/DRVS_PER_CTLR
];
/***********************************************************************\
* N per controller (presently 2) (DRVS_PER_CTLR) *
\***********************************************************************/
int fdu
; /* this unit number */
int fdsu
; /* this units number on this controller */
int type
; /* Drive type (HD, DD */
struct fd_type
*ft
; /* pointer to the type descriptor */
#define FD_OPEN 0x01 /* it's open */
#define FD_ACTIVE 0x02 /* it's active */
#define FD_MOTOR 0x04 /* motor should be on */
#define FD_MOTOR_WAIT 0x08 /* motor coming up */
int track
; /* where we think the head is */
/***********************************************************************\
* Throughout this file the following conventions will be used: *
* fd is a pointer to the fd_data struct for the drive in question *
* fdc is a pointer to the fdc_data struct for the controller *
* fdu is the floppy drive unit number *
* fdcu is the floppy controller unit number *
* fdsu is the floppy drive unit number on that controller. (sub-unit) *
\***********************************************************************/
typedef struct fd_data
*fd_p
;
typedef struct fdc_data
*fdc_p
;
#define TRACE0(arg) if(fd_debug) printf(arg)
#define TRACE1(arg1,arg2) if(fd_debug) printf(arg1,arg2)
#define TRACE1(arg1,arg2)
/* state needed for current transfer */
/****************************************************************************/
/* autoconfiguration stuff */
/****************************************************************************/
int fdprobe(), fdattach(), fd_turnoff();
struct isa_driver fddriver
= {
* probe for existance of controller
fdcu_t fdcu
= dev
->id_unit
;
if(fdc_data
[fdcu
].flags
& FDC_ATTACHED
)
printf("fdc: same unit (%d) used multiple times\n",fdcu
);
fdc_data
[fdcu
].baseport
= dev
->id_iobase
;
/* see if it can handle a command */
if (out_fdc(fdcu
,NE7CMD_SPECIFY
) < 0)
* wire controller into system, look for floppy units
fdcu_t fdcu
= dev
->id_unit
;
fdc_p fdc
= fdc_data
+ fdcu
;
fdc
->flags
|= FDC_ATTACHED
;
fdc
->dmachan
= dev
->id_drq
;
fdt
= rtcin(RTC_FDISKETTE
);
/* check for each floppy drive */
for (fdu
= (fdcu
* DRVS_PER_CTLR
),fdsu
= 0;
((fdu
< NFD
) && (fdsu
< DRVS_PER_CTLR
));
if ((fdt
& 0xf0) == RTCFDT_NONE
)
spinwait(1000); /* 1 sec */
out_fdc(fdcu
,NE7CMD_RECAL
); /* Recalibrate Function */
spinwait(1000); /* 1 sec */
/* anything responding */
out_fdc(fdcu
,NE7CMD_SENSEI
);
fd_data
[fdu
].fdsu
= fdsu
;
printf("fd%d: unit %d type ", fdcu
, fdu
);
if ((fdt
& 0xf0) == RTCFDT_12M
) {
printf("1.2MB 5.25in\n");
fd_data
[fdu
].ft
= fd_types
+ 1;
if ((fdt
& 0xf0) == RTCFDT_144M
) {
printf("1.44MB 3.5in\n");
fd_data
[fdu
].ft
= fd_types
+ 0;
/* Set transfer to 500kbps */
outb(fdc
->baseport
+fdctl
,0); /*XXX*/
/****************************************************************************/
/****************************************************************************/
register struct buf
*bp
; /* IO operation to perform */
register struct buf
*dp
,*dp0
,*dp1
;
fdu
= FDUNIT(minor(bp
->b_dev
));
/*type = FDTYPE(minor(bp->b_dev));*/
if ((fdu
>= NFD
) || (bp
->b_blkno
< 0)) {
printf("fdstrat: fdu = %d, blkno = %d, bcount = %d\n",
fdu
, bp
->b_blkno
, bp
->b_bcount
);
pg("fd:error in fdstrategy");
* Set up block calculations.
blknum
= (unsigned long) bp
->b_blkno
* DEV_BSIZE
/FDBLK
;
if (blknum
+ (bp
->b_bcount
/ FDBLK
) > nblocks
) {
bp
->b_resid
= bp
->b_bcount
;
bp
->b_cylin
= blknum
/ (fd
->ft
->sectrac
* fd
->ft
->heads
);
untimeout(fd_turnoff
,fdu
); /* a good idea */
/****************************************************************************/
/* motor control stuff */
/* remember to not deselect the drive we're working on */
/****************************************************************************/
set_motor(fdcu_t fdcu
, fdu_t fdu
, int reset
)
if(fd
= fdc_data
[fdcu
].fd
)/* yes an assign! */
m0
= fd_data
[fdcu
* DRVS_PER_CTLR
+ 0].flags
& FD_MOTOR
;
m1
= fd_data
[fdcu
* DRVS_PER_CTLR
+ 1].flags
& FD_MOTOR
;
outb(fdc_data
[fdcu
].baseport
+fdout
,
| (reset
? 0 : (FDO_FRST
|FDO_FDMAEN
))
| (reset
? 0 : (FDO_FRST
|FDO_FDMAEN
))
| (m1
? FDO_MOEN1
: 0)));
set_motor(fd
->fdc
->fdcu
,fd
->fdsu
,0);
fd
->flags
&= ~FD_MOTOR_WAIT
;
if((fd
->fdc
->fd
== fd
) && (fd
->fdc
->state
== MOTORWAIT
))
fd_pseudointr(fd
->fdc
->fdcu
);
if(!(fd
->flags
& FD_MOTOR
))
fd
->flags
|= FD_MOTOR_WAIT
;
timeout(fd_motor_on
,fdu
,hz
); /* in 1 sec its ok */
set_motor(fd
->fdc
->fdcu
,fd
->fdsu
,0);
/****************************************************************************/
/****************************************************************************/
int baseport
= fdc_data
[fdcu
].baseport
;
while ((i
= inb(baseport
+fdsts
) & (NE7_DIO
|NE7_RQM
))
!= (NE7_DIO
|NE7_RQM
) && j
-- > 0)
if (i
== NE7_RQM
) return -1;
i
= inb(baseport
+fddata
);
TRACE1("[fddata->0x%x]",(unsigned char)i
);
return inb(baseport
+fddata
);
out_fdc(fdcu_t fdcu
,int x
)
int baseport
= fdc_data
[fdcu
].baseport
;
/* Check that the direction bit is set */
while ((inb(baseport
+fdsts
) & NE7_DIO
) && i
-- > 0);
if (i
<= 0) return (-1); /* Floppy timed out */
/* Check that the floppy controller is ready for a command */
while ((inb(baseport
+fdsts
) & NE7_RQM
) == 0 && i
-- > 0);
if (i
<= 0) return (-1); /* Floppy timed out */
/* Send the command and return */
TRACE1("[0x%x->fddata]",x
);
/****************************************************************************/
/****************************************************************************/
fdu_t fdu
= FDUNIT(minor(dev
));
/*int type = FDTYPE(minor(dev));*/
if (fdu
>= NFD
) return(ENXIO
);
/*if (type >= NUMTYPES) return(ENXIO);*/
fd_data
[fdu
].flags
|= FD_OPEN
;
fdu_t fdu
= FDUNIT(minor(dev
));
fd_data
[fdu
].flags
&= ~FD_OPEN
;
/***************************************************************\
* We have just queued something.. if the controller is not busy *
* then simulate the case where it has just finished a command *
* So that it (the interrupt routine) looks on the queue for more*
* work to do and picks up what we just added. *
* If the controller is already busy, we need do nothing, as it *
* will pick up our work when the present work completes *
\***************************************************************/
register struct buf
*dp
,*bp
;
if(fdc_data
[fdcu
].state
== DEVIDLE
)
fdu_t fdu
= fdc_data
[fdcu
].fdu
;
dp
= &fdc_data
[fdcu
].head
;
out_fdc(fdcu
,NE7CMD_SENSED
);
out_fdc(fdcu
,fd_data
[fdu
].hddrv
);
out_fdc(fdcu
,NE7CMD_SENSEI
);
printf("fd%d: Operation timeout ST0 %b cyl %d ST3 %b\n",
fdc_data
[fdcu
].status
[0] = 0xc0;
fdc_data
[fdcu
].state
= IOTIMEDOUT
;
if( fdc_data
[fdcu
].retry
< 6)
fdc_data
[fdcu
].retry
= 6;
fdc_data
[fdcu
].fd
= (fd_p
) 0;
fdc_data
[fdcu
].state
= DEVIDLE
;
/* just ensure it has the right spl */
fd_pseudointr(fdcu_t fdcu
)
/***********************************************************************\
* keep calling the state machine until it returns a 0 *
* ALWAYS called at SPLBIO *
\***********************************************************************/
fdc_p fdc
= fdc_data
+ fdcu
;
while(fdstate(fdcu
, fdc
));
/***********************************************************************\
* The controller state machine. *
* if it returns a non zero value, it should be called again immediatly *
\***********************************************************************/
int fdstate(fdcu_t fdcu
, fdc_p fdc
)
int read
,head
,trac
,sec
,i
,s
,sectrac
,cyl
,st0
;
register struct buf
*dp
,*bp
;
/***********************************************\
* nothing left for this controller to do *
* Force into the IDLE state, *
\***********************************************/
printf("unexpected valid fd pointer (fdu = %d)\n"
TRACE1("[fdc%d IDLE]",fdcu
);
fdu
= FDUNIT(minor(bp
->b_dev
));
if (fdc
->fd
&& (fd
!= fdc
->fd
))
printf("confused fd pointers\n");
read
= bp
->b_flags
& B_READ
;
TRACE1("[%s]",fdstates
[fdc
->state
]);
TRACE1("(0x%x)",fd
->flags
);
untimeout(fd_turnoff
, fdu
);
timeout(fd_turnoff
,fdu
,4 * hz
);
case FINDWORK
: /* we have found new work */
/*******************************************************\
* If the next drive has a motor startup pending, then *
* it will start up in it's own good time *
\*******************************************************/
if(fd
->flags
& FD_MOTOR_WAIT
)
return(0); /* come back later */
/*******************************************************\
* Maybe if it's not starting, it SHOULD be starting *
\*******************************************************/
if (!(fd
->flags
& FD_MOTOR
))
else /* at least make sure we are selected */
set_motor(fdcu
,fd
->fdsu
,0);
if (bp
->b_cylin
== fd
->track
)
fdc
->state
= SEEKCOMPLETE
;
out_fdc(fdcu
,NE7CMD_SEEK
); /* Seek function */
out_fdc(fdcu
,fd
->fdsu
); /* Drive number */
out_fdc(fdcu
,bp
->b_cylin
* fd
->ft
->steptrac
);
return(0); /* will return later */
/* allow heads to settle */
timeout(fd_pseudointr
,fdcu
,hz
/50);
fdc
->state
= SEEKCOMPLETE
;
return(0); /* will return later */
case SEEKCOMPLETE
: /* SEEK DONE, START DMA */
/* Make sure seek really happened*/
int descyl
= bp
->b_cylin
* fd
->ft
->steptrac
;
out_fdc(fdcu
,NE7CMD_SENSEI
);
printf("fd%d: Seek to cyl %d failed; am at cyl %d (ST0 = 0x%x)\n", fdu
,
descyl
, cyl
, i
, NE7_ST0BITS
);
isa_dmastart(bp
->b_flags
, bp
->b_un
.b_addr
+fd
->skip
,
blknum
= (unsigned long)bp
->b_blkno
*DEV_BSIZE
/FDBLK
sectrac
= fd
->ft
->sectrac
;
sec
= blknum
% (sectrac
* fd
->ft
->heads
);
/*XXX*/ fd
->hddrv
= ((head
&1)<<2)+fdu
;
out_fdc(fdcu
,NE7CMD_READ
); /* READ */
out_fdc(fdcu
,NE7CMD_WRITE
); /* WRITE */
out_fdc(fdcu
,head
<< 2 | fdu
); /* head & unit */
out_fdc(fdcu
,fd
->track
); /* track */
out_fdc(fdcu
,sec
); /* sector XXX +1? */
out_fdc(fdcu
,fd
->ft
->secsize
); /* sector size */
out_fdc(fdcu
,sectrac
); /* sectors/track */
out_fdc(fdcu
,fd
->ft
->gap
); /* gap size */
out_fdc(fdcu
,fd
->ft
->datalen
); /* data length */
timeout(fd_timeout
,fdcu
,2 * hz
);
return(0); /* will return later */
case IOCOMPLETE
: /* IO DONE, post-analyze */
untimeout(fd_timeout
,fdcu
);
fdc
->status
[i
] = in_fdc(fdcu
);
isa_dmadone(bp
->b_flags
, bp
->b_un
.b_addr
+fd
->skip
,
if (fd
->skip
< bp
->b_bcount
)
/* set up next transfer */
blknum
= (unsigned long)bp
->b_blkno
*DEV_BSIZE
/FDBLK
bp
->b_cylin
= (blknum
/ (fd
->ft
->sectrac
* fd
->ft
->heads
));
dp
->b_actf
= bp
->av_forw
;
/* Try a reset, keep motor on */
set_motor(fdcu
,fd
->fdsu
,1);
set_motor(fdcu
,fd
->fdsu
,0);
outb(fdc
->baseport
+fdctl
,fd
->ft
->trans
);
TRACE1("[0x%x->fdctl]",fd
->ft
->trans
);
out_fdc(fdcu
,NE7CMD_SPECIFY
); /* specify command */
out_fdc(fdcu
,NE7CMD_RECAL
); /* Recalibrate Function */
return(0); /* will return later */
/* allow heads to settle */
timeout(fd_pseudointr
,fdcu
,hz
/30);
fdc
->state
= RECALCOMPLETE
;
return(0); /* will return later */
out_fdc(fdcu
,NE7CMD_SENSEI
);
printf("fd%d: recal failed ST0 %b cyl %d\n", fdu
,
/* Seek (probably) necessary */
return(1); /* will return immediatly */
if(fd
->flags
& FD_MOTOR_WAIT
)
return(0); /* time's not up yet */
return(1); /* will return immediatly */
printf("Unexpected FD int->");
out_fdc(fdcu
,NE7CMD_SENSEI
);
printf("ST0 = %lx, PCN = %lx\n",i
,sec
);
fdc
->status
[i
] = in_fdc(fdcu
);
printf("intr status :%lx %lx %lx %lx %lx %lx %lx ",
return(1); /* Come back immediatly to new state */
fdc_p fdc
= fdc_data
+ fdcu
;
register struct buf
*dp
,*bp
;
fdc
->state
= SEEKCOMPLETE
;
printf("fd%d: hard error (ST0 %b ",
fdc
->fdu
, fdc
->status
[0], NE7_ST0BITS
);
printf(" ST1 %b ", fdc
->status
[1], NE7_ST1BITS
);
printf(" ST2 %b ", fdc
->status
[2], NE7_ST2BITS
);
printf(" ST3 %b ", fdc
->status
[3], NE7_ST3BITS
);
printf("cyl %d hd %d sec %d)\n",
fdc
->status
[4], fdc
->status
[5], fdc
->status
[6]);
bp
->b_resid
= bp
->b_bcount
- fdc
->fd
->skip
;
dp
->b_actf
= bp
->av_forw
;