/*
 *	Name:			nt_flop.c
 *	Derived From:	DEC begat M88K begat NeXT finally begat Generic.
 *	Author:			Jason Proctor
 *	Created On:		Nov 8 1990
 *	Sccs ID:		10/13/92 @(#)nt_flop.c	1.9
 *	Purpose:		nt real floppy server.
 *
 *	(c)Copyright Insignia Solutions Ltd., 1990. All rights reserved.
 *
 *	Notes:
 *		Updated for 3.0 base by Jerry Richemont.
 *		Further updated by Ian Reid to support two floppy
 *		drives.  Support is compile time dependant on the
 *		standard SoftPC defines.
 *
 *		This implementation requires that you provide a
 *	host_rflop_drive_type() function which knows what kind of drive(s)
 *	your machine has; ie returns GFI_DRIVE_TYPE_xxxx.
 */

/********************************************************/

/* INCLUDES */

#include "nt.h"
#include "ntrtl.h"
#include "nturtl.h"
#include "windows.h"
#include "winioctl.h"

#include "insignia.h"
#include "host_def.h"


#include <stdio.h>
#include <errno.h>
#include TypesH

#include "xt.h"
#include "cpu.h"
#include "trace.h"
#include "error.h"
#include "fla.h"
#include "dma.h"
#include "config.h"
#include "debug.h"
#include "lock.h" 
#include "timer.h"
#include "floppy.h"
#include "cmos.h"
#include "gfi.h"

#include "nt_uis.h"
#include "nt_reset.h"
/********************************************************/

/* DEFINES */

#ifdef min
#undef min
#endif
#define min(a,b)	(a > b ? b : a)

#define PC_MAX_DRIVE_TYPES              2
#define PC_MAX_DENSITY_TYPES            2
#define PC_MAX_FLOPPY_TYPES       (PC_MAX_DRIVE_TYPES * PC_MAX_DENSITY_TYPES)

#define PC_HEADS_PER_DISKETTE	  	2
#define PC_N_VALUE			2
#define PC_BYTES_PER_SECTOR		512

/* disk buffer size, in bytes */
// KEEP SYNC WITH MAX_DISKIO_SIZE defined in nt_fdisk.c
#define BS_DISK_BUFFER_SIZE		0x9000

/* double stepping factor */
#define	DOUBLE_STEP_FACTOR		1

/* density types */
#define	DENSITY_LOW			0
#define	DENSITY_HIGH			1
#define DENSITY_EXTENDED		2
#define DENSITY_UNKNOWN 		100
/* motor states */
#define MOTOR_OFF			0
#define MOTOR_ON			1

#ifndef PROD
#define BREAK_ON_AND	    0x01
#define BREAK_ON_OR	    0x02
#define BREAK_ON_XOR	    0x03

UTINY	break_cylinder = 0xff;
UTINY	break_head = 0xff;
UTINY	break_sector = 0xff;
#endif

#ifndef PROD
DWORD	rflop_dbg = 0;

#define RFLOP_READ	0x01
#define RFLOP_WRITE	0x02
#define RFLOP_FORMAT	0x04
#define RFLOP_SEEK	0x08
#define RFLOP_READID	0x10
#define RFLOP_RESET	0x20
#define RFLOP_SPECIFY	0x40
#define RFLOP_READTRACK 0x80
#define RFLOP_RECAL	0x100
#define RFLOP_SENSEDRV	0x200
#define RFLOP_RATE	0x1000
#define RFLOP_CHANGE	0x2000
#define RFLOP_DRIVE_ON	0x4000
#define RFLOP_DRIVE_OFF 0x8000
#define RFLOP_OPEN	0x10000
#define RFLOP_CLOSE	0x20000
#define RFLOP_GUESS_MEDIA 0x40000

#define RFLOP_BREAK	0x80000000

#endif

/********************************************************/

/* TYPEDEFS */

struct flop_struct
{
	int trks_per_disk;
	int secs_per_trk;
};

/*
 * This structure contains all the drive specific information.  That is
 * status which is unique to each drive, and must therefore be maintained
 * on a per drive basis.
 */
typedef struct floppy_info
{
	HANDLE		diskette_fd;

/*
 * drive_type	- the highest density format which this drive supports,
 *		  e.g. GFI_DRIVE_TYPE_144, GFI_DRIVE_TYPE_288
 * flop_type	- the basic drive type, expressed as the lowest density
 *		  possible for this format. For 5.25" disks this is
 *		  GFI_DRIVE_TYPE_360, for 3.5" it is GFI_DRIVE_TYPE_720.
 */
	USHORT		drive_type;
	USHORT		flop_type;
	USHORT 		last_seek;
	USHORT 		last_head_seek;
/*
 * Change line state. 
 * This is a heuristic to try to fake up the correct change line behaviour 
 * without having a change line. The state of the change line is returned 
 * as CHANGED unless the diskette motor has been continuously ON since the 
 * last reset.
 */
	BOOL  		change_line_state;
	SHORT 		motor_state;
	SHORT 		media_density;
	USHORT 		max_track;

	USHORT 		secs_per_trk;
	USHORT 		trks_per_disk;
	BOOL		locked;
	BOOL		abort_lock;
	DWORD		align_factor;
        UTINY           C;
	UTINY		H;
	UTINY		R;
	UTINY		N;
	char		device_name[MAX_PATH];	/* device name */
} FL, *FLP;

#define FLOPPY_IDLE_PERIOD  0xFF

/* parameter passed from main thread to FDC thread */
typedef struct _FDC_PARMS{
FDC_CMD_BLOCK	* command_block;
FDC_RESULT_BLOCK * result_block;
} FDC_PARMS, *PFDC_PARMS;


/********************************************************/

/* PROTOTYPES */

/* As this function is called outside of this module, this
 * prototype should probably be in a global header file
 */
#ifdef EJECT_FLOPPY
GLOBAL void host_floppy_eject IPT1(UTINY, drive);
#endif /* EJECT_FLOPPY */

void host_flpy_heart_beat(void);

/* routines used internally */

/* routines called via vector table: all the prototypes are in gfi.h
** so everybody matches. If you want to use this file to base a 
** host floppy module on, you know that all the functions that must
** be declared properly for gfi to work will be.
**                                                    GM.
 */
ULONG nt_floppy_read (UTINY drive, ULONG Offset, ULONG Size, PBYTE Buffer);
ULONG nt_floppy_write (UTINY drive, ULONG Offset, ULONG Size, PBYTE Buffer);
BOOL nt_floppy_verify (UTINY drive, ULONG Offset, ULONG Size);
MEDIA_TYPE nt_floppy_get_media_type(BYTE drive, WORD cylinders, WORD sectors, WORD heads);
BOOL nt_floppy_format (UTINY drive, WORD Cylinder, WORD Head, MEDIA_TYPE media);
BOOL nt_floppy_close (UTINY drive);
BOOL nt_floppy_media_check (UTINY drive);
BOOL dismount_drive(FLP flp);

void nt_floppy_release_lock(void);

#ifndef PROD
VOID nt_rflop_break(VOID);
#endif

void fdc_command_completed (BYTE drive, BYTE fdc_command);
void fdc_thread (PFDC_PARMS fdc_parms);

LOCAL BOOL nt_gfi_rdiskette_init IPT1( UTINY, drive );
LOCAL VOID nt_gfi_rdiskette_term IPT1( FLP, flp );
LOCAL SHORT nt_rflop_drive_on IPT1( UTINY, drive );
LOCAL SHORT nt_rflop_drive_off IPT1( UTINY, drive );
LOCAL SHORT nt_rflop_change IPT1( UTINY, drive );
LOCAL SHORT nt_rflop_drive_type IPT1( UTINY, drive );
LOCAL SHORT nt_rflop_rate IPT2( UTINY, drive, half_word, rate);
LOCAL SHORT nt_rflop_reset IPT2( FDC_RESULT_BLOCK *, res, UTINY, drive );
 SHORT nt_rflop_command IPT2( FDC_CMD_BLOCK *, ip, FDC_RESULT_BLOCK *, res);
LOCAL HANDLE nt_rdiskette_open_drive IPT1 ( UTINY, drive );
SHORT guess_media_density IPT1 (UTINY, drive);
LOCAL VOID set_floppy_parms	IPT1 (FLP, flp);
LOCAL BOOL dos_compatible
	IPT5 (FLP, flp, UTINY, cyl, UTINY, hd, UTINY, sec, UTINY, n);
LOCAL int dos_offset
	IPT4 (FLP, flp, UTINY, cyl, UTINY, hd, UTINY, sec);
LOCAL VOID update_chrn(FLP flp, UTINY mt, UTINY eot, UTINY sector_count);
BOOL lock_drive (UTINY drive);
BOOL unlock_drive (UTINY drive);
HANDLE get_drive_handle (UTINY drive);
SHORT fdc_read_write ( FDC_CMD_BLOCK * ip, FDC_RESULT_BLOCK * res);



/********************************************************/
/** IMPORT **/
void fdisk_heart_beat(void);
ULONG  disk_read(HANDLE, PLARGE_INTEGER, ULONG, PBYTE);
ULONG  disk_write(HANDLE, PLARGE_INTEGER, ULONG, PBYTE);
BOOL   disk_verify(HANDLE, PLARGE_INTEGER, ULONG);
PBYTE  get_aligned_disk_buffer(void);
extern BYTE number_of_fdisk;
extern DWORD max_align_factor;
extern DWORD cur_align_factor;
extern WORD * pFDAccess;

/********************************************************/

/* STATIC GLOBALS */

 
FL floppy_data[MAX_FLOPPY];

 struct flop_struct floppy_tksc [6] =
{
	{0, 0},		/* GFI_DRIVE_TYPE_NULL */
	{40, 9},	/* GFI_DRIVE_TYPE_360  */
	{80, 15},	/* GFI_DRIVE_TYPE_12   */
	{80, 9},	/* GFI_DRIVE_TYPE_720  */
	{80, 18},	/* GFI_DRIVE_TYPE_144  */
	{80, 36}	/* GFI_DRIVE_TYPE_288  */
};
// table used to convert GFI diskette type to NT diskette type
static MEDIA_TYPE media_table[GFI_DRIVE_TYPE_MAX] = {
		    Unknown,
		    F5_360_512,
		    F5_1Pt2_512,
		    F3_720_512,
		    F3_1Pt44_512,
		    F3_2Pt88_512
		};

SHORT	density_state;
BOOL	density_changed = TRUE;
UTINY	last_drive = 0xff;
BOOL	fdc_reset = FALSE;
HANDLE	fdc_thread_handle = NULL;
UTINY	nt_floppy_idle_count;
extern UTINY number_of_floppy;
FDC_PARMS   fdc_parms;

/*
 * Debugging info only, for non-prod cases
 */
#ifndef PROD
 CHAR *cmd_name [] =
{
	"Invalid command (00)",		/* 00 */
	"Invalid command (01)",		/* 01 */
	"Read a Track",			/* 02 */
	"Specify",			/* 03 */
	"Sense Drive Status",		/* 04 */
	"Write Data",			/* 05 */
	"Read Data",			/* 06 */
	"Recalibrate",			/* 07 */
	"Sense Interrupt Status",	/* 08 */
	"Write Deleted Data",		/* 09 */
	"Read ID",			/* 0A */
	"Invalid Command (0B)",		/* 0B */
	"Read Deleted Data",		/* 0C */
	"Format a Track",		/* 0D */
	"Invalid Command (0E)",		/* 0E */
	"Seek",				/* 0F */
	"Invalid Command (10)",		/* 10 */
	"Scan Equal",			/* 11 */
	"Invalid Command (12)",		/* 12 */
	"Invalid Command (13)",		/* 13 */
	"Invalid Command (14)",		/* 14 */
	"Invalid Command (15)",		/* 15 */
	"Invalid Command (16)",		/* 16 */
	"Invalid Command (17)",		/* 17 */
	"Invalid Command (18)",		/* 18 */
	"Scan Low or Equal",		/* 19 */
	"Invalid Command (1A)",		/* 1A */
	"Invalid Command (1B)",		/* 1B */
	"Invalid Command (1C)",		/* 1C */
	"Scan High or Equal",		/* 1D */
	"Invalid Command (1E)",		/* 1E */
	"Invalid Command (1F)",		/* 1F */
};
#endif	/* PROD */

char	dump_buf[256];


/* the disk buffer, there is only one, even though there may be two
 * drives.  Should be O.K. as floppy disk accesses will be single
 * threaded.
 */
 UTINY *disk_buffer;

/* Report any errors in open_diskette() */
 int last_error = C_CONFIG_OP_OK;

/********************************************************/

/* GLOBAL FUNCTIONS */

/*   These functions called by config/UIF/startup now form the only
** interface between SoftPC and a floppy module. XXX_active() will
** turn the floppy emmulation in the module on by loading the global
** gfi_function_table[] with pointers to appropriate  functions
** defined in this module.
**     The floppy supported here is turned off by asking the empty floppy
** module to turn itself on in its place.
** 
**     This makes a nice orthogonal interface which keeps everything save
** the three control functions	(private). The functions that are put
** in the table are defined only in gfi.h as typedefs so they are easy to
** get right.
** 
** 	This enabling/disabling via the gfi_function_table[] does not 
** take place instead of any host ioctls/opens/closes etc that are needed 
** to actually open or close the device, it forms the interface for SoftPC.
** 
** 	Really, this approach is a small tidy up of the way things are
** already done; existing host floppy code will require very small changes.
** 
** 	GM
*/ 

/********************************************************/

/* Turn the floppy on and off. Off means release the driver so another
** process can use it.
*/

GLOBAL SHORT 
host_gfi_rdiskette_active IFN3(UTINY, hostID, BOOL, active, CHAR *, err)
{
	UTINY drive 	= hostID - C_FLOPPY_A_DEVICE;
	FLP flp 	= &floppy_data[drive];

	if(active)
	{
		if (!nt_gfi_rdiskette_init(drive))
		{
		  /* Device is not a valid floppy */

			return( C_CONFIG_NOT_VALID );
		}
		return(C_CONFIG_OP_OK);
	}
	else
	{
#ifdef  EJECT_FLOPPY
		host_floppy_eject(drive);
#endif  /* EJECT_FLOPPY */
		nt_gfi_rdiskette_term(flp);	/*  shutdown process */
		gfi_empty_active(hostID,TRUE,err);  /* Tell gfi 'empty' is now active */
		return(C_CONFIG_OP_OK);
	}
}

/********************************************************/


/*   Validate the floppy device name passed from the config system.
** Empty string is valid; it means 'no floppy'. Otherwise return OK is
** the name is 'probably' a valid device. It cannot be opened at this
** stage because if there is no floppy in the drive, the open will fail.
**
**		GM.
*/

GLOBAL SHORT 
host_gfi_rdiskette_valid IFN3(UTINY,hostID,ConfigValues *,vals,CHAR *,err)
{
#ifndef NTVDM
	UTINY           cmos_byte;
	UTINY drive 	= hostID - C_FLOPPY_A_DEVICE;
	FLP flp 	= &floppy_data[drive];

	if(!strcmp(vals->string,""))
		return(C_CONFIG_OP_OK);

	strcpy(flp->device_name, host_expand_environment_vars(vals->string));

	if(!host_validate_pathname(flp->device_name))
	{
		strcpy(err, host_strerror(errno));
		flp->device_name[0] = '\0';
		return( EG_MISSING_FILE );
	}
	if(!host_file_is_char_dev(flp->device_name))
	{
		flp->device_name[0] = '\0';
		return( EG_NOT_CHAR_DEV );
	}

	/* Check the CMOS RAM values */
	cmos_read_byte(CMOS_DISKETTE, &cmos_byte);
	if (drive == 0)
		cmos_byte >>= 4;

	cmos_byte &= 0xf;       /* compare nibble value only */
	flp->drive_type = host_rflop_drive_type(drive);
	if (cmos_byte != flp->drive_type)
		vals->rebootReqd = TRUE;
#endif
	return(C_CONFIG_OP_OK);
}

/********************************************************/

GLOBAL VOID 
host_gfi_rdiskette_change IFN2(UTINY, hostID, BOOL, apply)
{
#ifndef NTVDM
	FLP flp = &floppy_data[hostID - C_FLOPPY_A_DEVICE];

	if (apply) 
	{
		nt_gfi_rdiskette_term(flp);
	}
#endif
}

/********************************************************/

#ifdef EJECT_FLOPPY
GLOBAL void host_floppy_eject IFN1(UTINY, drive)
{
	CHAR           *ebuf;
	FLP             flp = &floppy_data[drive];
	BOOL            device_was_closed = FALSE;

	/* open the device */
	if (flp->diskette_fd == INVALID_HANDLE_VALUE)
	{
		device_was_closed = TRUE;
		(void) nt_rdiskette_open_drive(drive);
	}

	/* Do the ioctl, put your ioctl here 

	if (ioctl(flp->diskette_fd, SMFDEJECT) < 0)
	{
		ebuf = host_strerror(errno);
		assert1(NO, "host_eject_floppy: %s", ebuf);
	}*/

	/* Close the device if it wasn't open */

	if (device_was_closed)
	{
		nt_gfi_rdiskette_term(flp);
	}
	else
	{
		/* Line change ONLY if device was actively open */
		flp->change_line_state = TRUE;
	}
}

#endif                          /* EJECT_FLOPPY */


/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
/*:::::::::::::::::: FLOPPY heart beat call ::::::::::::::::::::::::::::::::::::*/
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
GLOBAL void host_flpy_heart_beat(void)
{
    if ( (VDMForWOW && pFDAccess && *pFDAccess)|| --nt_floppy_idle_count == 0)
	nt_floppy_release_lock();
    if (number_of_fdisk != 0)
	fdisk_heart_beat();

}




/********************************************************/

/* initialise GFI function table */
LOCAL BOOL nt_gfi_rdiskette_init IFN1(UTINY, drive)
{
	FLP flp;
	DISK_GEOMETRY	disk_geometry[20];
	ULONG	media_types;
	CHAR DeviceName[] = "\\\\.\\A:";
	NTSTATUS    status;
	IO_STATUS_BLOCK io_status_block;
	FILE_ALIGNMENT_INFORMATION align_info;

	flp = &floppy_data[drive];
	flp->diskette_fd = INVALID_HANDLE_VALUE;

	DeviceName[4] += drive;
	strcpy(flp->device_name, (const char *)DeviceName);
	/*
	* Initialise the floppy on the required drive:
	*
	*      0  - Drive A,  1  - Drive B
	*/

	flp->drive_type = GFI_DRIVE_TYPE_NULL;
	/* open the device */
	if ((flp->diskette_fd = nt_rdiskette_open_drive (drive)) == NULL) {
	    return FALSE;
	}
	// get alignment factor
	status = NtQueryInformationFile(flp->diskette_fd,
					&io_status_block,
					&align_info,
					sizeof(FILE_ALIGNMENT_INFORMATION),
					FileAlignmentInformation
					);
	if (!NT_SUCCESS(status)) {
	    nt_gfi_rdiskette_term(flp);
	    return(FALSE);
	}
	flp->align_factor = align_info.AlignmentRequirement;
	if (flp->align_factor > max_align_factor)
	    max_align_factor = flp->align_factor;


	// enumerate possible supported media for this drive
	// to figure out the drive type.
	status = NtDeviceIoControlFile(flp->diskette_fd,
				       NULL,
				       NULL,
				       NULL,
				       &io_status_block,
				       IOCTL_DISK_GET_MEDIA_TYPES,
				       NULL,
				       0L,
				       (PVOID)&disk_geometry,
				       sizeof(disk_geometry)
				       );
	 if (!NT_SUCCESS(status)) {
	    nt_gfi_rdiskette_term(flp);
	    return FALSE;
	}
	nt_gfi_rdiskette_term(flp);
	media_types = io_status_block.Information / sizeof(DISK_GEOMETRY);

	for (; media_types != 0; media_types--) {
		switch (disk_geometry[media_types - 1].MediaType) {
		    case F5_360_512:
			    if (flp->drive_type != GFI_DRIVE_TYPE_12)
				flp->drive_type = GFI_DRIVE_TYPE_360;
			    break;
		    case F5_1Pt2_512:
			    flp->drive_type = GFI_DRIVE_TYPE_12;
			    break;
		    case F3_720_512:
			    if (flp->drive_type != GFI_DRIVE_TYPE_144 &&
				flp->drive_type != GFI_DRIVE_TYPE_288)
				flp->drive_type = GFI_DRIVE_TYPE_720;
			    break;
		    case F3_1Pt44_512:
			    if (flp->drive_type != GFI_DRIVE_TYPE_288)
				flp->drive_type = GFI_DRIVE_TYPE_144;
			    break;
		    case F3_2Pt88_512:
			    flp->drive_type = GFI_DRIVE_TYPE_288;
			    break;
		}
	}
	if (flp->drive_type == GFI_DRIVE_TYPE_NULL)
	    return FALSE;
	/* configure its vectors here */
	gfi_function_table[drive].command_fn   = nt_rflop_command;
	gfi_function_table[drive].drive_on_fn  = nt_rflop_drive_on;
	gfi_function_table[drive].drive_off_fn = nt_rflop_drive_off;
	gfi_function_table[drive].reset_fn     = nt_rflop_reset;
	gfi_function_table[drive].high_fn      = nt_rflop_rate;
	gfi_function_table[drive].drive_type_fn= nt_rflop_drive_type;
	gfi_function_table[drive].change_fn    = nt_rflop_change;
	flp->C = flp->H = 0;
	flp->R = 1;
	flp->N = PC_N_VALUE;
	flp->locked = FALSE;
	flp->abort_lock = FALSE;
	return TRUE;
}

/********************************************************/

/* reset GFI function table */
/* currently drive is ignored */
LOCAL  VOID nt_gfi_rdiskette_term IFN1(FLP, flp)
{

	// NtOpenFile returns NULL if we can not open a handle
	// while win32 OpenFile/CreateFile returns INVALID_HANDLE_VALUE
	// if failed to open/create the file.
	if (flp->diskette_fd != NULL)
	{
//		host_clear_lock(flp->diskette_fd);
		NtClose(flp->diskette_fd);
		flp->diskette_fd = INVALID_HANDLE_VALUE;
	}
}

/********************************************************/

/* open the floppy device file */
LOCAL HANDLE nt_rdiskette_open_drive IFN1(UTINY, drive)
{

    CHAR NtDeviceName[] = "\\DosDevices\\A:";
    PUNICODE_STRING Unicode;
    ANSI_STRING DeviceNameA;
    NTSTATUS Status;
    OBJECT_ATTRIBUTES	FloppyObj;
    IO_STATUS_BLOCK IoStatusBlock;
    HANDLE  fd;

    NtDeviceName[12] += drive;

    RtlInitAnsiString( &DeviceNameA, NtDeviceName);

    Unicode = &NtCurrentTeb()->StaticUnicodeString;

    Status = RtlAnsiStringToUnicodeString(Unicode,
					  &DeviceNameA,
					  FALSE
					  );
    if ( !NT_SUCCESS(Status) )
	return NULL;


    InitializeObjectAttributes(
			       &FloppyObj,
			       Unicode,
			       OBJ_CASE_INSENSITIVE,
			       NULL,
			       NULL
			       );
    Status = NtOpenFile(
			&fd,
			(ACCESS_MASK) FILE_READ_ATTRIBUTES | SYNCHRONIZE,
			&FloppyObj,
			&IoStatusBlock,
			FILE_SHARE_READ | FILE_SHARE_WRITE,
			FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE
			);

    if (!NT_SUCCESS(Status))
	return NULL;
    else
	return fd;

}


ULONG nt_floppy_read(BYTE drive, ULONG Offset, ULONG Size, PBYTE Buffer)
{
    HANDLE  fd;
    LARGE_INTEGER large_integer;

    fd = get_drive_handle(drive);

    if (fd == INVALID_HANDLE_VALUE)
	return 0;
    large_integer.LowPart = Offset;
    large_integer.HighPart = 0;
    return(disk_read(fd, &large_integer, Size, Buffer));
}


ULONG nt_floppy_write(BYTE drive, ULONG Offset, ULONG Size, PBYTE Buffer)
{
    HANDLE  fd;
    LARGE_INTEGER large_integer;
    ULONG   size_returned;

    fd = get_drive_handle(drive);
    if (fd == INVALID_HANDLE_VALUE || !lock_drive(drive))
	return 0;

    large_integer.LowPart = Offset;
    large_integer.HighPart = 0;
    size_returned = disk_write(fd, &large_integer, Size, Buffer);
    unlock_drive(drive);
    return (size_returned);
}

BOOL nt_floppy_format(BYTE drive, WORD Cylinder, WORD Head, MEDIA_TYPE Media)
{
    FORMAT_PARAMETERS fmt;
    WORD    bad_track;
    ULONG   size_returned;
    HANDLE  fd;
    BOOL    result;

    result = FALSE;
    fmt.MediaType = Media;
    fmt.StartHeadNumber = fmt.EndHeadNumber = Head;
    fmt.StartCylinderNumber = fmt.EndCylinderNumber = Cylinder;
    fd = get_drive_handle(drive);
    if (fd == INVALID_HANDLE_VALUE || !lock_drive(drive))
	return FALSE;
    result = DeviceIoControl(fd,
			     IOCTL_DISK_FORMAT_TRACKS,
			     (PVOID) &fmt,
			     sizeof(fmt),
			     &bad_track,
			     sizeof(bad_track),
			     &size_returned,
			     NULL
			     );
    unlock_drive(drive);
    return result;
}

// for floppy, the ioctl call DISK_VERIFY doesn't work
// we have to use read for verification
BOOL nt_floppy_verify(BYTE drive, DWORD Offset, DWORD Size)
{
    HANDLE  fd;
    LARGE_INTEGER   large_integer;

    fd = get_drive_handle(drive);
    if (fd != INVALID_HANDLE_VALUE) {
	large_integer.LowPart = Offset;
	large_integer.HighPart = 0;
	return(disk_verify(fd,
			   &large_integer,
			   Size
			   ));
    }
    else
	return FALSE;
}


HANDLE get_drive_handle(UTINY drive)
{
    FLP   flp;
    WORD  wRetry = 3;

    flp = &floppy_data[drive];
    // assign new alignment factor  and grab the buffer
    cur_align_factor = flp->align_factor;
    if ((disk_buffer = get_aligned_disk_buffer()) == NULL)
	return (INVALID_HANDLE_VALUE);


    if (flp->diskette_fd != INVALID_HANDLE_VALUE && fdc_reset) {
	nt_floppy_close(drive);
	fdc_reset = FALSE;
    }
    if (flp->diskette_fd == INVALID_HANDLE_VALUE) {
	while (wRetry &&
	       (flp->diskette_fd = CreateFile ((const char *)flp->device_name,
					       GENERIC_READ | GENERIC_WRITE,
					       FILE_SHARE_READ | FILE_SHARE_WRITE,
					       NULL,
					       OPEN_EXISTING,
					       0,
					       0
					       )) == INVALID_HANDLE_VALUE) {
	    wRetry--;
	}
    }
    nt_floppy_idle_count = FLOPPY_IDLE_PERIOD;
    if(flp->diskette_fd != INVALID_HANDLE_VALUE)
	(*(pFDAccess))++;

    return (flp->diskette_fd);
}

BOOL nt_floppy_close(UTINY drive)
{
    FLP flp;
    flp = &floppy_data[drive];

#ifndef PROD
    if (rflop_dbg & RFLOP_CLOSE)
	sprintf(dump_buf, "Close drive %C: handle\n", drive + 'A');
	OutputDebugString(dump_buf);
#endif

    if (flp->diskette_fd != INVALID_HANDLE_VALUE) {
	if (flp->locked) {
	    unlock_drive(drive);
	}
	dismount_drive(flp);
	CloseHandle(flp->diskette_fd);
	flp->diskette_fd = INVALID_HANDLE_VALUE;
	(*(pFDAccess))--;
    }
    density_changed = TRUE;
    return TRUE;
}


MEDIA_TYPE
nt_floppy_get_media_type
(
BYTE	drive,
WORD	cylinders,
WORD	sectors,
WORD	heads
)
{
    FLP 	flp;
    BYTE	index;

    flp = &floppy_data[drive];
    if (heads == 2){
	index = flp->drive_type;
	switch (index) {
	    case GFI_DRIVE_TYPE_12:
		    if (cylinders == floppy_tksc[index].trks_per_disk &&
			sectors == floppy_tksc[index].secs_per_trk)
			break;
		    index = GFI_DRIVE_TYPE_360;

	    case GFI_DRIVE_TYPE_360:
		    if (cylinders != floppy_tksc[index].trks_per_disk ||
			sectors != floppy_tksc[index].secs_per_trk)
			index = GFI_DRIVE_TYPE_NULL;
		    break;

	    case GFI_DRIVE_TYPE_288:
		    if (cylinders == floppy_tksc[index].trks_per_disk &&
			sectors == floppy_tksc[index].secs_per_trk)
			break;
		    index = GFI_DRIVE_TYPE_144;

	    case GFI_DRIVE_TYPE_144:
		    if (cylinders == floppy_tksc[index].trks_per_disk &&
			sectors == floppy_tksc[index].secs_per_trk)
			break;
		    index = GFI_DRIVE_TYPE_720;

	    case GFI_DRIVE_TYPE_720:
		    if (cylinders == floppy_tksc[index].trks_per_disk &&
			sectors == floppy_tksc[index].secs_per_trk)
			break;
	    default:
		index = GFI_DRIVE_TYPE_NULL;
	}
    }
    else
	index = GFI_DRIVE_TYPE_NULL;
    return(media_table[index]);

}

BOOL nt_floppy_media_check (UTINY drive)
{
    FLP    flp;
    ULONG  size_returned;

    flp = &floppy_data[drive];
    if (flp->diskette_fd == INVALID_HANDLE_VALUE)
	return FALSE;
    return(DeviceIoControl(flp->diskette_fd,
			   IOCTL_DISK_CHECK_VERIFY,
			   NULL,
			   0,
			   NULL,
			   0,
			   &size_returned,
			   NULL
			   ));
}
void nt_floppy_release_lock(void)
{
    UTINY  drive;
    FLP    flp;

    for ( drive = 0; drive < number_of_floppy; drive++) {
	flp = &floppy_data[drive];
        if (flp->diskette_fd != INVALID_HANDLE_VALUE)
	    nt_floppy_close(drive);
    }
}

/********************************************************/

/* perform an FDC command */
 SHORT
nt_rflop_command
	  IFN2(FDC_CMD_BLOCK *, command_block, FDC_RESULT_BLOCK *,result_block)
{
	USHORT transfer_count; /* Surely counts cannot be negative?  GM  */
	UTINY	drive;
	FLP flp;
	BOOL failed = FALSE;
	UTINY C, H, N, S, D;
	USHORT dma_size;
	ULONG transfer_size;
	ULONG transferred_size;
	long transfer_start;
	sys_addr dma_address;
	DWORD	fdc_thread_id;
	BYTE	fdc_command;
	BOOL	must_lock;


	nt_floppy_idle_count = FLOPPY_IDLE_PERIOD;

	note_trace1 (GFI_VERBOSE, "FDC: %s command",
		cmd_name [get_type_cmd (command_block)]);

	drive = get_type_drive(command_block);

	flp = &floppy_data[drive];

	/* Clear result status registers */
	put_r0_ST0 (result_block, 0);
	put_r0_ST1 (result_block, 0);
	put_r0_ST2 (result_block, 0);

	fdc_command = get_type_cmd(command_block);
        /* for those commands which need a valid floppy be inserted
	   we may have to create an independent thread to perform
	   the real operation if there is currenly no media
	   in the drive. The reason of this independent thread is that
	   the FDC is always in its execution phase even though there is
	   not media in the drive. As soon as you insert a media(bad or
	   good), it then performs its operation, terminates the phase,
	   raises interrupt and enters result phase. Some applications just
	   do a read id and  then wait the interrupt to occur no matter how
	   long the user will take to insert a media. To do this I broke up
	   the fdc_command routine so that both main and the fdc thread can
	   use the same code. There is not a good point that we can close the
	   thread handle as soon as it terminated. Therefore, we close the
	   handle on next fdc command
	*/
	if (fdc_thread_handle != NULL) {
	    CloseHandle(fdc_thread_handle);
	    fdc_thread_handle = NULL;
	}

	if ( (must_lock = (fdc_command == FDC_WRITE_DATA || fdc_command == FDC_FORMAT_TRACK)) ||
	     fdc_command == FDC_READ_DATA ||
	     fdc_command == FDC_READ_ID ||
	     fdc_command == FDC_READ_TRACK) {
	    // this might fail due to media changed and from FDC point of
	    // view, media change is meaningless. Therefore, we close the
	    // handle to the drive and reopen it so that the file system
	    // will mount  a new volume for us. Then we check the the
	    // media again. If it still fails, we are sure that there is
	    // no media in the drive so we go ahead to create a thread.
	    if (!nt_floppy_media_check(drive)) {
		nt_floppy_close(drive);
		get_drive_handle(drive);
		if (!nt_floppy_media_check(drive)) {
		    fdc_parms.command_block = command_block;
		    fdc_parms.result_block = result_block;
		    fdc_thread_handle = CreateThread(NULL,
					   0,
					   (LPTHREAD_START_ROUTINE)fdc_thread,
					   (PVOID)&fdc_parms,
					   0,
					   &fdc_thread_id
                                           );
		   return FAILURE;
		}
		else { // media changed
		    fdc_read_write(command_block, result_block);
		    return SUCCESS;
		}

	    }
	    else {
		fdc_read_write(command_block, result_block);
		return SUCCESS;
	    }
	}

	/* get disk bumpf */
	C = get_c0_cyl (command_block);
	H = get_c0_hd (command_block);
	S = get_c0_sector (command_block);
	N = get_c0_N (command_block);

	/* block timer to prevent interrupted system calls */
	host_block_timer ();

	switch (get_type_cmd (command_block))
	{

	case FDC_SPECIFY:
#ifndef PROD
		if (rflop_dbg & RFLOP_SPECIFY) {
		    OutputDebugString("Specify\n");
		    if (rflop_dbg & RFLOP_BREAK)
			nt_rflop_break();
		}
#endif
		break;


	case FDC_SENSE_DRIVE_STATUS:

#ifndef PROD
		if (rflop_dbg & RFLOP_SENSEDRV)
		    OutputDebugString("Sense Drive Status\n");
#endif
		D = get_c7_drive (command_block);
		put_r2_ST3_fault (result_block,0);
		put_r2_ST3_ready (result_block,1);
		put_r2_ST3_track_0 (result_block,(flp->last_head_seek == 0?1:0));
		put_r2_ST3_two_sided (result_block,1);
		put_r2_ST3_head_address (result_block,0);
		put_r2_ST3_unit (result_block,D);
		break;

	/* RECALIBRATE and SEEK do not really return any results */
	/* However, we return results here which are used by gfi.c */
	/* to construct the results for any following SenseInterruptStatus command */
	case FDC_RECALIBRATE:

#ifndef PROD
		if (rflop_dbg & RFLOP_RECAL)
		    OutputDebugString("Recalibrate\n");
#endif
		D = get_c5_drive (command_block);
		put_r3_ST0 (result_block,0);
		put_r1_ST0_int_code (result_block,0);
		put_r1_ST0_seek_end (result_block,1);
		put_r1_ST0_unit (result_block,D);
		put_r3_PCN (result_block,0);
		flp->last_seek = flp->last_head_seek = 0;
		flp->C = 0;
		break;
			
	case FDC_SEEK:

		D = get_c8_drive (command_block);
		C = get_c8_new_cyl (command_block);

#ifndef PROD
		if (rflop_dbg & RFLOP_SEEK) {
		    sprintf(dump_buf, "Seek: D C = %d %d \n", D, C);
		    OutputDebugString(dump_buf);
		    if (rflop_dbg & RFLOP_BREAK)
			nt_rflop_break();
		}
#endif

		put_r3_ST0(result_block,0);
		put_r1_ST0_head_address(result_block,1);
		put_r1_ST0_seek_end(result_block,1);
		put_r1_ST0_int_code(result_block,0);
		put_r1_ST0_unit(result_block,D);
		put_r3_PCN(result_block,C);
		flp->last_seek =  C;
		flp->last_head_seek = min(flp->last_seek,flp->max_track);
		flp->C = C;
		break;
		
	default:

#ifndef     PROD
		sprintf(dump_buf, "Receive unsupported command: command = %d\n",
			  get_type_cmd(command_block));
		OutputDebugString(dump_buf);
#endif

		put_r0_ST0 (result_block, 0);
		put_r1_ST0_int_code (result_block, 2);

		note_trace1 (GFI_VERBOSE,"FDC: Unimplemented command, type %d",
				get_type_cmd (command_block));
	}

tidy_and_return:
#ifndef PROD
	if (io_verbose & GFI_VERBOSE) {
		fprintf(trace_file,
		    "FDC: results %02x %02x %02x %02x %02x %02x %02x\n\n",
		    result_block[0], result_block[1], result_block[2],
		    result_block[3], result_block[4], result_block[5],
		    result_block[6]);
	}
#endif /* !PROD */

	host_release_timer ();

	return SUCCESS;
}

/********************************************************/

/* turn the motor on */
LOCAL  SHORT
nt_rflop_drive_on IFN1(UTINY, drive)
{
	FLP flp = &floppy_data[drive];

	note_trace0 (GFI_VERBOSE, "FDC: Drive on command");
#ifndef PROD
	if (rflop_dbg & RFLOP_DRIVE_ON) {
	    sprintf(dump_buf, "drive on: drive = %d\n", drive);
	    OutputDebugString(dump_buf);
	    if (rflop_dbg & RFLOP_BREAK)
		nt_rflop_break();
	}
#endif

	if (drive >= number_of_floppy)
	{
		note_trace1 (GFI_VERBOSE, 
			"FDC: Invalid drive %d accessed", drive);

		return (FAILURE);
	}

	flp->motor_state = MOTOR_ON;

	return (SUCCESS);
}

/********************************************************/

/* turn the motor off */
LOCAL  SHORT
nt_rflop_drive_off IFN1(UTINY, drive)
{
	FLP flp = &floppy_data[drive];
	note_trace0 (GFI_VERBOSE, "FDC: Drive off command");
#ifndef PROD
	if (rflop_dbg & RFLOP_DRIVE_OFF) {
	    sprintf(dump_buf, "drive off: drive = %d\n", drive);
	    OutputDebugString(dump_buf);
	    if (rflop_dbg & RFLOP_BREAK)
		nt_rflop_break();
	}
#endif

	if (drive >= number_of_floppy)
	{
		note_trace1 (GFI_VERBOSE,
			"FDC: Invalid drive %d accessed", drive);

		return (FAILURE);
	}

	flp->motor_state = MOTOR_OFF;

	/* I believe the line below makes booting off of low density
	 * diskettes problematical, particularly after restarts.
	 * Make your own mind up, the DEC code does it, the Sparc 
	 * doesn't (as at 11/9/92)
// we have no reason to do so in NT. As far as change line concerned,
// the file system will tell us "media has been changed" when we ask
// it to do some real work.
//	flp->change_line_state = TRUE;
	 */

	return (SUCCESS);
}

/********************************************************/

/* set the data transfer rate
 * This controls the "density" of the floppy: the rate MUST
 * match the actual media density for the disk controller to
 * be able to read the sectors.
 */
LOCAL  SHORT
nt_rflop_rate IFN2(UTINY, drive, half_word, rate)
{
    short   new_density;
// basically, "set rate applied to every drive since we have
// only one FDC(and mutiple drive).

#if 0
	FLP flp = &floppy_data[drive];

	switch (rate)
	{
		/* 2.88M high-density floppies */
		case DCR_RATE_1000:

			flp->density_state = DENSITY_EXTENDED;
			set_floppy_parms (flp);
			break;

		/* 1.2M or 1.44M high-density floppies */
		case DCR_RATE_500:

			flp->density_state = DENSITY_HIGH;
			set_floppy_parms (flp);
			break;

		/* 360K or 720K low-density floppies */
		case DCR_RATE_250:
		case DCR_RATE_300:

			flp->density_state = DENSITY_LOW;
			set_floppy_parms (flp);
			break;

		/* crapola density passed */
		default:

			return FAILURE;
	}
	note_trace2 (GFI_VERBOSE, "FDC: Set rate %0x => density %d",
		rate, flp->density_state);
	/* read floppy's boot sector */
	/* to determine the real density */
//	guess_media_density (drive);
#endif
#ifndef PROD
	if (rflop_dbg & RFLOP_RATE) {
	    sprintf(dump_buf, "set rate: rate = %d\n", rate);
	    OutputDebugString(dump_buf);
	    if (rflop_dbg & RFLOP_BREAK)
		nt_rflop_break();
	}
#endif

    switch (rate) {
	case DCR_RATE_1000:
		new_density = DENSITY_EXTENDED;
		break;
	case DCR_RATE_500:
		new_density = DENSITY_HIGH;
		break;
	case DCR_RATE_300:
	case DCR_RATE_250:
		new_density = DENSITY_LOW;
		break;
	default:
		return FAILURE;

    }
    if (new_density != density_state) {
	density_state = new_density;
	density_changed = TRUE;
    }
    return SUCCESS;
}
 

/********************************************************/

/* return the state of the change line */
LOCAL  SHORT
nt_rflop_change IFN1(UTINY, drive)
{
	FLP flp = &floppy_data[drive];
	note_trace1 (GFI_VERBOSE, "FDC: change_line %c",
		flp->change_line_state? 'T':'F');

	// if fla has been reset or the current change line is on(no media),
	// close the drive and reopen it. This is done because
	// nt_floppy_media_check(IOCTL_DISK_CHECK_VERIFY) will continue
	// to report media change even the users have a new disketter inserted.
	//
	if (fdc_reset || flp->change_line_state) {
	    fdc_reset = FALSE;
	    nt_floppy_close(drive);
	    get_drive_handle(drive);
	}
	flp->change_line_state = !nt_floppy_media_check(drive);

#ifndef PROD
	if (rflop_dbg & RFLOP_CHANGE) {
	    sprintf(dump_buf, "Check Change Line: line = %d\n", flp->change_line_state);
	    OutputDebugString(dump_buf);
	    if (rflop_dbg & RFLOP_BREAK)
		nt_rflop_break();
	}
#endif

	return(flp->change_line_state);
}

/********************************************************/

/* return the type of the drive */
LOCAL  SHORT
nt_rflop_drive_type IFN1(UTINY, drive)
{
	FLP flp = &floppy_data[drive];


/* setup base media type depending on drive type */
// I don't understand why we have to do this stuff every time.
	switch (flp->drive_type)
	{
		/* 5.25" drives */
		case GFI_DRIVE_TYPE_360:
		case GFI_DRIVE_TYPE_12:

			flp->flop_type = GFI_DRIVE_TYPE_360;
			break;

		/* 3.5" drives */
		case GFI_DRIVE_TYPE_720:
		case GFI_DRIVE_TYPE_144:
		case GFI_DRIVE_TYPE_288:

			flp->flop_type = GFI_DRIVE_TYPE_720;
			break;

		default:
			break;
	}

	set_floppy_parms(flp);
	note_trace2 (GFI_VERBOSE, "FDC: flop_type %d density %d",
		flp->flop_type, flp->drive_type - flp->flop_type);

	return (flp->drive_type);
}

/********************************************************/

/* close and reopen the device */
LOCAL  SHORT
nt_rflop_reset IFN2(FDC_RESULT_BLOCK *, result_block, UTINY, drive)
{
	FLP flp = &floppy_data[drive];

	note_trace0 (GFI_VERBOSE, "FDC: Reset command"); 

#ifndef  PROD
	if (rflop_dbg & RFLOP_RESET) {
	    OutputDebugString("reset\n");
	    if (rflop_dbg & RFLOP_BREAK)
		nt_rflop_break();
	}
#endif
	/* clear change line */
	flp->change_line_state = FALSE;
	fdc_reset = TRUE;

	if (fdc_thread_handle) {  // signal thread to exit
	    CloseHandle(fdc_thread_handle);
	    fdc_thread_handle = NULL;
	}
        return (SUCCESS);
}

 BOOL lock_drive(UTINY drive)
{
    ULONG   size_returned;
    WORD    wRetry;
    FLP     flp;
    int     iReply;
    char    FormatString[32];
    char    DriveLetter[32];


    flp = &floppy_data[drive];

    if (!flp->locked) {

           // try and lock the device
RetryLock:
        wRetry = 4;
        do {
            if (DeviceIoControl(flp->diskette_fd,
                                 FSCTL_LOCK_VOLUME,
                                 NULL,
                                 0,
                                 NULL,
                                 0,
                                 &size_returned,
                                 NULL))
               {
                break;
              }
              nt_floppy_idle_count = FLOPPY_IDLE_PERIOD;
              Sleep(10);
        } while (--wRetry);

        if (!wRetry) {
#ifndef PROD
            printf(dump_buf, "Lock drive failed, %ld\n", GetLastError());
#endif

            if (!LoadString(GetModuleHandle(NULL), ED_DRIVENUM,
                            FormatString,sizeof(FormatString)) )
                {
                 strcpy(FormatString,"Drive %c: ");
                 }

            sprintf(DriveLetter, FormatString, (char)(drive + (UTINY)'A'));

            iReply = RcMessageBox(ED_LOCKDRIVE, DriveLetter, NULL,
                         RMB_ABORT | RMB_RETRY | RMB_IGNORE | RMB_ICON_BANG);

            if (iReply == RMB_RETRY)
                goto RetryLock;
        }
        else {
	    flp->locked = TRUE;
        }
    }
    return (flp->locked);
}

 BOOL unlock_drive(UTINY drive)
{
    ULONG   size_returned;
    FLP     flp;

    flp = &floppy_data[drive];
    flp->locked = FALSE;
    return(DeviceIoControl(flp->diskette_fd,
			   FSCTL_UNLOCK_VOLUME,
			   NULL,
			   0,
			   NULL,
			   0,
			   &size_returned,
			   NULL
			   ));
}


LOCAL BOOL dismount_drive (FLP flp)
{

    HANDLE   fd;
    ULONG    size_returned;

    return DeviceIoControl(
			   flp->diskette_fd,
			   FSCTL_DISMOUNT_VOLUME,
			   NULL,
			   0,
			   NULL,
			   0,
			   &size_returned,
			   NULL
			   );
}


// this is the independent thread which performs FDC operation.
// this thread is not created from the beginning, instead, it was
// created on demand.
void fdc_thread(PFDC_PARMS fdc_parms)
{
    BYTE    drive, fdc_command;
    FDC_CMD_BLOCK *  command_block;

    command_block  = fdc_parms->command_block;

    drive = get_type_drive(command_block);
    fdc_command = get_type_cmd(command_block);
    while (TRUE) {
	nt_floppy_idle_count = FLOPPY_IDLE_PERIOD;
	// if there is media inserted, perform the operation
	// and enter result phase.
	if (get_drive_handle(drive) != INVALID_HANDLE_VALUE &&
	    nt_floppy_media_check(drive)) {
	    // force the file system to remount the volume
	    nt_floppy_close(drive);
	    // and then perform the operation
	    fdc_read_write (command_block, fdc_parms->result_block);
	    // raise an interrupt
	    fdc_command_completed(drive, fdc_command);
	    break;
	}
	// if reset happen, quit
	if (fdc_thread_handle == NULL)
	    break;
    }
}

SHORT
fdc_read_write (
FDC_CMD_BLOCK * command_block,
FDC_RESULT_BLOCK * result_block
)
{

	USHORT transfer_count; /* Surely counts cannot be negative?  GM  */
	FLP flp;
	BOOL failed = FALSE;
	UTINY C, H, N, S, D, drive, fdc_command;
	USHORT dma_size;
	ULONG transfer_size;
	ULONG transferred_size;
	long transfer_start;
	sys_addr dma_address;

	drive = get_type_drive(command_block);
	fdc_command = get_type_cmd(command_block);

	/* get disk bumpf */
	C = get_c0_cyl (command_block);
	H = get_c0_hd (command_block);
	S = get_c0_sector (command_block);
	N = get_c0_N (command_block);

	flp = &floppy_data[drive];
	/* block timer to prevent interrupted system calls */
	host_block_timer ();
	if (fdc_command != FDC_FORMAT_TRACK) {
	    if ((density_changed || drive != last_drive) &&
		guess_media_density(drive) != DENSITY_UNKNOWN) {
		set_floppy_parms(flp);
		density_changed = FALSE;
		last_drive = drive;
	    }
	    if (density_state != flp->media_density) {
		put_r0_ST0 (result_block, 0x40);
		put_r0_ST1 (result_block, 0);
		put_r1_ST1_no_address_mark (result_block,1);
		put_r0_ST2 (result_block, 0);
#ifndef PROD
		sprintf(dump_buf, "density mismatch: %d <-> %d\n", density_state,
			flp->media_density);
		OutputDebugString(dump_buf);
#endif
		goto fdc_read_write_exit;
	    }
	}


	/* 
	 * Do common setup processing, if read or write
	 */
	if (fdc_command == FDC_READ_DATA ||
	    fdc_command == FDC_WRITE_DATA) {
	    /*
	     * Find out how much gunk to transfer
	     */
	    dma_enquire (DMA_DISKETTE_CHANNEL, &dma_address, &dma_size);
	    transfer_size = dma_size + 1;
#ifndef PROD
	    if (transfer_size > BS_DISK_BUFFER_SIZE)
		always_trace2("FDC: transfer size ( %d ) greater than disk buffer size %d\n", transfer_size, BS_DISK_BUFFER_SIZE);
#endif 	/* PROD */
	    /* check params passed are DOS compatible */
	    if (! dos_compatible (flp, C, H, S, N) ||
		density_state != flp->media_density) {
		    sprintf(dump_buf, "Incompatible DOS diskette, C H R N = %d %d %d %d\n",
			    C, H, S, N);
		    OutputDebugString(dump_buf);
// do not pop up this annoy message because some applications are simply
// "probing" the diskette. We just fail the call.
//		host_direct_access_error((ULONG) NOSUPPORT_FLOPPY);
#ifndef PROD

		if (!dos_compatible (flp, C, H, S, N)) {
			note_trace0 (GFI_VERBOSE,
				     "Refused: not DOS compatible");
		}
		if (density_state != flp->media_density) {
			note_trace0 (GFI_VERBOSE,
				     "Refused: density mismatch");
		}
#endif /* !PROD */
		/* Sector not found or wrong size */
		put_r0_ST0 (result_block,0x40);
		put_r0_ST1 (result_block,0);
		if (density_state != flp->media_density) {
			put_r1_ST1_no_address_mark (result_block,1);
		} else {
			put_r1_ST1_no_data (result_block,1);
		}
		put_r0_ST2 (result_block,0);
		goto fdc_read_write_exit;
	    }
	    /* work out start position on floppy and sector count */
	    transfer_start = dos_offset (flp, C, H, S);
	    transfer_count = transfer_size / PC_BYTES_PER_SECTOR;
#ifndef PROD
	    if (rflop_dbg & (RFLOP_READ | RFLOP_WRITE)) {
		 sprintf(dump_buf, "Read/Write Sector: start offset = 0x%lx\n",
			  transfer_start);
		 OutputDebugString(dump_buf);
		 sprintf(dump_buf, "Read/Write Sector: size = 0x%x bytes\n", transfer_size);
		 OutputDebugString(dump_buf);
	    }
#endif

	}

	switch (fdc_command)
	{
	case FDC_READ_DATA:
#ifndef PROD
		if (rflop_dbg & RFLOP_READ) {
		    sprintf(dump_buf, "Read Sectors: C H R N = %d %d %d %d\n",
			     C, H, S, N);
		    OutputDebugString(dump_buf);
		    if (rflop_dbg & RFLOP_BREAK)
			nt_rflop_break();
		}
#endif

		if (!failed) {
		    transferred_size = nt_floppy_read(drive,
						      transfer_start,
						      transfer_size,
						      disk_buffer
						      );
		    if (transferred_size != transfer_size) {
			last_error = GetLastError();
			sprintf(dump_buf, "Read Error, code = %lx\n", last_error);
			OutputDebugString(dump_buf);
			failed = TRUE;
		    }
		    else {
			dma_request (DMA_DISKETTE_CHANNEL,
				     (char *)disk_buffer, (USHORT)transfer_size);
		    }
		}

		if (failed){
			put_r0_ST0 (result_block, 0x40);
			put_r0_ST1 (result_block, 0);
			put_r1_ST1_no_data (result_block, 1);
			put_r0_ST2 (result_block, 0);
		} 
		else {
			put_r0_ST0 (result_block, 0x04);
			put_r0_ST1 (result_block, 0);
			put_r0_ST2 (result_block, 0);
			put_r1_ST0_unit (result_block, drive);
			put_r1_ST0_head_address(result_block, H);
		}

		flp->C = C;
		flp->H = H;
		flp->R = S;
		flp->N = N;
		update_chrn (flp,
			     get_c0_MT(command_block),
			     get_c0_EOT(command_block),
			    (UTINY) transfer_count
			     );
		/* What should these really be? */
		put_r0_cyl (result_block, flp->C);
		put_r0_head (result_block, flp->H);
		put_r0_sector (result_block, flp->R);
		put_r0_N (result_block, flp->N);
		break;

	case FDC_WRITE_DATA:
#ifndef PROD
		if (rflop_dbg & RFLOP_WRITE) {
		    sprintf(dump_buf, "Write Sectors: C H R N = %d %d %d %d\n",
			     C, H, S, N);
		    OutputDebugString(dump_buf);
		    if (rflop_dbg & RFLOP_BREAK)
			nt_rflop_break();
		}
#endif
		if (!failed) {
		    /* copy from Intel space */
		    dma_request (DMA_DISKETTE_CHANNEL, (char *) disk_buffer,
				 (USHORT)transfer_size);
		    transferred_size = nt_floppy_write(drive,
						       transfer_start,
						       transfer_size,
						       disk_buffer
						       );
		    if (transferred_size != transfer_size) {
			last_error = GetLastError();
			sprintf(dump_buf, "Write Error, code = %lx\n", last_error);
			OutputDebugString(dump_buf);
			failed = TRUE;
		    }
		}

		/* Clear down result bytes */
		put_r0_ST0 (result_block, 0);
		put_r0_ST1 (result_block, 0);
		put_r0_ST2 (result_block, 0);

		if (failed)
		{
			put_r1_ST0_int_code (result_block, 1);

			/* make sure we get the correct error for EROFS */
			if (last_error == ERROR_WRITE_PROTECT)
				put_r1_ST1_write_protected (result_block, 1);
			else
				put_r1_ST1_no_data (result_block, 1);
		} 
		else
		{
			put_r1_ST0_head_address (result_block, H);
			put_r1_ST0_unit(result_block, drive);
		}

		flp->C = C;
		flp->H = H;
		flp->R = S;
		flp->N = N;

		update_chrn (flp,
			     get_c1_MT(command_block),
			     get_c1_EOT(command_block),
			     (UTINY)transfer_count
			     );
		put_r0_cyl (result_block, flp->C);
		put_r0_head (result_block, flp->H);
		put_r0_sector (result_block, flp->R);
		put_r0_N (result_block, flp->N);
		break;

	case FDC_READ_TRACK:
#ifndef PROD
		if (rflop_dbg & RFLOP_READTRACK) {
		    OutputDebugString("Read Tracks\n");
		    if (rflop_dbg & RFLOP_BREAK)
			nt_rflop_break();
		}
#endif

		break;

	case FDC_FORMAT_TRACK:

		dma_enquire (DMA_DISKETTE_CHANNEL, &dma_address, &dma_size);
		transfer_size = dma_size + 1;
				    /* copy from Intel space */
		dma_request (DMA_DISKETTE_CHANNEL, (char *) disk_buffer,
			     (USHORT)transfer_size);

		D = get_c8_drive(command_block);
		H = get_c8_head(command_block);
		flp = &floppy_data[D];
#ifndef PROD
		if (rflop_dbg & RFLOP_FORMAT) {
		    sprintf(dump_buf, "Format Track: C H Media = %d %d %d \n",
			      flp->last_seek, H, flp->flop_type + density_state);
		    OutputDebugString(dump_buf);
		    if (rflop_dbg & RFLOP_BREAK)
			nt_rflop_break();
		}
#endif
		if (!nt_floppy_format(D,
				      flp->last_seek,
				      H,
				      media_table[flp->flop_type + density_state]
				      )) {
		    last_error = GetLastError();
		    sprintf(dump_buf, "Format Error, code = %lx\n", last_error);
		    OutputDebugString(dump_buf);
		    failed = TRUE;
		}
		if (!failed) {
		    put_r0_ST0 (result_block, 0);
		    put_r0_ST1 (result_block, 0);
		    put_r0_ST2 (result_block, 0);
		    // C H R N are meaningless on formatting
		}
		else {
		    put_r0_ST0 (result_block, 0x40);
		    put_r1_ST0_head_address (result_block, H);
		    put_r1_ST0_unit(result_block, D);
		    put_r0_ST1 (result_block, 0);
		    if (last_error == ERROR_WRITE_PROTECT) {
			put_r1_ST1_write_protected (result_block, 1);
		    }
		    put_r0_ST2 (result_block, 0);
		}
		break;

	case FDC_READ_ID:

		H = get_c4_head(command_block);
		/* check if cylinder number massaging required */
		if ((flp->flop_type + density_state) == GFI_DRIVE_TYPE_360)
		{
			/* 5.25" low density, 40 tracks */
			put_c0_cyl (result_block, flp->last_seek / 2);
			C = flp->last_seek / 2;
		}
		else
		{
			/* no massage required, 80 tracks */
			put_r0_cyl (result_block, flp->last_seek);
			C = flp->last_seek;
		}
		if (flp->C < flp->trks_per_disk) {
		    put_r1_ST0_unit(result_block, drive);
		    put_r1_ST0_head_address(result_block, H);
		    put_r0_head (result_block, H);
		    put_r0_sector (result_block, flp->R);
		    put_r0_N (result_block, flp->N);
		    C = flp->C;
		    put_r0_cyl(result_block, flp->C);
		}
		else
		    C = flp->trks_per_disk - 1;

		put_r0_cyl(result_block, C);
#ifndef PROD
		if (rflop_dbg & RFLOP_READID) {
		    sprintf(dump_buf, "Read ID: C H R N = %d %d %d %d\n",
			     C, H, flp->R, flp->N);
		    OutputDebugString(dump_buf);
		    if (rflop_dbg & RFLOP_BREAK)
			nt_rflop_break();
		}
#endif
	}

	if (failed)
	    density_changed = TRUE;
fdc_read_write_exit:

    return SUCCESS;

}
/********************************************************/

/* INTERNALLY USED FUNCTIONS */

/* In order to read the data on the floppy, the floppy controller must
 * be set to the same density (rate) as was used to write the data.
 * A mismatch in densities will cause read failures, and DOS uses these
 * failures as a way to probe the diskette for the correct density.
 *
 * To emulate the floppy controller correctly, we must somehow
 * guess the density of the media and produce fake "read failures" if the
 * controller density doesn't match the media density.
 *
 * On the assumption that the operating system has already done this,
 * and that we are looking at a DOS floppy, nt_flop.c can read the
 * "total number of sectors" value from the boot sector and guess
 * the density accordingly. There should be no need for this function
 * if you have fairly direct access to the disk controller.
 */
 int probelist[] = { 720-1, 1440-1, 2400-1, 2880-1, 5760-1, 0-1};

 SHORT
guess_media_density IFN1(UTINY, drive)
{
	int total_sectors;
	int err;
	int *probe;
	FLP flp;
	ULONG	transferred_size;

	flp = &floppy_data[drive];
	transferred_size = nt_floppy_read(drive,
					  0L,
					  PC_BYTES_PER_SECTOR,
					  (PBYTE) disk_buffer
					  );

	if (transferred_size != PC_BYTES_PER_SECTOR) {
	    last_error = GetLastError();
	    OutputDebugString("Unknown Media\n");
	    /* assume that the disk is unformatted */
	    return(flp->media_density = DENSITY_UNKNOWN);/* impossible value */
	}


	/* check for a DOS boot block
 	 *
	 * AccessPC has shown that 0x55, 0xaa is not the only magic
	 * number in use, and it might be better to check the total_sectors
	 * number itself for a valid size. This algorithm is safe, but may
	 * do unnecessary disk reads if an different magic number is used.
	 */

	/* the AA, 55 signature sometime doesn't work at all, It should
	   be done as DOS */

	if ((disk_buffer[0] == 0x69 || disk_buffer[0] == 0xE9 ||
	     (disk_buffer[0] == 0xEB && disk_buffer[2] == 0x90)) &&
	     (disk_buffer[21] & 0xF0) == 0xF0 ) {
		/* read total number of sectors, and thus deduce density
		 */
		total_sectors = disk_buffer [20] * 256 + disk_buffer [19];
	} else {
		note_trace2 (GFI_VERBOSE,
			"not a DOS boot block: magic = %02x %02x",
			disk_buffer[510], disk_buffer[511]);

		/* probe disk by reading last sectors for each size
		 * (in order) until the read fails.
	 	 */
		total_sectors = 0;
		for (probe=probelist; *probe != 0; probe++) {
		    transferred_size = nt_floppy_read(drive,
						      (*probe)*PC_BYTES_PER_SECTOR,
						      PC_BYTES_PER_SECTOR,
						      disk_buffer
						      );
		    if (transferred_size != PC_BYTES_PER_SECTOR)
				break;	/* out of the for loop */
		     total_sectors = (*probe) + 1;
		}
	}

	switch (total_sectors)
	{
	case 0:
		note_trace0( GFI_VERBOSE, "total_sectors = 0 - unformatted");
		flp->media_density = DENSITY_UNKNOWN;	/* impossible value */
		break;
		
	case 720:	
	case 1440:	
		flp->media_density = DENSITY_LOW;
		break;	

	case 2400:	
	case 2880:	
		flp->media_density = DENSITY_HIGH;
		break;	

	case 5760:	
		flp->media_density = DENSITY_EXTENDED;
		break;	

	default:
		note_trace1 (GFI_VERBOSE,
			"total sectors = %d? Assume high density",
			total_sectors);
		flp->media_density = DENSITY_HIGH;
		break;
	}

#ifndef PROD
	note_trace1 (GFI_VERBOSE, "guess_media_density %d",
		flp->media_density);
	if (flp->media_density != density_state) {
		note_trace0 (GFI_VERBOSE,
			"media & controller densities are incompatible!\n");
	}
#endif /* !PROD */
	return(flp->media_density);
}

/********************************************************/

/*
 * dos_offset() calculates the offset in bytes of the required sector 
 * from the start of the nt virtual disk file for a given track
 * and sector.	This maps the floppy data onto the nt file in an
 * interleaved format with the data for each head adjacent for a 
 * given cylinder.
 */

LOCAL  int
dos_offset IFN4(FLP, flp, UTINY, cyl, UTINY, hd, UTINY, sec)
{
	int ret;

	ret = (((cyl * PC_HEADS_PER_DISKETTE * flp->secs_per_trk)
	 	+ (hd * flp->secs_per_trk)
	 	+ (sec - 1)) * PC_BYTES_PER_SECTOR) ;

	note_trace1(GFI_VERBOSE, "Dos offset %d", ret);
	return (ret);
}

/********************************************************/

/*
 * dos_compatible() returns TRUE if the command block's
 * cylinder/head/sector is DOS-compatible
 */

LOCAL  BOOL
dos_compatible IFN5(FLP, flp, UTINY, cyl, UTINY, hd, UTINY, sec, UTINY, n)
{
	BOOL ret;

	ret = ((hd <= PC_HEADS_PER_DISKETTE)
		&& (cyl < flp->trks_per_disk) 
		&& (sec <= flp->secs_per_trk)
		&& (n == PC_N_VALUE));

	return (ret);
}

/********************************************************/

LOCAL  VOID
set_floppy_parms IFN1(FLP, flp)
{
	int index = flp->flop_type + density_state;

	flp->secs_per_trk = 
		floppy_tksc [index].secs_per_trk;

	flp->trks_per_disk = 
		floppy_tksc [index].trks_per_disk;

	flp->max_track = flp->trks_per_disk - 1;
	note_trace2(GFI_VERBOSE, "set_floppy_parms: secs_per_trk %d, trks_per_disk %d", flp->secs_per_trk, flp->trks_per_disk);

}

/********************************************************/


#ifndef PROD
VOID nt_rflop_break(VOID)
{
}

#endif

LOCAL VOID update_chrn (
FLP	flp,
UTINY	mt,
UTINY	eot,
UTINY	sector_count
)
{
    UTINY new_sector;

#ifndef PROD
    if (flp->C == break_cylinder &&
	flp->H == break_head &&
	flp->R == break_sector)
	nt_rflop_break();
#endif

    new_sector = flp->R + sector_count - 1;
    if (new_sector > eot && mt != 0) {
	flp->H = 1;
	new_sector >>= 1;
    }
    flp->R =  (new_sector == eot) ? 1 : new_sector + 1;

    if (mt != 0 && new_sector == eot) {
	if(flp->H == 1)
	    flp->C++;
	flp->H ^= 1;
    }
    else {
	if (new_sector == eot)
	    flp->C++;
    }
}
