mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-28 21:49:27 +00:00
219 lines
4.9 KiB
C++
219 lines
4.9 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_;
|
|
};
|
|
|
|
}
|