/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/hdisk.c 6     11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  Virtual hard disk device

  Copyright (c) 2003-2005 Peter Schorn (core disk interface code)
  Copyright (c) 2006 Richard A. Cini

  From file <altairz80_hdsk.c:> in SIMH (http://simh.trailing-edge.com/):
  <Start of Notice>
  "Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Peter Schorn shall not
   be used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Peter Schorn."
  <End of Notice>

  General Comments:

  The virtual hard disk is accessed through a single I/O port (0xfd). The VHDISK is
  an "intelligent" DMA device, meaning that commands sent to it are in the form of a
  device parameter block (an I/O packet). Any data returned is returned directly to
  a buffer in emulator memory whose address was sent to the VHDISK in the DPB packet.

  Presently, the VHDISK supports only the following few commands, which are very
  basic and akin to the Int25 and Int26 BIOS commands. Higher-level software would
  provide the abstraction necessary to perform key functions like disk formatting
  and file-handle-based I/O.

	1. RESET
		ld	b,32
		ld	a,hdsk_reset
	l	out	(0fdh),a
		dec	b
		jp	nz,l

	2. ABSOLUTE DISK READ/ABSOLUTE DISK WRITE
	; parameter block
	cmd:		db	hdsk_read or hdsk_write
	hd:			db	0	; 0 .. 7, defines hard disk to be used
	sector:		db	0	; 0 .. 31, defines sector
	track:		dw	0	; 0 .. 2047, defines track
	dma:		dw	0	; defines where result is placed in memory

	; routine to execute
		ld	b,7			; size of parameter block
		ld	hl,cmd		; start address of parameter block
	l	ld	a,(hl)		; get byte of parameter block
		out	(0fdh),a	; send it to port
		inc	hl			; point to next byte
		dec	b			; decrement counter
		jp	nz,l		; again, if not done
		in	a,(0fdh)	; get result code


Change Log:
  2005/09/20  RAC -- Began coding. Should work but there are no Altair32
  						specific images yet.
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/06/02  RAC -- Refinements to make it work better with A32
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400

  Notes:
  Boot ROM assumes that the system has at least 24k of RAM.
  
  Present configuration assumes that the HDISK does not have an on-screen
  representation. If we want an on-screen visual, we need to add the UI
  code to support it. Sound and blinking LEDs are possible options.
\************************************************************************/
#include <windows.h>	// required for all Windows applications
#include <windowsx.h>	// message cracker macros
#include <commdlg.h>	// Windows common dialogs
#include <commctrl.h>	// Windows common controls
#include <stdlib.h>		// C standard lib
#include <string.h>		// C-lib - strings
#include <stdio.h>		// C-lib 
#include "altair32.h"	// includes "resource.h" and exports from other major modules
#include "comcthlp.h"	// common control macros
#include "i8080.h"		// processor emulation


/**  Typedefs & Defines  **************************************/
#define HDSK_BOOT_ADDRESS		0x5c00
#define HDSK_SECTOR_SIZE		128				/* size of sector		*/
#define HDSK_SECTORS_PER_TRACK	32				/* sectors per track	*/
#define HDS_MAX_TRACKS			2048			/* number of tracks		*/
#define HDSK_TRACK_SIZE			(HDSK_SECTOR_SIZE * HDSK_SECTORS_PER_TRACK)
#define HDSK_CAPACITY			(HDSK_TRACK_SIZE * HDS_MAX_TRACKS)	/* raw size of 8,388,608 */
#define HDSK_NUMBER				8				/* number of hard disks	*/

// codes to return to CP/M
#define CPM_OK					0			/* indicates to CP/M everything ok		*/
#define CPM_ERROR				1			/* indicates to CP/M an error condition	*/
#define CPM_EMPTY				0xe5		/* default CP/M fill byte              	*/

// unit_t->status codes; positive logic
#define VDISK_ATTM	0		// attached bit0
#define VDISK_ATT	0x01
#define VDISK_ROM	1		// read-only bit1
#define VDISK_RO	0x02
#define VDISK_ERRM  7		// general error bit7
#define VDISK_ERR	0x80


// Physical drive definition. Maximum image size is 4g (256*256*65536)
// although there are practical limitations in CP/M that probably
// prevent using a size that large.
typedef struct tagUNIT {
	uint8	flags;			// status bitmask (attached, R/O, etc.)
	uint8	stcode;		// extended status code {user/optional}
	FILE	*fileref;		// file handle
	char	*filename;		// container image file name
	long	l_offset;		// lseek offset in container file
	uint8	sect_size;		// 0-255	CP/M is 128
	uint8	sect_per_trk;	// 0-255	32
	word	num_tracks;	// 0-65535	2047
} unit_t;


/**  Forward Prototypes  **************************************/
// Need routines to tie code to Windows UI
static uint32 hdsk_in(const uint32 port);
static uint32 hdsk_out(const uint32 data);
static uint32 checkParameters(void);
static uint32 doSeek(void);
static uint32 doRead(void);
static uint32 doWrite(void);


/**  Module Globals - Private  ********************************/
static uint32 hdskLastCommand = HDISK_NONE;	// last command
static uint32 hdskCommandPosition = 0;			// position in DPB
static uint32 selectedDisk;					// current active disk
static uint32 selectedSector;					// current sector
static uint32 selectedTrack;					// current track
static uint32 selectedDMA;						// current DMA address
static uint32 udata1;							// current user_data value
static uint32 hdskTrace;						// trace_enabled flag

// Declare drives -- max of 8 but assuming only 1 for now.
static unit_t hdsk_unit[] = {
	{VDISK_ERR, 0, NULL, NULL, 0, HDSK_SECTOR_SIZE, HDSK_SECTORS_PER_TRACK, HDS_MAX_TRACKS},
	{VDISK_ERR, 0, NULL, NULL, 0, HDSK_SECTOR_SIZE, HDSK_SECTORS_PER_TRACK, HDS_MAX_TRACKS},
	{VDISK_ERR, 0, NULL, NULL, 0, HDSK_SECTOR_SIZE, HDSK_SECTORS_PER_TRACK, HDS_MAX_TRACKS},
	{VDISK_ERR, 0, NULL, NULL, 0, HDSK_SECTOR_SIZE, HDSK_SECTORS_PER_TRACK, HDS_MAX_TRACKS},
	{VDISK_ERR, 0, NULL, NULL, 0, HDSK_SECTOR_SIZE, HDSK_SECTORS_PER_TRACK, HDS_MAX_TRACKS},
	{VDISK_ERR, 0, NULL, NULL, 0, HDSK_SECTOR_SIZE, HDSK_SECTORS_PER_TRACK, HDS_MAX_TRACKS},
	{VDISK_ERR, 0, NULL, NULL, 0, HDSK_SECTOR_SIZE, HDSK_SECTORS_PER_TRACK, HDS_MAX_TRACKS},
	{VDISK_ERR, 0, NULL, NULL, 0, HDSK_SECTOR_SIZE, HDSK_SECTORS_PER_TRACK, HDS_MAX_TRACKS} };

static const int32 hdskBoot[256] = {
	0xf3, 0x06, 0x80, 0x3e, 0x0e, 0xd3, 0xfe, 0x05, /* 5c00-5c07 */
	0xc2, 0x05, 0x5c, 0x3e, 0x16, 0xd3, 0xfe, 0x3e, /* 5c08-5c0f */
	0x12, 0xd3, 0xfe, 0xdb, 0xfe, 0xb7, 0xca, 0x20, /* 5c10-5c17 */
	0x5c, 0x3e, 0x0c, 0xd3, 0xfe, 0xaf, 0xd3, 0xfe, /* 5c18-5c1f */
	0x06, 0x20, 0x3e, 0x01, 0xd3, 0xfd, 0x05, 0xc2, /* 5c20-5c27 */
	0x24, 0x5c, 0x11, 0x08, 0x00, 0x21, 0x00, 0x00, /* 5c28-5c2f */
	0x0e, 0xb8, 0x3e, 0x02, 0xd3, 0xfd, 0x3a, 0x37, /* 5c30-5c37 */
	0xff, 0xd6, 0x08, 0xd3, 0xfd, 0x7b, 0xd3, 0xfd, /* 5c38-5c3f */
	0x7a, 0xd3, 0xfd, 0xaf, 0xd3, 0xfd, 0x7d, 0xd3, /* 5c40-5c47 */
	0xfd, 0x7c, 0xd3, 0xfd, 0xdb, 0xfd, 0xb7, 0xca, /* 5c48-5c4f */
	0x53, 0x5c, 0x76, 0x79, 0x0e, 0x80, 0x09, 0x4f, /* 5c50-5c57 */
	0x0d, 0xc2, 0x60, 0x5c, 0xfb, 0xc3, 0x00, 0x00, /* 5c58-5c5f */
	0x1c, 0x1c, 0x7b, 0xfe, 0x20, 0xca, 0x73, 0x5c, /* 5c60-5c67 */
	0xfe, 0x21, 0xc2, 0x32, 0x5c, 0x1e, 0x00, 0x14, /* 5c68-5c6f */
	0xc3, 0x32, 0x5c, 0x1e, 0x01, 0xc3, 0x32, 0x5c, /* 5c70-5c77 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c78-5c7f */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c80-5c87 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c88-5c8f */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c90-5c97 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c98-5c9f */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ca0-5ca7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ca8-5caf */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cb0-5cb7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cb8-5cbf */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cc0-5cc7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cc8-5ccf */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cd0-5cd7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cd8-5cdf */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ce0-5ce7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ce8-5cef */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cf0-5cf7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cf8-5cff */
};


/**************************************************************\
*   I/O Instruction Handlers  *********************************|
***************************************************************|
*
*	I/O instruction handlers are called from the CPU module
*  	when an IN or OUT instruction is issued.
*
*  	Each function is passed an 'io' flag, where 0 means a read
*  	from the port, and 1 means a write to the port.  On input,
*	the actual input is passed as the return value.  On output,
*	'data' is written to the device.
\**************************************************************/
/*--  hdiskfdh ------------------------------------------------
	CPU I/O interface routine
	
	Params:		io_direction, data
	Uses:		bootrom, Mem, PageAccessRights
	Returns:	status byte
	Comments:	Virtual hard disk is a DMA device so no data is
				returned on a read from this register. A write
				to this register serves to transfer the DPB
				from the calling program to the virtual hard
				disk "controller".

				Performing a "read" implicitly executes the
				command in the DPB and returns the result
				code from the execution of that command.
-------------------------------------------------------------*/
int hdskfdh(int io, int data)
{

	switch (io) {
		default:					// DEFAULT
		case 0:						// READ
			return (int)hdsk_in(0xfd);		// 'port' not used
			
		case 1:						// WRITE
			hdsk_out(data);
			return 0;
	}
}


/**************************************************************\
*   Public Routines  ******************************************|
\**************************************************************/
/*-- HDISK_IoCtl  ---------------------------------------------
	Called from emulator init; initializes controller state.
	
	Params:		(uint32) mode startup/shutdown
	Uses:		hdisk_unit array
	Returns:	Success/failure
	Comments:	Probably only useful if power toggled since
				hdisk array is initialized on startup to the
				proper values.

				If we ultimately decide that we need a UI for
				the hard disk, the GDI resources could be
				allocated and deallocated here.
-------------------------------------------------------------*/
BOOL HDISK_IoCtl(uint32 mode)
{

	int i = 0;


	switch(mode){
		case 0:		// startup
			for (i=0; i < HDSK_NUMBER; i++){
				hdsk_unit[i].flags = VDISK_ERR;
				hdsk_unit[i].stcode = 0;
				hdsk_unit[i].fileref = NULL;
				hdsk_unit[i].filename = NULL;	
				hdsk_unit[i].l_offset = 0;
				hdsk_unit[i].sect_size = HDSK_SECTOR_SIZE;
				hdsk_unit[i].sect_per_trk = HDSK_SECTORS_PER_TRACK;
				hdsk_unit[i].num_tracks = HDS_MAX_TRACKS;
			}
			break;

		case 1:		// shutdown
			for (i=0; i < HDSK_NUMBER; i++){
				if(hdsk_unit[i].fileref != NULL){
					fclose(hdsk_unit[i].fileref);
				}
				
				hdsk_unit[i].flags = VDISK_ERR;
				hdsk_unit[i].stcode = 0;
				hdsk_unit[i].fileref = NULL;
				hdsk_unit[i].filename = NULL;	
				hdsk_unit[i].l_offset = 0;
				hdsk_unit[i].sect_size = HDSK_SECTOR_SIZE;
				hdsk_unit[i].sect_per_trk = HDSK_SECTORS_PER_TRACK;
				hdsk_unit[i].num_tracks = HDS_MAX_TRACKS;
			}
			break;

		default:	// other - don't do anything
			break;
	}
	return TRUE;
}


/*-- HDISK_OpenContainerFile  --------------------------------
	Called from main window message handler (menu item) to open
	a container file and hook it into the drive code.
	
	Params:		None
	Uses:		hdisk_unit array
	Returns:	Success/failure
	Comments:	
-------------------------------------------------------------*/
BOOL HDISK_OpenContainerFile(void)
{

	BOOL bNewDisk = FALSE;
	char DlgCaption[] = {"Open Hard Disk File"};
	char outfile[_MAX_PATH];
	FILE *pfp;
	uint32 i;

	// get filename to open from user
	if(!GetFilenameFromUser(DlgCaption, outfile, &bNewDisk)){
		// GetOpenFileName was cancelled
		return FALSE;
	}

	/*	Got a filename; could be old or new. On return, if bNewDisk
		is TRUE, then we have to create the file. If FALSE, then we
		can just use the filename as-is.
	*/

	if(bNewDisk){
		// create new container file
		if((pfp=fopen(outfile, "wb")) == NULL){
			// error exit
			return FALSE;
		}
		
		for(i = 0; i < HDSK_CAPACITY; i++){
			fputc((int)CPM_EMPTY, pfp);
		}

		fflush(pfp);
		fclose(pfp);
	}

	// File now most certainly exists, so (re-)open it and hook it to the
	// emulator.
	if((pfp=fopen(outfile, "rb+")) == NULL){
		// error exit
		return FALSE;
	}
	
	// File is open, so tie it to the emulator. Only one allowed for now.
	hdsk_unit[0].flags = VDISK_ATT;
	hdsk_unit[0].stcode = 0;
	hdsk_unit[0].fileref = pfp;
	hdsk_unit[0].filename = just_filename(outfile);	
	hdsk_unit[0].l_offset = 0;
	hdsk_unit[0].sect_size = HDSK_SECTOR_SIZE;
	hdsk_unit[0].sect_per_trk = HDSK_SECTORS_PER_TRACK;
	hdsk_unit[0].num_tracks = HDS_MAX_TRACKS;

	return TRUE;
}


/*--  hdsk_boot  ----------------------------------------------
	Performs bootstrap function on drive
	
	Params:		device parameter block
	Uses:		bootrom, Mem, PageAccessRights
	Returns:	Success/failure
	Comments:	Need to figure out best way for user to
				initiate boot sequence. Maybe selecting
				"Boot Hard Disk" from menu should halt the
				emulator, ask the user for the hard disk image
				name and then automatically jump to the
				bootstrap code. Maybe it should be two steps?
-------------------------------------------------------------*/
BOOL hdsk_boot(hdprmblk_t dpb)
{
	uint32 i;
	
	if (MEMSIZE < 24576) {
		HOST_ThrowErrMsg("At least 24KB RAM is required to boot from hard disk.\n");
		return FALSE;
	}

	// copy bootrom to memory
	for (i = 0; i < 256; i++) {
		SetMem(i + HDSK_BOOT_ADDRESS, hdskBoot[i] & 0xff);
		PageAccessRights[i + HDSK_BOOT_ADDRESS] = 1;	// mark as ROM
	}

	// Force a JMP to the new code from here??
#if 0
	// Halt emulation
	simstate.bPrun = RUN_OFF;
	simstate.runstate = HALTED;
	SYS_Set8080Speed(simstate.orig_up_speed);
	// Set new PC
	I80Regs.nPC.W = HDSK_BOOT_ADDRESS;
	MBR = GetMem( I80Regs.nPC.W );
	// Go!
	simstate.bPrun = RUN_ON;
	simstate.runstate = RUNNING;
#endif
	
	return TRUE;
}


/**************************************************************\
*   Private Routines  *****************************************|
\**************************************************************/
/*--  hdsk_in  ------------------------------------------------
	Input from hard disk
	
	Params:		port_number (unused)
	Uses:		module globals
	Returns:	Success/failure
	Comments:	
-------------------------------------------------------------*/
static uint32 hdsk_in(const uint32 port)
{
	uint32 result;
	
	if ((hdskCommandPosition == 6) && ((hdskLastCommand == HDISK_READ) || (hdskLastCommand == HDISK_WRITE))){
		result = checkParameters() ? ((hdskLastCommand == HDISK_READ) ? doRead() : doWrite()) : CPM_ERROR;
		hdskLastCommand = HDISK_NONE;
		hdskCommandPosition = 0;
		return result;
	}
	else {
		// bad command error
	}
	return CPM_OK;
}


/*--  hdsk_out  -----------------------------------------------
	Output to hard disk
	
	Params:		data byte to output
	Uses:		module gloals
	Returns:	Success/failure
	Comments:	This routine "clocks the data out" to the
				virtual hard disk, using a state machine
				concept to transfer the DPB data and set
				the proper internal variables.
-------------------------------------------------------------*/
static uint32 hdsk_out(const uint32 data)
{

	switch(hdskLastCommand){
		//case HDISK_RESET: implied in "default" case
		
		case HDISK_READ:
		case HDISK_WRITE:
			switch(hdskCommandPosition) {
				case 0:
					selectedDisk = data;
					hdskCommandPosition++;
					break;
				case 1:
					selectedSector = data;
					hdskCommandPosition++;
					break;
				case 2:
					selectedTrack = data;
					hdskCommandPosition++;
					break;
				case 3:
					selectedTrack += (data << 8);
					hdskCommandPosition++;
					break;
				case 4:
					selectedDMA = data;
					hdskCommandPosition++;
					break;
				case 5:
					selectedDMA += (data << 8);
					hdskCommandPosition++;
					break;
				default:
					hdskLastCommand = HDISK_NONE;
					hdskCommandPosition = 0;
			}
			break;

		default:		// 		HDISK_RESET too
			hdskLastCommand = data;
			hdskCommandPosition = 0;
	}
	return 0; /* ignored, since OUT */
}


/*--  checkParameters  ----------------------------------------
	Parameter validation layer. Values changed to "safe"
	values if they are out of range. 
	
	Params:		none
	Uses:		module globals
	Returns:	Success/failure
	Comments:	
-------------------------------------------------------------*/
static uint32 checkParameters(void)
{

	uint8 currentFlag;

	
	if ((selectedDisk < 0) || (selectedDisk >= HDSK_NUMBER)){
		selectedDisk = 0;
	}
	
	currentFlag = hdsk_unit[selectedDisk].flags;
	
	if ((currentFlag & VDISK_ATT) == 0){
		return FALSE; /* cannot read or write */
	}
	
	if ((selectedSector < 0) || (selectedSector >= HDSK_SECTORS_PER_TRACK)){
		selectedSector = 0;
	}
	
	if ((selectedTrack < 0) || (selectedTrack >= HDS_MAX_TRACKS)){
		selectedTrack = 0;
	}
	
	selectedDMA &= ADDRMASK;

	if (hdskTrace) {
		//message6("%s HDSK%d Sector=%02d Track=%04d DMA=%04x",
		//	(hdskLastCommand == hdsk_read) ? "Read" : "Write",
		//	selectedDisk, selectedSector, selectedTrack, selectedDMA);
	}
	return TRUE;
}


/*--  doSeek  -------------------------------------------------
	Perform sector seek within VHDISK image
	
	Params:		nothing
	Uses:		hdisk_unit; data location variables
	Returns:	Success/failure
	Comments:	
-------------------------------------------------------------*/
static uint32 doSeek(void)
{
	
	unit_t *uptr = &hdsk_unit[selectedDisk];
	
	if (fseek(uptr -> fileref,
		HDSK_TRACK_SIZE * selectedTrack + HDSK_SECTOR_SIZE * selectedSector, SEEK_SET)) {
		return CPM_ERROR;
	}
	else {
		return CPM_OK;
	}
}


/*--  doRead  -------------------------------------------------
	Performs read from VHDISK image
	
	Params:		nothing
	Uses:		hdisk_unit, SetMem
	Returns:	Success/failure; retrieved data placed in DMA
				buffer at address provided in DPB
	Comments:	
-------------------------------------------------------------*/
static uint32 doRead(void)
{

	uint32 i;
	uint8 hdskbuf[HDSK_SECTOR_SIZE];	/* data buffer	*/
	unit_t *uptr = &hdsk_unit[selectedDisk];
	

	if (doSeek()){
		return CPM_ERROR;
	}
	if (fread(hdskbuf, HDSK_SECTOR_SIZE, 1, uptr -> fileref) != 1){
		for (i = 0; i < HDSK_SECTOR_SIZE; i++){
			hdskbuf[i] = CPM_EMPTY;
		}
		return CPM_OK; /* allows the creation of empty hard disks */
	}
	for (i = 0; i < HDSK_SECTOR_SIZE; i++) {
		SetMem(selectedDMA + i, hdskbuf[i]);
	}
	return CPM_OK;
}


/*--  doWrite  ------------------------------------------------
	Performs write to VHDISK image
	
	Params:		none
	Uses:		hdisk_unit, GetMem
	Returns:	Success/failure
	Comments:	Data to be written to the VHDISK is retrieved
				directly from the emulator memory starting
				at the DMA address provided in the DPB
-------------------------------------------------------------*/
static uint32 doWrite(void)
{

	uint32 i;
	uint8 hdskbuf[HDSK_SECTOR_SIZE];	/* data buffer	*/
	unit_t *uptr = &hdsk_unit[selectedDisk];
	

	if (((uptr -> flags) & VDISK_RO) == 0){ /* write enabled */
		if (doSeek()){
			return CPM_ERROR;
		}
		
		for (i = 0; i < HDSK_SECTOR_SIZE; i++){
			hdskbuf[i] = GetMem(selectedDMA + i);
		}
		
		if (fwrite(hdskbuf, HDSK_SECTOR_SIZE, 1, uptr -> fileref) != 1){
			return CPM_ERROR;
		}
	}
	else {
		// sector is write-locked
		return CPM_ERROR;
	}
	return CPM_OK;
}
/* end of file: hdisk.c */
