text: // 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 MAME_MACHINE_CORVUSHD_H
  #define MAME_MACHINE_CORVUSHD_H
  
  #pragma once
  
  #include "imagedev/harddriv.h"
  #include <ctype.h>
  
  class corvus_hdc_device :  public device_t
  {
  public:
  	// Status Register Bits
  	static constexpr uint8_t CONTROLLER_BUSY      = 0x80; // Set = Busy, Clear = Ready
  	static constexpr uint8_t CONTROLLER_DIRECTION = 0x40; // Set = Controller->Host, Clear = Host->Controller
  
  	// construction/destruction
  	corvus_hdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t 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:
  	static constexpr unsigned MAX_COMMAND_SIZE = 4096;   // The maximum size of a command packet (the controller only has 5K of RAM...)
  
  	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_t address_msn_and_drive;// Most significant nibble: Most significant nibble of sector address, Least significant nibble: Drive #
  		uint8_t address_lsb;          // Least significant byte of sector address
  		uint8_t address_mid;          // Middle byte of sector address
  	};
  
  	uint8_t   m_status;             // Controller status byte (DIRECTION + BUSY/READY)
  	// Prep mode
  	bool    m_prep_mode;          // Whether the controller is in Prep Mode or not
  	uint8_t   m_prep_drv;           // If in prep mode, Corvus drive id (1..15) being prepped
  	// Physical drive info
  	uint8_t   m_sectors_per_track;  // Number of sectors per track for this drive
  	uint8_t   m_tracks_per_cylinder;// Number of tracks per cylinder (heads)
  	uint16_t  m_cylinders_per_drive;// Number of cylinders per drive
  	// Command Processing
  	uint16_t  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_t  m_recv_bytes;         // Number of bytes expected to be received from Host
  	uint16_t  m_xmit_bytes;         // Number of bytes expected to be transmitted to host
  	// Timing-related values
  	uint16_t  m_last_cylinder;      // Last cylinder accessed - for calculating seek times
  	uint32_t  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_t       raw_data[MAX_COMMAND_SIZE];
  		//
  		// Basic interpretation of code and modifier
  		//
  		struct {
  			uint8_t   code;       // First byte of data is the code (command)
  			uint8_t   modifier;   // Second byte of data is the modifier
  		} command;
  		//
  		// Basic response code
  		//
  		struct {
  			uint8_t   status;     // Status code returned by the command executed
  		} single_byte_response;
  		//
  		// Read sector command
  		//
  		struct {
  			uint8_t   code;       // Command code
  			dadr_t  dadr;       // Encoded drive and sector to read
  		} read_sector_command;
  		//
  		// 128-byte Read Sector response
  		//
  		struct {
  			uint8_t   status;     // Status code returned by command executed
  			uint8_t   data[128];  // Data returned from read
  		} read_128_response;
  		//
  		// 256-byte Read Sector response
  		//
  		struct {
  			uint8_t   status;     // Status code returned by command executed
  			uint8_t   data[256];  // Data returned from read
  		} read_256_response;
  		//
  		// 512-byte Read Sector response
  		//
  		struct {
  			uint8_t   status;     // Status code returned by command executed
  			uint8_t   data[512];  // Data returned by read
  		} read_512_response;
  		//
  		// Write 128-byte sector command
  		//
  		struct {
  			uint8_t   code;       // Command code
  			dadr_t  dadr;       // Encoded drive and sector to write
  			uint8_t   data[128];  // Data to be written
  		} write_128_command;
  		//
  		// Write 256-byte sector command
  		//
  		struct {
  			uint8_t   code;       // Command code
  			dadr_t  dadr;       // Encoded drive and sector to write
  			uint8_t   data[256];  // Data to be written
  		} write_256_command;
  		//
  		// Write 512-byte sector command
  		//
  		struct {
  			uint8_t   code;       // Command Code
  			dadr_t  dadr;       // Encoded drive and sector to write
  			uint8_t   data[512];  // Data to be written
  		} write_512_command;
  		//
  		// Semaphore Lock command
  		//
  		struct {
  			uint8_t   code;       // Command code
  			uint8_t   modifier;   // Command code modifier
  			uint8_t   name[8];    // Semaphore name
  		} lock_semaphore_command;
  		//
  		// Semaphore Unlock command
  		//
  		struct {
  			uint8_t   code;       // Command code
  			uint8_t   modifier;   // Command code modifier
  			uint8_t   name[8];    // Semaphore name
  		} unlock_semaphore_command;
  		//
  		// Semaphore Lock/Unlock response
  		//
  		struct {
  			uint8_t   status;     // Disk access status
  			uint8_t   result;     // Semaphore action status
  			uint8_t   unused[10]; // Unused
  		} semaphore_locking_response;
  		//
  		// Initialize Semaphore table command
  		//
  		struct {
  			uint8_t   code;       // Command code
  			uint8_t   modifier;   // Command code modifier
  			uint8_t   unused[3];  // Unused
  		} init_semaphore_command;
  		//
  		// Semaphore Status command
  		//
  		struct {
  			uint8_t   code;       // Command code
  			uint8_t   modifier;   // Command code modifier
  			uint8_t   zero_three; // Don't ask me...
  			uint8_t   unused[2];  // Unused
  		} semaphore_status_command;
  		//
  		// Semaphore Status response
  		//
  		struct {
  			uint8_t   status;     // Disk access status
  			uint8_t   table[256]; // Contents of the semaphore table
  		} semaphore_status_response;
  		//
  		// Get Drive Parameters command (0x10)
  		//
  		struct {
  			uint8_t   code;       // Command code
  			uint8_t   drive;      // Drive number (starts at 1)
  		} get_drive_parameters_command;
  		//
  		// Get Drive Parameters command response
  		//
  		struct {
  			uint8_t   status;                     // Status code returned by command executed
  			uint8_t   firmware_desc[31];          // Firmware string description
  			uint8_t   firmware_rev;               // Firmware revision number
  			uint8_t   rom_version;                // ROM Version
  			struct {
  				uint8_t   sectors_per_track;      // Sectors/Track
  				uint8_t   tracks_per_cylinder;    // Tracks/Cylinder (heads)
  				struct {
  					uint8_t   lsb;
  					uint8_t   msb;
  				} cylinders_per_drive;          // Byte-flipped Cylinders/Drive
  			} track_info;
  			struct {
  				uint8_t   lsb;                    // Least significant byte
  				uint8_t   midb;                   // Middle byte
  				uint8_t   msb;                    // Most significant byte
  			} capacity;                         // 24-bit value, byte-flipped (lsb..msb)
  			uint8_t   unused[16];
  			uint8_t   interleave;                 // Interleave factor
  			struct {
  				uint8_t   mux_parameters[12];
  				uint8_t   pipe_name_table_ptr[2]; // Pointer to table of 64 entries, 8 bytes each (table of names)
  				uint8_t   pipe_ptr_table_ptr[2];  // Pointer to table of 64 entries, 8 bytes each.  See pp. 29 - Mass Storage GTI
  				uint8_t   pipe_area_size[2];      // Size of pipe area (lsb, msb)
  				struct {
  					uint8_t   track_offset[2];
  				} vdo_table[7];                 // Virtual drive table
  				uint8_t   lsi11_vdo_table[8];
  				uint8_t   lsi11_spare_table[8];
  			} table_info;
  			uint8_t   drive_number;               // Physical drive number
  			struct {
  				uint8_t   lsb;                    // Least
  				uint8_t   midb;                   // Middle
  				uint8_t   msb;                    // Most
  			} physical_capacity;                // Physical capacity of drive
  		} drive_param_response;
  		//
  		// 2-byte Boot command (0x14)
  		//
  		struct {
  			uint8_t   code;       // Command code
  			uint8_t   boot_block; // Which boot block to read (0-7)
  		} old_boot_command;
  		//
  		// Put drive into prep mode command (0x11)
  		//
  		struct {
  			uint8_t   code;               // Command code
  			uint8_t   drive;              // Drive number (starts at 1)
  			uint8_t   prep_block[512];    // Machine code payload
  		} prep_mode_command;
  		//
  		// Read Firmware command (Prep Mode 0x32)
  		//
  		struct {
  			uint8_t   code;       // Command Code
  			uint8_t   encoded_h_s;// Encoded Head (bits 7-5) / Sector (bits 4-0)
  		} read_firmware_command;
  		//
  		// Write Firmware command (Prep Mode 0x33)
  		//
  		struct {
  			uint8_t   code;       // Command Code
  			uint8_t   encoded_h_s; // Encoded Head (bits 7-5) / Sector (bits 4-0)
  			uint8_t   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_t   code;       // Command Code
  			uint8_t   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_t   status;       // Disk access status
  			uint8_t   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_t   lsb;
  			uint8_t   msb;
  		} spared_track[8];          // Spared track table (0xffff indicates end)
  		uint8_t   interleave;         // Interleave factor
  		uint8_t   reserved;
  		struct {
  			uint8_t track_offset[2];  // Virtual drive offsets (lsb, msb) 0xffff indicates unused
  		} vdo_table[7];
  		uint8_t   lsi11_vdo_table[8];
  		uint8_t   lsi11_spare_table[8];
  		uint8_t   reserved2[432];
  		struct {
  			uint8_t   lsb;
  			uint8_t   msb;
  		} revh_spare_table[16];
  	};
  
  	// Structure of Block #3, the Constellation Parameter Block
  	struct constellation_parameter_block_t {
  		uint8_t   mux_parameters[12];
  		uint8_t   pipe_name_table_ptr[2];
  		uint8_t   pipe_ptr_table_ptr[2];
  		uint8_t   pipe_area_size[2];
  		uint8_t   reserved[470];
  		uint8_t   software_protection[12];
  		uint8_t   serial_number[12];
  	};
  
  	// Structure of Block #7, the Semaphore Table Block
  	struct semaphore_table_block_t {
  		union {
  			uint8_t   semaphore_table[256];           // Table consists of 256 bytes
  			struct {
  				uint8_t   semaphore_name[8];          // Each semaphore name is 8 bytes
  			} semaphore_entry[32];                  // 32 Entries
  		} semaphore_block;
  		uint8_t   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_t  recv_bytes;                         // Number of bytes from host for this command
  		uint16_t  xmit_bytes;                         // Number of bytes to return to host
  	};
  
  	void dump_buffer(uint8_t *buffer, uint16_t length);
  	bool parse_hdc_command(uint8_t data);
  	uint8_t corvus_write_sector(uint8_t drv, uint32_t sector, uint8_t *buffer, int len);
  	uint8_t corvus_write_logical_sector(dadr_t *dadr, uint8_t *buffer, int len);
  	uint8_t corvus_read_sector(uint8_t drv, uint32_t sector, uint8_t *buffer, int len);
  	uint8_t corvus_read_logical_sector(dadr_t *dadr, uint8_t *buffer, int len);
  	uint8_t corvus_lock_semaphore(uint8_t *name);
  	uint8_t corvus_unlock_semaphore(uint8_t *name);
  	uint8_t corvus_init_semaphore_table();
  	uint8_t corvus_get_drive_parameters(uint8_t drv);
  	uint8_t corvus_read_boot_block(uint8_t block);
  	uint8_t corvus_enter_prep_mode(uint8_t drv, uint8_t *prep_block);
  	uint8_t corvus_exit_prep_mode();
  	uint8_t corvus_read_firmware_block(uint8_t head, uint8_t sector);
  	uint8_t corvus_write_firmware_block(uint8_t head, uint8_t sector, uint8_t *buffer);
  	uint8_t corvus_format_drive(uint8_t *pattern, uint16_t 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
  DECLARE_DEVICE_TYPE(CORVUS_HDC, corvus_hdc_device)
  
  #endif // MAME_MACHINE_CORVUSHD_H
  