//	
//	corvus_hd
//	
//	Implementation of a Corvus Hard Drive / Host Bus Adapter pair.  The drive
//	being emulated is a Rev B drive, functionally speaking, rather than an Omnidrive.
//	
//	The Corvus Flat Cable HBA is a very simplistic device due to the fact that most
//	of the smarts are in the Hard Drive itself.  What's in the hard drive includes a 
//  Z80 processor, 4K of EPROM and 5KB of RAM.  Ultimately, a true emulation would include
//  the on-boad controller; however, that is outside the current scope of this code.  Maybe
//  if I could get a Rev. B/H drive, it could be reverse-engineered to do this.
//
//	The Flat Cable controller has two registers:
//
//	Data -				Single byte bidirectional data transfer
//	Status Register -	Bit 7 - Controller Ready -- off = ready, on = not ready
//						Bit 6 - Bus Direction -- off = host-to-controller, on = controller-to-host
//
//	Layout of a Corvus Hard Disk is as follows:
//
//	Blk	Len	Description
//	---	---	-----------
//	0	1	Boot Block
//	1	1	Disk Parameter Block
//	2	1	Diagnostic Block (prep code)
//	3	1	Constellation Parameter Block
//	4	2	Dispatcher Code
//	6	2	Pipes and Semaphores code (Semaphore table contained in block 7, bytes 1-256)
//	8	10	Mirror Controller Code
//	18	2	LSI-11 Controller Code
//	20	2	Pipes Controller Code
//	22	3	Reserved for Future Use
//	25	8	Boot Blocks 0-7.  Apple II uses 0-3, Concept uses 4-7
//	33	4	Active User Table
//	37	3	Reserved
//
//	All of the above blocks are initialized by the DDIAG program.  This can be found on the
//	Concept FSYSGEN floppy.
//		- Boot blocks and code blocks are initialized using the "Update Firmware on Disk" function.
//		- Disk Parameter Block is initialized using the "Display/Modify Drive Parameters" function
//
//	An on-disk structure is written with the SYSGEN utility on the same disk.  Password is "HAI"
//
//
//	Corvus Hard Disk performance characteristics (from a 6MB Rev B-E drive)
//
//		Average Latency: 6.25ms
//		Average Access Time: 125ms (and you thought YOUR drive was slow...)
//		Maximum Access Time: 240ms
//		Maximum Access Time (single track): 3ms
//		Data Transfer Rate: 960Kb/sec
//		Rotational Speed: 4800RPM
//
//	Brett Wyer
//
//
//	TODO: 
//		Implement READY line glitch after last byte of command (Disk System Tech Ref pp. 3)
//		Implement Read-after-Write (always happens on Rev B/H drives per Mass Storage GTI pp. 12)
//		Implement Multiple physical drive support - Up to four
//		Implement Drive Illegal Addresses (seek past last sector)
//		Implement Switches on front of drive (LSI-11, MUX, Format, Reset)
//		Implement an inter-sector delay during the FORMAT command (format happens too quickly now)
//

#include "driver.h"
#include "devices/harddriv.h"
#include "includes/corvushd.h"
#include "timer.h"

#define VERBOSE 0
#define VERBOSE_RESPONSES 0
#define VERSION 1
#define MAX_COMMAND_SIZE 4096	// The maximum size of a command packet (the controller only has 5K of RAM...)
#define SPARE_TRACKS 7			// This is a Rev B drive, so 7 it is
#define CALLBACK_CTH_MODE 1		// Set to Controller-to-Host mode when callback fires
#define CALLBACK_HTC_MODE 2		// Set to Host-to-Controller mode when callback fires
#define CALLBACK_SAME_MODE 3	// Leave mode the same when callback fires
#define CALLBACK_TIMEOUT 4		// Four seconds have elapsed.  We're timing out
#define TRACK_SEEK_TIME 1667	// Track-to-track seek time in microseconds (Maximum Access Time / Total Cylinders)
#define INTERBYTE_DELAY 5		// Inter-byte delay in microseconds communicating between controller and host
#define INTERSECTOR_DELAY 25000	// 25ms delay between sectors (4800 RPM = 80 Rev/Second.  Maximum 2 sectors transferred / Rev)

//
// Structures
//

// Sector addressing scheme for Rev B/H drives used in various commands (Called a DADR in the docs)
typedef struct {
	UINT8 address_msn_and_drive;// Most significant nibble: Most signficant nibble of sector address, Least significant nibble: Drive #
	UINT8 address_lsb;			// Least significant byte of sector address
	UINT8 address_mid;			// Middle byte of sector address
} dadr_t;

// Controller structure
typedef struct {
	UINT8	status;				// Controller status byte (DIRECTION + BUSY/READY)
	char	prep_mode;			// Whether the controller is in Prep Mode or not
	// Physical drive info
	UINT8	sectors_per_track;	// Number of sectors per track for this drive
	UINT8	tracks_per_cylinder;// Number of tracks per cylinder (heads)
	UINT16	cylinders_per_drive;// Number of cylinders per drive
	// Command Processing
	UINT16	offset;				// Current offset into raw_data buffer
	char	awaiting_modifier;	// We've received a two-byte command and we're waiting for the mod
	UINT16	recv_bytes;			// Number of bytes expected to be received from Host
	UINT16	xmit_bytes;			// Number of bytes expected to be transmitted to host
	// Timing-related values
	UINT16	last_cylinder;		// Last cylinder accessed - for calculating seek times
	UINT32	delay;				// Delay in microseconds for callback
	mame_timer	*timeout_timer;	// Four-second timer for timeouts

	//
	// Union below represents both an input and output buffer and interpretations of it
	//
	union {
		//
		// Raw Buffer
		//
		UINT8		raw_data[MAX_COMMAND_SIZE];
		//
		// Basic interpretation of code and modifier
		//
		struct {
			UINT8	code;		// First byte of data is the code (command)
			UINT8	modifier;	// Second byte of data is the modifier
		} command;
		//
		// Basic response code
		//
		struct {
			UINT8	status;		// Status code returned by the command executed
		} single_byte_response;
		//
		// Read sector command
		//
		struct {
			UINT8	code;		// Command code
			dadr_t	dadr;		// Encoded drive and sector to read
		} read_sector_command;
		//
		// 128-byte Read Sector response
		//
		struct {
			UINT8	status;		// Status code returned by command executed
			UINT8	data[128];	// Data returned from read
		} read_128_response;
		//
		// 256-byte Read Sector response
		//
		struct {
			UINT8	status;		// Status code returned by command executed
			UINT8	data[256];	// Data returned from read
		} read_256_reponse;
		//
		// 512-byte Read Sector response
		//
		struct {
			UINT8	status;		// Status code returned by command executed
			UINT8	data[512];	// Data returned by read
		} read_512_response;
		//
		// Write 128-byte sector command
		//
		struct {
			UINT8	code;		// Command code
			dadr_t	dadr;		// Encoded drive and sector to write
			UINT8	data[128];	// Data to be written
		} write_128_command;
		//
		// Write 256-byte sector command
		//
		struct {
			UINT8	code;		// Command code
			dadr_t	dadr;		// Encoded drive and sector to write
			UINT8	data[256];	// Data to be written
		} write_256_command;
		//
		// Write 512-byte sector command
		//
		struct {
			UINT8	code;		// Command Code
			dadr_t	dadr;		// Encoded drive and sector to write
			UINT8	data[512];	// Data to be written
		} write_512_command;
		//
		// Semaphore Lock command
		//
		struct {
			UINT8	code;		// Command code
			UINT8	modifier;	// Command code modifier
			UINT8	name[8];	// Semaphore name
		} lock_semaphore_command;
		//
		// Semaphore Unlock command
		//
		struct {
			UINT8	code;		// Command code
			UINT8	modifier;	// Command code modifier
			UINT8	name[8];	// Semaphore name
		} unlock_semaphore_command;
		//
		// Semaphore Lock/Unlock response
		//
		struct {
			UINT8	status;		// Disk access status
			UINT8	result;		// Semaphore action status
			UINT8	unused[10];	// Unused
		} semaphore_locking_response;
		//
		// Initialize Semaphore table command
		//
		struct {
			UINT8	code;		// Command code
			UINT8	modifier;	// Command code modifier
			UINT8	unused[3];	// Unused
		} init_semaphore_command;
		//
		// Semaphore Status command
		//
		struct {
			UINT8	code;		// Command code
			UINT8	modifier;	// Command code modifier
			UINT8	zero_three;	// Don't ask me...
			UINT8	unused[2];	// Unused
		} semaphore_status_command;
		//
		// Semaphore Status response
		//
		struct {
			UINT8	status;		// Disk access status
			UINT8	table[256];	// Contents of the semaphore table
		} semaphore_status_response;
		//
		// Get Drive Parameters command (0x10)
		//
		struct {
			UINT8	code;		// Command code
			UINT8	drive;		// Drive number (starts at 1)
		} get_drive_parameters_command;
		//
		// Get Drive Parameters command response
		//
		struct {
			UINT8	status;						// Status code returned by command executed
			UINT8	firmware[32];				// Firmware message
			UINT8	rom_version;				// ROM Version
			struct {
				UINT8	sectors_per_track;		// Sectors/Track
				UINT8	tracks_per_cylinder;	// Tracks/Cylinder (heads)
				struct {
					UINT8	lsb;
					UINT8	msb;
				} cylinders_per_drive;			// Byte-flipped Cylinders/Drive
			} track_info;
			struct {
				UINT8	lsb;					// Least significant byte
				UINT8	midb;					// Middle byte
				UINT8	msb;					// Most significant byte
			} capacity;							// 24-bit value, byte-flipped (lsb..msb)
			UINT8	unused[16];
			UINT8	interleave;					// Interleave factor
			struct {
				UINT8	mux_parameters[12];
				UINT8	pipe_name_table_ptr[2];	// Pointer to table of 64 entries, 8 bytes each (table of names)
				UINT8	pipe_ptr_table_ptr[2];	// Pointer to table of 64 entries, 8 bytes each.  See pp. 29 - Mass Storage GTI
				UINT8	pipe_area_size[2];		// Size of pipe area (lsb, msb)
				struct {
					UINT8	track_offset[2];
				} vdo_table[7];					// Virtual drive table
				UINT8	lsi11_vdo_table[8];
				UINT8	lsi11_spare_table[8];
			} table_info;
			UINT8	drive_number;				// Physical drive number
			struct {
				UINT8	lsb;					// Least
				UINT8	midb;					// Middle
				UINT8	msb;					// Most
			} physical_capacity;				// Physical capacity of drive
		} drive_param_response;
		//
		// 2-byte Boot command (0x14)
		//
		struct {
			UINT8	code;		// Command code
			UINT8	boot_block;	// Which boot block to read (0-7)
		} old_boot_command;
		//
		// Read Firmware command (Prep Mode 0x32)
		//
		struct {
			UINT8	code;		// Command Code
			UINT8	encoded_h_s;// Encoded Head (bits 7-5) / Sector (bits 4-0)
		} read_firmware_command;
		//
		// Write Firmware command (Prep Mode 0x33)
		//
		struct {
			UINT8	code;		// Command Code
			UINT8	encoded_h_s; // Encoded Head (bits 7-5) / Sector (bits 4-0)
			UINT8	data[512];	// Data to be written
		} write_firmware_command;
		//
		// Format Drive command (Prep Mode 0x01)
		//
		// Note that the following is a BLATANT ASSUMPTION.  Technically, the Format Drive command
		// uses a variable-length buffer for the pattern.  Unfortunately, the docs don't explain how to determine the
		// length of the buffer passed.  I assume it's a timeout; however, the docs happen to say that
		// all Corvus diagnostic programs send 513 bytes total, including the command, so I'm going with that.
		//
		struct {
			UINT8	code;		// Command Code
			UINT8	pattern[512]; // Pattern to be written
		} format_drive_revbh_command;
	} buffer;
} corvus_hdc_t;

// Structure of Block #1, the Disk Parameter Block
typedef struct {
	struct {
		UINT8	lsb;
		UINT8	msb;
	} spared_track[8];			// Spared track table (0xffff indicates end)
	UINT8	interleave;			// Interleave factor
	UINT8	reserved;
	struct {
		UINT8 track_offset[2];	// Virtual drive offsets (lsb, msb) 0xffff indicates unused
	} vdo_table[7];
	UINT8	lsi11_vdo_table[8];
	UINT8	lsi11_spare_table[8];
	UINT8	reserved2[432];
	struct {
		UINT8	lsb;
		UINT8	msb;
	} revh_spare_table[16];
} disk_parameter_block_t;

// Structure of Block #3, the Constellation Parameter Block
typedef struct {
	UINT8	mux_parameters[12];
	UINT8	pipe_name_table_ptr[2];
	UINT8	pipe_ptr_table_ptr[2];
	UINT8	pipe_area_size[2];
	UINT8	reserved[470];
	UINT8	software_protection[12];
	UINT8	serial_number[12];
} constellation_parameter_block_t;

// Structure of Block #7, the Semaphore Table Block
typedef struct {
	union {
		UINT8	semaphore_table[256];			// Table consists of 256 bytes
		struct {
			UINT8	semaphore_name[8];			// Each semaphore name is 8 bytes
		} semaphore_entry[32];					// 32 Entries
	} semaphore_block;
	UINT8	unused[256];						// Remaining half of block is unused
} semaphore_table_block_t;

// Command size structure (number of bytes to xmit and recv for each command)
typedef struct {
	UINT16	recv_bytes;							// Number of bytes from host for this command
	UINT16	xmit_bytes;							// Number of bytes to return to host
} corvus_cmd_t;

//
// Prototypes
//
static hard_disk_file *corvus_hdc_file(int id);
static void corvus_hdc_callback(int function);

//
// Globals
//
static corvus_hdc_t	corvus_hdc;					// The controller itself
static corvus_cmd_t	corvus_cmd[0xf5][0xc1];		// Command sizes and their return sizes
static corvus_cmd_t	corvus_prep_cmd[0x82];		// Prep Command sizes and their return sizes



#if VERBOSE
//
// Dump_Buffer
//
// Dump a buffer to the error log in a nice format.
//
// Pass:
//		buffer:	Data to be dumped
//		length: Number of bytes to be dumped
//
// Returns:
//		nada
//
void dump_buffer(UINT8 *buffer, UINT16 length) {

#include <ctype.h>

	UINT16	offset;
	char	ascii_dump[16];
	int		cpos;

	logerror("dump_buffer: Dump of %d bytes:\n", length);
	logerror("Base  00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ASCII\n");
	logerror("----  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ----------------");

	for(offset=0; offset < length; offset++) {
		if(offset % 16 == 0) {					// WHY IS 0 % 16 == 0???
			if(offset > 0 && offset % 16 == 0) 
				logerror("%16.16s", ascii_dump);
			logerror("\n%4.4x: %2.2x ", offset, *(buffer + offset));
		} else {
			logerror("%2.2x ", *(buffer + offset));
		}
		ascii_dump[offset % 16] = isprint(*(buffer + offset)) ? *(buffer + offset) : '.';
	}
	if(offset % 16)
		logerror("%.*s", (16 - (offset % 16)) * 3, "                                                     ");
	logerror("%.*s\n", offset % 16 ? offset % 16 : 16, ascii_dump);
}
#endif



//
// Parse_HDC_Command
//
// Process the first byte received from the host.  Do some initial evaluation and
// return either TRUE or FALSE as to whether the command was invalid or not.
//
// Note that recv_bytes and xmit_bytes in the corvus_hdc structure are updated as
// a side-effect of this command, as is awaiting_modifier.
//
// Pass:
//		data:	Initial byte received from the host in Host to Controller mode
//
// Returns:
//		Whether the command was invalid or not (TRUE = invalid command)
//
static UINT8 parse_hdc_command(UINT8 data) {

	corvus_hdc_t *c = &corvus_hdc;

	c->awaiting_modifier = FALSE;				// This is the case by definition

	#if VERBOSE
	logerror("parse_hdc_command: Called with data: 0x%2.2x, Prep mode is: %d\n", data, c->prep_mode);
	#endif

	if(!c->prep_mode) {
		switch(data) {
			//
			// Single-byte commands - Non-Prep mode
			//
			case READ_SECTOR_256:
			case WRITE_SECTOR_256:
			case READ_CHUNK_128:
			case READ_CHUNK_256:
			case READ_CHUNK_512:
			case WRITE_CHUNK_128:
			case WRITE_CHUNK_256:
			case WRITE_CHUNK_512:
			case READTEMPBLOCK:
			case WRITETEMPBLOCK:
			case BOOT:
			case READ_BOOT_BLOCK:
			case GET_DRIVE_PARAMETERS:
		//	case PARK_HEADS_REVH:
			case PARK_HEADS_OMNI:
			case ECHO:
			case PREP_MODE_SELECT:
				c->recv_bytes = corvus_cmd[data][0].recv_bytes;
				c->xmit_bytes = corvus_cmd[data][0].xmit_bytes;
				#if VERBOSE
				logerror("parse_hdc_command: Single byte command recoginized: 0x%2.2x, to recv: %d, to xmit: %d\n", data, 
					c->recv_bytes, c->xmit_bytes);
				#endif
				break;
			//
			// Double-byte commands
			//
			case SEMAPHORE_LOCK_CODE:
		//	case SEMAPHORE_UNLOCK_CODE:
			case SEMAPHORE_INIT_CODE:
		//	case PIPE_READ_CODE:
		//	case PIPE_WRITE_CODE:
		//	case PIPE_CLOSE_CODE:
		//	case PIPE_STATUS_CODE:
		//	case SEMAPHORE_STATUS_CODE:
			case PIPE_OPEN_WRITE_CODE:
		//	case PIPE_AREA_INIT_CODE:
		//	case PIPE_OPEN_READ_CODE:
			case ADDACTIVE_CODE:
		//	case DELACTIVEUSR_REVBH_CODE:
		//	case DELACTIVEUSR_OMNI_CODE:
		//	case DELACTIVENUM_OMNI_CODE:
		//	case FINDACTIVE_CODE:
				c->awaiting_modifier = TRUE;
				#if VERBOSE
				logerror("parse_hdc_command: Double byte command recoginized: 0x%2.2x\n", data);
				#endif
				break;

			default:							// This is an INVALID command
				c->recv_bytes = 1;
				c->xmit_bytes = 1;
				#if VERBOSE
				logerror("parse_hdc_command: Invalid command detected: 0x%2.2x\n", data);
				#endif
				return TRUE;
		}
	} else {
		switch(data) {
			//
			// Prep Commands
			//
			case PREP_MODE_SELECT:				// Apparently I need to be able to do this while in Prep mode
			case PREP_RESET_DRIVE:
			case PREP_FORMAT_DRIVE:
			case PREP_FILL_DRIVE_OMNI:
			case PREP_VERIFY:
			case PREP_READ_FIRMWARE:
			case PREP_WRITE_FIRMWARE:
				c->recv_bytes = corvus_prep_cmd[data].recv_bytes;
				c->xmit_bytes = corvus_prep_cmd[data].xmit_bytes;
				#if VERBOSE
				logerror("parse_hdc_command: Prep command recognized: 0x%2.2x, to recv: %d, to xmit: %d\n", data, 
					c->recv_bytes, c->xmit_bytes);
				#endif
				break;

			default:							// This is an INVALID prep command
				c->recv_bytes = 1;
				c->xmit_bytes = 1;
				#if VERBOSE
				logerror("parse_hdc_command: Invalid Prep command detected: 0x%2.2x\n", data);
				#endif
				return TRUE;
		}
	}	// if(!prep_mode)

	return FALSE;
}



//
// Corvus_Write_Sector
//
// Write a variably-sized chunk of data to the CHD file
//
// Pass:
//		drv:	Drive number to write to
//		sector:	Physical sector number to write to
//		buffer:	Buffer to write
//		len:	Length of the buffer (amount of data to write)
//
// Returns:
//		status:	Command status
//
static UINT8 corvus_write_sector(UINT8 drv, UINT32 sector, UINT8 *buffer, int len) {

	corvus_hdc_t
			*c = &corvus_hdc;
	hard_disk_file
			*disk;				// Structures for interface to CHD routines
	UINT8	tbuffer[512];		// Buffer to hold an entire sector
	UINT16	cylinder;			// Cylinder this sector resides on

	#if VERBOSE
	logerror("corvus_write_sector: Write Drive: %d, physical sector: 0x%5.5x\n", drv, sector);
	#endif

	if(drv >= device_count(IO_HARDDISK)) {
		logerror("corvus_write_sector: Attempt to write to non-existant drive: %d\n", drv);
		return STAT_FATAL_ERR | STAT_DRIVE_NOT_ONLINE;
	}

	disk = corvus_hdc_file(drv);
	if(!disk) {
		logerror("corvus_write_sector: Failure returned by corvus_hdc_file(%d)\n", drv);
		return STAT_FATAL_ERR | STAT_DRIVE_NOT_ONLINE;
	}

	//
	// Calculate what cylinder the sector resides on for timing purposes
	//
	cylinder = (double) sector / (double) c->sectors_per_track / (double) c->tracks_per_cylinder;
	c->delay = abs(c->last_cylinder - cylinder) * TRACK_SEEK_TIME + INTERSECTOR_DELAY;

	//
	// Corvus supports write sizes of 128, 256 and 512 bytes.  In the case of a write smaller than
	// the sector size of 512 bytes, the sector is read, the provided data is overlayed and then the
	// sector is written back out.  See pp. 5 of the Mass Storage Systems GTI for the details of this
	// wonderful functionality.
	//
	if(len == 512) {
		hard_disk_write(disk, sector, 1, buffer);
	} else {
		hard_disk_read(disk, sector, 1, tbuffer);		// Read the existing data into our temporary buffer
		memcpy(tbuffer, buffer, len);					// Overlay the data with the buffer passed
		c->delay += INTERSECTOR_DELAY;					// Add another delay because of the Read / Write
		hard_disk_write(disk, sector, 1, tbuffer);		// Re-write the data
	}

	c->last_cylinder = cylinder;

	#if VERBOSE
	logerror("corvus_write_sector: Full sector dump on a write of %d bytes follows:\n", len);
	dump_buffer(len == 512 ? buffer : tbuffer, 512);
	#endif

	return STAT_SUCCESS;
}



//
// Corvus_Write_Logical_Sector
//
// Write a variably-sized chunk of data to the user area of the virtual Corvus drive
//
// Pass:
//		dadr:	Corvus-encoded Disk Address -- Logical Sector
//		buffer:	Buffer holding the data to be written to the disk
//		len:	Length of the buffer
//
// Returns:
//		status:	Corvus status
//
static UINT8 corvus_write_logical_sector(dadr_t *dadr, UINT8 *buffer, int len) {

	corvus_hdc_t
			*c = &corvus_hdc;
	UINT8	status;				// Status returned from Physical Sector read
	UINT8	drv;				// Drive number (1 - 15)
	UINT32	sector;				// Sector number on drive

	//
	// Unencode the first byte of the DADR
	//
	// High-order nibble of first byte is the most-significant nibble of the sector address
	// Low-order nibble of first byte is the drive id
	//
	// For example: 0x23 would decode to Drive ID #3, high-order nibble: 0x02.
	//
	drv = (dadr->address_msn_and_drive & 0x0f) - 1;
	sector = (dadr->address_msn_and_drive & 0xf0 << 12) | (dadr->address_mid << 8) | dadr->address_lsb;

	#if VERBOSE
	logerror("corvus_write_logical_sector: Writing based on DADR: 0x%6.6x, logical sector: 0x%5.5x, drive: %d\n", 
		dadr->address_msn_and_drive << 16 | dadr->address_lsb << 8 | dadr->address_mid, sector, drv);
	#endif

	//
	// Shift the logical sector address forward by the number of firmware cylinders (2) + the number of spare tracks (7)
	//
	sector += (c->tracks_per_cylinder * c->sectors_per_track * 2) + (SPARE_TRACKS * c->sectors_per_track);

	status = corvus_write_sector(drv, sector, buffer, len);

	if(status != STAT_SUCCESS)
		c->xmit_bytes = 1;

	return status;
}


//
// Corvus_Read_Sector
//
// Read a variably-sized chunk of data from the CHD file
//
// Pass:
//		drv:	Drive number to read from
//		sector:	Physical sector number to read from
//		buffer:	Buffer to hold the data read from the disk
//		len:	Length of the buffer
//
// Returns:
//		status:	Corvus status
//
static UINT8 corvus_read_sector(UINT8 drv, UINT32 sector, UINT8 *buffer, int len) {

	corvus_hdc_t
			*c = &corvus_hdc;
	hard_disk_file
			*disk;				// Structures for interface to CHD routines
	UINT8	tbuffer[512];		// Buffer to store full sector results in
	UINT16	cylinder;

	#if VERBOSE
	logerror("corvus_read_sector: Read Drive: %d, physical sector: 0x%5.5x\n", drv, sector);
	#endif

	if(drv >= device_count(IO_HARDDISK)) {
		logerror("corvus_read_sector: Attempt to read from non-existant drive: %d\n", drv);
		return STAT_FATAL_ERR | STAT_DRIVE_NOT_ONLINE;
	}

	disk = corvus_hdc_file(drv);
	if(!disk) {
		logerror("corvus_read_sector: Failure returned by corvus_hdc_file(%d)\n", drv);
		return STAT_FATAL_ERR | STAT_DRIVE_NOT_ONLINE;
	}

	//
	// Calculate what cylinder the sector resides on for timing purposes
	//
	cylinder = (double) sector / (double) c->sectors_per_track / (double) c->tracks_per_cylinder;
	c->delay = abs(c->last_cylinder - cylinder) * TRACK_SEEK_TIME + INTERSECTOR_DELAY;

	hard_disk_read(disk, sector, 1, tbuffer);

	memcpy(buffer, tbuffer, len);

	c->last_cylinder = cylinder;

	#if VERBOSE
	logerror("corvus_read_sector: Data read follows:\n");
	dump_buffer(tbuffer, len);
	#endif

	return STAT_SUCCESS;
}



//
// Corvus_Read_Logical_Sector
//
// Read a variably-sized chunk of data from the user area of the virtual Corvus drive
//
// Pass:
//		dadr:	Corvus-encoded Disk Address -- Logical Sector
//		buffer:	Buffer to hold the data read from the disk
//		len:	Length of the buffer
//
// Returns:
//		status:	Corvus status
//
static UINT8 corvus_read_logical_sector(dadr_t *dadr, UINT8 *buffer, int len) {

	corvus_hdc_t
			*c = &corvus_hdc;
	UINT8	status;								// Status returned from Physical Sector read
	UINT8	drv;								// Drive number (1 - 15)
	UINT32	sector;								// Sector number on drive

	//
	// Unencode the first byte of the DADR
	//
	// High-order nibble of first byte is the most-significant nibble of the sector address
	// Low-order nibble of first byte is the drive id
	//
	// For example: 0x23 would decode to Drive ID #3, high-order nibble: 0x02.
	//
	drv = (dadr->address_msn_and_drive & 0x0f) - 1;
	sector = (dadr->address_msn_and_drive & 0xf0 << 12) | (dadr->address_mid << 8) | dadr->address_lsb;

	#if VERBOSE
	logerror("corvus_read_logical_sector: Reading based on DADR: 0x%6.6x, logical sector: 0x%5.5x, drive: %d\n", 
		dadr->address_msn_and_drive << 16 | dadr->address_lsb << 8 | dadr->address_mid, sector, drv);
	#endif

	//
	// Shift the logical sector address forward by the number of firmware cylinders (2) + the number of spare tracks (7)
	//
	sector += (c->tracks_per_cylinder * c->sectors_per_track * 2) + (SPARE_TRACKS * c->sectors_per_track);

	status = corvus_read_sector(drv, sector, buffer, len);

	if(status != STAT_SUCCESS)
		c->xmit_bytes = 1;

	return status;
}



//
// Corvus_Lock_Semaphore
//
// Lock a semaphore in the semaphore table
//
// Pass:
//		name:	Name of the semaphore to lock
//
// Returns:
//		status:	Disk status
//
// Side-effects:
//		Fills in the semaphore result code
//
static UINT8 corvus_lock_semaphore(UINT8 *name) {

	corvus_hdc_t
			*c = &corvus_hdc;
	semaphore_table_block_t
			semaphore_table;
	UINT8	offset = 0;
	UINT8	found = FALSE;
	UINT8	blank_offset = 32;	// Initialize to invalid offset
	UINT8	status;				// Status returned from Physical Sector read

	//
	// Read the semaphore table from the drive
	//
	status = corvus_read_sector(0, 7, semaphore_table.semaphore_block.semaphore_table, 256);
	if(status != STAT_SUCCESS) {
		logerror("corvus_lock_semaphore: Error reading semaphore table, status: 0x%2.2x\n", status);
		c->buffer.semaphore_locking_response.result = SEM_DISK_ERROR;
		return status;
	}

	//
	// Search the semaphore table to see if the semaphore already exists--if so it's locked
	// Also look for the first blank entry to stick the new one into
	//
	do {
		if(blank_offset == 32 && strncmp((char *) &semaphore_table.semaphore_block.semaphore_entry[offset], "        ", 8) == 0)
			blank_offset = offset;
		if(strncmp((char *) &semaphore_table.semaphore_block.semaphore_entry[offset], (char *) name, 8) == 0) {
			found = TRUE;
			break;
		}
	} while( ++offset < 32 );

	//
	// Deal with the found status
	//
	// - Stick it into the table if we didn't find it and there's room
	// - Respond with a "set" status if we did find it
	//
	// Once that's done, write the updated table to the disk
	//
	if(!found) {
		if(blank_offset == 32) {
			c->buffer.semaphore_locking_response.result = SEM_TABLE_FULL;					// No space for the semaphore!
		} else {
			c->buffer.semaphore_locking_response.result = SEM_PRIOR_STATE_NOT_SET;			// It wasn't there already
			memcpy(&semaphore_table.semaphore_block.semaphore_entry[blank_offset], name, 8);// Stick it into the table
			status = corvus_write_sector(0, 7, semaphore_table.semaphore_block.semaphore_table, 256);
			if(status != STAT_SUCCESS) {
				logerror("corvus_lock_semaphore: Error updating semaphore table, status: 0x%2.2x\n", status);
				c->buffer.semaphore_locking_response.result = SEM_DISK_ERROR;
				return status;
			}
		}
	} else {
		c->buffer.semaphore_locking_response.result = SEM_PRIOR_STATE_SET;					// It's already locked -- sorry
	}

	return STAT_SUCCESS;
}



//
// Corvus_Unlock_Semaphore
//
// Unock a semaphore in the semaphore table
//
// Pass:
//		name:	Name of the semaphore to unlock
//
// Returns:
//		status:	Disk status
//
// Side-effects:
//		Fills in the semaphore result code
//
static UINT8 corvus_unlock_semaphore(UINT8 *name) {

	corvus_hdc_t
			*c = &corvus_hdc;
	semaphore_table_block_t
			semaphore_table;
	UINT8	offset = 0;
	UINT8	found = FALSE;
	UINT8	status;				// Status returned from Physical Sector read

	//
	// Read the semaphore table from the drive
	//
	status = corvus_read_sector(0, 7, semaphore_table.semaphore_block.semaphore_table, 256);
	if(status != STAT_SUCCESS) {
		logerror("corvus_unlock_semaphore: Error reading semaphore table, status: 0x%2.2x\n", status);
		c->buffer.semaphore_locking_response.result = SEM_DISK_ERROR;
		return status;
	}

	//
	// Search the semaphore table to see if the semaphore already exists--if so it's locked
	//
	do {
		if(strncmp((char *) &semaphore_table.semaphore_block.semaphore_entry[offset], (char *) name, 8) == 0) {
			found = TRUE;
			break;
		}
	} while( ++offset < 32 );

	//
	// Deal with the found status
	//
	// - If we didn't find it, just respond that it wasn't there
	// - If we did find it, respond with a "set" status and clear it
	//
	// Once that's done, write the updated table to the disk
	//
	if(!found) {
		c->buffer.semaphore_locking_response.result = SEM_PRIOR_STATE_NOT_SET;				// It wasn't there already
	} else {
		c->buffer.semaphore_locking_response.result = SEM_PRIOR_STATE_SET;					// It was there
		memcpy(&semaphore_table.semaphore_block.semaphore_entry[offset], "        ", 8);	// Clear it
		status = corvus_write_sector(0, 7, semaphore_table.semaphore_block.semaphore_table, 256);
		if(status != STAT_SUCCESS) {
			logerror("corvus_unlock_semaphore: Error updating semaphore table, status: 0x%2.2x\n", status);
			c->buffer.semaphore_locking_response.result = SEM_DISK_ERROR;
			return status;
		}
	}

	return STAT_SUCCESS;
}



//
// Corvus_Init_Semaphore_Table
//
// Zap all of the semaphores from the table (set them to blanks)
//
// Pass:
//		Nothing
//
// Returns:
//		Disk status
//
//
static UINT8 corvus_init_semaphore_table( void ) {

	semaphore_table_block_t
			semaphore_table;
	UINT8	status;

	memset(semaphore_table.semaphore_block.semaphore_table, 0x20, 256);

	status = corvus_write_sector(0, 7, semaphore_table.semaphore_block.semaphore_table, 256);
	if(status != STAT_SUCCESS) {
		logerror("corvus_init_semaphore_table: Error updating semaphore table, status: 0x%2.2x\n", status);
		return status;
	}

	return STAT_SUCCESS;
}



//
// Corvus_Get_Drive_Parameters
//
// Fills in the Drive Parameter packet based on the opened CHD file
//
// Pass:
//		drv:	Drive number to get parameters from
//
// Returns:
//		Status of command
//
static UINT8 corvus_get_drive_parameters(UINT8 drv) {

	corvus_hdc_t
			*c = &corvus_hdc;
	UINT16	capacity;							// Number of usable 512-byte blocks
	UINT16	raw_capacity;						// Number of actual 512-byte blocks
	union {
		UINT8
			buffer[512];
		disk_parameter_block_t
			dpb;
	} raw_disk_parameter_block;					// Buffer for the Disk Parameter Block
	union {
		UINT8
			buffer[512];
		constellation_parameter_block_t
			cpb;
	} raw_constellation_parameter_block;		// Buffer for the Constellation Parameter Block
	UINT8	status;								// Status to return

	//
	// Make sure a valid drive is being accessed
	//
	drv -= 1;									// Internally, drives start at 0

	if(drv >= device_count(IO_HARDDISK)) {
		logerror("corvus_get_drive_parameters: Attempt to retrieve parameters from non-existant drive: %d\n", drv);
		c->xmit_bytes = 1;
		return STAT_FATAL_ERR | STAT_DRIVE_NOT_ONLINE;
	}

	//
	// Read the Disk Parameter Block (Sector 1) from the drive
	//
	status = corvus_read_sector(drv, 1, raw_disk_parameter_block.buffer, 512);
	if(status != STAT_SUCCESS) {
		logerror("corvus_get_drive_parameters: Error status returned reading Disk Parameter Block -- status: 0x%2.2x\n", status);
		c->xmit_bytes = 1;
		return status;
	}

	//
	// Read the Constellation Parameter Block (Sector 3) from the drive
	//
	status = corvus_read_sector(drv, 3, raw_constellation_parameter_block.buffer, 512);
	if(status != STAT_SUCCESS) {
		logerror("corvus_get_drive_parameters: Error status returned reading Constellation Parameter Block -- status: 0x%2.2x\n", status);
		c->xmit_bytes = 1;
		return status;
	}

	//
	// Build up the parameter packet
	//
	strcpy((char *) c->buffer.drive_param_response.firmware, "V18.4AP   -- CONST II - 11/82  %");	// Pulled from some firmware...
	c->buffer.drive_param_response.rom_version = VERSION;
	c->buffer.drive_param_response.track_info.sectors_per_track = c->sectors_per_track;
	c->buffer.drive_param_response.track_info.tracks_per_cylinder = c->tracks_per_cylinder;
	c->buffer.drive_param_response.track_info.cylinders_per_drive.msb = (c->cylinders_per_drive & 0xff00) >> 8;
	c->buffer.drive_param_response.track_info.cylinders_per_drive.lsb = (c->cylinders_per_drive & 0x00ff);

	//
	// Calculate the user capacity of the drive based on total capacity less spare tracks and firmware tracks
	//
	raw_capacity = c->tracks_per_cylinder * c->cylinders_per_drive * c->sectors_per_track; // Total capacity
	capacity = raw_capacity - ((c->tracks_per_cylinder * c->sectors_per_track * 2) + (SPARE_TRACKS * c->sectors_per_track));
	c->buffer.drive_param_response.capacity.msb = (capacity & 0xff0000) >> 16;
	c->buffer.drive_param_response.capacity.midb = (capacity & 0x00ff00) >> 8;
	c->buffer.drive_param_response.capacity.lsb = (capacity & 0x0000ff);

	//
	// Fill in the information from the Disk Parameter Block and Constellation Parameter Block
	//
	c->buffer.drive_param_response.interleave = raw_disk_parameter_block.dpb.interleave;
	memcpy(c->buffer.drive_param_response.table_info.mux_parameters, raw_constellation_parameter_block.cpb.mux_parameters, 12);
	memcpy(c->buffer.drive_param_response.table_info.pipe_name_table_ptr,
		raw_constellation_parameter_block.cpb.pipe_name_table_ptr, 2);
	memcpy(c->buffer.drive_param_response.table_info.pipe_ptr_table_ptr,
		raw_constellation_parameter_block.cpb.pipe_ptr_table_ptr, 2);
	memcpy(c->buffer.drive_param_response.table_info.pipe_area_size, raw_constellation_parameter_block.cpb.pipe_area_size, 2);
	memcpy(c->buffer.drive_param_response.table_info.vdo_table, raw_disk_parameter_block.dpb.vdo_table, 14);
	memcpy(c->buffer.drive_param_response.table_info.lsi11_vdo_table, raw_disk_parameter_block.dpb.lsi11_vdo_table, 8);
	memcpy(c->buffer.drive_param_response.table_info.lsi11_spare_table, raw_disk_parameter_block.dpb.lsi11_spare_table, 8);

	c->buffer.drive_param_response.drive_number = drv + 1;
	c->buffer.drive_param_response.physical_capacity.msb = (raw_capacity & 0xff0000) >> 16;
	c->buffer.drive_param_response.physical_capacity.midb = (raw_capacity & 0x00ff00) >> 8;
	c->buffer.drive_param_response.physical_capacity.lsb = (raw_capacity & 0x0000ff);

	#if VERBOSE
	logerror("corvus_get_drive_parameters: Drive Parameter packet follows:\n");
	dump_buffer(c->buffer.raw_data, 110);
	#endif

	return STAT_SUCCESS;
}



//
// Corvus_Read_Boot_Block
//
// Old-style Boot (0x14) command boot block reader
// 
// Pass:
//		block:	Boot block number to read (0-7)
//
// Returns:
//		status:	Status of read operation
//
static UINT8 corvus_read_boot_block(UINT8 block) {

	corvus_hdc_t	*c = &corvus_hdc;			// Pick up global controller structure

	#if VERBOSE
	logerror("corvus_read_boot_block: Reading boot block: %d\n", block);
	#endif

	return corvus_read_sector(0, 25 + block, c->buffer.read_512_response.data, 512);
	
}



//
// Corvus_Read_Firmware_Block
//
// Reads firmware information from the first cylinder of the drive
//
// Pass:
//		head:	Head number
//		sector:	Sector number
//
// Returns:
//		Status of command
//
static UINT8 corvus_read_firmware_block(UINT8 head, UINT8 sector) {

	corvus_hdc_t
			*c = &corvus_hdc;	// Pick up global controller structure
	UINT16	relative_sector;	// Relative sector on drive for Physical Read
	UINT8	status;

	relative_sector = head * c->sectors_per_track + sector;

	#if VERBOSE
	logerror("corvus_read_firmware_block: Reading firmware head: 0x%2.2x, sector: 0x%2.2x, relative_sector: 0x%2.2x\n",
		head, sector, relative_sector);
	#endif

	status = corvus_read_sector(0, relative_sector, c->buffer.read_512_response.data, 512);		// TODO: Which drive should Prep Mode talk to ???
	return status;
}



//
// Corvus_Write_Firmware_Block
//
// Writes firmware information to the first cylinder of the drive
//
// Pass:
//		head:	Head number
//		sector:	Sector number
//		buffer:	Data to be written
//
// Returns:
//		Status of command
//
static UINT8 corvus_write_firmware_block(UINT8 head, UINT8 sector, UINT8 *buffer) {

	corvus_hdc_t
			*c = &corvus_hdc;	// Pick up global controller structure
	UINT16	relative_sector;	// Relative sector on drive for Physical Read
	UINT8	status;

	relative_sector = head * c->sectors_per_track + sector;

	#if VERBOSE
	logerror("corvus_write_firmware_block: Writing firmware head: 0x%2.2x, sector: 0x%2.2x, relative_sector: 0x%2.2x\n",
		head, sector, relative_sector);
	#endif

	status = corvus_write_sector(0, relative_sector, buffer, 512);	// TODO: Which drive should Prep Mode talk to ???
	return status;
}



//
// Corvus_Format_Drive
//
// Write the pattern provided across the entire disk
//
// Pass:
//		pattern: 512-byte buffer containing the pattern to write to the whole drive
//
// Returns:
//		Status of command
//
static UINT8 corvus_format_drive(UINT8 *pattern, UINT16 len) {

	corvus_hdc_t
			*c = &corvus_hdc;
	UINT32	sector;
	UINT32	max_sector;
	UINT8	status = 0;
	UINT8	tbuffer[512];

	max_sector = c->sectors_per_track * c->tracks_per_cylinder * c->cylinders_per_drive;

	//
	// If we were passed less than 512 bytes, fill the buffer up with the first byte passed (for Omnidrive Format command)
	//
	if(len < 512) {
		memset(tbuffer, *pattern, 512);
		pattern = tbuffer;
		len = 512;
	}

	#if VERBOSE
	logerror("corvus_format_drive: Formatting drive with 0x%5.5x sectors, pattern buffer (passed length: %d)follows\n", max_sector, len);
	dump_buffer(pattern, 512);
	#endif

	for(sector = 0; sector <= max_sector; sector++) {
		status = corvus_write_sector(0, sector, pattern, 512);
		if(status != STAT_SUCCESS) {
			logerror("corvus_format_drive: Error while formatting drive in corvus_write_sector--sector: 0x%5.5x, status: 0x%x2.2x\n",
				sector, status);
			break;
		}
	}

	return status;
}



//
// Corvus_HDC_File
//
// Returns a hard_disk_file object for a given virtual hard drive device in the concept
//
// Pass:
//		id:		Drive number (1 - 15)
//
// Returns:
//		hard_disk_file object
//
static hard_disk_file *corvus_hdc_file(int id) {

	mess_image *img;
	
	img = image_from_devtype_and_index(IO_HARDDISK, id);
	if (!image_exists(img))
		return NULL;

	return mess_hd_get_hard_disk_file(img);
}



//
// Corvus_Process_Command_Packet
//
// Having received a complete packet from the host, process it
//
// Pass:
//		Invalid_Command_Flag:	Invalid command flag responses are handled in this routine
//
// Returns:
//		Nothing
//
static void corvus_process_command_packet(UINT8 invalid_command_flag) {

	corvus_hdc_t	*c = &corvus_hdc;

	#if VERBOSE && VERBOSE_RESPONSES
	logerror("corvus_hdc_data_w: Complete packet received.  Dump follows:\n");
	dump_buffer(c->buffer.raw_data, c->offset);
	#endif

	if(!invalid_command_flag) {
		if(!c->prep_mode) {
			switch(c->buffer.command.code) {
				//
				// Read / Write Chunk commands
				//
				case READ_CHUNK_128:
					c->buffer.read_128_response.status =
						corvus_read_logical_sector(&c->buffer.read_sector_command.dadr, c->buffer.read_128_response.data, 128);
					break;
				case READ_SECTOR_256:
				case READ_CHUNK_256:
					c->buffer.read_256_reponse.status =
						corvus_read_logical_sector(&c->buffer.read_sector_command.dadr, c->buffer.read_256_reponse.data, 256);
					break;
				case READ_CHUNK_512:
					c->buffer.read_512_response.status =
						corvus_read_logical_sector(&c->buffer.read_sector_command.dadr, c->buffer.read_512_response.data, 512);
					break;
				case WRITE_CHUNK_128:
					c->buffer.single_byte_response.status =
						corvus_write_logical_sector(&c->buffer.write_128_command.dadr, c->buffer.write_128_command.data, 128);
					break;
				case WRITE_SECTOR_256:
				case WRITE_CHUNK_256:
					c->buffer.single_byte_response.status =
						corvus_write_logical_sector(&c->buffer.write_256_command.dadr, c->buffer.write_256_command.data, 256);
					break;
				case WRITE_CHUNK_512:
					c->buffer.single_byte_response.status =
						corvus_write_logical_sector(&c->buffer.write_512_command.dadr, c->buffer.write_512_command.data, 512);
					break;
				//
				// Semaphore commands
				//
				case SEMAPHORE_LOCK_CODE:
			//	case SEMAPHORE_UNLOCK_CODE:
				case SEMAPHORE_INIT_CODE:
			//	case SEMAPHORE_STATUS_CODE:
					switch(c->buffer.command.modifier) {
						case SEMAPHORE_LOCK_MOD:
							c->buffer.semaphore_locking_response.status = corvus_lock_semaphore(c->buffer.lock_semaphore_command.name);
							break;
						case SEMAPHORE_UNLOCK_MOD:
							c->buffer.semaphore_locking_response.status =
								corvus_unlock_semaphore(c->buffer.unlock_semaphore_command.name);
							break;
						case SEMAPHORE_INIT_MOD:
							c->buffer.single_byte_response.status = corvus_init_semaphore_table();
							break;
						case SEMAPHORE_STATUS_MOD:
							c->buffer.semaphore_status_response.status = 
								corvus_read_sector(0, 7, c->buffer.semaphore_status_response.table, 256);
							break;
						default:
							invalid_command_flag = TRUE;
					}
					break;
				//
				// Miscellaneous commands
				//
				case BOOT:
					c->buffer.read_512_response.status =
						corvus_read_boot_block(c->buffer.old_boot_command.boot_block);
					break;
				case GET_DRIVE_PARAMETERS:
					c->buffer.drive_param_response.status =
						corvus_get_drive_parameters(c->buffer.get_drive_parameters_command.drive);
					break;
				case PREP_MODE_SELECT:
					c->prep_mode = TRUE;
					c->buffer.single_byte_response.status = STAT_SUCCESS;
					break;
				default:
					c->xmit_bytes = 1;						// Return a fatal status
					c->buffer.single_byte_response.status = STAT_FAULT | STAT_FATAL_ERR;
					logerror("corvus_hdc_data_w: Unimplemented command, returning FATAL FAULT status!\n");
					break;
			}
		} else {	// In Prep mode
			switch(c->buffer.command.code) {
				case PREP_MODE_SELECT:
					c->prep_mode = TRUE;
					c->buffer.single_byte_response.status = STAT_SUCCESS;
					break;
				case PREP_RESET_DRIVE:
					c->prep_mode = FALSE;
					c->buffer.single_byte_response.status = STAT_SUCCESS;
					break;
				case PREP_READ_FIRMWARE:
					c->buffer.drive_param_response.status =
						corvus_read_firmware_block((c->buffer.read_firmware_command.encoded_h_s & 0xe0) >> 5,
							c->buffer.read_firmware_command.encoded_h_s & 0x1f);
					break;
				case PREP_WRITE_FIRMWARE:
					c->buffer.drive_param_response.status =
						corvus_write_firmware_block((c->buffer.write_firmware_command.encoded_h_s & 0xe0) >> 5,
							c->buffer.write_firmware_command.encoded_h_s & 0x1f, c->buffer.write_firmware_command.data);
					break;
				case PREP_FORMAT_DRIVE:
					c->buffer.drive_param_response.status =
						corvus_format_drive(c->buffer.format_drive_revbh_command.pattern, c->offset - 512);
					break;
				default:
					c->xmit_bytes = 1;
					c->buffer.single_byte_response.status = STAT_FAULT | STAT_FATAL_ERR;
					logerror("corvus_hdc_data_w: Unimplemented Prep command, returning FATAL FAULT status!\n");
			}
		}
		#if VERBOSE && VERBOSE_RESPONSES
		logerror("corvus_hdc_data_w: Command execution complete, status: 0x%2.2x.  Response dump follows:\n",
			c->buffer.single_byte_response.status);
		dump_buffer(c->buffer.raw_data, c->xmit_bytes);
		#endif

	} // if(!invalid_command_flag)

	//
	// Use a separate "if" in case the Invalid Command Flag was set as a result of a two-byte command
	//
	if(invalid_command_flag) {
		//
		// An Illegal command was detected (Truly invalid, not just unimplemented)
		//
		c->buffer.single_byte_response.status = 
			STAT_FATAL_ERR | STAT_ILL_CMD_OP_CODE;		// Respond with an Illegal Op Code

		logerror("corvus_hdc_data_w: Illegal Command, status: 0x%2.2x\n", c->buffer.single_byte_response.status);
	}
	//
	// Command execution complete, free up the controller
	//
	c->offset = 0;									// Point to beginning of buffer for response

	#if VERBOSE
	logerror("corvus_hdc_data_w: Setting one-time mame timer of %d microseconds to simulate disk function\n", c->delay);
	#endif

	//
	// Set up timers for command completion and timeout from host
	//
	mame_timer_set(double_to_mame_time(TIME_IN_USEC(c->delay)), CALLBACK_CTH_MODE, corvus_hdc_callback);
	mame_timer_enable(c->timeout_timer, 0);			// We've received enough data, disable the timeout timer

	c->delay = 0;									// Reset delay for next function
}



//
// Corvus_HDC_Callback
//
// Callback routine for completion of controller functions
//
// Pass:
//		Callback Function
//
// Returns:
//		Nothing
//
static void corvus_hdc_callback(int function) {

	corvus_hdc_t	*c = &corvus_hdc;

	switch(function) {
		case CALLBACK_CTH_MODE:
			c->status |= CONTROLLER_DIRECTION;				// Set to Controller-to-Host, Ready mode
			c->status &= ~(CONTROLLER_BUSY);

			#if VERBOSE
			logerror("corvus_hdc_callback: Callback executed with function CALLBACK_CTH_MODE\n");
			#endif

			break;
		case CALLBACK_HTC_MODE:
			c->status &= ~(CONTROLLER_DIRECTION |
				CONTROLLER_BUSY);							// Set to Host-to-Controller, Ready mode

			#if VERBOSE
			logerror("corvus_hdc_callback: Callback executed with function CALLBACK_HTC_MODE\n");
			#endif

			break;
		case CALLBACK_SAME_MODE:
			c->status &= ~(CONTROLLER_BUSY);				// Set the controller to Ready mode

			break;
		case CALLBACK_TIMEOUT:								// We reached a four-second timeout threshold
			if(c->offset < c->recv_bytes || (c->offset > c->recv_bytes && c->recv_bytes != 0)) {
				c->buffer.single_byte_response.status = STAT_TIMEOUT;
				c->status |= CONTROLLER_DIRECTION;
				c->status &= ~(CONTROLLER_BUSY);
				c->recv_bytes = 0;
				c->xmit_bytes = 1;
				logerror("corvus_hdc_callback: Exceeded four-second timeout for data from host, resetting communications\n");
			} else { // if(c->recv_bytes == 0) 				   This was a variable-size command
				#if VERBOSE
				logerror("corvus_hdc_callback: Executing variable-length command via four-second timeout\n");
				#endif
				corvus_process_command_packet(0);			// Process the command
			}
			break;
		default:
			logerror("corvus_hdc_callback: FATAL ERROR -- Unknown callback function: %d\n", function);
			assert(0);
	}
	if(function != CALLBACK_SAME_MODE) {
		mame_timer_enable(c->timeout_timer, 0);				// Disable the four-second timer now that we're done
	}
}



//
// Corvus_HDC_Init
//
// Global routine to initialize the Hard Disk Controller structures and arrays
//
// Pass:
//		Nothing
//
// Returns:
//		NULL if there's no file to attach to
//
UINT8 corvus_hdc_init() {

	corvus_hdc_t			*c = &corvus_hdc;	// Pick up global controller structure
	hard_disk_file	*disk;				// Structures for interface to CHD routines
	hard_disk_info	*info;

	if((disk = corvus_hdc_file(0)))				// Attach to the CHD file
		info = hard_disk_get_info(disk);		// Pick up the Head/Cylinder/Sector info
	else
		return 0;

	c->status &= ~(CONTROLLER_DIRECTION | CONTROLLER_BUSY);	// Host-to-controller mode, Idle (awaiting command from Host mode)
	c->prep_mode = FALSE;						// We're not in Prep Mode
	c->sectors_per_track = info->sectors;
	c->tracks_per_cylinder = info->heads;
	c->cylinders_per_drive = info->cylinders;
	c->offset = 0;								// Buffer is empty
	c->awaiting_modifier = FALSE;				// We're not in the middle of a two-byte command
	c->xmit_bytes = 0;							// We don't have anything to say to the host
	c->recv_bytes = 0;							// We aren't waiting on additional data from the host

	c->timeout_timer = mame_timer_alloc(corvus_hdc_callback);	// Set up a timer to handle the four-second host-to-controller timeout
	mame_timer_adjust(c->timeout_timer, double_to_mame_time(TIME_IN_SEC(4)), CALLBACK_TIMEOUT, double_to_mame_time(0));
	mame_timer_enable(c->timeout_timer, 0);		// Start this timer out disabled

	#if VERBOSE
	logerror("corvus_hdc_init: Attached to drive image: H:%d, C:%d, S:%d\n", info->heads, info->cylinders, info->sectors);
	#endif

	//
	// Define all of the packet sizes for the commands
	//

	// Read / Write commands
	corvus_cmd[READ_SECTOR_256][0].recv_bytes = 4;
	corvus_cmd[READ_SECTOR_256][0].xmit_bytes = 257;
	corvus_cmd[WRITE_SECTOR_256][0].recv_bytes = 260;
	corvus_cmd[WRITE_SECTOR_256][0].xmit_bytes = 1;
	corvus_cmd[READ_CHUNK_128][0].recv_bytes = 4;
	corvus_cmd[READ_CHUNK_128][0].xmit_bytes = 129;
	corvus_cmd[READ_CHUNK_256][0].recv_bytes = 4;
	corvus_cmd[READ_CHUNK_256][0].xmit_bytes = 257;
	corvus_cmd[READ_CHUNK_512][0].recv_bytes = 4;
	corvus_cmd[READ_CHUNK_512][0].xmit_bytes = 513;
	corvus_cmd[WRITE_CHUNK_128][0].recv_bytes = 132;
	corvus_cmd[WRITE_CHUNK_128][0].xmit_bytes = 1;
	corvus_cmd[WRITE_CHUNK_256][0].recv_bytes = 260;
	corvus_cmd[WRITE_CHUNK_256][0].xmit_bytes = 1;
	corvus_cmd[WRITE_CHUNK_512][0].recv_bytes = 516;
	corvus_cmd[WRITE_CHUNK_512][0].xmit_bytes = 1;

	// Semaphore commands
	corvus_cmd[SEMAPHORE_LOCK_CODE][SEMAPHORE_LOCK_MOD].recv_bytes = 10;
	corvus_cmd[SEMAPHORE_LOCK_CODE][SEMAPHORE_LOCK_MOD].xmit_bytes = 12;
	corvus_cmd[SEMAPHORE_UNLOCK_CODE][SEMAPHORE_UNLOCK_MOD].recv_bytes = 10;
	corvus_cmd[SEMAPHORE_UNLOCK_CODE][SEMAPHORE_UNLOCK_MOD].xmit_bytes = 12;
	corvus_cmd[SEMAPHORE_INIT_CODE][SEMAPHORE_INIT_MOD].recv_bytes = 5;
	corvus_cmd[SEMAPHORE_INIT_CODE][SEMAPHORE_INIT_MOD].xmit_bytes = 1;
	corvus_cmd[SEMAPHORE_STATUS_CODE][SEMAPHORE_STATUS_MOD].recv_bytes = 5;
	corvus_cmd[SEMAPHORE_STATUS_CODE][SEMAPHORE_STATUS_MOD].xmit_bytes = 257;

	// Pipe commands
	corvus_cmd[PIPE_READ_CODE][PIPE_READ_MOD].recv_bytes =	5;
	corvus_cmd[PIPE_READ_CODE][PIPE_READ_MOD].xmit_bytes =	516;
	corvus_cmd[PIPE_WRITE_CODE][PIPE_WRITE_MOD].recv_bytes = 517;
	corvus_cmd[PIPE_WRITE_CODE][PIPE_WRITE_MOD].xmit_bytes = 12;
	corvus_cmd[PIPE_CLOSE_CODE][PIPE_CLOSE_MOD].recv_bytes = 5;
	corvus_cmd[PIPE_CLOSE_CODE][PIPE_CLOSE_MOD].xmit_bytes = 12;
	corvus_cmd[PIPE_STATUS_CODE][PIPE_STATUS_MOD].recv_bytes = 5;
	corvus_cmd[PIPE_STATUS_CODE][PIPE_STATUS_MOD].xmit_bytes = 513;	// There are actually two possibilities here
	corvus_cmd[PIPE_OPEN_WRITE_CODE][PIPE_OPEN_WRITE_MOD].recv_bytes = 10;
	corvus_cmd[PIPE_OPEN_WRITE_CODE][PIPE_OPEN_WRITE_MOD].xmit_bytes = 12;
	corvus_cmd[PIPE_AREA_INIT_CODE][PIPE_AREA_INIT_MOD].recv_bytes = 10;
	corvus_cmd[PIPE_AREA_INIT_CODE][PIPE_AREA_INIT_MOD].xmit_bytes = 12;
	corvus_cmd[PIPE_OPEN_READ_CODE][PIPE_OPEN_READ_MOD].recv_bytes = 10;
	corvus_cmd[PIPE_OPEN_READ_CODE][PIPE_OPEN_READ_MOD].xmit_bytes = 12;

	// Active User Table Commands
	corvus_cmd[ADDACTIVE_CODE][ADDACTIVE_MOD].recv_bytes = 18;
	corvus_cmd[ADDACTIVE_CODE][ADDACTIVE_MOD].xmit_bytes = 2;
	corvus_cmd[DELACTIVEUSR_REVBH_CODE][DELACTIVEUSR_REVBH_MOD].recv_bytes = 18;
	corvus_cmd[DELACTIVEUSR_REVBH_CODE][DELACTIVEUSR_REVBH_MOD].xmit_bytes = 2;
	corvus_cmd[DELACTIVENUM_OMNI_CODE][DELACTIVENUM_OMNI_MOD].recv_bytes = 18;
	corvus_cmd[DELACTIVENUM_OMNI_CODE][DELACTIVENUM_OMNI_MOD].xmit_bytes = 2;
	corvus_cmd[DELACTIVEUSR_OMNI_CODE][DELACTIVEUSR_OMNI_MOD].recv_bytes = 18;
	corvus_cmd[DELACTIVEUSR_OMNI_CODE][DELACTIVEUSR_OMNI_MOD].xmit_bytes = 2;
	corvus_cmd[FINDACTIVE_CODE][FINDACTIVE_MOD].recv_bytes = 18;
	corvus_cmd[FINDACTIVE_CODE][FINDACTIVE_MOD].xmit_bytes = 17;
	corvus_cmd[READTEMPBLOCK][0].recv_bytes = 2;
	corvus_cmd[READTEMPBLOCK][0].xmit_bytes = 513;
	corvus_cmd[WRITETEMPBLOCK][0].recv_bytes = 514;
	corvus_cmd[WRITETEMPBLOCK][0].xmit_bytes = 1;

	// Miscellaneous Commands
	corvus_cmd[BOOT][0].recv_bytes = 2;
	corvus_cmd[BOOT][0].xmit_bytes = 513;
	corvus_cmd[READ_BOOT_BLOCK][0].recv_bytes = 3;
	corvus_cmd[READ_BOOT_BLOCK][0].xmit_bytes = 513;
	corvus_cmd[GET_DRIVE_PARAMETERS][0].recv_bytes = 2;
	corvus_cmd[GET_DRIVE_PARAMETERS][0].xmit_bytes = 129;
	corvus_cmd[PARK_HEADS_REVH][0].recv_bytes = 514;
	corvus_cmd[PARK_HEADS_REVH][0].xmit_bytes = 1;
	corvus_cmd[PARK_HEADS_OMNI][0].recv_bytes = 1;
	corvus_cmd[PARK_HEADS_OMNI][0].xmit_bytes = 1;
	corvus_cmd[ECHO][0].recv_bytes = 513;
	corvus_cmd[ECHO][0].xmit_bytes = 513;

	// Put Drive in Prep Mode
	corvus_cmd[PREP_MODE_SELECT][0].recv_bytes = 514;
	corvus_cmd[PREP_MODE_SELECT][0].xmit_bytes = 1;

	// Prep Mode Commands
	corvus_prep_cmd[PREP_MODE_SELECT].recv_bytes = 514;
	corvus_prep_cmd[PREP_MODE_SELECT].xmit_bytes = 1;
	corvus_prep_cmd[PREP_RESET_DRIVE].recv_bytes = 1;
	corvus_prep_cmd[PREP_RESET_DRIVE].xmit_bytes = 1;
	corvus_prep_cmd[PREP_FORMAT_DRIVE].recv_bytes = 0;
	corvus_prep_cmd[PREP_FORMAT_DRIVE].xmit_bytes = 1;
	corvus_prep_cmd[PREP_FILL_DRIVE_OMNI].recv_bytes = 3;
	corvus_prep_cmd[PREP_FILL_DRIVE_OMNI].xmit_bytes = 1;
	corvus_prep_cmd[PREP_VERIFY].recv_bytes = 1;
	corvus_prep_cmd[PREP_VERIFY].xmit_bytes = 0;
	corvus_prep_cmd[PREP_READ_FIRMWARE].recv_bytes = 2;
	corvus_prep_cmd[PREP_READ_FIRMWARE].xmit_bytes = 513;
	corvus_prep_cmd[PREP_WRITE_FIRMWARE].recv_bytes = 514;
	corvus_prep_cmd[PREP_WRITE_FIRMWARE].xmit_bytes = 1;

	#if VERBOSE
	logerror("corvus_hdc_init: Drive structures initialized\n");
	#endif

	return TRUE;
}



//
// Corvus_HDC_Status_R
//
// Global routine to read the Status Register from the Controller (Controller to Host)
//
// Pass:
//		Nothing
//
// Returns:
//		Value in the controller status register
//
READ8_HANDLER ( corvus_hdc_status_r ) {

	corvus_hdc_t *c = &corvus_hdc;

	return c->status;
}



//
// Corvus_HDC_Data_R
//
// Read the Data Register from the Controller (Controller to Host).  If transmission is complete,
// as defined as offset == bytes to transmit, reset the status to Host-to-Controller mode and Idle
// when complete.
//
// Pass:
//		Nothing
//
// Returns:
//		Value in the controller data register
//
READ8_HANDLER ( corvus_hdc_data_r ) {

	corvus_hdc_t *c = &corvus_hdc;
	UINT8 result;

	if((c->status & CONTROLLER_DIRECTION) == 0) {	// Check to see if we're in Controller-to-Host mode
		logerror("corvus_hdc_data_r: Data register read when in Host-to-Controller mode (status: 0x%2.2x)\n", c->status);
		return 0;
	}

	if((c->status & CONTROLLER_BUSY) != 0) {		// Check to see if we're Busy
		logerror("corvus_hdc_data_r: Data register read when Busy (status: 0x%2.2x)\n", c->status);
		return 0;
	}

	result = c->buffer.raw_data[c->offset++];

	if(c->offset == c->xmit_bytes) {
		#if VERBOSE
		logerror("corvus_hdc_data_r: Finished transmitting %d bytes of data.  Returning to idle mode.\n", c->xmit_bytes);
		#endif

		c->offset = 0;			// We've reached the end of valid data
		c->xmit_bytes = 0;		// We don't have anything more to say
		c->recv_bytes = 0;		// No active commands
		
		mame_timer_set(double_to_mame_time(TIME_IN_USEC(INTERBYTE_DELAY)), CALLBACK_HTC_MODE, corvus_hdc_callback);

//		c->status &= ~(CONTROLLER_DIRECTION | CONTROLLER_BUSY);	// Put us in Idle, Host-to-Controller mode
	} else {
		//
		// Not finished with this packet.  Insert an interbyte delay and then let the host continue
		//
		mame_timer_set(double_to_mame_time(TIME_IN_USEC(INTERBYTE_DELAY)), CALLBACK_SAME_MODE, corvus_hdc_callback);		
	}

	return result;
}



//
// Corvus_HDC_Data_W
//
// Write to the Data Register on the Controller (Host to Controller)
//
// Pass:
//		Value to write to controller data register
//
// Returns:
//		Nothing
//
WRITE8_HANDLER ( corvus_hdc_data_w ) {

	corvus_hdc_t	*c = &corvus_hdc;
	static UINT8	invalid_command_flag;		// I hate this, but it saves a lot more tests

	//
	// Received a byte -- check to see if we should really respond
	//
	if((c->status & CONTROLLER_DIRECTION) != 0) {		// System wrote to controller when controller wasn't listening
		logerror("corvus_hdc_data_w: Data register written when in Controller-to-Host mode (status: 0x%2.2x, data: 0x%2.2x)\n",
			c->status, data);
		return;
	}

	if((c->status & CONTROLLER_BUSY) != 0) {			// System wrote to controller when controller was busy
		logerror("corvus_hdc_data_w: Data register written when controller not Ready (status: 0x%2.2x, data: 0x%2.2x)\n",
			c->status, data);
		return;
	}

	//
	// We're supposed to be paying attention.  Make a decision about the data received
	//
	if(c->offset == 0)	{													// First byte of a packet
		#if VERBOSE
		logerror("corvus_hdc_data_w: Received a byte with c->offset == 0.  Processing as command: 0x%2.2x\n", data);
		#endif
		invalid_command_flag = parse_hdc_command(data);
		mame_timer_reset(c->timeout_timer, double_to_mame_time(TIME_IN_SEC(4)));
		mame_timer_enable(c->timeout_timer, 1);								// Start our four-second timer
	} else if(c->offset == 1 && c->awaiting_modifier) {						// Second byte of a packet
		#if VERBOSE
		logerror("corvus_hdc_data_w: Received a byte while awaiting modifier with c->offset == 0.  Processing as modifier: 0x%2.2x\n", data);
		#endif
		c->awaiting_modifier = FALSE;
		c->recv_bytes = corvus_cmd[c->buffer.command.code][data].recv_bytes;
		c->xmit_bytes = corvus_cmd[c->buffer.command.code][data].xmit_bytes;
	}

	c->buffer.raw_data[c->offset++] = data;

	assert(c->offset <= MAX_COMMAND_SIZE);									// Something is wrong, or I undersized the buffer

	//
	// We now have enough information to make a decision whether to execute the command, respond with a fatal response
	// or just wait for more data.  If we can do something, execute the command.  Otherwise, just fall through and return
	// to the user with us Ready for more data and in Host-to-Controller mode.
	//
	if(c->offset == c->recv_bytes) {						// We've received enough data to process
		corvus_process_command_packet(invalid_command_flag);
	} else {
		//
		// Reset the four-second timer since we received some data
		//
		mame_timer_reset(c->timeout_timer, double_to_mame_time(TIME_IN_SEC(4)));

		//
		// Make the controller busy for a few microseconds while the command is processed
		//
		c->status |= CONTROLLER_BUSY;
		mame_timer_set(double_to_mame_time(TIME_IN_USEC(INTERBYTE_DELAY)), CALLBACK_SAME_MODE, corvus_hdc_callback);
	}
}
