mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			219 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
//
 | 
						|
//  CommandDecoder.hpp
 | 
						|
//  Clock Signal
 | 
						|
//
 | 
						|
//  Created by Thomas Harte on 24/11/2023.
 | 
						|
//  Copyright © 2023 Thomas Harte. All rights reserved.
 | 
						|
//
 | 
						|
 | 
						|
#pragma once
 | 
						|
 | 
						|
#include <cstdint>
 | 
						|
#include <cstddef>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
namespace Intel::i8272 {
 | 
						|
 | 
						|
enum class Command {
 | 
						|
	ReadData = 0x06,
 | 
						|
	ReadDeletedData = 0x0c,
 | 
						|
 | 
						|
	WriteData = 0x05,
 | 
						|
	WriteDeletedData = 0x09,
 | 
						|
 | 
						|
	ReadTrack = 0x02,
 | 
						|
	ReadID = 0x0a,
 | 
						|
	FormatTrack = 0x0d,
 | 
						|
 | 
						|
	ScanLow = 0x11,
 | 
						|
	ScanLowOrEqual = 0x19,
 | 
						|
	ScanHighOrEqual = 0x1d,
 | 
						|
 | 
						|
	Recalibrate = 0x07,
 | 
						|
	Seek = 0x0f,
 | 
						|
 | 
						|
	SenseInterruptStatus = 0x08,
 | 
						|
	Specify = 0x03,
 | 
						|
	SenseDriveStatus = 0x04,
 | 
						|
 | 
						|
	Invalid = 0x00,
 | 
						|
};
 | 
						|
 | 
						|
class CommandDecoder {
 | 
						|
public:
 | 
						|
	/// Add a byte to the current command.
 | 
						|
	void push_back(uint8_t byte) {
 | 
						|
		command_.push_back(byte);
 | 
						|
	}
 | 
						|
 | 
						|
	/// Reset decoding.
 | 
						|
	void clear() {
 | 
						|
		command_.clear();
 | 
						|
	}
 | 
						|
 | 
						|
	/// @returns @c true if an entire command has been received; @c false if further bytes are needed.
 | 
						|
	bool has_command() const {
 | 
						|
		if(!command_.size()) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		static constexpr std::size_t required_lengths[32] = {
 | 
						|
			0,	0,	9,	3,	2,	9,	9,	2,
 | 
						|
			1,	9,	2,	0,	9,	6,	0,	3,
 | 
						|
			0,	9,	0,	0,	0,	0,	0,	0,
 | 
						|
			0,	9,	0,	0,	0,	9,	0,	0,
 | 
						|
		};
 | 
						|
 | 
						|
		return command_.size() >= required_lengths[command_[0] & 0x1f];
 | 
						|
	}
 | 
						|
 | 
						|
	/// @returns The command requested. Valid only if @c has_command() is @c true.
 | 
						|
	Command command() const {
 | 
						|
		const auto command = Command(command_[0] & 0x1f);
 | 
						|
 | 
						|
		switch(command) {
 | 
						|
			case Command::ReadData:		case Command::ReadDeletedData:
 | 
						|
			case Command::WriteData:	case Command::WriteDeletedData:
 | 
						|
			case Command::ReadTrack:	case Command::ReadID:
 | 
						|
			case Command::FormatTrack:
 | 
						|
			case Command::ScanLow:		case Command::ScanLowOrEqual:
 | 
						|
			case Command::ScanHighOrEqual:
 | 
						|
			case Command::Recalibrate:	case Command::Seek:
 | 
						|
			case Command::SenseInterruptStatus:
 | 
						|
			case Command::Specify:		case Command::SenseDriveStatus:
 | 
						|
				return command;
 | 
						|
 | 
						|
			default: return Command::Invalid;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	//
 | 
						|
	// Commands that specify geometry; i.e.
 | 
						|
	//
 | 
						|
	//	* ReadData;
 | 
						|
	//	* ReadDeletedData;
 | 
						|
	//	* WriteData;
 | 
						|
	//	* WriteDeletedData;
 | 
						|
	//	* ReadTrack;
 | 
						|
	//	* ScanEqual;
 | 
						|
	//	* ScanLowOrEqual;
 | 
						|
	//	* ScanHighOrEqual.
 | 
						|
	//
 | 
						|
 | 
						|
	/// @returns @c true if this command specifies geometry, in which case geomtry() is well-defined.
 | 
						|
	/// @c false otherwise.
 | 
						|
	bool has_geometry() const	{	return command_.size() == 9;	}
 | 
						|
	struct Geometry {
 | 
						|
		uint8_t cylinder, head, sector, size, end_of_track;
 | 
						|
	};
 | 
						|
	Geometry geometry() const {
 | 
						|
		Geometry result;
 | 
						|
		result.cylinder = command_[2];
 | 
						|
		result.head = command_[3];
 | 
						|
		result.sector = command_[4];
 | 
						|
		result.size = command_[5];
 | 
						|
		result.end_of_track = command_[6];
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	//
 | 
						|
	// Commands that imply data access; i.e.
 | 
						|
	//
 | 
						|
	//	* ReadData;
 | 
						|
	//	* ReadDeletedData;
 | 
						|
	//	* WriteData;
 | 
						|
	//	* WriteDeletedData;
 | 
						|
	//	* ReadTrack;
 | 
						|
	//	* ReadID;
 | 
						|
	//	* FormatTrack;
 | 
						|
	//	* ScanLow;
 | 
						|
	//	* ScanLowOrEqual;
 | 
						|
	//	* ScanHighOrEqual.
 | 
						|
	//
 | 
						|
 | 
						|
	/// @returns @c true if this command involves reading or writing data, in which case target() will be valid.
 | 
						|
	/// @c false otherwise.
 | 
						|
	bool is_access() const {
 | 
						|
		switch(command()) {
 | 
						|
			case Command::ReadData:		case Command::ReadDeletedData:
 | 
						|
			case Command::WriteData:	case Command::WriteDeletedData:
 | 
						|
			case Command::ReadTrack:	case Command::ReadID:
 | 
						|
			case Command::FormatTrack:
 | 
						|
			case Command::ScanLow:		case Command::ScanLowOrEqual:
 | 
						|
			case Command::ScanHighOrEqual:
 | 
						|
				return true;
 | 
						|
 | 
						|
			default:
 | 
						|
				return false;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	struct AccessTarget {
 | 
						|
		uint8_t drive, head;
 | 
						|
		bool mfm, skip_deleted;
 | 
						|
	};
 | 
						|
	AccessTarget target() const {
 | 
						|
		AccessTarget result;
 | 
						|
		result.drive = command_[1] & 0x03;
 | 
						|
		result.head = (command_[1] >> 2) & 0x01;
 | 
						|
		result.mfm = command_[0] & 0x40;
 | 
						|
		result.skip_deleted = command_[0] & 0x20;
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
	uint8_t drive_head() const {
 | 
						|
		return command_[1] & 7;
 | 
						|
	}
 | 
						|
 | 
						|
	//
 | 
						|
	// Command::FormatTrack
 | 
						|
	//
 | 
						|
 | 
						|
	struct FormatSpecs {
 | 
						|
		uint8_t bytes_per_sector;
 | 
						|
		uint8_t sectors_per_track;
 | 
						|
		uint8_t gap3_length;
 | 
						|
		uint8_t filler;
 | 
						|
	};
 | 
						|
	FormatSpecs format_specs() const {
 | 
						|
		FormatSpecs result;
 | 
						|
		result.bytes_per_sector = command_[2];
 | 
						|
		result.sectors_per_track = command_[3];
 | 
						|
		result.gap3_length = command_[4];
 | 
						|
		result.filler = command_[5];
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	//
 | 
						|
	// Command::Seek
 | 
						|
	//
 | 
						|
 | 
						|
	/// @returns The desired target track.
 | 
						|
	uint8_t seek_target() const {
 | 
						|
		return command_[2];
 | 
						|
	}
 | 
						|
 | 
						|
	//
 | 
						|
	// Command::Specify
 | 
						|
	//
 | 
						|
 | 
						|
	struct SpecifySpecs {
 | 
						|
		// The below are all in milliseconds.
 | 
						|
		uint8_t step_rate_time;
 | 
						|
		uint8_t head_unload_time;
 | 
						|
		uint8_t head_load_time;
 | 
						|
		bool use_dma;
 | 
						|
	};
 | 
						|
	SpecifySpecs specify_specs() const {
 | 
						|
		SpecifySpecs result;
 | 
						|
		result.step_rate_time = 16 - (command_[1] >> 4);				// i.e. 1 to 16ms
 | 
						|
		result.head_unload_time = uint8_t((command_[1] & 0x0f) << 4);	// i.e. 16 to 240ms
 | 
						|
		result.head_load_time = command_[2] & ~1;						// i.e. 2 to 254 ms in increments of 2ms
 | 
						|
		result.use_dma = !(command_[2] & 1);
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
private:
 | 
						|
	std::vector<uint8_t> command_;
 | 
						|
};
 | 
						|
 | 
						|
}
 |