Minix2.0/src/kernel/floppy.c
/* This file contains the device dependent part of the driver for the Floppy
* Disk Controller (FDC) using the NEC PD765 chip.
*
* The file contains one entry point:
*
* floppy_task: main entry when system is brought up
* floppy_stop: stop all activity
*
* Changes:
* 27 Oct. 1986 by Jakob Schripsema: fdc_results fixed for 8 MHz
* 28 Nov. 1986 by Peter Kay: better resetting for 386
* 06 Jan. 1988 by Al Crew: allow 1.44 MB diskettes
* 1989 by Bruce Evans: I/O vector to keep up with 1-1 interleave
* 13 May 1991 by Don Chapman: renovated the errors loop.
* 1991 by Bruce Evans: len[] / motors / reset / step rate / ...
* 14 Feb 1992 by Andy Tanenbaum: check drive density on opens only
* 27 Mar 1992 by Kees J. Bot: last details on density checking
* 04 Apr 1992 by Kees J. Bot: device dependent/independent split
*/
#include "kernel.h"
#include "driver.h"
#include "drvlib.h"
#include <ibm/diskparm.h>
/* I/O Ports used by floppy disk task. */
#define DOR 0x3F2 /* motor drive control bits */
#define FDC_STATUS 0x3F4 /* floppy disk controller status register */
#define FDC_DATA 0x3F5 /* floppy disk controller data register */
#define FDC_RATE 0x3F7 /* transfer rate register */
#define DMA_ADDR 0x004 /* port for low 16 bits of DMA address */
#define DMA_TOP 0x081 /* port for top 4 bits of 20-bit DMA addr */
#define DMA_COUNT 0x005 /* port for DMA count (count = bytes - 1) */
#define DMA_FLIPFLOP 0x00C /* DMA byte pointer flip-flop */
#define DMA_MODE 0x00B /* DMA mode port */
#define DMA_INIT 0x00A /* DMA init port */
#define DMA_RESET_VAL 0x06
/* Status registers returned as result of operation. */
#define ST0 0x00 /* status register 0 */
#define ST1 0x01 /* status register 1 */
#define ST2 0x02 /* status register 2 */
#define ST3 0x00 /* status register 3 (return by DRIVE_SENSE) */
#define ST_CYL 0x03 /* slot where controller reports cylinder */
#define ST_HEAD 0x04 /* slot where controller reports head */
#define ST_SEC 0x05 /* slot where controller reports sector */
#define ST_PCN 0x01 /* slot where controller reports present cyl */
/* Fields within the I/O ports. */
/* Main status register. */
#define CTL_BUSY 0x10 /* bit is set when read or write in progress */
#define DIRECTION 0x40 /* bit is set when reading data reg is valid */
#define MASTER 0x80 /* bit is set when data reg can be accessed */
/* Digital output port (DOR). */
#define MOTOR_SHIFT 4 /* high 4 bits control the motors in DOR */
#define ENABLE_INT 0x0C /* used for setting DOR port */
/* ST0. */
#define ST0_BITS 0xF8 /* check top 5 bits of seek status */
#define TRANS_ST0 0x00 /* top 5 bits of ST0 for READ/WRITE */
#define SEEK_ST0 0x20 /* top 5 bits of ST0 for SEEK */
/* ST1. */
#define BAD_SECTOR 0x05 /* if these bits are set in ST1, recalibrate */
#define WRITE_PROTECT 0x02 /* bit is set if diskette is write protected */
/* ST2. */
#define BAD_CYL 0x1F /* if any of these bits are set, recalibrate */
/* ST3 (not used). */
#define ST3_FAULT 0x80 /* if this bit is set, drive is sick */
#define ST3_WR_PROTECT 0x40 /* set when diskette is write protected */
#define ST3_READY 0x20 /* set when drive is ready */
/* Floppy disk controller command bytes. */
#define FDC_SEEK 0x0F /* command the drive to seek */
#define FDC_READ 0xE6 /* command the drive to read */
#define FDC_WRITE 0xC5 /* command the drive to write */
#define FDC_SENSE 0x08 /* command the controller to tell its status */
#define FDC_RECALIBRATE 0x07 /* command the drive to go to cyl 0 */
#define FDC_SPECIFY 0x03 /* command the drive to accept params */
#define FDC_READ_ID 0x4A /* command the drive to read sector identity */
#define FDC_FORMAT 0x4D /* command the drive to format a track */
/* DMA channel commands. */
#define DMA_READ 0x46 /* DMA read opcode */
#define DMA_WRITE 0x4A /* DMA write opcode */
/* Parameters for the disk drive. */
#define HC_SIZE 2880 /* # sectors on largest legal disk (1.44MB) */
#define NR_HEADS 0x02 /* two heads (i.e., two tracks/cylinder) */
#define MAX_SECTORS 18 /* largest # sectors per track */
#define DTL 0xFF /* determines data length (sector size) */
#define SPEC2 0x02 /* second parameter to SPECIFY */
#define MOTOR_OFF 3*HZ /* how long to wait before stopping motor */
#define WAKEUP 2*HZ /* timeout on I/O, FDC won't quit. */
/* Error codes */
#define ERR_SEEK (-1) /* bad seek */
#define ERR_TRANSFER (-2) /* bad transfer */
#define ERR_STATUS (-3) /* something wrong when getting status */
#define ERR_READ_ID (-4) /* bad read id */
#define ERR_RECALIBRATE (-5) /* recalibrate didn't work properly */
#define ERR_DRIVE (-6) /* something wrong with a drive */
#define ERR_WR_PROTECT (-7) /* diskette is write protected */
#define ERR_TIMEOUT (-8) /* interrupt timeout */
/* No retries on some errors. */
#define err_no_retry(err) ((err) <= ERR_WR_PROTECT)
/* Encoding of drive type in minor device number. */
#define DEV_TYPE_BITS 0x7C /* drive type + 1, if nonzero */
#define DEV_TYPE_SHIFT 2 /* right shift to normalize type bits */
#define FORMAT_DEV_BIT 0x80 /* bit in minor to turn write into format */
/* Miscellaneous. */
#define MAX_ERRORS 6 /* how often to try rd/wt before quitting */
#define MAX_RESULTS 7 /* max number of bytes controller returns */
#define NR_DRIVES 2 /* maximum number of drives */
#define DIVISOR 128 /* used for sector size encoding */
#define SECTOR_SIZE_CODE 2 /* code to say "512" to the controller */
#define TIMEOUT 500 /* milliseconds waiting for FDC */
#define NT 7 /* number of diskette/drive combinations */
#define UNCALIBRATED 0 /* drive needs to be calibrated at next use */
#define CALIBRATED 1 /* no calibration needed */
#define BASE_SECTOR 1 /* sectors are numbered starting at 1 */
#define NO_SECTOR 0 /* current sector unknown */
#define NO_CYL (-1) /* current cylinder unknown, must seek */
#define NO_DENS 100 /* current media unknown */
#define BSY_IDLE 0 /* busy doing nothing */
#define BSY_IO 1 /* doing I/O */
#define BSY_WAKEN 2 /* got a wakeup call */
/* Variables. */
PRIVATE struct floppy { /* main drive struct, one entry per drive */
int fl_curcyl; /* current cylinder */
int fl_hardcyl; /* hardware cylinder, as opposed to: */
int fl_cylinder; /* cylinder number addressed */
int fl_sector; /* sector addressed */
int fl_head; /* head number addressed */
char fl_calibration; /* CALIBRATED or UNCALIBRATED */
char fl_density; /* NO_DENS = ?, 0 = 360K; 1 = 360K/1.2M; etc.*/
char fl_class; /* bitmap for possible densities */
struct device fl_geom; /* Geometry of the drive */
struct device fl_part[NR_PARTITIONS]; /* partition's base & size */
} floppy[NR_DRIVES], *f_fp;
/* Gather transfer data for each sector. */
PRIVATE struct trans { /* precomputed transfer params */
unsigned tr_count; /* byte count */
struct iorequest_s *tr_iop; /* belongs to this I/O request */
phys_bytes tr_phys; /* user physical address */
phys_bytes tr_dma; /* DMA physical address */
} ftrans[MAX_SECTORS];
PRIVATE unsigned f_count; /* this many bytes to transfer */
PRIVATE unsigned f_nexttrack; /* don't do blocks above this */
PRIVATE int motor_status; /* bitmap of current motor status */
PRIVATE int motor_goal; /* bitmap of desired motor status */
PRIVATE int need_reset; /* set to 1 when controller must be reset */
PRIVATE int d; /* diskette/drive combination */
PRIVATE int f_drive; /* selected drive */
PRIVATE int f_device; /* selected minor device */
PRIVATE int f_opcode; /* DEV_READ or DEV_WRITE */
PRIVATE int f_sectors; /* sectors per track of the floppy */
PRIVATE int f_must; /* must do part of the next track? */
PRIVATE int f_busy; /* BSY_IDLE, BSY_IO, BSY_WAKEN */
PRIVATE int current_spec1; /* latest spec1 sent to the controller */
PRIVATE struct device *f_dv; /* device's base and size */
PRIVATE struct disk_parameter_s fmt_param; /* parameters for format */
PRIVATE char f_results[MAX_RESULTS];/* the controller can give lots of output */
/* Seven combinations of diskette/drive are supported.
*
* # Drive diskette Sectors Tracks Rotation Data-rate Comment
* 0 360K 360K 9 40 300 RPM 250 kbps Standard PC DSDD
* 1 1.2M 1.2M 15 80 360 RPM 500 kbps AT disk in AT drive
* 2 720K 360K 9 40 300 RPM 250 kbps Quad density PC
* 3 720K 720K 9 80 300 RPM 250 kbps Toshiba, et al.
* 4 1.2M 360K 9 40 360 RPM 300 kbps PC disk in AT drive
* 5 1.2M 720K 9 80 360 RPM 300 kbps Toshiba in AT drive
* 6 1.44M 1.44M 18 80 300 RPM 500 kbps PS/2, et al.
*
* In addition, 720K diskettes can be read in 1.44MB drives, but that does
* not need a different set of parameters. This combination uses
*
* X 1.44M 720K 9 80 300 RPM 250 kbps PS/2, et al.
*/
PRIVATE char gap[NT] =
{0x2A, 0x1B, 0x2A, 0x2A, 0x23, 0x23, 0x1B}; /* gap size */
PRIVATE char rate[NT] =
{0x02, 0x00, 0x02, 0x02, 0x01, 0x01, 0x00}; /* 2=250,1=300,0=500 kbps*/
PRIVATE char nr_sectors[NT] =
{9, 15, 9, 9, 9, 9, 18}; /* sectors/track */
PRIVATE int nr_blocks[NT] =
{720, 2400, 720, 1440, 720, 1440, 2880}; /* sectors/diskette*/
PRIVATE char steps_per_cyl[NT] =
{1, 1, 2, 1, 2, 1, 1}; /* 2 = dbl step */
PRIVATE char mtr_setup[NT] =
{1*HZ/4,3*HZ/4,1*HZ/4,4*HZ/4,3*HZ/4,3*HZ/4,4*HZ/4}; /* in ticks */
PRIVATE char spec1[NT] =
{0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF}; /* step rate, etc. */
PRIVATE char test_sector[NT] =
{4*9, 14, 2*9, 4*9, 2*9, 4*9, 17}; /* to recognize it */
#define b(d) (1 << (d)) /* bit for density d. */
/* The following table is used with the test_sector array to recognize a
* drive/floppy combination. The sector to test has been determined by
* looking at the differences in gap size, sectors/track, and double stepping.
* This means that types 0 and 3 can't be told apart, only the motor start
* time differs. If a read test succeeds then the drive is limited to the
* set of densities it can support to avoid unnecessary tests in the future.
*/
PRIVATE struct test_order {
char t_density; /* floppy/drive type */
char t_class; /* limit drive to this class of densities */
} test_order[NT-1] = {
{ 6, b(3) | b(6) }, /* 1.44M {720K, 1.44M} */
{ 1, b(1) | b(4) | b(5) }, /* 1.2M {1.2M, 360K, 720K} */
{ 3, b(2) | b(3) | b(6) }, /* 720K {360K, 720K, 1.44M} */
{ 4, b(1) | b(4) | b(5) }, /* 360K {1.2M, 360K, 720K} */
{ 5, b(1) | b(4) | b(5) }, /* 720K {1.2M, 360K, 720K} */
{ 2, b(2) | b(3) }, /* 360K {360K, 720K} */
/* Note that type 0 is missing, type 3 can read/write it too (alas). */
};
FORWARD _PROTOTYPE( struct device *f_prepare, (int device) );
FORWARD _PROTOTYPE( char *f_name, (void) );
FORWARD _PROTOTYPE( void f_cleanup, (void) );
FORWARD _PROTOTYPE( int f_schedule, (int proc_nr, struct iorequest_s *iop) );
FORWARD _PROTOTYPE( int f_finish, (void) );
FORWARD _PROTOTYPE( void defuse, (void) );
FORWARD _PROTOTYPE( void dma_setup, (struct trans *tp) );
FORWARD _PROTOTYPE( void start_motor, (void) );
FORWARD _PROTOTYPE( void stop_motor, (void) );
FORWARD _PROTOTYPE( int seek, (struct floppy *fp) );
FORWARD _PROTOTYPE( int f_transfer, (struct floppy *fp, struct trans *tp) );
FORWARD _PROTOTYPE( int fdc_results, (void) );
FORWARD _PROTOTYPE( int f_handler, (int irq) );
FORWARD _PROTOTYPE( void fdc_out, (int val) );
FORWARD _PROTOTYPE( int recalibrate, (struct floppy *fp) );
FORWARD _PROTOTYPE( void f_reset, (void) );
FORWARD _PROTOTYPE( void send_mess, (void) );
FORWARD _PROTOTYPE( int f_intr_wait, (void) );
FORWARD _PROTOTYPE( void f_timeout, (void) );
FORWARD _PROTOTYPE( int read_id, (struct floppy *fp) );
FORWARD _PROTOTYPE( int f_do_open, (struct driver *dp, message *m_ptr) );
FORWARD _PROTOTYPE( int test_read, (int density) );
FORWARD _PROTOTYPE( void f_geometry, (struct partition *entry));
/* Entry points to this driver. */
PRIVATE struct driver f_dtab = {
f_name, /* current device's name */
f_do_open, /* open or mount request, sense type of diskette */
do_nop, /* nothing on a close */
do_diocntl, /* get or set a partitions geometry */
f_prepare, /* prepare for I/O on a given minor device */
f_schedule, /* precompute cylinder, head, sector, etc. */
f_finish, /* do the I/O */
f_cleanup, /* cleanup before sending reply to user process */
f_geometry /* tell the geometry of the diskette */
};
/*===========================================================================*
* floppy_task *
*===========================================================================*/
PUBLIC void floppy_task()
{
/* Initialize the floppy structure. */
struct floppy *fp;
for (fp = &floppy[0]; fp < &floppy[NR_DRIVES]; fp++) {
fp->fl_curcyl = NO_CYL;
fp->fl_density = NO_DENS;
fp->fl_class = ~0;
}
put_irq_handler(FLOPPY_IRQ, f_handler);
enable_irq(FLOPPY_IRQ); /* ready for floppy interrupts */
driver_task(&f_dtab);
}
/*===========================================================================*
* f_prepare *
*===========================================================================*/
PRIVATE struct device *f_prepare(device)
int device;
{
/* Prepare for I/O on a device. */
/* Leftover jobs after an I/O error must be removed */
if (f_count > 0) defuse();
f_device = device;
f_drive = device & ~(DEV_TYPE_BITS | FORMAT_DEV_BIT);
if (f_drive < 0 || f_drive >= NR_DRIVES) return(NIL_DEV);
f_fp = &floppy[f_drive];
f_dv = &f_fp->fl_geom;
d = f_fp->fl_density;
f_sectors = nr_sectors[d];
f_must = TRUE; /* the first transfers must be done */
/* A partition? */
if ((device &= DEV_TYPE_BITS) >= MINOR_fd0a)
f_dv = &f_fp->fl_part[(device - MINOR_fd0a) >> DEV_TYPE_SHIFT];
return f_dv;
}
/*===========================================================================*
* f_name *
*===========================================================================*/
PRIVATE char *f_name()
{
/* Return a name for the current device. */
static char name[] = "fd3";
name[2] = '0' + f_drive;
return name;
}
/*===========================================================================*
* f_cleanup *
*===========================================================================*/
PRIVATE void f_cleanup()
{
/* Start watchdog timer to turn all motors off in a few seconds.
* There is a race here. An old watchdog might bite before the
* new delay is installed, and turn of the motors prematurely.
* This cannot be solved simply by resetting motor_goal after
* sending the message, because the new watchdog might bite
* before motor_goal is reset. Then the motors would stay on
* until after the next floppy access. This could be fixed with
* extra code (call the clock task twice in some cases). Or
* stop_motor() could be replaced by send_mess(), and send a
* STOP_MOTOR message to be accepted by the clock task. This
* would be slower but have the advantage that this comment could
* be deleted!
*
* Since it is not likely and not serious for an old watchdog to
* bite, accept that possibility for now. A full solution to the
* motor madness requires a lots of extra work anyway, such as
* a separate timer for each motor, and smaller delays for motors
* that have just been turned off or start faster than the spec.
* (is there a motor-ready bit?).
*/
motor_goal = 0;
clock_mess(MOTOR_OFF, stop_motor);
}
/*===========================================================================*
* f_schedule *
*===========================================================================*/
PRIVATE int f_schedule(proc_nr, iop)
int proc_nr; /* process doing the request */
struct iorequest_s *iop; /* pointer to read or write request */
{
int r, opcode, spanning;
unsigned long pos;
unsigned block; /* Seen any 32M floppies lately? */
unsigned nbytes, count, dma_count;
phys_bytes user_phys, dma_phys;
struct trans *tp, *tp0;
/* Ignore any alarm to turn motor off, now there is work to do. */
motor_goal = motor_status;
/* This many bytes to read/write */
nbytes = iop->io_nbytes;
if ((nbytes & SECTOR_MASK) != 0) return(iop->io_nbytes = EINVAL);
/* From/to this position on disk */
pos = iop->io_position;
if ((pos & SECTOR_MASK) != 0) return(iop->io_nbytes = EINVAL);
/* To/from this user address */
user_phys = numap(proc_nr, (vir_bytes) iop->io_buf, nbytes);
if (user_phys == 0) return(iop->io_nbytes = EINVAL);
/* Read, write or format? */
opcode = iop->io_request & ~OPTIONAL_IO;
if (f_device & FORMAT_DEV_BIT) {
if (opcode != DEV_WRITE) return(iop->io_nbytes = EIO);
if (nbytes != BLOCK_SIZE) return(iop->io_nbytes = EINVAL);
phys_copy(user_phys + SECTOR_SIZE, vir2phys(&fmt_param),
(phys_bytes) sizeof fmt_param);
/* Check that the number of sectors in the data is reasonable, to
* avoid division by 0. Leave checking of other data to the FDC.
*/
if (fmt_param.sectors_per_cylinder == 0)
return(iop->io_nbytes = EIO);
/* Only the first sector of the parameters now needed. */
iop->io_nbytes = nbytes = SECTOR_SIZE;
}
/* Which block on disk and how close to EOF? */
if (pos >= f_dv->dv_size) return(OK); /* At EOF */
if (pos + nbytes > f_dv->dv_size) nbytes = f_dv->dv_size - pos;
block = (f_dv->dv_base + pos) >> SECTOR_SHIFT;
spanning = FALSE; /* set if the block spans a track */
/* While there are "unscheduled" bytes in the request: */
do {
count = nbytes;
if (f_count > 0 && block >= f_nexttrack) {
/* The new job leaves the track, finish all gathered jobs */
if ((r = f_finish()) != OK) return(r);
f_must = spanning;
}
if (f_count == 0) {
/* This is the first job, compute cylinder and head */
f_opcode = opcode;
f_fp->fl_cylinder = block / (NR_HEADS * f_sectors);
f_fp->fl_hardcyl = f_fp->fl_cylinder * steps_per_cyl[d];
f_fp->fl_head = (block % (NR_HEADS * f_sectors)) / f_sectors;
/* See where the next track starts, one is trouble enough */
f_nexttrack = (f_fp->fl_cylinder * NR_HEADS
+ f_fp->fl_head + 1) * f_sectors;
}
/* Don't do track spanning I/O. */
if (block + (count >> SECTOR_SHIFT) > f_nexttrack)
count = (f_nexttrack - block) << SECTOR_SHIFT;
/* Memory chunk to DMA. */
dma_phys = user_phys;
dma_count = dma_bytes_left(dma_phys);
#if _WORD_SIZE > 2
/* The DMA chip uses a 24 bit address, so don't DMA above 16MB. */
if (dma_phys >= 0x1000000) dma_count = 0;
#endif
if (dma_count < count) {
/* Nearing a 64K boundary. */
if (dma_count >= SECTOR_SIZE) {
/* Can read a few sectors before hitting the
* boundary.
*/
count = dma_count & ~SECTOR_MASK;
} else {
/* Must use the special buffer for this. */
count = SECTOR_SIZE;
dma_phys = tmp_phys;
}
}
/* Store the I/O parameters in the ftrans slots for the sectors to
* read. The first slot specifies all sectors, the ones following
* it each specify one sector less. This allows I/O to be started
* in the middle of a block.
*/
tp = tp0 = &ftrans[block % f_sectors];
block += count >> SECTOR_SHIFT;
nbytes -= count;
f_count += count;
if (!(iop->io_request & OPTIONAL_IO)) f_must = TRUE;
do {
tp->tr_count = count;
tp->tr_iop = iop;
tp->tr_phys = user_phys;
tp->tr_dma = dma_phys;
tp++;
user_phys += SECTOR_SIZE;
dma_phys += SECTOR_SIZE;
count -= SECTOR_SIZE;
} while (count > 0);
spanning = TRUE; /* the rest of the block may span a track */
} while (nbytes > 0);
return(OK);
}
/*===========================================================================*
* f_finish *
*===========================================================================*/
PRIVATE int f_finish()
{
/* Carry out the I/O requests gathered in ftrans[]. */
struct floppy *fp = f_fp;
struct trans *tp;
int r, errors;
if (f_count == 0) return(OK); /* Spurious finish. */
/* If all the requests are optional then don't read from the next track.
* (There may be enough buffers to read the next track, but doing so is
* unwise. It's no good to be greedy on a slow device.)
*/
if (!f_must) {
defuse();
return(EAGAIN);
}
/* See if motor is running; if not, turn it on and wait */
start_motor();
/* Let read_id find out the next sector to read/write if it pays to do so.
* Note that no read_id is done while formatting if there is one format
* request per track as there should be.
*/
fp->fl_sector = f_count >= (6 * SECTOR_SIZE) ? 0 : BASE_SECTOR;
do {
/* This loop allows a failed operation to be repeated. */
errors = 0;
for (;;) {
/* First check to see if a reset is needed. */
if (need_reset) f_reset();
/* Set the stepping rate */
if (current_spec1 != spec1[d]) {
fdc_out(FDC_SPECIFY);
current_spec1 = spec1[d];
fdc_out(current_spec1);
fdc_out(SPEC2);
}
/* Set the data rate */
if (pc_at) out_byte(FDC_RATE, rate[d]);
/* If we are going to a new cylinder, perform a seek. */
r = seek(fp);
if (fp->fl_sector == NO_SECTOR) {
/* Don't retry read_id too often, we need tp soon */
if (errors > 0) fp->fl_sector = BASE_SECTOR;
/* Find out what the current sector is */
if (r == OK) r = read_id(fp);
}
/* Look for the next job in ftrans[] */
if (fp->fl_sector != NO_SECTOR) {
for (;;) {
if (fp->fl_sector >= BASE_SECTOR + f_sectors)
fp->fl_sector = BASE_SECTOR;
tp = &ftrans[fp->fl_sector - BASE_SECTOR];
if (tp->tr_count > 0) break;
fp->fl_sector++;
}
/* Do not transfer more than f_count bytes. */
if (tp->tr_count > f_count) tp->tr_count = f_count;
}
if (r == OK && tp->tr_dma == tmp_phys
&& f_opcode == DEV_WRITE) {
/* Copy the bad user buffer to the DMA buffer. */
phys_copy(tp->tr_phys, tp->tr_dma,
(phys_bytes) tp->tr_count);
}
/* Set up the DMA chip and perform the transfer. */
if (r == OK) {
dma_setup(tp);
r = f_transfer(fp, tp);
}
if (r == OK && tp->tr_dma == tmp_phys
&& f_opcode == DEV_READ) {
/* Copy the DMA buffer to the bad user buffer. */
phys_copy(tp->tr_dma, tp->tr_phys,
(phys_bytes) tp->tr_count);
}
if (r == OK) break; /* if successful, exit loop */
/* Don't retry if write protected or too many errors. */
if (err_no_retry(r) || ++errors == MAX_ERRORS) {
if (fp->fl_sector != 0) tp->tr_iop->io_nbytes = EIO;
return(EIO);
}
/* Recalibrate if halfway, but bail out if optional I/O. */
if (errors == MAX_ERRORS / 2) {
fp->fl_calibration = UNCALIBRATED;
if (tp->tr_iop->io_request & OPTIONAL_IO)
return(tp->tr_iop->io_nbytes = EIO);
}
}
f_count -= tp->tr_count;
tp->tr_iop->io_nbytes -= tp->tr_count;
} while (f_count > 0);
/* Defuse the leftover partial jobs. */
defuse();
return(OK);
}
/*===========================================================================*
* defuse *
*===========================================================================*/
PRIVATE void defuse()
{
/* Invalidate leftover requests in the transfer array. */
struct trans *tp;
for (tp = ftrans; tp < ftrans + MAX_SECTORS; tp++) tp->tr_count = 0;
f_count = 0;
}
/*===========================================================================*
* dma_setup *
*===========================================================================*/
PRIVATE void dma_setup(tp)
struct trans *tp; /* pointer to the transfer struct */
{
/* The IBM PC can perform DMA operations by using the DMA chip. To use it,
* the DMA (Direct Memory Access) chip is loaded with the 20-bit memory address
* to be read from or written to, the byte count minus 1, and a read or write
* opcode. This routine sets up the DMA chip. Note that the chip is not
* capable of doing a DMA across a 64K boundary (e.g., you can't read a
* 512-byte block starting at physical address 65520).
*/
/* Set up the DMA registers. (The comment on the reset is a bit strong,
* it probably only resets the floppy channel.)
*/
out_byte(DMA_INIT, DMA_RESET_VAL); /* reset the dma controller */
out_byte(DMA_FLIPFLOP, 0); /* write anything to reset it */
out_byte(DMA_MODE, f_opcode == DEV_WRITE ? DMA_WRITE : DMA_READ);
out_byte(DMA_ADDR, (int) tp->tr_dma >> 0);
out_byte(DMA_ADDR, (int) tp->tr_dma >> 8);
out_byte(DMA_TOP, (int) (tp->tr_dma >> 16));
out_byte(DMA_COUNT, (tp->tr_count - 1) >> 0);
out_byte(DMA_COUNT, (tp->tr_count - 1) >> 8);
out_byte(DMA_INIT, 2); /* some sort of enable */
}
/*===========================================================================*
* start_motor *
*===========================================================================*/
PRIVATE void start_motor()
{
/* Control of the floppy disk motors is a big pain. If a motor is off, you
* have to turn it on first, which takes 1/2 second. You can't leave it on
* all the time, since that would wear out the diskette. However, if you turn
* the motor off after each operation, the system performance will be awful.
* The compromise used here is to leave it on for a few seconds after each
* operation. If a new operation is started in that interval, it need not be
* turned on again. If no new operation is started, a timer goes off and the
* motor is turned off. I/O port DOR has bits to control each of 4 drives.
* The timer cannot go off while we are changing with the bits, since the
* clock task cannot run while another (this) task is active, so there is no
* need to lock().
*/
int motor_bit, running;
message mess;
motor_bit = 1 << f_drive; /* bit mask for this drive */
running = motor_status & motor_bit; /* nonzero if this motor is running */
motor_goal = motor_status | motor_bit;/* want this drive running too */
out_byte(DOR, (motor_goal << MOTOR_SHIFT) | ENABLE_INT | f_drive);
motor_status = motor_goal;
/* If the motor was already running, we don't have to wait for it. */
if (running) return; /* motor was already running */
clock_mess(mtr_setup[d], send_mess); /* motor was not running */
receive(CLOCK, &mess); /* wait for clock interrupt */
}
/*===========================================================================*
* stop_motor *
*===========================================================================*/
PRIVATE void stop_motor()
{
/* This routine is called by the clock interrupt after several seconds have
* elapsed with no floppy disk activity. It checks to see if any drives are
* supposed to be turned off, and if so, turns them off.
*/
if (motor_goal != motor_status) {
out_byte(DOR, (motor_goal << MOTOR_SHIFT) | ENABLE_INT);
motor_status = motor_goal;
}
}
/*===========================================================================*
* floppy_stop *
*===========================================================================*/
PUBLIC void floppy_stop()
{
/* Stop all activity. */
motor_goal = 0;
stop_motor();
}
/*===========================================================================*
* seek *
*===========================================================================*/
PRIVATE int seek(fp)
struct floppy *fp; /* pointer to the drive struct */
{
/* Issue a SEEK command on the indicated drive unless the arm is already
* positioned on the correct cylinder.
*/
int r;
message mess;
/* Are we already on the correct cylinder? */
if (fp->fl_calibration == UNCALIBRATED)
if (recalibrate(fp) != OK) return(ERR_SEEK);
if (fp->fl_curcyl == fp->fl_hardcyl) return(OK);
/* No. Wrong cylinder. Issue a SEEK and wait for interrupt. */
fdc_out(FDC_SEEK);
fdc_out((fp->fl_head << 2) | f_drive);
fdc_out(fp->fl_hardcyl);
if (need_reset) return(ERR_SEEK); /* if controller is sick, abort seek */
if (f_intr_wait() != OK) return(ERR_TIMEOUT);
/* Interrupt has been received. Check drive status. */
fdc_out(FDC_SENSE); /* probe FDC to make it return status */
r = fdc_results(); /* get controller status bytes */
if (r != OK || (f_results[ST0] & ST0_BITS) != SEEK_ST0
|| f_results[ST1] != fp->fl_hardcyl) {
/* seek failed, may need a recalibrate */
return(ERR_SEEK);
}
/* give head time to settle on a format, no retrying here! */
if (f_device & FORMAT_DEV_BIT) {
clock_mess(2, send_mess);
receive(CLOCK, &mess);
}
fp->fl_curcyl = fp->fl_hardcyl;
return(OK);
}
/*===========================================================================*
* f_transfer *
*===========================================================================*/
PRIVATE int f_transfer(fp, tp)
struct floppy *fp; /* pointer to the drive struct */
struct trans *tp; /* pointer to the transfer struct */
{
/* The drive is now on the proper cylinder. Read, write or format 1 block. */
int r, s;
/* Never attempt a transfer if the drive is uncalibrated or motor is off. */
if (fp->fl_calibration == UNCALIBRATED) return(ERR_TRANSFER);
if ((motor_status & (1 << f_drive)) == 0) return(ERR_TRANSFER);
/* The command is issued by outputting several bytes to the controller chip.
*/
if (f_device & FORMAT_DEV_BIT) {
fdc_out(FDC_FORMAT);
fdc_out((fp->fl_head << 2) | f_drive);
fdc_out(fmt_param.sector_size_code);
fdc_out(fmt_param.sectors_per_cylinder);
fdc_out(fmt_param.gap_length_for_format);
fdc_out(fmt_param.fill_byte_for_format);
} else {
fdc_out(f_opcode == DEV_WRITE ? FDC_WRITE : FDC_READ);
fdc_out((fp->fl_head << 2) | f_drive);
fdc_out(fp->fl_cylinder);
fdc_out(fp->fl_head);
fdc_out(fp->fl_sector);
fdc_out(SECTOR_SIZE_CODE);
fdc_out(f_sectors);
fdc_out(gap[d]); /* sector gap */
fdc_out(DTL); /* data length */
}
/* Block, waiting for disk interrupt. */
if (need_reset) return(ERR_TRANSFER); /* if controller is sick, abort op */
if (f_intr_wait() != OK) return(ERR_TIMEOUT);
/* Get controller status and check for errors. */
r = fdc_results();
if (r != OK) return(r);
if (f_results[ST1] & WRITE_PROTECT) {
printf("%s: diskette is write protected.\n", f_name());
return(ERR_WR_PROTECT);
}
if ((f_results[ST0] & ST0_BITS) != TRANS_ST0) return(ERR_TRANSFER);
if (f_results[ST1] | f_results[ST2]) return(ERR_TRANSFER);
if (f_device & FORMAT_DEV_BIT) return(OK);
/* Compare actual numbers of sectors transferred with expected number. */
s = (f_results[ST_CYL] - fp->fl_cylinder) * NR_HEADS * f_sectors;
s += (f_results[ST_HEAD] - fp->fl_head) * f_sectors;
s += (f_results[ST_SEC] - fp->fl_sector);
if ((s << SECTOR_SHIFT) != tp->tr_count) return(ERR_TRANSFER);
/* This sector is next for I/O: */
fp->fl_sector = f_results[ST_SEC];
return(OK);
}
/*==========================================================================*
* fdc_results *
*==========================================================================*/
PRIVATE int fdc_results()
{
/* Extract results from the controller after an operation, then allow floppy
* interrupts again.
*/
int result_nr, status;
struct milli_state ms;
/* Extract bytes from FDC until it says it has no more. The loop is
* really an outer loop on result_nr and an inner loop on status.
*/
result_nr = 0;
milli_start(&ms);
do {
/* Reading one byte is almost a mirror of fdc_out() - the DIRECTION
* bit must be set instead of clear, but the CTL_BUSY bit destroys
* the perfection of the mirror.
*/
status = in_byte(FDC_STATUS) & (MASTER | DIRECTION | CTL_BUSY);
if (status == (MASTER | DIRECTION | CTL_BUSY)) {
if (result_nr >= MAX_RESULTS) break; /* too many results */
f_results[result_nr++] = in_byte(FDC_DATA);
continue;
}
if (status == MASTER) { /* all read */
enable_irq(FLOPPY_IRQ);
return(OK); /* only good exit */
}
} while (milli_elapsed(&ms) < TIMEOUT);
need_reset = TRUE; /* controller chip must be reset */
enable_irq(FLOPPY_IRQ);
return(ERR_STATUS);
}
/*==========================================================================*
* f_handler *
*==========================================================================*/
PRIVATE int f_handler(irq)
int irq;
{
/* FDC interrupt, send message to floppy task. */
interrupt(FLOPPY);
return 0;
}
/*===========================================================================*
* fdc_out *
*===========================================================================*/
PRIVATE void fdc_out(val)
int val; /* write this byte to floppy disk controller */
{
/* Output a byte to the controller. This is not entirely trivial, since you
* can only write to it when it is listening, and it decides when to listen.
* If the controller refuses to listen, the FDC chip is given a hard reset.
*/
struct milli_state ms;
if (need_reset) return; /* if controller is not listening, return */
/* It may take several tries to get the FDC to accept a command. */
milli_start(&ms);
while ((in_byte(FDC_STATUS) & (MASTER | DIRECTION)) != (MASTER | 0)) {
if (milli_elapsed(&ms) >= TIMEOUT) {
/* Controller is not listening. Hit it over the head. */
need_reset = TRUE;
return;
}
}
out_byte(FDC_DATA, val);
}
/*===========================================================================*
* recalibrate *
*===========================================================================*/
PRIVATE int recalibrate(fp)
struct floppy *fp; /* pointer tot he drive struct */
{
/* The floppy disk controller has no way of determining its absolute arm
* position (cylinder). Instead, it steps the arm a cylinder at a time and
* keeps track of where it thinks it is (in software). However, after a
* SEEK, the hardware reads information from the diskette telling where the
* arm actually is. If the arm is in the wrong place, a recalibration is done,
* which forces the arm to cylinder 0. This way the controller can get back
* into sync with reality.
*/
int r;
/* Issue the RECALIBRATE command and wait for the interrupt. */
start_motor(); /* can't recalibrate with motor off */
fdc_out(FDC_RECALIBRATE); /* tell drive to recalibrate itself */
fdc_out(f_drive); /* specify drive */
if (need_reset) return(ERR_SEEK); /* don't wait if controller is sick */
if (f_intr_wait() != OK) return(ERR_TIMEOUT);
/* Determine if the recalibration succeeded. */
fdc_out(FDC_SENSE); /* issue SENSE command to request results */
r = fdc_results(); /* get results of the FDC_RECALIBRATE command*/
fp->fl_curcyl = NO_CYL; /* force a SEEK next time */
if (r != OK || /* controller would not respond */
(f_results[ST0] & ST0_BITS) != SEEK_ST0 || f_results[ST_PCN] != 0) {
/* Recalibration failed. FDC must be reset. */
need_reset = TRUE;
return(ERR_RECALIBRATE);
} else {
/* Recalibration succeeded. */
fp->fl_calibration = CALIBRATED;
return(OK);
}
}
/*===========================================================================*
* f_reset *
*===========================================================================*/
PRIVATE void f_reset()
{
/* Issue a reset to the controller. This is done after any catastrophe,
* like the controller refusing to respond.
*/
int i;
message mess;
/* Disable interrupts and strobe reset bit low. */
need_reset = FALSE;
/* It is not clear why the next lock is needed. Writing 0 to DOR causes
* interrupt, while the PC documentation says turning bit 8 off disables
* interrupts. Without the lock:
* 1) the interrupt handler sets the floppy mask bit in the 8259.
* 2) writing ENABLE_INT to DOR causes the FDC to assert the interrupt
* line again, but the mask stops the cpu being interrupted.
* 3) the sense interrupt clears the interrupt (not clear which one).
* and for some reason the reset does not work.
*/
lock();
motor_status = 0;
motor_goal = 0;
out_byte(DOR, 0); /* strobe reset bit low */
out_byte(DOR, ENABLE_INT); /* strobe it high again */
unlock();
receive(HARDWARE, &mess); /* collect the RESET interrupt */
/* The controller supports 4 drives and returns a result for each of them.
* Collect all the results now. The old version only collected the first
* result. This happens to work for 2 drives, but it doesn't work for 3
* or more drives, at least with only drives 0 and 2 actually connected
* (the controller generates an extra interrupt for the middle drive when
* drive 2 is accessed and the driver panics).
*
* It would be better to keep collecting results until there are no more.
* For this, fdc_results needs to return the number of results (instead
* of OK) when it succeeds.
*/
for (i = 0; i < 4; i++) {
fdc_out(FDC_SENSE); /* probe FDC to make it return status */
(void) fdc_results(); /* flush controller */
}
for (i = 0; i < NR_DRIVES; i++) /* clear each drive */
floppy[i].fl_calibration = UNCALIBRATED;
/* The current timing parameters must be specified again. */
current_spec1 = 0;
}
/*===========================================================================*
* send_mess *
*===========================================================================*/
PRIVATE void send_mess()
{
/* This routine is called when the clock task has timed out on motor startup.*/
message mess;
send(FLOPPY, &mess);
}
/*===========================================================================*
* f_intr_wait *
*===========================================================================*/
PRIVATE int f_intr_wait()
{
/* Wait for an interrupt, but not forever. The FDC may have all the time of
* the world, but we humans do not.
*/
message mess;
f_busy = BSY_IO;
clock_mess(WAKEUP, f_timeout);
receive(HARDWARE, &mess);
if (f_busy == BSY_WAKEN) {
/* No interrupt from the FDC, this means that there is probably no
* floppy in the drive. Get the FDC down to earth and return error.
*/
f_reset();
return(ERR_TIMEOUT);
}
f_busy = BSY_IDLE;
return(OK);
}
/*===========================================================================*
* f_timeout *
*===========================================================================*/
PRIVATE void f_timeout()
{
/* When it takes too long for the FDC to get an interrupt (no floppy in the
* drive), this routine is called. It sets a flag and fakes a hardware
* interrupt.
*/
if (f_busy == BSY_IO) {
f_busy = BSY_WAKEN;
interrupt(FLOPPY);
}
}
/*==========================================================================*
* read_id *
*==========================================================================*/
PRIVATE int read_id(fp)
struct floppy *fp; /* pointer to the drive struct */
{
/* Determine current cylinder and sector. */
int result;
/* Never attempt a read id if the drive is uncalibrated or motor is off. */
if (fp->fl_calibration == UNCALIBRATED) return(ERR_READ_ID);
if ((motor_status & (1 << f_drive)) == 0) return(ERR_READ_ID);
/* The command is issued by outputting 2 bytes to the controller chip. */
fdc_out(FDC_READ_ID); /* issue the read id command */
fdc_out( (f_fp->fl_head << 2) | f_drive);
/* Block, waiting for disk interrupt. */
if (need_reset) return(ERR_READ_ID); /* if controller is sick, abort op */
if (f_intr_wait() != OK) return(ERR_TIMEOUT);
/* Get controller status and check for errors. */
result = fdc_results();
if (result != OK) return(result);
if ((f_results[ST0] & ST0_BITS) != TRANS_ST0) return(ERR_READ_ID);
if (f_results[ST1] | f_results[ST2]) return(ERR_READ_ID);
/* The next sector is next for I/O: */
f_fp->fl_sector = f_results[ST_SEC] + 1;
return(OK);
}
/*==========================================================================*
* f_do_open *
*==========================================================================*/
PRIVATE int f_do_open(dp, m_ptr)
struct driver *dp;
message *m_ptr; /* pointer to open message */
{
/* Handle an open on a floppy. Determine diskette type if need be. */
int dtype;
struct test_order *top;
/* Decode the message parameters. */
if (f_prepare(m_ptr->DEVICE) == NIL_DEV) return(ENXIO);
dtype = f_device & DEV_TYPE_BITS; /* get density from minor dev */
if (dtype >= MINOR_fd0a) dtype = 0;
if (dtype != 0) {
/* All types except 0 indicate a specific drive/medium combination.*/
dtype = (dtype >> DEV_TYPE_SHIFT) - 1;
if (dtype >= NT) return(ENXIO);
f_fp->fl_density = dtype;
f_fp->fl_geom.dv_size = (long) nr_blocks[dtype] << SECTOR_SHIFT;
return(OK);
}
if (f_device & FORMAT_DEV_BIT) return(EIO); /* Can't format /dev/fdx */
/* No need to test if the motor is still running. */
if (motor_status & (1 << f_drive)) return(OK);
/* The device opened is /dev/fdx. Experimentally determine drive/medium.
* First check fl_density. If it is not NO_DENS, the drive has been used
* before and the value of fl_density tells what was found last time. Try
* that first.
*/
if (f_fp->fl_density != NO_DENS && test_read(f_fp->fl_density) == OK)
return(OK);
/* Either drive type is unknown or a different diskette is now present.
* Use test_order to try them one by one.
*/
for (top = &test_order[0]; top < &test_order[NT-1]; top++) {
dtype = top->t_density;
/* Skip densities that have been proven to be impossible */
if (!(f_fp->fl_class & (1 << dtype))) continue;
if (test_read(dtype) == OK) {
/* The test succeeded, use this knowledge to limit the
* drive class to match the density just read.
*/
f_fp->fl_class &= top->t_class;
return(OK);
}
/* Test failed, wrong density or did it time out? */
if (f_busy == BSY_WAKEN) break;
}
f_fp->fl_density = NO_DENS;
return(EIO); /* nothing worked */
}
/*==========================================================================*
* test_read *
*==========================================================================*/
PRIVATE int test_read(density)
int density;
{
/* Try to read the highest numbered sector on cylinder 2. Not all floppy
* types have as many sectors per track, and trying cylinder 2 finds the
* ones that need double stepping.
*/
message m;
int r, device;
f_fp->fl_density = density;
device = ((density + 1) << DEV_TYPE_SHIFT) + f_drive;
f_fp->fl_geom.dv_size = (long) nr_blocks[density] << SECTOR_SHIFT;
m.m_type = DEV_READ;
m.DEVICE = device;
m.PROC_NR = FLOPPY;
m.COUNT = SECTOR_SIZE;
m.POSITION = (long) test_sector[density] * SECTOR_SIZE;
m.ADDRESS = (char *) tmp_buf;
r = do_rdwt(&f_dtab, &m);
if (r != SECTOR_SIZE) return(EIO);
partition(&f_dtab, f_drive, P_FLOPPY);
return(OK);
}
/*============================================================================*
* f_geometry *
*============================================================================*/
PRIVATE void f_geometry(entry)
struct partition *entry;
{
entry->cylinders = nr_blocks[d] / (NR_HEADS * f_sectors);
entry->heads = NR_HEADS;
entry->sectors = f_sectors;
}