Minix1.5/kernel/floppy.c

Compare this file to the similar file:
Show the results in this format:

/* This file contains a driver for a Floppy Disk Controller (FDC) using the
 * NEC PD765 chip.
 *
 * The driver supports the following operations (using message format m2):
 *
 *    m_type      DEVICE    PROC_NR     COUNT    POSITION  ADDRESS
 * ----------------------------------------------------------------
 * |  DISK_READ | device  | proc nr |  bytes  |  offset | buf ptr |
 * |------------+---------+---------+---------+---------+---------|
 * | DISK_WRITE | device  | proc nr |  bytes  |  offset | buf ptr |
 * ----------------------------------------------------------------
 * |SCATTERED_IO| device  | proc nr | requests|         | iov ptr |
 * ----------------------------------------------------------------
 *
 * The file contains one entry point:
 *
 *   floppy_task:	main entry when system is brought up
 *
 *  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
 *		1990 by Bruce Evans: formatting
 */

#include "kernel.h"
#include <minix/callnr.h>
#include <minix/com.h>
#include <minix/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_M2         0x00C	/* DMA status port */
#define DMA_M1         0x00B	/* DMA status 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_MASK      0xF0	/* these 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 SECTOR_SIZE      512	/* physical sector size in bytes */
#define HC_SIZE         2880	/* # sectors on largest legal disk (1.44MB) */
#define NR_HEADS        0x02	/* two heads (i.e., two tracks/cylinder) */
#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 */

/* 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_RECALIBRATE   -4	/* recalibrate didn't work properly */
#define ERR_WR_PROTECT    -5	/* diskette is write protected */
#define ERR_DRIVE         -6	/* something wrong with a drive */
#define ERR_READ_ID       -7	/* bad read id */

/* 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 MOTOR_RUNNING   0xFF	/* message type for clock interrupt */
#define MAX_ERRORS         3	/* 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 MAX_FDC_RETRY    100	/* max # times to try to output to FDC */
#define NT                 7	/* number of diskette/drive combinations */
#define AUTOMATIC	0x3F	/* bit map allowing both 3.5 and 5.25 disks */
				/* except for drive type 6, because that is */
				/* hard to distinguish from drive type 1 */
#define THREE_INCH	0x48	/* bit map allowing only 3.5 inch diskettes */
#define FIVE_INCH	0x37	/* bit map allowing only 5.25 inch diskettes */

/* Variables. */
PRIVATE struct floppy {		/* main drive struct, one entry per drive */
  int fl_opcode;		/* FDC_READ, FDC_WRITE or FDC_FORMAT */
  int fl_curcyl;		/* current cylinder */
  int fl_procnr;		/* which proc wanted this operation? */
  int fl_drive;			/* drive number addressed */
  int fl_cylinder;		/* cylinder number addressed */
  int fl_sector;		/* sector addressed */
  int fl_head;			/* head number addressed */
  int fl_count;			/* byte count */
  vir_bytes fl_address;		/* user virtual address */
  char fl_results[MAX_RESULTS];	/* the controller can give lots of output */
  char fl_calibration;		/* CALIBRATED or UNCALIBRATED */
  char fl_density;		/* 0 = 360K/360K; 1 = 360K/1.2M; etc. */
  char fl_auto_type;		/* nonzero to allow search for working type */
  struct disk_parameter_s fl_param;	/* parameters for format */
} floppy[NR_DRIVES];

#define UNCALIBRATED       0	/* drive needs to be calibrated at next use */
#define CALIBRATED         1	/* no calibration needed */

PRIVATE int motor_status;	/* current motor status is in 4 high bits */
PRIVATE int motor_goal;		/* desired motor status is in 4 high bits */
PRIVATE int prev_motor;		/* which motor was started last */
PRIVATE int need_reset;		/* set to 1 when controller must be reset */
PRIVATE int d;			/* diskette/drive combination */
PRIVATE int current_spec1;	/* latest spec1 sent to the controller */

PRIVATE message mess;		/* message buffer for in and out */

PRIVATE char len[] = {-1,0,1,-1,2,-1,-1,3,-1,-1,-1,-1,-1,-1,-1,4};
PRIVATE char base_sector = 1;	/* physical sectors form range starting here */

/* 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 int gap[NT] =
	{0x2A, 0x1B, 0x2A, 0x2A, 0x23, 0x23, 0x1B}; /* gap size */
PRIVATE int rate[NT] = 
	{0x02, 0x00, 0x02, 0x02, 0x01, 0x01, 0x00}; /* 2=250,1=300,0=500 kbps*/
PRIVATE int 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 int steps_per_cyl[NT] = 
	{1,    1,    2,    1,    2,    1,     1};   /* 2 = dbl step */
PRIVATE int 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, 0xAF}; /* step rate, etc. */

/* This driver hunts around for the proper density by simply trying them all
 * until it finds one that works.  By defining DEFAULT_CLASS, one can reduce 
 * the searching to only 5.25 inch or only 3.5 inch types.  The array
 * drive_class contains a bit map for each drive, telling which of the NT
 * combinations defined above should be tried.
 */
PRIVATE char drive_class[NR_DRIVES] = {
#if (DEFAULT_CLASS == 3)
	THREE_INCH, THREE_INCH	/* drive 0 = 3.5 inch, drive 1 also */
#endif
#if (DEFAULT_CLASS == 5)
	FIVE_INCH, FIVE_INCH	/* drive 0 = 5.25 inch, drive 1 also */
#endif
#if (DEFAULT_CLASS != 3 && DEFAULT_CLASS != 5)
	AUTOMATIC, AUTOMATIC	/* both drives can handle both types */
#endif
};

FORWARD void clock_mess();
FORWARD void dma_setup();
FORWARD int do1_rdwt();
FORWARD int do_rdwt();
FORWARD int f_do_vrdwt();
FORWARD void f_reset();
FORWARD void fdc_out();
FORWARD int fdc_results();
FORWARD int read_id();
FORWARD int recalibrate();
FORWARD int seek();
FORWARD void send_mess();
FORWARD void start_motor();
FORWARD void stop_motor();
FORWARD int transfer();

/*===========================================================================*
 *				floppy_task				     * 
 *===========================================================================*/
PUBLIC void floppy_task()
{
/* Main program of the floppy disk driver task. */

  int r, caller, proc_nr;

  cim_floppy();			/* ready for floppy interrupts */

  /* Here is the main loop of the disk task.  It waits for a message, carries
   * it out, and sends a reply.
   */
  while (TRUE) {
	/* First wait for a request to read or write a disk block. */
	receive(ANY, &mess);	/* get a request to do some work */
	if (mess.m_source < 0)
		panic("disk task got message from ", mess.m_source);

	/* Ignore any alarm to turn motor off, now there is work to do. */
	motor_goal = motor_status;

	caller = mess.m_source;
	proc_nr = mess.PROC_NR;

	/* Now carry out the work. */
	switch(mess.m_type) {
	    case DISK_READ:
	    case DISK_WRITE:	r = do_rdwt(&mess, FALSE);	break;
	    case SCATTERED_IO:	r = f_do_vrdwt(&mess);	break;
	    default:		r = EINVAL;		break;
	}

	/* Start watch_dog timer to turn all motors off in a few seconds. */
	motor_goal = ENABLE_INT;
	clock_mess(MOTOR_OFF, stop_motor);

	/* Finally, prepare and send the reply message. */
	mess.m_type = TASK_REPLY;	
	mess.REP_PROC_NR = proc_nr;
	mess.REP_STATUS = r;	/* # of bytes transferred or error code */
	send(caller, &mess);	/* send reply to caller */
  }
}


/*===========================================================================*
 *				do_rdwt					     * 
 *===========================================================================*/
PRIVATE int do_rdwt(m_ptr, dont_retry)
message *m_ptr;			/* pointer to read or write message */
int dont_retry;			/* nonzero to skip retry after error */
{
/* Do a single read or write request. */

  register struct floppy *fp;
  int r, sectors, drive, errors;
  off_t block;
  unsigned dtype;
  phys_bytes param_phys;
  phys_bytes user_param_phys;

  /* Decode the message parameters. */
  drive = m_ptr->DEVICE & ~(DEV_TYPE_BITS | FORMAT_DEV_BIT);
  if (drive < 0 || drive >= NR_DRIVES) return(EIO);
  fp = &floppy[drive];		/* 'fp' points to entry for this drive */
  fp->fl_drive = drive;		/* save drive number explicitly */
  fp->fl_opcode = (m_ptr->m_type == DISK_READ ? FDC_READ : FDC_WRITE);
  if (m_ptr->DEVICE & FORMAT_DEV_BIT) {
	if (fp->fl_opcode == FDC_READ) return(EIO);
	fp->fl_opcode = FDC_FORMAT;
	param_phys = umap(proc_ptr, D, (vir_bytes) &fp->fl_param,
			  (vir_bytes) sizeof fp->fl_param);
	user_param_phys = numap(m_ptr->PROC_NR,
				(vir_bytes) (m_ptr->ADDRESS + BLOCK_SIZE / 2),
				(vir_bytes) sizeof fp->fl_param);
	phys_copy(user_param_phys, param_phys,(phys_bytes)sizeof fp->fl_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 (fp->fl_param.sectors_per_cylinder == 0) return(EIO);
  }
  dtype = m_ptr->DEVICE & DEV_TYPE_BITS;
  if (dtype != 0) {
	dtype = (dtype >> DEV_TYPE_SHIFT) - 1;
	if (dtype >= NT) return(EIO);
	fp->fl_density = dtype;
	fp->fl_auto_type = FALSE;
  } else
	fp->fl_auto_type = TRUE;

  if (m_ptr->POSITION % BLOCK_SIZE != 0) return(EINVAL);
  block = m_ptr->POSITION/SECTOR_SIZE;
  if (block >= HC_SIZE) return(0);	/* sector is beyond end of all disks */

  d = fp->fl_density;		/* diskette/drive combination */
  if (fp->fl_auto_type) {
	/* Check bit map to skip illegal densities. */
	while ((drive_class[drive] & (1 << d)) == 0) d = (d + 1) % NT;
  }

  /* Store the message parameters in the fp->fl array. */
  fp->fl_density=d;
  sectors = (m_ptr->DEVICE & FORMAT_DEV_BIT ?
	     fp->fl_param.sectors_per_cylinder : nr_sectors[d]);
  fp->fl_cylinder = (int) (block / (NR_HEADS * sectors));
  fp->fl_sector = base_sector + (int) (block % sectors);
  fp->fl_head = (int) (block % (NR_HEADS * sectors)) / sectors;
  fp->fl_count = m_ptr->COUNT;
  fp->fl_address = (vir_bytes) m_ptr->ADDRESS;
  fp->fl_procnr = m_ptr->PROC_NR;
  if (fp->fl_count != BLOCK_SIZE) return(EINVAL);

  errors = 0;

  /* This loop allows a failed operation to be repeated.  It is really a
   * nested loop allowing MAX_ERRORS errors for each of NT drive types.
   */
  while (errors <= MAX_ERRORS * NT) {
	if (++errors % MAX_ERRORS == 0) {

#if 0 /* this works well except for programs like mkfs which seek to end 1st*/
		/* A lot of errors probably means that we are trying the
		 * wrong drive type.  Try another one if the cylinder is <= 1.
		 * Otherwise, assume the disk is unchanged and give up.
		 */
		if (fp->fl_cylinder > 1)
			return(block >= nr_blocks[d] ? 0 : EIO);
#else
		/* A lot of errors probably means that we are trying the
		 * wrong drive type.  Try another one.
		 */
#endif
		if (!fp->fl_auto_type) return(EIO);
                d++;

		/* Check bit map to skip illegal densities. */
		while ((drive_class[drive] & (1 << d)) == 0) d = (d + 1) % NT;
 		fp->fl_density = d;

		sectors = nr_sectors[d];
		fp->fl_cylinder = (int) (block / (NR_HEADS * sectors));
		fp->fl_sector = base_sector + (int) (block % sectors);
		fp->fl_head = (int)(block%(NR_HEADS*sectors)) / sectors;
 		need_reset = 1;
	}
  	if (block >= nr_blocks[d]) continue;

	/* 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);
		fdc_out(current_spec1=spec1[d]);
		fdc_out(SPEC2);
	}

 	/* Set the data rate */
	if (pc_at) out_byte(FDC_RATE, rate[d]);

	/* Now set up the DMA chip. */
	dma_setup(fp);

	/* See if motor is running; if not, turn it on and wait */
	start_motor(fp);

	/* If we are going to a new cylinder, perform a seek. */
	r = seek(fp);

	/* Perform the transfer. */
	if (r == OK) r = transfer(fp);
	if (r == OK) break;	/* if successful, exit loop */
	if (dont_retry) break;	/* retries not wanted */
	if (r == ERR_WR_PROTECT) break;	/* retries won't help */
  }
  return(r == OK ? fp->fl_count : EIO);
}


/*===========================================================================*
 *				dma_setup				     * 
 *===========================================================================*/
PRIVATE void dma_setup(fp)
struct floppy *fp;		/* pointer to the drive 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).
 */

  int mode, low_addr, high_addr, top_addr, low_ct, high_ct, top_end;
  vir_bytes vir, ct;
  phys_bytes user_phys;

  mode = (fp->fl_opcode == FDC_READ ? DMA_READ : DMA_WRITE);
  vir = (vir_bytes) fp->fl_address;
  ct = (vir_bytes) fp->fl_count;
  user_phys = numap(fp->fl_procnr, vir, ct);
  low_addr  = (int) (user_phys >>  0) & BYTE;
  high_addr = (int) (user_phys >>  8) & BYTE;
  top_addr  = (int) (user_phys >> 16) & BYTE;
  low_ct  = (int) ( (ct - 1) >> 0) & BYTE;
  high_ct = (int) ( (ct - 1) >> 8) & BYTE;

  /* Check to see if the transfer will require the DMA address counter to
   * go from one 64K segment to another.  If so, do not even start it, since
   * the hardware does not carry from bit 15 to bit 16 of the DMA address.
   * Also check for bad buffer address.  These errors mean FS contains a bug.
   */
  if (user_phys == 0) panic("FS gave floppy disk driver bad addr", (int) vir);
  top_end = (int) (((user_phys + ct - 1) >> 16) & BYTE);
  if (top_end != top_addr)panic("Trying to DMA across 64K boundary", top_addr);

  /* Now set up the DMA registers. */
  out_byte(DMA_INIT, DMA_RESET_VAL);        /* reset the dma controller */
  out_byte(DMA_M2, mode);	/* set the DMA mode */
  out_byte(DMA_M1, mode);	/* set it again */
  out_byte(DMA_ADDR, low_addr);	/* output low-order 8 bits */
  out_byte(DMA_ADDR, high_addr);/* output next 8 bits */
  out_byte(DMA_TOP, top_addr);	/* output highest 4 bits */
  out_byte(DMA_COUNT, low_ct);	/* output low 8 bits of count - 1 */
  out_byte(DMA_COUNT, high_ct);	/* output high 8 bits of count - 1 */
  out_byte(DMA_INIT, 2);	/* initialize DMA */
}


/*===========================================================================*
 *				start_motor				     * 
 *===========================================================================*/
PRIVATE void start_motor(fp)
struct floppy *fp;		/* pointer to the drive struct */
{
/* 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;

  motor_bit = 1 << (fp->fl_drive + 4);	/* bit mask for this drive */
  running = motor_status & motor_bit;	/* nonzero if this motor is running */
  motor_goal = motor_bit | ENABLE_INT | fp->fl_drive;
  if (motor_status & prev_motor) motor_goal |= prev_motor;
  out_byte(DOR, motor_goal);
  motor_status = motor_goal;
  prev_motor = motor_bit;	/* record motor started for next time */

  /* 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_MASK) != (motor_status & MOTOR_MASK) ) {
	out_byte(DOR, motor_goal);
	motor_status = motor_goal;
  }
}


/*===========================================================================*
 *				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;

  /* 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_cylinder) return(OK);

  /* No.  Wrong cylinder.  Issue a SEEK and wait for interrupt. */
  fdc_out(FDC_SEEK);		/* start issuing the SEEK command */
  fdc_out( (fp->fl_head << 2) | fp->fl_drive);
  fdc_out(fp->fl_cylinder * steps_per_cyl[d]);
  if (need_reset) return(ERR_SEEK);	/* if controller is sick, abort seek */
  receive(HARDWARE, &mess);

  /* Interrupt has been received.  Check drive status. */
  fdc_out(FDC_SENSE);		/* probe FDC to make it return status */
  r = fdc_results(fp);		/* get controller status bytes */
  if ( (fp->fl_results[ST0] & ST0_BITS) != SEEK_ST0) r = ERR_SEEK;
  if (fp->fl_results[ST1] != fp->fl_cylinder * steps_per_cyl[d]) r = ERR_SEEK;
  if (r != OK) 
	if (recalibrate(fp) != OK) return(ERR_SEEK);
  fp->fl_curcyl = (r == OK ? fp->fl_cylinder : -1);
  if (r == OK && ((d == 6) || (d == 3))) {/* give head time to settle on 3.5 */
	clock_mess(2, send_mess);
	receive(CLOCK, &mess);
  }
  return(r);
}


/*===========================================================================*
 *				transfer				     * 
 *===========================================================================*/
PRIVATE int transfer(fp)
register struct floppy *fp;	/* pointer to the drive 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>>(fp->fl_drive+4)) & 1) == 0) return(ERR_TRANSFER);

  /* The command is issued by outputting 9 bytes to the controller chip. */
  fdc_out(fp->fl_opcode);	/* issue the read, write or format command */
  fdc_out( (fp->fl_head << 2) | fp->fl_drive);
  if (fp->fl_opcode == FDC_FORMAT) {
	fdc_out(fp->fl_param.sector_size_code);
	fdc_out(fp->fl_param.sectors_per_cylinder);
	fdc_out(fp->fl_param.gap_length_for_format);
	fdc_out(fp->fl_param.fill_byte_for_format);
  } else {
	fdc_out(fp->fl_cylinder);
	fdc_out(fp->fl_head);
	fdc_out(fp->fl_sector);
	fdc_out( (int) len[SECTOR_SIZE/DIVISOR]);	/* sector size code */
	fdc_out(nr_sectors[d]);
	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 */
  receive(HARDWARE, &mess);

  /* Get controller status and check for errors. */
  r = fdc_results(fp);
  if (r != OK) return(r);
  if ( (fp->fl_results[ST1] & BAD_SECTOR) || (fp->fl_results[ST2] & BAD_CYL) )
	fp->fl_calibration = UNCALIBRATED;
  if (fp->fl_results[ST1] & WRITE_PROTECT) {
	printf("Diskette in drive %d is write protected.\n", fp->fl_drive);
	return(ERR_WR_PROTECT);
  }
  if ((fp->fl_results[ST0] & ST0_BITS) != TRANS_ST0) return(ERR_TRANSFER);
  if (fp->fl_results[ST1] | fp->fl_results[ST2]) return(ERR_TRANSFER);

  if (fp->fl_opcode == FDC_FORMAT) return(OK);

  /* Compare actual numbers of sectors transferred with expected number. */
  s =  (fp->fl_results[ST_CYL] - fp->fl_cylinder) * NR_HEADS * nr_sectors[d];
  s += (fp->fl_results[ST_HEAD] - fp->fl_head) * nr_sectors[d];
  s += (fp->fl_results[ST_SEC] - fp->fl_sector);
  if (s * SECTOR_SIZE != fp->fl_count) return(ERR_TRANSFER);
  return(OK);
}


/*==========================================================================*
 *				fdc_results				    *
 *==========================================================================*/
PRIVATE int fdc_results(fp)
struct floppy *fp;		/* pointer to the drive struct */
{
/* Extract results from the controller after an operation, then allow floppy
 * interrupts again.
 */

  int result_nr;
  register int retries;
  register int status;

  /* 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;
  retries = MAX_FDC_RETRY;
  while (TRUE) {
	/* 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 */
		fp->fl_results[result_nr++] = in_byte(FDC_DATA);
		retries = MAX_FDC_RETRY;
		continue;
	}
	if (status == MASTER) {	/* all read */
		cim_floppy();
		return(OK);	/* only good exit */
	}
	if (--retries == 0) break;	/* time out */
  }
  need_reset = TRUE;		/* controller chip must be reset */
  cim_floppy();
  return(ERR_STATUS);
}


/*===========================================================================*
 *				fdc_out					     * 
 *===========================================================================*/
PRIVATE void fdc_out(val)
register 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.
 */

  register int retries;

  if (need_reset) return;	/* if controller is not listening, return */

  /* It may take several tries to get the FDC to accept a command. */
  retries = MAX_FDC_RETRY;
  while ( (in_byte(FDC_STATUS) & (MASTER | DIRECTION)) != (MASTER | 0) )
	if (--retries == 0) {
  /* Controller is not listening.  Hit it over the head with a hammer. */
		need_reset = TRUE;
		return;
	}
  out_byte(FDC_DATA, val);
}


/*===========================================================================*
 *			 	recalibrate				     * 
 *===========================================================================*/
PRIVATE int recalibrate(fp)
register 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(fp);		/* can't recalibrate with motor off */
  fdc_out(FDC_RECALIBRATE);	/* tell drive to recalibrate itself */
  fdc_out(fp->fl_drive);	/* specify drive */
  if (need_reset) return(ERR_SEEK);	/* don't wait if controller is sick */
  receive(HARDWARE, &mess);	/* wait for interrupt message */

  /* Determine if the recalibration succeeded. */
  fdc_out(FDC_SENSE);		/* issue SENSE command to request results */
  r = fdc_results(fp);		/* get results of the FDC_RECALIBRATE command */
  fp->fl_curcyl = -1;		/* force a SEEK next time */
  if (r != OK ||		/* controller would not respond */
     (fp->fl_results[ST0]&ST0_BITS) != SEEK_ST0 || fp->fl_results[ST_PCN] !=0){
	/* Recalibration failed.  FDC must be reset. */
	need_reset = TRUE;
	fp->fl_calibration = UNCALIBRATED;
	return(ERR_RECALIBRATE);
  } else {
	/* Recalibration succeeded. */
	fp->fl_calibration = CALIBRATED;
	if (ps||((d == 6) || (d == 3))) {/* give head time to settle on 3.5 */
		clock_mess(2, send_mess);
		receive(CLOCK, &mess);
	}
#if RECORD_FLOPPY_SKEW
/* This might be used to determine nr_sectors.  This is not quite the right
 * place for it may be called for a format operation.  Then an error is
 * normal, but kills the operation.
 */
	{
		static char skew[32];
	
		for (r = 0; r < sizeof skew / sizeof skew[0]; ++r) {
			read_id(fp);
			skew[r] = fp->fl_results[5];
		}
	}
#endif
	return(OK);
  }
}


/*===========================================================================*
 *				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;

  /* 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 */
  fdc_out(FDC_SENSE);		/* probe FDC to make it return status */
  fdc_results(&floppy[0]);	/* flush controller using scratch structure */
  for (i=0; i<NR_DRIVES; i++)	/* Clear each drive. */
	floppy[i].fl_calibration = UNCALIBRATED;

  /* Tell FDC drive parameters. */
  fdc_out(FDC_SPECIFY);		/* specify some timing parameters */
  fdc_out(current_spec1=spec1[d]); /* step-rate and head-unload-time */
  fdc_out(SPEC2);		/* head-load-time and non-dma */
}


/*===========================================================================*
 *				clock_mess				     * 
 *===========================================================================*/
PRIVATE void clock_mess(ticks, func)
int ticks;			/* how many clock ticks to wait */
void (*func)();			/* function to call upon time out */
{
/* Send the clock task a message. */

  mess.m_type = SET_ALARM;
  mess.CLOCK_PROC_NR = FLOPPY;
  mess.DELTA_TICKS = (long) ticks;
  mess.FUNC_TO_CALL = func;
  sendrec(CLOCK, &mess);
}


/*===========================================================================*
 *				send_mess				     * 
 *===========================================================================*/
PRIVATE void send_mess()
{
/* This routine is called when the clock task has timed out on motor startup.*/

  send(FLOPPY, &mess);
}


/*==========================================================================*
 *				f_do_vrdwt				    *
 *==========================================================================*/
PRIVATE int f_do_vrdwt(m_ptr)
message *m_ptr;			/* pointer to read or write message */
{
/* Carry out a scattered read or write request. */

  int base;
  off_t block;
  int cylinder;
  int dist;
  struct floppy *fp;
  static struct iorequest_s iovec[NR_BUFS];
  phys_bytes iovec_phys;
  int last_plus1;
  int limit;
  int mindist;
  unsigned nr_requests;
  int nsector;
  int request;
  phys_bytes user_iovec_phys;
  message vmessage;

  vmessage = *m_ptr;		/* global message will be clobbered */
  m_ptr = &vmessage;

  /* Fetch i/o vector from caller's space. */
  nr_requests = m_ptr->COUNT;
  if (nr_requests > sizeof iovec / sizeof iovec[0])
	panic("FS gave floppy driver too big an i/o vector", nr_requests);
  iovec_phys = umap(proc_ptr, D, (vir_bytes) iovec, (vir_bytes) sizeof iovec);
  user_iovec_phys = numap(m_ptr->PROC_NR, (vir_bytes) m_ptr->ADDRESS,
			  (vir_bytes) (nr_requests * sizeof iovec[0]));
  if (user_iovec_phys == 0)
	panic("FS gave floppy driver bad i/o vector", (int) m_ptr->ADDRESS);
  phys_copy(user_iovec_phys, iovec_phys,
	    (phys_bytes) nr_requests * sizeof iovec[0]);

  /* Determine the number of sectors and the last sector.  It only hurts
   * efficiency if these are wrong after a disk change.
   */
  fp = &floppy[(m_ptr->DEVICE & ~(DEV_TYPE_BITS|FORMAT_DEV_BIT)) % NR_DRIVES];
  nsector = nr_sectors[fp->fl_density];
  read_id(fp);			/* should reorganize and seek() before this */
  fp->fl_sector = fp->fl_results[5];	/* last sector accessed */

  for (base = 0; base < nr_requests; base = limit) {

	/* Handle all requests on the same cylinder as the base request. */
	block = iovec[base].io_position / SECTOR_SIZE;
	cylinder = (int) (block / (NR_HEADS * nsector));

	/* Find the request for the closest sector on the base cylinder. */
	for (request = limit = base, mindist = 9999; limit < nr_requests;
	     ++limit) {
		block = iovec[limit].io_position / SECTOR_SIZE;
		if (cylinder != (int) (block / (NR_HEADS * nsector))) break;
		dist = base_sector + (int) (block % nsector) - fp->fl_sector;
		if (dist < 0) dist += nsector;
		if (dist > 0 && dist < mindist) {
			/* Closer.  Ignore dist == 0 which is furthest! */
			request = limit;
			mindist = dist;
		}
	}

	/* Do the actual i/o in the good order just found. */
	last_plus1 = (request == base) ? limit : request;
	do {
		if (request >= limit) request = base;
		if (do1_rdwt(&iovec[request], m_ptr) != OK) {
			/* Abort both loops, to avoid reading-ahead of
			 * bad blocks, especially off the end of the disk.
			 */
			request = last_plus1 - 1;
			limit = nr_requests;
		}
	} while (++request != last_plus1);

	/* Advance last sector from the base sector of the last block.
	 * Advance another sector after that to allow time for seek, except
	 * for the last sectors on a cylinder, for which the gap at the end
	 * gives enough time.
	 */
	fp->fl_sector += BLOCK_SIZE / SECTOR_SIZE;
	while (fp->fl_sector >= nsector + base_sector)
		fp->fl_sector -= nsector;
	if (fp->fl_sector == base_sector)
		fp->fl_sector = base_sector + nsector - 1;
  }

  /* Return most results in caller's i/o vector. */
  phys_copy(iovec_phys, user_iovec_phys,
	    (phys_bytes) nr_requests * sizeof iovec[0]);
  return(OK);
}


/*==========================================================================*
 *				do1_rdwt				    *
 *==========================================================================*/
PRIVATE int do1_rdwt(iop, m_ptr)
register struct iorequest_s *iop;
register message *m_ptr;
{
/* Convert from scattered i/o entry to partially built message and do i/o.
 * There are too many conversions, so to keep up on slow machines it will
 * be necessary to do more preparation at a high level, e.g., when looping
 * over sectors on the same cylinder, do not recompute the cylinder over
 * and over, and avoid all long arithmetic.
 */

  int result;
#if FLOPPY_TIMING
#define MAX_TIMED_BLOCK (HC_SIZE / (BLOCK_SIZE / SECTOR_SIZE))
  off_t block;
  static struct {
	unsigned short start;
	unsigned short finish;
  } fl_times[MAX_TIMED_BLOCK];
#endif

  m_ptr->POSITION = iop->io_position;
  m_ptr->ADDRESS = iop->io_buf;
  m_ptr->COUNT = iop->io_nbytes;
  m_ptr->m_type = iop->io_request & ~OPTIONAL_IO;
#if FLOPPY_TIMING
  block = m_ptr->POSITION/BLOCK_SIZE;
  if (block < MAX_TIMED_BLOCK) fl_times[block].start = read_counter();
#endif
  result = do_rdwt(m_ptr, iop->io_request & OPTIONAL_IO);
#if FLOPPY_TIMING
  if (block < MAX_TIMED_BLOCK) fl_times[block].finish = read_counter();
#endif
  if (result == 0) return(!OK);	/* EOF */
  if (result < 0) {
	iop->io_nbytes = result;
	if (iop->io_request & OPTIONAL_IO) return(!OK);	/* abort if optional */
  } else
	iop->io_nbytes -= result;
  return(OK);
}


/*==========================================================================*
 *				read_id					    *
 *==========================================================================*/
PRIVATE int read_id(fp)
register 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>>(fp->fl_drive+4)) & 1) == 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( (fp->fl_head << 2) | fp->fl_drive);

  /* Block, waiting for disk interrupt. */
  if (need_reset) return(ERR_READ_ID);	/* if controller is sick, abort op */
  receive(HARDWARE, &mess);

  /* Get controller status and check for errors. */
  result = fdc_results(fp);
  if (result != OK) return(result);
  if ( (fp->fl_results[ST1] & BAD_SECTOR) || (fp->fl_results[ST2] & BAD_CYL) )
	fp->fl_calibration = UNCALIBRATED;
  if ((fp->fl_results[ST0] & ST0_BITS) != TRANS_ST0) return(ERR_READ_ID);
  if (fp->fl_results[ST1] | fp->fl_results[ST2]) return(ERR_READ_ID);

  return(OK);
}