// license:BSD-3-Clause
// copyright-holders:Brett Wyer, Raphael Nabet
/*****************************************************************************
 *
 * includes/corvushd.h
 *
 * Implementation of the Corvus Systems Flat Cable Hard Disk controller
 *
 * Corvus Model 6 (5 MB): IMI 5006H hard disk (-chs 144,4,20 -ss 512)
 * Corvus Model 11 (10 MB): IMI 5012H hard disk (-chs 358,3,20 -ss 512)
 * Corvus Model 20 (20 MB): IMI 5018H hard disk (-chs 388,5,20 -ss 512)
 *
 ****************************************************************************/

#ifndef CORVUSHD_H_
#define CORVUSHD_H_

#include "emu.h"
#include "imagedev/harddriv.h"
#include <ctype.h>

//
// Controller Commands
//

// Read/Write Commands

#define READ_SECTOR_256         0x02
#define WRITE_SECTOR_256        0x03
#define READ_CHUNK_128          0x12
#define READ_CHUNK_256          0x22
#define READ_CHUNK_512          0x32
#define WRITE_CHUNK_128         0x13
#define WRITE_CHUNK_256         0x23
#define WRITE_CHUNK_512         0x33

// Semaphore Commands

#define SEMAPHORE_LOCK_CODE     0x0b
#define SEMAPHORE_LOCK_MOD          0x01
#define SEMAPHORE_UNLOCK_CODE   0x0b
#define SEMAPHORE_UNLOCK_MOD        0x11
#define SEMAPHORE_INIT_CODE     0x1a
#define SEMAPHORE_INIT_MOD          0x10
#define SEMAPHORE_STATUS_CODE   0x1a
#define SEMAPHORE_STATUS_MOD        0x41

// Pipe Commands

#define PIPE_READ_CODE          0x1a
#define PIPE_READ_MOD               0x20
#define PIPE_WRITE_CODE         0x1a
#define PIPE_WRITE_MOD              0x21
#define PIPE_CLOSE_CODE         0x1a
#define PIPE_CLOSE_MOD              0x40
#define PIPE_STATUS_CODE        0x1a
#define PIPE_STATUS_MOD             0x41
#define PIPE_OPEN_WRITE_CODE    0x1b
#define PIPE_OPEN_WRITE_MOD         0x80
#define PIPE_AREA_INIT_CODE     0x1b
#define PIPE_AREA_INIT_MOD          0xa0
#define PIPE_OPEN_READ_CODE     0x1b
#define PIPE_OPEN_READ_MOD          0xc0

// Active User Table Commands

#define ADDACTIVE_CODE          0x34
#define ADDACTIVE_MOD               0x03
#define DELACTIVEUSR_REVBH_CODE 0x34
#define DELACTIVEUSR_REVBH_MOD      0x00
#define DELACTIVENUM_OMNI_CODE  0x34
#define DELACTIVENUM_OMNI_MOD       0x00
#define DELACTIVEUSR_OMNI_CODE  0x34
#define DELACTIVEUSR_OMNI_MOD       0x31
#define FINDACTIVE_CODE         0x34
#define FINDACTIVE_MOD              0x05
#define READTEMPBLOCK           0xc4
#define WRITETEMPBLOCK          0xb4

// Miscellaneous Commands

#define BOOT                    0x14
#define READ_BOOT_BLOCK         0x44
#define GET_DRIVE_PARAMETERS    0x10
#define PARK_HEADS_REVH         0x11
#define PARK_HEADS_OMNI         0x80
#define ECHO                    0xf4

// Put drive in Prep Mode

#define PREP_MODE_SELECT        0x11

// Prep Mode Commands

#define PREP_RESET_DRIVE        0x00
#define PREP_FORMAT_DRIVE       0x01
#define PREP_FILL_DRIVE_OMNI    0x81
#define PREP_VERIFY             0x07
#define PREP_READ_FIRMWARE      0x32
#define PREP_WRITE_FIRMWARE     0x33

//
// Controller Status Codes
//

// Disk status codes

#define STAT_SUCCESS            0x00

#define STAT_HEADER_FAULT       0x00
#define STAT_SEEK_TIMEOUT       0x01
#define STAT_SEEK_FAULT         0x02
#define STAT_SEEK_ERROR         0x03
#define STAT_HEADER_CRC_ERROR   0x04
#define STAT_REZERO_FAULT       0x05
#define STAT_REZERO_TIMEOUT     0x06
#define STAT_DRIVE_NOT_ONLINE   0x07
#define STAT_WRITE_FAULT        0x08
#define STAT_NOT_USED           0x09
#define STAT_READ_DATA_FAULT    0x0a
#define STAT_DATA_CRC_ERROR     0x0b
#define STAT_SECTOR_LOCATE_ERR  0x0c
#define STAT_WRITE_PROTECTED    0x0d
#define STAT_ILL_SECTOR_ADDRESS 0x0e
#define STAT_ILL_CMD_OP_CODE    0x0f
#define STAT_DRIVE_NOT_ACK      0x10
#define STAT_ACK_STUCK_ACTIVE   0x11
#define STAT_TIMEOUT            0x12
#define STAT_FAULT              0x13
#define STAT_CRC                0x14
#define STAT_SEEK               0x15
#define STAT_VERIFICATION       0x16
#define STAT_SPEED_ERROR        0x17
#define STAT_ILL_ADDRESS        0x18
#define STAT_RW_FAULT_ERROR     0x19
#define STAT_SERVO_ERROR        0x1a
#define STAT_GUARD_BAND         0x1b
#define STAT_PLO_ERROR          0x1c
#define STAT_RW_UNSAFE          0x1d

// Disk status modifiers (added to status code)

#define STAT_RECOVERABLE_ERR    0x20
#define STAT_VERIFY_ERR         0x40
#define STAT_FATAL_ERR          0x80

// Semaphore status codes

#define SEM_PRIOR_STATE_NOT_SET 0x00
#define SEM_PRIOR_STATE_SET     0x80
#define SEM_TABLE_FULL          0xfd
#define SEM_DISK_ERROR          0xfe

// Pipe Status codes

#define PIPE_REQ_SUCCESSFUL     0x00
#define PIPE_EMPTY_PIPE_READ    0x08
#define PIPE_NOT_OPEN           0x09
#define PIPE_WRITE_TO_FULL_PIPE 0x0a
#define PIPE_OPEN_OPEN_PIPE     0x0b
#define PIPE_PIPE_NOT_EXIST     0x0c
#define PIPE_NO_ROOM_FOR_NEW    0x0d
#define PIPE_ILLEGAL_COMMAND    0x0e
#define PIPE_AREA_NOT_INIT      0x0f

// Pipe State codes

#define PIPE_OPEN_WRITE_EMPTY   0x01
#define PIPE_OPEN_READ_EMPTY    0x02
#define PIPE_NOT_OPEN_FULL      0x80
#define PIPE_OPEN_WRITE_FULL    0x81
#define PIPE_OPEN_READ_FULL     0x82

// Status Register Bits

#define CONTROLLER_BUSY         0x80    // Set = Busy, Clear = Ready
#define CONTROLLER_DIRECTION    0x40    // Set = Controller->Host, Clear = Host->Controller

#define MAX_COMMAND_SIZE 4096   // The maximum size of a command packet (the controller only has 5K of RAM...)

class corvus_hdc_t :  public device_t
{
public:
	// construction/destruction
	corvus_hdc_t(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);

	DECLARE_READ8_MEMBER( read );
	DECLARE_WRITE8_MEMBER( write );
	DECLARE_READ8_MEMBER( status_r );

protected:
	// device-level overrides
	virtual void device_start() override;
	virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;

private:
	enum
	{
		TIMER_TIMEOUT,
		TIMER_COMMAND
	};

	// Sector addressing scheme for Rev B/H drives used in various commands (Called a DADR in the docs)
	struct dadr_t {
		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
	};

	UINT8   m_status;             // Controller status byte (DIRECTION + BUSY/READY)
	// Prep mode
	bool    m_prep_mode;          // Whether the controller is in Prep Mode or not
	UINT8   m_prep_drv;           // If in prep mode, Corvus drive id (1..15) being prepped
	// Physical drive info
	UINT8   m_sectors_per_track;  // Number of sectors per track for this drive
	UINT8   m_tracks_per_cylinder;// Number of tracks per cylinder (heads)
	UINT16  m_cylinders_per_drive;// Number of cylinders per drive
	// Command Processing
	UINT16  m_offset;             // Current offset into raw_data buffer
	bool    m_awaiting_modifier;  // We've received a two-byte command and we're waiting for the mod
	UINT16  m_recv_bytes;         // Number of bytes expected to be received from Host
	UINT16  m_xmit_bytes;         // Number of bytes expected to be transmitted to host
	// Timing-related values
	UINT16  m_last_cylinder;      // Last cylinder accessed - for calculating seek times
	UINT32  m_delay;              // Delay in microseconds for callback
	emu_timer   *m_timeout_timer; // Four-second timer for timeouts
	emu_timer *m_cmd_timer;
	bool   m_invalid_command_flag;       // I hate this, but it saves a lot more tests

	//
	// 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_response;
		//
		// 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_desc[31];          // Firmware string description
			UINT8   firmware_rev;               // Firmware revision number
			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;
		//
		// Put drive into prep mode command (0x11)
		//
		struct {
			UINT8   code;               // Command code
			UINT8   drive;              // Drive number (starts at 1)
			UINT8   prep_block[512];    // Machine code payload
		} prep_mode_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;
		//
		// Verify Drive command (Prep Mode 0x07)
		//
		// On the real Corvus controller, this is a variable length response.  If the
		// number of bad sectors is greater than zero, an additional four bytes will
		// follow for each bad sector.  We don't emulate bad sectors, so we always
		// return a count of 0.  That makes this a fixed length response of 2 bytes.
		//
		struct {
			UINT8   status;       // Disk access status
			UINT8   bad_sectors;  // Number of bad sectors (always zero)
		} verify_drive_response;
	} m_buffer;

	// Structure of Block #1, the Disk Parameter Block
	struct disk_parameter_block_t {
		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];
	};

	// Structure of Block #3, the Constellation Parameter Block
	struct constellation_parameter_block_t {
		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];
	};

	// Structure of Block #7, the Semaphore Table Block
	struct semaphore_table_block_t {
		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
	};

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

	void dump_buffer(UINT8 *buffer, UINT16 length);
	bool parse_hdc_command(UINT8 data);
	UINT8 corvus_write_sector(UINT8 drv, UINT32 sector, UINT8 *buffer, int len);
	UINT8 corvus_write_logical_sector(dadr_t *dadr, UINT8 *buffer, int len);
	UINT8 corvus_read_sector(UINT8 drv, UINT32 sector, UINT8 *buffer, int len);
	UINT8 corvus_read_logical_sector(dadr_t *dadr, UINT8 *buffer, int len);
	UINT8 corvus_lock_semaphore(UINT8 *name);
	UINT8 corvus_unlock_semaphore(UINT8 *name);
	UINT8 corvus_init_semaphore_table();
	UINT8 corvus_get_drive_parameters(UINT8 drv);
	UINT8 corvus_read_boot_block(UINT8 block);
	UINT8 corvus_enter_prep_mode(UINT8 drv, UINT8 *prep_block);
	UINT8 corvus_exit_prep_mode();
	UINT8 corvus_read_firmware_block(UINT8 head, UINT8 sector);
	UINT8 corvus_write_firmware_block(UINT8 head, UINT8 sector, UINT8 *buffer);
	UINT8 corvus_format_drive(UINT8 *pattern, UINT16 len);
	hard_disk_file *corvus_hdc_file(int id);
	void corvus_process_command_packet(bool local_invalid_command_flag);

	corvus_cmd_t corvus_cmd[0xf5][0xc1];     // Command sizes and their return sizes
	corvus_cmd_t corvus_prep_cmd[0x82];      // Prep Command sizes and their return sizes
};


// device type definition
extern const device_type CORVUS_HDC;

#endif /* CORVUSHD_H_ */
