This is the mail archive of the ecos-devel@sources.redhat.com mailing list for the eCos project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [ECOS] load data from floppy - floppy driver


Nick Garnett <nickg@ecoscentric.com> writes:

[ Please keep me in CC's, as I'm not subscribed to -devel. ]

> > Idle thought: I wonder if it would be possible to put a
> > sectors->bytes / bytes->sectors conversion layer into a small
> > library, or if it's too specific / would be too wasteful (require
> > copying and replicating lots of memory).

> That might be the way to go. Another thought is to put it into a
> second driver layer, or into a "filesystem" that just presents raw
> access to some underlying block device. It is certainly a good idea
> to separate the low-level driver, which should work in sectors, from
> any higher level semantics we might want to layer on top of it.

I am going to go out on a limb and send you guys the floppy code that
I have created by reworking the minix floppy.c with some eCos
functionality.

It's a long ways from perfect, but my impetus to hack on it is much
less, as it does what I need.  Maybe I could beat it into better shape
with some guidance.  The big things missing are a write function, as
well as config functions if they are needed for anything.  There are
probably numerous other defects (I suspect I got some of the timing
functions wrong).  It's probably worth comparing with the original
minix driver, which is quite solid and apparently works for many
people.

That said, it does read floppies, which might be useful to someone out
there.

In any case, thanks for all the help you've given me,
-- 
David N. Welton
   Consulting: http://www.dedasys.com/
     Personal: http://www.dedasys.com/davidw/
Free Software: http://www.dedasys.com/freesoftware/
   Apache Tcl: http://tcl.apache.org/

/* This file contains the device dependent part of the driver for the Floppy
 * Disk Controller (FDC) using the NEC PD765 chip.
 *
 * The file contains two entry points:
 *
 *   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
 *	14 May  2000 by Kees J. Bot: d-d/i rewrite.
 */

/*
 * floppy.c modified to run under eCos operating system.
 *
 * August 2003, David N. Welton <davidw@dedasys.com>
 *
 */

#include <cyg/kernel/kapi.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/io/devtab.h>
#include <pkgconf/hal.h>
#include <errno.h>


/* This should be equal to 100. */
#define HZ (int)(1.0 / ((CYGNUM_HAL_RTC_NUMERATOR / CYGNUM_HAL_RTC_DENOMINATOR) * 0.000000001))

static cyg_uint32 f_handler_isr(cyg_vector_t vector,
				cyg_addrword_t data);
static void floppy_dsr(cyg_vector_t vector,
		       cyg_ucount32 count,
		       cyg_addrword_t data);

/* Minix compatibility defines. */
#define PRIVATE static
#define FORWARD static
#define _PROTOTYPE(function, params)	function params
typedef unsigned char u8_t;
typedef struct { unsigned int _[2]; } u64_t;

typedef void* vir_bytes;	/* virtual addresses and lengths in bytes */

typedef struct {
    vir_bytes iov_addr;		/* address of an I/O buffer */
    unsigned int iov_size;		/* sizeof an I/O buffer */
} iovec_t;

#define out_byte(p, v) HAL_WRITE_UINT8(p, v)
#define in_byte(p, v) HAL_READ_UINT8(p, v)

#	define DEV_SCATTER  8	/* fcn code for writing from a vector */
#	define DEV_GATHER   9	/* fcn code for reading into a vector */

/* XXX  */
#define TRUE		  1

#define NIL_DEV NULL

#define PUBLIC

#define MINOR_fd0p0	(28<<2)
#define P_FLOPPY	0

/* End of minix compatability declarations. */


/* Base and size of a partition in bytes. */
struct device {
    long long dv_base;
    long long dv_size;
};


/* DMA_SECTORS may be increased to speed up DMA based drivers. */
#define DMA_SECTORS        1	/* DMA buffer size (must be >= 1) */

#define SECTOR_SIZE      512	/* physical sector size in bytes */
#define SECTOR_SHIFT       9	/* for division */
#define SECTOR_MASK      511	/* and remainder */

/* Size of the DMA buffer buffer in bytes. */
#define DMA_BUF_SIZE	(DMA_SECTORS * SECTOR_SIZE)

#define NR_PARTITIONS	4	/* number of entries in partition table */

/* We use this as the DMA buffer. */
PRIVATE u8_t buffer[(unsigned) 2 * DMA_BUF_SIZE];

/* Global floppy interrupt handler. */
static cyg_vector_t floppy_vector = CYGNUM_HAL_INTERRUPT_FDD;
static cyg_interrupt floppy_interrupt;
static cyg_handle_t floppy_handle;

/* Interrupt flag handle. */
static cyg_flag_t floppy_flag;

/* Generic clock/alarm declarations. */
static cyg_handle_t counter_hdl;
static cyg_handle_t system_clock;
static cyg_tick_count_t ticks;

/* Alarm handlers for floppy timeout. */
static cyg_handle_t timeout_alarm_hdl;
static cyg_alarm timeout_alarm_obj;

/* Alarm handlers to schedule a motor stop. */
static cyg_handle_t motor_timeout_hdl;
static cyg_alarm motor_timeout_alarm_obj;

struct disk_parameter_s {
    char spec1;
    char spec2;
    char motor_turnoff_sec;
    char sector_size_code;
    char sectors_per_cylinder;
    char gap_length;
    char dtl;
    char gap_length_for_format;
    char fill_byte_for_format;
    char head_settle_msec;
    char motor_start_eigth_sec;
};

/* 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        (-1)	/* 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	/* busy doing I/O */
#define BSY_WAKEN	   2	/* got a wakeup call */

/* Seven combinations of diskette/drive are supported.
 *
 * # Diskette Drive  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   360K    720K     9       40     300 RPM  250 kbps   Quad density PC
 * 3   720K    720K     9       80     300 RPM  250 kbps   Toshiba, et al.
 * 4   360K    1.2M     9       40     360 RPM  300 kbps   PC disk in AT drive
 * 5   720K    1.2M     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
 *
 * 3   720K    1.44M    9       80     300 RPM  250 kbps   PS/2, et al.
 */
PRIVATE struct density {
    u8_t	secpt;		/* sectors per track */
    u8_t	cyls;		/* tracks per side */
    u8_t	steps;		/* steps per cylinder (2 = double step) */
    u8_t	test;		/* sector to try for density test */
    u8_t	rate;		/* data rate (2=250, 1=300, 0=500 kbps) */
    cyg_uint32 	start;		/* motor start (clock ticks) */
    u8_t	gap;		/* gap size */
    u8_t	spec1;		/* first specify byte (SRT/HUT) */
} fdensity[NT] = {
    {  9, 40, 1, 4*9, 2, 6*HZ/8, 0x2A, 0xDF }, /*  360K / 360K  */
    { 15, 80, 1,  14, 0, 4*HZ/8, 0x1B, 0xD1 }, /*  1.2M / 1.2M  */
    {  9, 40, 2, 2*9, 2, 4*HZ/8, 0x2A, 0xE1 }, /*  360K / 720K  */
    {  9, 80, 1, 4*9, 2, 6*HZ/8, 0x2A, 0xDF }, /*  720K / 720K  */
    {  9, 40, 2, 2*9, 1, 4*HZ/8, 0x23, 0xE1 }, /*  360K / 1.2M  */
    {  9, 80, 1, 4*9, 1, 4*HZ/8, 0x23, 0xE1 }, /*  720K / 1.2M  */
    { 18, 80, 1,  17, 0, 4*HZ/8, 0x1B, 0xD1 }, /* 1.44M / 1.44M */
};

/* 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.
 */

#define b(d)	(1 << (d))	/* bit for density d. */

PRIVATE struct test_order {
    u8_t	t_density;	/* floppy/drive type */
    u8_t	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, which is
     * why the type 3 parameters have been pessimized to be like type 0.
     */
};

/* Variables. */
PRIVATE struct floppy {		/* main drive struct, one entry per drive */
    unsigned fl_curcyl;		/* current cylinder */
    unsigned fl_hardcyl;		/* hardware cylinder, as opposed to: */
    unsigned fl_cylinder;		/* cylinder number addressed */
    unsigned fl_sector;		/* sector addressed */
    unsigned fl_head;		/* head number addressed */
    char fl_calibration;		/* CALIBRATED or UNCALIBRATED */
    u8_t fl_density;		/* NO_DENS = ?, 0 = 360K; 1 = 360K/1.2M; etc.*/
    u8_t 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];

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 unsigned f_drive;	/* selected drive */
PRIVATE unsigned f_device;	/* selected minor device */
PRIVATE struct floppy *f_fp;	/* current drive */
PRIVATE struct density *f_dp;	/* current density parameters */
PRIVATE struct density *prev_dp;/* previous density parameters */
PRIVATE unsigned f_sectors;	/* equal to f_dp->secpt (needed a lot) */
PRIVATE int f_busy;		/* BSY_IDLE, BSY_IO, BSY_WAKEN */
PRIVATE struct device *f_dv;	/* device's base and size */
PRIVATE struct disk_parameter_s fmt_param; /* parameters for format */
PRIVATE u8_t f_results[MAX_RESULTS];/* the controller can give lots of output */

FORWARD _PROTOTYPE( struct device *f_prepare, (int device) );
FORWARD _PROTOTYPE( char *f_name, (void) );
FORWARD _PROTOTYPE( void f_cleanup, (void) );
FORWARD _PROTOTYPE( int f_transfer, (int opcode, unsigned int position,
				     iovec_t *iov, unsigned nr_req) );
FORWARD _PROTOTYPE( void dma_setup, (int opcode) );
FORWARD _PROTOTYPE( void start_motor, (void) );
FORWARD _PROTOTYPE( int floppy_seek, (void) );
FORWARD _PROTOTYPE( int fdc_transfer, (int opcode) );
FORWARD _PROTOTYPE( int fdc_results, (void) );
FORWARD _PROTOTYPE( int f_handler, (int irq) );
FORWARD _PROTOTYPE( int fdc_command, (u8_t *cmd, int len) );
FORWARD _PROTOTYPE( void fdc_out, (int val) );
FORWARD _PROTOTYPE( int recalibrate, (void) );
FORWARD _PROTOTYPE( void f_reset, (void) );
FORWARD _PROTOTYPE( void send_mess, (void) );
FORWARD _PROTOTYPE( int f_intr_wait, (void) );
FORWARD _PROTOTYPE( int read_id, (void) );
FORWARD _PROTOTYPE( int test_read, (int density) );
//FORWARD _PROTOTYPE( void f_geometry, (struct partition *entry));

static void stop_motor (cyg_handle_t alarm_handle, cyg_addrword_t data);
static bool floppy_init(struct cyg_devtab_entry *tab);
static void f_timeout();
static void schedule_motor_stop();

/* IO table for eCos floppy driver. */

static Cyg_ErrNo
floppy_open(struct cyg_devtab_entry **tab,
	    struct cyg_devtab_entry *st,
	    const char *name);

static Cyg_ErrNo
floppy_bwrite( cyg_io_handle_t handle, const void *buf, cyg_uint32 *len,
	       cyg_uint32 pos )
{
    schedule_motor_stop();
    return ENOERR;
}

/*
 *-----------------------------------------------------------------------------
 *
 * floppy_bread --
 *
 * 	Read data from the floppy disk.
 *
 * Results:
 *
 * 	Returns an error if it is passed up from lower level
 * 	functions.
 *
 * Side Effects:
 *
 *	Fills buf with data read, up to the value of *len.  Sets *len
 *	to the amount of data actually read.  Schedules floppy motor
 *	to be stopped if another IO request is not received.
 *
 *-----------------------------------------------------------------------------
 */

Cyg_ErrNo
floppy_bread(cyg_io_handle_t handle, void *buf,
	     cyg_uint32 *len, cyg_uint32 pos)
{
    int rescode;
    iovec_t iov;
    int original_len;

    original_len = *len;
    iov.iov_addr = buf;
    iov.iov_size = *len * SECTOR_SIZE;

    rescode = f_transfer(DEV_GATHER, pos * SECTOR_SIZE, &iov, 1);
    *len = (original_len - iov.iov_size) / SECTOR_SIZE;
    schedule_motor_stop();
    return rescode;
}

static Cyg_ErrNo
floppy_get_config( cyg_io_handle_t handle,
		   cyg_uint32 key,
		   void* buf,
		   cyg_uint32* len)
{
    return EINVAL;
}

static Cyg_ErrNo
floppy_set_config(cyg_io_handle_t handle,
		  cyg_uint32 key,
		  void* buf,
		  cyg_uint32* len)
{
    return EINVAL;
}


BLOCK_DEVIO_TABLE(cyg_io_floppy_ops,
		  floppy_bwrite,
		  floppy_bread,
		  0, /* no select */
		  NULL,
		  NULL);

/* Device description. */

BLOCK_DEVTAB_ENTRY(cyg_io_floppydev,
		   "/dev/fd",
		   NULL, /* Not dependant on other devices. */
		   &cyg_io_floppy_ops,
		   floppy_init,
		   floppy_open,
		   (void *)NULL); /* No private information.  */

/*===========================================================================*
 *				floppy_init				     *
 *===========================================================================*/
static bool floppy_init(struct cyg_devtab_entry *tab)
{
    cyg_priority_t floppy_priority = 1;

    struct floppy *fp;

    /* Initialize the floppy structure. */

    for (fp = &floppy[0]; fp < &floppy[NR_DRIVES]; fp++) {
	fp->fl_curcyl = NO_CYL;
	fp->fl_density = NO_DENS;
	fp->fl_class = ~0;
    }

    /* Flag used to signal interrupts. */
    cyg_flag_init(&floppy_flag);

    /* Set up clock for timeout alarms. */
    system_clock = cyg_real_time_clock();
    cyg_clock_to_counter(system_clock, &counter_hdl);

    /* This alarm is for floppy timeouts. */
    cyg_alarm_create(
	counter_hdl,
	f_timeout,
	(cyg_addrword_t)NULL,
	&timeout_alarm_hdl,
	&timeout_alarm_obj);

    /* This alarm is to shut the motor down if it is inactive for a
     * certain period of time. */
    cyg_alarm_create(
	counter_hdl,
	stop_motor,
	(cyg_addrword_t)NULL,
	&motor_timeout_hdl,
	&motor_timeout_alarm_obj);

    /* Set up floppy interrupt handler. */
    cyg_interrupt_create(
	floppy_vector,
	floppy_priority,
	0,
	&f_handler_isr,
	&floppy_dsr,
	&floppy_handle,
	&floppy_interrupt);

    cyg_interrupt_attach(floppy_handle);

#ifdef MINIX
    put_irq_handler(FLOPPY_IRQ, f_handler);
    enable_irq(FLOPPY_IRQ);	/* ready for floppy interrupts */

    driver_task(&f_dtab);
#endif
    return true;
}


/*===========================================================================*
 *				f_prepare				     *
 *===========================================================================*/
PRIVATE struct device *f_prepare(device)
    int device;
{
/* Prepare for I/O on a device. */

    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;
    if (f_fp->fl_density < NT) {
	f_dp = &fdensity[f_fp->fl_density];
	f_sectors = f_dp->secpt;
	f_fp->fl_geom.dv_size = (NR_HEADS * f_sectors
				 * f_dp->cyls) * SECTOR_SIZE;
    }

    /* A partition? */
    if ((device &= DEV_TYPE_BITS) >= MINOR_fd0p0) {
	f_dv = &f_fp->fl_part[(device - MINOR_fd0p0) >> DEV_TYPE_SHIFT];
    }

    return f_dv;
}


/*===========================================================================*
 *				f_name					     *
 *===========================================================================*/
PRIVATE char *f_name()
{
/* Return a name for the current device. */
    static char name[] = "fd0";

    name[2] = '0' + f_drive;
    return name;
}

/*
 *-----------------------------------------------------------------------------
 *
 *  schedule_motor_stop --
 *
 *	After reads or writes, schedule the motor to be shut down if
 *	it's not used again soon.
 *
 * Results:
 *
 *	None.
 *
 * Side Effects:
 *
 *	Initializes an alarm that will call a function to stop the
 *	motor.
 *
 *-----------------------------------------------------------------------------
 */

PRIVATE void schedule_motor_stop()
{
    /* 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;

    cyg_alarm_initialize(motor_timeout_hdl,
			 cyg_current_time() + MOTOR_OFF, 0);

}


/*
 *-----------------------------------------------------------------------------
 *
 * f_transfer --
 *
 * 	This function takes an opcode, either gather or scatter (read
 * 	or write), and performs the actual IO with the floppy.  Note
 * 	that both position and iov.iov_size must be divisible by the
 * 	SECTOR_SIZE (512).
 *
 * Results:
 *
 *	Returns an error on failure, 0 on success.
 *
 * Side Effects:
 *
 *	Fills iov.iov_addr with data read from the floppy on read.
 *	Writes take data from this address and send it to the disk.
 *	Sets iov.iov_size to 0 if all data has been correctly
 *	read/written.
 *
 *-----------------------------------------------------------------------------
 */

PRIVATE int f_transfer(opcode, position, iov, nr_req)
    int opcode;		/* DEV_GATHER or DEV_SCATTER */
    unsigned int position;	/* offset on device to read or write */
    iovec_t *iov;	/* pointer to read or write request vector */
    unsigned nr_req;	/* length of request vector */
{
    struct floppy *fp = f_fp;
    iovec_t *iop, *iov_end = iov + nr_req;
    int r, errors;
    unsigned block;	/* Seen any 32M floppies lately? */
    unsigned nbytes, count, chunk, sector;
    long dv_size = f_dv->dv_size;
    vir_bytes user_addr;
    vir_bytes uaddrs[MAX_SECTORS], *up;
    u8_t cmd[3];

    /* Check disk address. */
    if ((position & SECTOR_MASK) != 0) {
	return(EINVAL);
    }

    errors = 0;
    while (nr_req > 0) {
	/* How many bytes to transfer? */
	nbytes = 0;
	for (iop = iov; iop < iov_end; iop++)
	    nbytes += iop->iov_size;

	/* Which block on disk and how close to EOF? */
	if (position >= dv_size)
	    return(0);		/* At EOF */
	if (position + nbytes > dv_size)
	    nbytes = dv_size - position;
	block = (f_dv->dv_base + position) / SECTOR_SIZE;

	if ((nbytes & SECTOR_MASK) != 0) {
	    return(EINVAL);
	}

	/* Using a formatting device? */
	if (f_device & FORMAT_DEV_BIT) {
	    if (opcode != DEV_SCATTER) {
		return(EIO);
	    }
	    if (iov->iov_size < SECTOR_SIZE + sizeof(fmt_param)) {
		return(EINVAL);
	    }

	    memcpy(&fmt_param,
		   (iov->iov_addr) + SECTOR_SIZE,
		   sizeof(fmt_param));
	    /*  phys_copy(user_base + iov->iov_addr + 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(EIO);
	    }
	    /* Only the first sector of the parameters now needed. */
	    iov->iov_size = nbytes = SECTOR_SIZE;
	}

	/* Only try one sector if there were errors. */
	if (errors > 0)
	    nbytes = SECTOR_SIZE;

	/* Compute cylinder and head of the track to access. */
	fp->fl_cylinder = block / (NR_HEADS * f_sectors);
	fp->fl_hardcyl = fp->fl_cylinder * f_dp->steps;
	fp->fl_head = (block % (NR_HEADS * f_sectors)) / f_sectors;

	/* For each sector on this track compute the user address it is to
	 * go or to come from.
	 */
	for (up = uaddrs; up < uaddrs + MAX_SECTORS; up++)
	    *up = 0;
	count = 0;
	iop = iov;
	sector = block % f_sectors;
	for (;;) {
	    user_addr = iop->iov_addr;
	    chunk = iop->iov_size;
	    if ((chunk & SECTOR_MASK) != 0) {
		return(EINVAL);
	    }

	    while (chunk > 0) {
		uaddrs[sector++] = user_addr;
		chunk -= SECTOR_SIZE;
		user_addr += SECTOR_SIZE;
		count += SECTOR_SIZE;
		if (sector == f_sectors || count == nbytes)
		    goto track_set_up;
	    }
	    iop++;
	}
    track_set_up:

	/* First check to see if a reset is needed. */
	if (need_reset) {
	    f_reset();
	}

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

	/* Set the stepping rate and data rate */
	if (f_dp != prev_dp) {
	    cmd[0] = FDC_SPECIFY;
	    cmd[1] = f_dp->spec1;
	    cmd[2] = SPEC2;
	    (void) fdc_command(cmd, 3);
	    out_byte(FDC_RATE, f_dp->rate);
	    prev_dp = f_dp;
	}

	/* If we are going to a new cylinder, perform a seek. */
	r = floppy_seek();
	/* Avoid read_id() if we don't plan to read much. */
	if (fp->fl_sector == NO_SECTOR && count < (6 * SECTOR_SIZE))
	    fp->fl_sector = 0;

	for (nbytes = 0; nbytes < count; nbytes += SECTOR_SIZE) {
	    if (fp->fl_sector == NO_SECTOR) {
		/* Find out what the current sector is.  This often
		 * fails right after a seek, so try it twice.
		 */
		if (r == 0 && read_id() != 0) r = read_id();
	    }

	    /* Look for the next job in uaddrs[] */
	    if (r == 0) {
		for (;;) {
		    if (fp->fl_sector >= f_sectors) {
			fp->fl_sector = 0;
		    }

		    up = &uaddrs[fp->fl_sector];
		    if (*up != 0)
			break;
		    fp->fl_sector++;
		}
	    }

	    if (r == 0 && opcode == DEV_SCATTER) {
		/* Copy the user bytes to the DMA buffer. */
		memcpy(buffer, *up, SECTOR_SIZE);
		/* phys_copy(user_base + *up, tmp_phys,
		   (phys_bytes) SECTOR_SIZE);  */
	    }

	    /* Set up the DMA chip and perform the transfer. */
	    if (r == 0) {
		dma_setup(opcode);
		r = fdc_transfer(opcode);
	    }

	    if (r == 0 && opcode == DEV_GATHER) {
		/* Copy the DMA buffer to "user space". */
		memcpy(*up, buffer, SECTOR_SIZE);
		/* phys_copy(tmp_phys, user_base + *up,
		   (phys_bytes) SECTOR_SIZE);  */
	    }
	    if (r != 0) {
		/* Don't retry if write protected or too many errors. */
		if (err_no_retry(r) || ++errors == MAX_ERRORS) {
		    return(EIO);
		}
		/* Recalibrate if halfway. */
		if (errors == MAX_ERRORS / 2)
		    fp->fl_calibration = UNCALIBRATED;

		nbytes = 0;
		break;		/* retry */
	    }
	}
	/* Book the bytes successfully transferred. */
	position += nbytes;
	for (;;) {
	    if (nbytes < iov->iov_size) {
		/* Not done with this one yet. */
		iov->iov_addr += nbytes;
		iov->iov_size -= nbytes;
		break;
	    }
	    nbytes -= iov->iov_size;
	    iov->iov_addr += iov->iov_size;
	    iov->iov_size = 0;
	    if (nbytes == 0) {
		/* The rest is optional, so we return to give FS a
		 * chance to think it over.
		 */
		return(0);
	    }
	    iov++;
	    nr_req--;
	}
    }
    return(0);
}


/*===========================================================================*
 *				dma_setup				     *
 *===========================================================================*/
PRIVATE void dma_setup(opcode)
    int opcode;			/* DEV_GATHER or DEV_SCATTER */
{
    /* 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, opcode == DEV_SCATTER ? DMA_WRITE : DMA_READ);
    out_byte(DMA_ADDR, (unsigned) buffer >>  0);
    out_byte(DMA_ADDR, (unsigned) buffer >>  8);
    out_byte(DMA_TOP, (unsigned) ((unsigned int)buffer >> 16));
    out_byte(DMA_COUNT, (SECTOR_SIZE - 1) >> 0);
    out_byte(DMA_COUNT, (SECTOR_SIZE - 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;

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

    /* Wait... */
    cyg_thread_delay(f_dp->start);
}


/*===========================================================================*
 *				stop_motor				     *
 *===========================================================================*/
PRIVATE void stop_motor(cyg_handle_t alarm_handle,
			cyg_addrword_t data)
{
    /* 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((cyg_handle_t)NULL, (cyg_addrword_t)NULL);
}


/*===========================================================================*
 *				floppy_seek			       	     *
 *===========================================================================*/
PRIVATE int floppy_seek()
{
    /* Issue a SEEK command on the indicated drive unless the arm is
     * already positioned on the correct cylinder.
     */

    struct floppy *fp = f_fp;
    int r;
    u8_t cmd[3];

    /* Are we already on the correct cylinder? */
    if (fp->fl_calibration == UNCALIBRATED) {
	if (recalibrate() != 0) {
	    return(ERR_SEEK);
	}
    }
    if (fp->fl_curcyl == fp->fl_hardcyl) return(0);

    /* No.  Wrong cylinder.  Issue a SEEK and wait for interrupt. */
    cmd[0] = FDC_SEEK;
    cmd[1] = (fp->fl_head << 2) | f_drive;
    cmd[2] = fp->fl_hardcyl;
    if (fdc_command(cmd, 3) != 0) {
	return(ERR_SEEK);
    }

    if (f_intr_wait() != 0) {
	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 != 0 || (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) {
	cyg_thread_delay(2);
	/* XXX clock_mess(2, send_mess);
	   receive(CLOCK, &mess);  */
    }
    fp->fl_curcyl = fp->fl_hardcyl;
    fp->fl_sector = NO_SECTOR;
    return(0);
}


/*===========================================================================*
 *				fdc_transfer				     *
 *===========================================================================*/
PRIVATE int fdc_transfer(opcode)
    int opcode;			/* DEV_GATHER or DEV_SCATTER */
{
    /* The drive is now on the proper cylinder.  Read, write or format
     * 1 block. */

    struct floppy *fp = f_fp;
    int r, s;
    u8_t cmd[9];

    /* 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) {
	cmd[0] = FDC_FORMAT;
	cmd[1] = (fp->fl_head << 2) | f_drive;
	cmd[2] = fmt_param.sector_size_code;
	cmd[3] = fmt_param.sectors_per_cylinder;
	cmd[4] = fmt_param.gap_length_for_format;
	cmd[5] = fmt_param.fill_byte_for_format;
	if (fdc_command(cmd, 6) != 0)
	    return(ERR_TRANSFER);
    } else {
	cmd[0] = opcode == DEV_SCATTER ? FDC_WRITE : FDC_READ;
	cmd[1] = (fp->fl_head << 2) | f_drive;
	cmd[2] = fp->fl_cylinder;
	cmd[3] = fp->fl_head;
	cmd[4] = BASE_SECTOR + fp->fl_sector;
	cmd[5] = SECTOR_SIZE_CODE;
	cmd[6] = f_sectors;
	cmd[7] = f_dp->gap;	/* sector gap */
	cmd[8] = DTL;		/* data length */
	if (fdc_command(cmd, 9) != 0)
	    return(ERR_TRANSFER);
    }

    /* Block, waiting for disk interrupt. */
    if (f_intr_wait() != 0) {
	return(ERR_TIMEOUT);
    }

    /* Get controller status and check for errors. */
    r = fdc_results();
    if (r != 0) 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(0);

    /* 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] - BASE_SECTOR - fp->fl_sector);
    if (s != 1) return(ERR_TRANSFER);

    /* This sector is next for I/O: */
    fp->fl_sector = f_results[ST_SEC] - BASE_SECTOR;
    return(0);
}


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

    int result_nr, status;
    unsigned int startticks;

    /* 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);  */
    startticks = cyg_current_time();
    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.
	 */
	in_byte(FDC_STATUS, status);
	status &= (MASTER | DIRECTION | CTL_BUSY);
	if (status == (MASTER | DIRECTION | CTL_BUSY)) {
	    if (result_nr >= MAX_RESULTS) break;	/* too many results */
	    in_byte(FDC_DATA, f_results[result_nr++]);
	    continue;
	}
	if (status == MASTER) {	/* all read */
//XXX	    enable_irq(FLOPPY_IRQ);
	    return(0);	/* only good exit */
	}
    } while (cyg_current_time() - startticks < (TIMEOUT * 1000000) / 838);
//    } while (milli_elapsed(&ms) < TIMEOUT);
    need_reset = TRUE;		/* controller chip must be reset */
//XXX    enable_irq(FLOPPY_IRQ);
    return(ERR_STATUS);
}

/*
 *-----------------------------------------------------------------------------
 *
 * f_handler_isr --
 *
 *	'Bottom half' of floppy interrupt.
 *
 * Results:
 *
 *	Returns flag indicating that the DSR needs to run.
 *
 * Side Effects:
 *
 *	Masks the interrupt - which is unmaksed in the DSR.
 *
 *-----------------------------------------------------------------------------
 */

static cyg_uint32 f_handler_isr(cyg_vector_t vector, cyg_addrword_t data)
{
    cyg_interrupt_mask(vector);
    cyg_interrupt_acknowledge(vector);

    return (CYG_ISR_HANDLED | CYG_ISR_CALL_DSR);
}

/*
 *-----------------------------------------------------------------------------
 *
 * floppy_dsr --
 *
 *	'Top half' of the floppy handler.  This one actually wakes up
 *	the code that is waiting for the interrupt, since this isn't
 *	possible in the ISR.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *
 *	Sets the 'floppy_flag', which other routines wait on to know
 *	that they have incoming data from the floppy drive.  Unmasks
 *	the interrupt so that more can be received.
 *
 *-----------------------------------------------------------------------------
 */

static void floppy_dsr(cyg_vector_t vector, cyg_ucount32 count,
		cyg_addrword_t data)
{
    /* Set flag - interrupt has occurred. */
    f_busy = BSY_IDLE;
    cyg_flag_setbits(&floppy_flag, 1);

    cyg_interrupt_unmask(vector);
}

/*===========================================================================*
 *				fdc_command				     *
 *===========================================================================*/
PRIVATE int fdc_command(cmd, len)
    u8_t *cmd;		/* command bytes */
    int len;		/* command length */
{
    /* Output a command to the controller. */

    /* Schedule a wakeup call. */
    cyg_alarm_initialize(timeout_alarm_hdl,
			 cyg_current_time() + WAKEUP, 0);

    f_busy = BSY_IO;
    while (len > 0) {
	fdc_out(*cmd++);
	len--;
    }
    cyg_alarm_disable(timeout_alarm_hdl);
    return(need_reset ? ERR_DRIVE : 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.
     */
    char res;
    unsigned int startticks;

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

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


/*===========================================================================*
 *				recalibrate				     *
 *===========================================================================*/
PRIVATE int recalibrate()
{
    /* 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.
     */

    struct floppy *fp = f_fp;
    int r;
    u8_t cmd[2];

    /* Issue the RECALIBRATE command and wait for the interrupt. */
    cmd[0] = FDC_RECALIBRATE;	/* tell drive to recalibrate itself */
    cmd[1] = f_drive;		/* specify drive */
    if (fdc_command(cmd, 2) != 0) {
	return(ERR_SEEK);
    }
    if (f_intr_wait() != 0) {
	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 */
    fp->fl_sector = NO_SECTOR;
    if (r != 0 ||		/* 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(0);
    }
}


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

    /* 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.
     */
    (void) fdc_command((u8_t *) 0, 0);	/* need only the timer */
    cyg_drv_isr_lock();
    motor_status = 0;
    motor_goal = 0;
    out_byte(DOR, 0);		/* strobe reset bit low */
    out_byte(DOR, ENABLE_INT);	/* strobe it high again */
    cyg_drv_isr_unlock();
    /* collect the RESET interrupt */
    cyg_flag_wait(
	&floppy_flag,
	1,
	CYG_FLAG_WAITMODE_CLR | CYG_FLAG_WAITMODE_AND);

    /* 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 0) 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. */
    prev_dp = NULL;
}


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

    f_busy = BSY_IO;

    if (cyg_flag_timed_wait(
	    &floppy_flag,
	    1,
	    CYG_FLAG_WAITMODE_AND|CYG_FLAG_WAITMODE_CLR,
	    cyg_current_time() + WAKEUP) == 0) {
	f_busy = BSY_WAKEN;
    }

    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.
	 */
	need_reset = TRUE;
	return(ERR_TIMEOUT);
    }
    return(0);
}


/*===========================================================================*
 *				f_timeout				     *
 *===========================================================================*/
PRIVATE void f_timeout(cyg_handle_t alarm_handle,
		       cyg_addrword_t data)
{
    /* 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;
	cyg_flag_setbits(&floppy_flag, 1);
    }
}


/*==========================================================================*
 *				read_id					    *
 *==========================================================================*/
PRIVATE int read_id()
{
/* Determine current cylinder and sector. */

    struct floppy *fp = f_fp;
    int result;
    u8_t cmd[2];

    /* 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. */
    cmd[0] = FDC_READ_ID;		/* issue the read id command */
    cmd[1] = (fp->fl_head << 2) | f_drive;
    if (fdc_command(cmd, 2) != 0) return(ERR_READ_ID);
    if (f_intr_wait() != 0) {
	return(ERR_TIMEOUT);
    }

    /* Get controller status and check for errors. */
    result = fdc_results();
    if (result != 0) 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: */
    fp->fl_sector = f_results[ST_SEC] - BASE_SECTOR + 1;
    return(0);
}


/*==========================================================================*
 *				f_do_open				    *
 *==========================================================================*/
/* PRIVATE int f_do_open(dp, m_ptr)
   struct driver *dp;
   message *m_ptr;		  */
/* pointer to open message */

/*
 *-----------------------------------------------------------------------------
 *
 * floppy_open --
 *
 *	Called when /dev/fd is opened.
 *
 * Results:
 *
 *
 *
 *-----------------------------------------------------------------------------
 */

static Cyg_ErrNo
floppy_open(struct cyg_devtab_entry **tab,
	    struct cyg_devtab_entry *st,
	    const char *name)
{
    /* Handle an open on a floppy.  Determine diskette type if need
     * be. */

    /* For eCos, this is called when cyg_io_lookup() is called for
     * this device. */

    int dtype;
    struct test_order *top;

    /* We pay attention to interrupts only after the floppy has been
     * "opened". */
    cyg_interrupt_unmask(floppy_vector);

    /* Decode the message parameters. */
    /* Hard code first drive XXX */
    //  XXX  if (f_prepare(m_ptr->DEVICE) == NIL_DEV)
    if (f_prepare(0) == NIL_DEV)
	return(ENXIO);

    dtype = f_device & DEV_TYPE_BITS;	/* get density from minor dev */
    if (dtype >= MINOR_fd0p0)
	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;
	(void) f_prepare(f_device);	/* Recompute parameters. */
	return(0);
    }
    if (f_device & FORMAT_DEV_BIT) return(EIO);	/* Can't format /dev/fdN */

    /* The device opened is /dev/fdN.  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 the motor
     * is still running then assume nothing changed.
     */
    if (f_fp->fl_density != NO_DENS) {
	if (motor_status & (1 << f_drive))
	    return(0);
	if (test_read(f_fp->fl_density) == 0)
	    return(0);
    }

    /* 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) == 0) {
	    /* 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(0);
	}
	/* 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.
     */
    int device;
    unsigned int position;
    iovec_t iovec1;
    char buf[(unsigned) 2 * DMA_BUF_SIZE]; /* I guess we should do
					    * something better,
					    * because this space isn't
					    * really used below. XXX */

    f_fp->fl_density = density;
    device = ((density + 1) << DEV_TYPE_SHIFT) + f_drive;

    (void) f_prepare(device);
    position = (unsigned int) f_dp->test << SECTOR_SHIFT;
    iovec1.iov_addr = buf;
    iovec1.iov_size = SECTOR_SIZE;
    f_transfer(DEV_GATHER, position, &iovec1, 1);
    if (iovec1.iov_size != 0)
	return(EIO);

// XXX    partition(&f_dtab, f_drive, P_FLOPPY);
    return(0);
}


/*============================================================================*
 *				f_geometry				      *
 *============================================================================*/
/* PRIVATE void f_geometry(entry)
   struct partition *entry;
   {
   entry->cylinders = f_dp->cyls;
   entry->heads = NR_HEADS;
   entry->sectors = f_sectors;
   }
*/
#define SECTOR_SIZE 512

#include <stdlib.h>

#include "byte2sec.h"

/*
 *-----------------------------------------------------------------------------
 *
 * bytes2sectors --
 *
 * 	Takes a length and position in bytes, and allocates a buffer,
 * 	based on sector size to hold the other buffer.
 *
 * Results:
 *	Returns -1 on error, 0 if nothing is wrong.
 *
 * Side Effects:
 *
 *	Allocates a buffer that is usually freed by sector2bytes.
 *
 *-----------------------------------------------------------------------------
 */

int bytes2sectors (unsigned int bytelen, unsigned int inpos,
		   char **newbuf, unsigned int *sectorlen, unsigned int *outpos)
{
    int sector_offset = 0;

    sector_offset = inpos - (*outpos * SECTOR_SIZE);
    *outpos = inpos / SECTOR_SIZE;
    *sectorlen = ((bytelen + sector_offset) / SECTOR_SIZE) + 1;
    *newbuf = malloc(*sectorlen * SECTOR_SIZE);
    if (*newbuf == NULL) {
	diag_printf("Error malloc'ing %d bytes in bytes2sectors\n",
		    *sectorlen * SECTOR_SIZE);
	return -1;
    }
    return 0;
}

/*
 *-----------------------------------------------------------------------------
 *
 * sector2bytes --
 *
 * 	Takes a buffer in sector size chunks, and transfers it to a
 * 	'byte sized' buffer.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *
 *	Free's the sector-sized buffer.
 *
 *-----------------------------------------------------------------------------
 */

int sector2bytes (char *newbuf, unsigned int newpos,
		  char *oldbuf, unsigned int *oldlen, unsigned int oldpos)
{
    unsigned int sector_offset;

    sector_offset = oldpos - (newpos * SECTOR_SIZE);
    memcpy(oldbuf, newbuf + sector_offset, *oldlen);
    free(newbuf);
}
int bytes2sectors (unsigned int bytelen, unsigned int inpos,
		   char **newbuf, unsigned int *sectorlen, unsigned int *outpos);

int sector2bytes (char *newbuf, unsigned int newpos,
		  char *oldbuf, unsigned int *oldlen, unsigned int oldpos);

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]