mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-05 21:32:55 +00:00
Merge pull request #194 from TomHarte/8272Write
Introduces initial implementations of the 8272's write data, write deleted data, read track and format track
This commit is contained in:
commit
d7bed958b3
@ -140,9 +140,7 @@ template <class T> class CRTC6845 {
|
||||
character_is_visible_ = true;
|
||||
}
|
||||
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
bus_state_.refresh_address &= 0x3fff;
|
||||
bus_handler_.perform_bus_cycle(bus_state_);
|
||||
perform_bus_cycle();
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,6 +173,12 @@ template <class T> class CRTC6845 {
|
||||
}
|
||||
|
||||
private:
|
||||
inline void perform_bus_cycle() {
|
||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||
bus_state_.refresh_address &= 0x3fff;
|
||||
bus_handler_.perform_bus_cycle(bus_state_);
|
||||
}
|
||||
|
||||
Personality personality_;
|
||||
T &bus_handler_;
|
||||
BusState bus_state_;
|
||||
|
@ -7,10 +7,11 @@
|
||||
//
|
||||
|
||||
#include "i8272.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace Intel;
|
||||
using namespace Intel::i8272;
|
||||
|
||||
#define SetDataRequest() (main_status_ |= 0x80)
|
||||
#define ResetDataRequest() (main_status_ &= ~0x80)
|
||||
@ -51,13 +52,16 @@ using namespace Intel;
|
||||
#define SetBadCylinder() (status_[2] |= 0x02)
|
||||
#define SetMissingDataAddressMark() (status_[2] |= 0x01)
|
||||
|
||||
i8272::i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
|
||||
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
|
||||
bus_handler_(bus_handler),
|
||||
main_status_(0),
|
||||
interesting_event_mask_((int)Event8272::CommandByte),
|
||||
resume_point_(0),
|
||||
delay_time_(0),
|
||||
head_timers_running_(0) {
|
||||
head_timers_running_(0),
|
||||
expects_input_(false),
|
||||
drives_seeking_(0) {
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
}
|
||||
|
||||
@ -75,7 +79,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
}
|
||||
|
||||
// update seek status of any drives presently seeking
|
||||
if(main_status_ & 0xf) {
|
||||
if(drives_seeking_) {
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c].phase == Drive::Seeking) {
|
||||
drives_[c].step_rate_counter += cycles.as_int();
|
||||
@ -84,13 +88,14 @@ void i8272::run_for(Cycles cycles) {
|
||||
while(steps--) {
|
||||
// Perform a step.
|
||||
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
|
||||
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
|
||||
drives_[c].drive->step(direction);
|
||||
drives_[c].head_position += direction;
|
||||
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
|
||||
|
||||
// Check for completion.
|
||||
if(drives_[c].seek_is_satisfied()) {
|
||||
drives_[c].phase = Drive::CompletedSeeking;
|
||||
if(drives_[c].target_head_position == -1) drives_[c].head_position = 0;
|
||||
drives_seeking_--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -124,9 +129,15 @@ void i8272::set_register(int address, uint8_t value) {
|
||||
// if not ready for commands, do nothing
|
||||
if(!DataRequest() || DataDirectionToProcessor()) return;
|
||||
|
||||
// accumulate latest byte in the command byte sequence
|
||||
command_.push_back(value);
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
if(expects_input_) {
|
||||
input_ = value;
|
||||
has_input_ = true;
|
||||
ResetDataRequest();
|
||||
} else {
|
||||
// accumulate latest byte in the command byte sequence
|
||||
command_.push_back(value);
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t i8272::get_register(int address) {
|
||||
@ -179,9 +190,6 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \
|
||||
set_data_mode(Scanning);
|
||||
|
||||
#define CLEAR_STATUS() \
|
||||
status_[0] = status_[1] = status_[2] = 0;
|
||||
|
||||
#define SET_DRIVE_HEAD_MFM() \
|
||||
active_drive_ = command_[1]&3; \
|
||||
active_head_ = (command_[1] >> 2)&1; \
|
||||
@ -218,6 +226,7 @@ void i8272::posit_event(int event_type) {
|
||||
// Resets busy and non-DMA execution, clears the command buffer, sets the data mode to scanning and flows
|
||||
// into wait_for_complete_command_sequence.
|
||||
wait_for_command:
|
||||
expects_input_ = false;
|
||||
set_data_mode(Storage::Disk::MFMController::DataMode::Scanning);
|
||||
ResetBusy();
|
||||
ResetNonDMAExecution();
|
||||
@ -232,99 +241,73 @@ void i8272::posit_event(int event_type) {
|
||||
WAIT_FOR_EVENT(Event8272::CommandByte)
|
||||
SetBusy();
|
||||
|
||||
static const size_t required_lengths[32] = {
|
||||
0, 0, 9, 3, 2, 9, 9, 2,
|
||||
1, 9, 2, 9, 0, 6, 0, 3,
|
||||
0, 9, 0, 0, 0, 0, 0, 0,
|
||||
0, 9, 0, 0, 0, 9, 0, 0,
|
||||
};
|
||||
|
||||
if(command_.size() < required_lengths[command_[0] & 0x1f]) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
status_[0] = status_[1] = status_[2] = 0;
|
||||
|
||||
// If this is not clearly a command that's safe to carry out in parallel to a seek, end all seeks.
|
||||
switch(command_[0] & 0x1f) {
|
||||
case 0x03: // specify
|
||||
case 0x04: // sense drive status
|
||||
case 0x07: // recalibrate
|
||||
case 0x08: // sense interrupt status
|
||||
case 0x0f: // seek
|
||||
break;
|
||||
|
||||
default:
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c].phase == Drive::Seeking) {
|
||||
drives_[c].phase = Drive::NotSeeking;
|
||||
drives_seeking_--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Jump to the proper place.
|
||||
switch(command_[0] & 0x1f) {
|
||||
case 0x06: // read data
|
||||
case 0x0b: // read deleted data
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto read_data;
|
||||
|
||||
case 0x05: // write data
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
case 0x09: // write deleted data
|
||||
goto write_data;
|
||||
|
||||
case 0x09: // write deleted data
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto write_deleted_data;
|
||||
case 0x02: goto read_track;
|
||||
case 0x0a: goto read_id;
|
||||
case 0x0d: goto format_track;
|
||||
case 0x11: goto scan_low;
|
||||
case 0x19: goto scan_low_or_equal;
|
||||
case 0x1d: goto scan_high_or_equal;
|
||||
case 0x07: goto recalibrate;
|
||||
case 0x08: goto sense_interrupt_status;
|
||||
case 0x03: goto specify;
|
||||
case 0x04: goto sense_drive_status;
|
||||
case 0x0f: goto seek;
|
||||
|
||||
case 0x02: // read track
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto read_track;
|
||||
|
||||
case 0x0a: // read ID
|
||||
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto read_id;
|
||||
|
||||
case 0x0d: // format track
|
||||
if(command_.size() < 6) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto format_track;
|
||||
|
||||
case 0x11: // scan low
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto scan_low;
|
||||
|
||||
case 0x19: // scan low or equal
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto scan_low_or_equal;
|
||||
|
||||
case 0x1d: // scan high or equal
|
||||
if(command_.size() < 9) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto scan_high_or_equal;
|
||||
|
||||
case 0x07: // recalibrate
|
||||
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto recalibrate;
|
||||
|
||||
case 0x08: // sense interrupt status
|
||||
ResetDataRequest();
|
||||
goto sense_interrupt_status;
|
||||
|
||||
case 0x03: // specify
|
||||
if(command_.size() < 3) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto specify;
|
||||
|
||||
case 0x04: // sense drive status
|
||||
if(command_.size() < 2) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto sense_drive_status;
|
||||
|
||||
case 0x0f: // seek
|
||||
if(command_.size() < 3) goto wait_for_complete_command_sequence;
|
||||
ResetDataRequest();
|
||||
goto seek;
|
||||
|
||||
default: // invalid
|
||||
ResetDataRequest();
|
||||
goto invalid;
|
||||
default: goto invalid;
|
||||
}
|
||||
|
||||
// Performs the read data or read deleted data command.
|
||||
read_data:
|
||||
printf("Read [deleted?] data, sector %02x %02x %02x %02x\n", command_[2], command_[3], command_[4], command_[5]);
|
||||
|
||||
// Decodes drive, head and density, loads the head, loads the internal cylinder, head, sector and size registers,
|
||||
// and searches for a sector that meets those criteria. If one is found, inspects the instruction in use and
|
||||
// jumps to an appropriate handler.
|
||||
read_write_find_header:
|
||||
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
||||
// cylinder, head, sector and size registers from the command stream.
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
LOAD_HEAD();
|
||||
CLEAR_STATUS();
|
||||
cylinder_ = command_[2];
|
||||
head_ = command_[3];
|
||||
sector_ = command_[4];
|
||||
size_ = command_[5];
|
||||
|
||||
read_next_data:
|
||||
|
||||
// Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until
|
||||
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
|
||||
// values in the internal registers.
|
||||
@ -334,7 +317,7 @@ void i8272::posit_event(int event_type) {
|
||||
if(!index_hole_limit_) {
|
||||
// Two index holes have passed wihout finding the header sought.
|
||||
SetNoData();
|
||||
goto abort_read;
|
||||
goto abort_read_write;
|
||||
}
|
||||
READ_HEADER();
|
||||
if(get_crc_generator().get_value()) {
|
||||
@ -343,10 +326,34 @@ void i8272::posit_event(int event_type) {
|
||||
}
|
||||
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
|
||||
|
||||
// Branch to whatever is supposed to happen next
|
||||
switch(command_[0] & 0x1f) {
|
||||
case 0x06: // read data
|
||||
case 0x0b: // read deleted data
|
||||
goto read_data_found_header;
|
||||
|
||||
case 0x05: // write data
|
||||
case 0x09: // write deleted data
|
||||
goto write_data_found_header;
|
||||
}
|
||||
|
||||
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
||||
abort_read_write:
|
||||
SetAbnormalTermination();
|
||||
goto post_st012chrn;
|
||||
|
||||
// Performs the read data or read deleted data command.
|
||||
read_data:
|
||||
printf("Read data [%02x %02x %02x %02x ... %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[8]);
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
read_next_data:
|
||||
goto read_write_find_header;
|
||||
|
||||
// Finds the next data block and sets data mode to reading, setting an error flag if the on-disk deleted
|
||||
// flag doesn't match the sort the command was looking for.
|
||||
read_data_found_header:
|
||||
FIND_DATA();
|
||||
distance_into_section_ = 0;
|
||||
if((get_latest_token().type == Token::Data) != ((command_[0]&0xf) == 0x6)) {
|
||||
if(!(command_[0]&0x20)) {
|
||||
// SK is not set; set the error flag but read this sector before finishing.
|
||||
@ -356,14 +363,15 @@ void i8272::posit_event(int event_type) {
|
||||
goto read_next_data;
|
||||
}
|
||||
}
|
||||
|
||||
distance_into_section_ = 0;
|
||||
set_data_mode(Reading);
|
||||
|
||||
// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting
|
||||
// data request once the byte has been taken. Continues until all bytes have been read.
|
||||
//
|
||||
// TODO: signal if the CPU is too slow and missed a byte; at the minute it'll just silently miss. Also allow for other
|
||||
// ways that sector size might have been specified.
|
||||
get_byte:
|
||||
// TODO: consider DTL.
|
||||
read_data_get_byte:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
@ -373,11 +381,11 @@ void i8272::posit_event(int event_type) {
|
||||
switch(event_type) {
|
||||
case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal.
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << size_)) goto get_byte;
|
||||
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
|
||||
break;
|
||||
case (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived
|
||||
SetOverrun();
|
||||
goto abort_read;
|
||||
goto abort_read_write;
|
||||
break;
|
||||
case (int)Event::IndexHole:
|
||||
break;
|
||||
@ -390,7 +398,7 @@ void i8272::posit_event(int event_type) {
|
||||
// This implies a CRC error in the sector; mark as such and temrinate.
|
||||
SetDataError();
|
||||
SetDataFieldDataError();
|
||||
goto abort_read;
|
||||
goto abort_read_write;
|
||||
}
|
||||
|
||||
// check whether that's it: either the final requested sector has been read, or because
|
||||
@ -403,21 +411,79 @@ void i8272::posit_event(int event_type) {
|
||||
// For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N
|
||||
goto post_st012chrn;
|
||||
|
||||
abort_read:
|
||||
SetAbnormalTermination();
|
||||
goto post_st012chrn;
|
||||
|
||||
write_data:
|
||||
printf("Write data unimplemented!!\n");
|
||||
goto wait_for_command;
|
||||
printf("Write [deleted] data\n");
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
|
||||
write_deleted_data:
|
||||
printf("Write deleted data unimplemented!!\n");
|
||||
goto wait_for_command;
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort_read_write;
|
||||
}
|
||||
|
||||
read_track:
|
||||
printf("Read track unimplemented!!\n");
|
||||
goto wait_for_command;
|
||||
write_next_data:
|
||||
goto read_write_find_header;
|
||||
|
||||
write_data_found_header:
|
||||
begin_writing();
|
||||
|
||||
if(get_is_double_density()) {
|
||||
for(int c = 0; c < 50; c++) {
|
||||
write_byte(0x4e);
|
||||
}
|
||||
for(int c = 0; c < 12; c++) {
|
||||
write_byte(0x00);
|
||||
}
|
||||
} else {
|
||||
for(int c = 0; c < 11; c++) {
|
||||
write_byte(0xff);
|
||||
}
|
||||
for(int c = 0; c < 6; c++) {
|
||||
write_byte(0x00);
|
||||
}
|
||||
}
|
||||
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
|
||||
{
|
||||
bool is_deleted = (command_[0] & 0x1f) == 0x09;
|
||||
if(get_is_double_density()) {
|
||||
get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
write_byte(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
} else {
|
||||
get_crc_generator().reset();
|
||||
get_crc_generator().add(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
write_raw_short(is_deleted ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark);
|
||||
}
|
||||
}
|
||||
|
||||
SetDataDirectionFromProcessor();
|
||||
SetDataRequest();
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
|
||||
write_loop:
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(!has_input_) {
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort_read_write;
|
||||
}
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ < (128 << size_)) {
|
||||
SetDataRequest();
|
||||
goto write_loop;
|
||||
}
|
||||
|
||||
write_crc();
|
||||
expects_input_ = false;
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
end_writing();
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
// Performs the read ID command.
|
||||
read_id:
|
||||
@ -433,7 +499,7 @@ void i8272::posit_event(int event_type) {
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
SetNoData();
|
||||
goto abort_read;
|
||||
goto abort_read_write;
|
||||
}
|
||||
READ_HEADER();
|
||||
|
||||
@ -445,9 +511,156 @@ void i8272::posit_event(int event_type) {
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
// Performs read track.
|
||||
read_track:
|
||||
printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]);
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
|
||||
// Wait for the index hole.
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
|
||||
sector_ = 0;
|
||||
index_hole_limit_ = 2;
|
||||
|
||||
// While not index hole again, stream all sector contents until EOT sectors have been read.
|
||||
read_track_next_sector:
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
if(!sector_) {
|
||||
SetMissingAddressMark();
|
||||
goto abort_read_write;
|
||||
} else {
|
||||
goto post_st012chrn;
|
||||
}
|
||||
}
|
||||
READ_HEADER();
|
||||
|
||||
FIND_DATA();
|
||||
distance_into_section_ = 0;
|
||||
SetDataDirectionToProcessor();
|
||||
read_track_get_byte:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
|
||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty);
|
||||
ResetDataRequest();
|
||||
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
|
||||
|
||||
sector_++;
|
||||
if(sector_ < command_[6]) goto read_track_next_sector;
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
// Performs format [/write] track.
|
||||
format_track:
|
||||
printf("Fromat track unimplemented!!\n");
|
||||
goto wait_for_command;
|
||||
printf("Format track\n");
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort_read_write;
|
||||
}
|
||||
|
||||
LOAD_HEAD();
|
||||
|
||||
// Wait for the index hole.
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
begin_writing();
|
||||
|
||||
// Write start-of-track.
|
||||
if(get_is_double_density()) {
|
||||
for(int c = 0; c < 80; c++) write_byte(0x4e);
|
||||
for(int c = 0; c < 12; c++) write_byte(0x00);
|
||||
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMIndexSync);
|
||||
write_byte(Storage::Encodings::MFM::IndexAddressByte);
|
||||
for(int c = 0; c < 50; c++) write_byte(0x4e);
|
||||
} else {
|
||||
for(int c = 0; c < 40; c++) write_byte(0xff);
|
||||
for(int c = 0; c < 6; c++) write_byte(0x00);
|
||||
write_raw_short(Storage::Encodings::MFM::FMIndexAddressMark);
|
||||
for(int c = 0; c < 26; c++) write_byte(0xff);
|
||||
}
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
sector_ = 0;
|
||||
|
||||
format_track_write_sector:
|
||||
if(get_is_double_density()) {
|
||||
for(int c = 0; c < 12; c++) write_byte(0x00);
|
||||
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
write_byte(Storage::Encodings::MFM::IDAddressByte);
|
||||
} else {
|
||||
for(int c = 0; c < 6; c++) write_byte(0x00);
|
||||
get_crc_generator().reset();
|
||||
write_raw_short(Storage::Encodings::MFM::FMIDAddressMark);
|
||||
}
|
||||
|
||||
// Write the sector header, obtaining its contents
|
||||
// from the processor.
|
||||
SetDataDirectionFromProcessor();
|
||||
SetDataRequest();
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
format_track_write_header:
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
// TODO: overrun?
|
||||
header_[distance_into_section_] = input_;
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
distance_into_section_++;
|
||||
if(distance_into_section_ < 4) {
|
||||
SetDataRequest();
|
||||
goto format_track_write_header;
|
||||
}
|
||||
write_crc();
|
||||
|
||||
// Write the sector body.
|
||||
if(get_is_double_density()) {
|
||||
for(int c = 0; c < 22; c++) write_byte(0x4e);
|
||||
for(int c = 0; c < 12; c++) write_byte(0x00);
|
||||
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
|
||||
get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
write_byte(Storage::Encodings::MFM::DataAddressByte);
|
||||
} else {
|
||||
for(int c = 0; c < 11; c++) write_byte(0xff);
|
||||
for(int c = 0; c < 6; c++) write_byte(0x00);
|
||||
get_crc_generator().reset();
|
||||
write_raw_short(Storage::Encodings::MFM::FMDataAddressMark);
|
||||
}
|
||||
for(int c = 0; c < (128 << command_[2]); c++) {
|
||||
write_byte(command_[5]);
|
||||
}
|
||||
write_crc();
|
||||
|
||||
// Write the prescribed gap.
|
||||
if(get_is_double_density()) {
|
||||
for(int c = 0; c < command_[4]; c++) write_byte(0x4e);
|
||||
} else {
|
||||
for(int c = 0; c < command_[4]; c++) write_byte(0xff);
|
||||
}
|
||||
|
||||
// Consider repeating.
|
||||
sector_++;
|
||||
if(sector_ < command_[3])
|
||||
goto format_track_write_sector;
|
||||
|
||||
// Otherwise, pad out to the index hole.
|
||||
format_track_pad:
|
||||
write_byte(get_is_double_density() ? 0x4e : 0xff);
|
||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
||||
if(event_type != (int)Event::IndexHole) goto format_track_pad;
|
||||
|
||||
end_writing();
|
||||
|
||||
cylinder_ = header_[0];
|
||||
head_ = header_[1];
|
||||
sector_ = header_[2] + 1;
|
||||
size_ = header_[3];
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
scan_low:
|
||||
printf("Scan low unimplemented!!\n");
|
||||
@ -465,25 +678,38 @@ void i8272::posit_event(int event_type) {
|
||||
// occurs in ::run_for; this merely establishes that seeking should be ongoing.
|
||||
recalibrate:
|
||||
seek:
|
||||
printf((command_.size() > 2) ? "Seek\n" : "Recalibrate\n");
|
||||
|
||||
// Declines to act if a seek is already ongoing; otherwise resets all status registers, sets the drive
|
||||
// into seeking mode, sets the drive's main status seeking bit, and sets the target head position: for
|
||||
// a recalibrate the target is -1 and ::run_for knows that -1 means the terminal condition is the drive
|
||||
// returning that its at track zero, and that it should reset the drive's current position once reached.
|
||||
if(drives_[command_[1]&3].phase != Drive::Seeking) {
|
||||
{
|
||||
int drive = command_[1]&3;
|
||||
drives_[drive].phase = Drive::Seeking;
|
||||
drives_[drive].steps_taken = 0;
|
||||
drives_[drive].target_head_position = (command_.size() > 2) ? command_[2] : -1;
|
||||
drives_[drive].step_rate_counter = 0;
|
||||
drives_[drive].seek_failed = false;
|
||||
|
||||
// Check whether any steps are even needed.
|
||||
// Increment the seeking count if this drive wasn't already seeking.
|
||||
if(drives_[drive].phase != Drive::Seeking) {
|
||||
drives_seeking_++;
|
||||
}
|
||||
|
||||
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
|
||||
// in could damage your drive motor).
|
||||
drives_[drive].phase = Drive::Seeking;
|
||||
drives_[drive].step_rate_counter = 8000 * step_rate_time_;
|
||||
drives_[drive].steps_taken = 0;
|
||||
drives_[drive].seek_failed = false;
|
||||
main_status_ |= 1 << (command_[1]&3);
|
||||
|
||||
// If this is a seek, set the processor-supplied target location; otherwise it is a recalibrate,
|
||||
// which means resetting the current state now but aiming to hit '-1' (which the stepping code
|
||||
// up in run_for understands to mean 'keep going until track 0 is active').
|
||||
if(command_.size() > 2) {
|
||||
drives_[drive].target_head_position = command_[2];
|
||||
printf("Seek to %02x\n", command_[2]);
|
||||
} else {
|
||||
drives_[drive].target_head_position = -1;
|
||||
drives_[drive].head_position = 0;
|
||||
printf("Recalibrate\n");
|
||||
}
|
||||
|
||||
// Check whether any steps are even needed; if not then mark as completed already.
|
||||
if(drives_[drive].seek_is_satisfied()) {
|
||||
drives_[drive].phase = Drive::CompletedSeeking;
|
||||
} else {
|
||||
main_status_ |= 1 << (command_[1]&3);
|
||||
drives_seeking_--;
|
||||
}
|
||||
}
|
||||
goto wait_for_command;
|
||||
@ -505,8 +731,8 @@ void i8272::posit_event(int event_type) {
|
||||
if(found_drive != -1) {
|
||||
drives_[found_drive].phase = Drive::NotSeeking;
|
||||
status_[0] = (uint8_t)found_drive;
|
||||
SetSeekEnd();
|
||||
main_status_ &= ~(1 << found_drive);
|
||||
SetSeekEnd();
|
||||
|
||||
result_stack_.push_back(drives_[found_drive].head_position);
|
||||
result_stack_.push_back(status_[0]);
|
||||
@ -519,21 +745,26 @@ void i8272::posit_event(int event_type) {
|
||||
// Performs specify.
|
||||
specify:
|
||||
// Just store the values, and terminate the command.
|
||||
step_rate_time_ = command_[1] &0xf0; // i.e. 16 to 240m
|
||||
head_unload_time_ = command_[1] & 0x0f; // i.e. 1 to 16ms
|
||||
head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
|
||||
printf("Specify\n");
|
||||
step_rate_time_ = 16 - (command_[1] >> 4); // i.e. 1 to 16ms
|
||||
head_unload_time_ = (command_[1] & 0x0f) << 4; // i.e. 16 to 240ms
|
||||
head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
|
||||
|
||||
if(!head_unload_time_) head_unload_time_ = 16;
|
||||
if(!head_load_time_) head_load_time_ = 2;
|
||||
dma_mode_ = !(command_[2] & 1);
|
||||
goto wait_for_command;
|
||||
|
||||
sense_drive_status:
|
||||
printf("Sense drive status\n");
|
||||
{
|
||||
int drive = command_[1] & 3;
|
||||
result_stack_.push_back(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(drives_[drive].drive->has_disk() ? 0x20 : 0x00) | // ready, approximately (TODO)
|
||||
0x40 // write protected
|
||||
(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) |
|
||||
(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00)
|
||||
);
|
||||
}
|
||||
goto post_result;
|
||||
@ -562,6 +793,12 @@ void i8272::posit_event(int event_type) {
|
||||
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the
|
||||
// last thing in it will be returned first.
|
||||
post_result:
|
||||
printf("Result to %02x: ", command_[0] & 0x1f);
|
||||
for(size_t c = 0; c < result_stack_.size(); c++) {
|
||||
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||
ResetNonDMAExecution();
|
||||
SetDataRequest();
|
||||
@ -581,3 +818,16 @@ bool i8272::Drive::seek_is_satisfied() {
|
||||
return (target_head_position == head_position) ||
|
||||
(target_head_position == -1 && drive->get_is_track_zero());
|
||||
}
|
||||
|
||||
void i8272::set_dma_acknowledge(bool dack) {
|
||||
}
|
||||
|
||||
void i8272::set_terminal_count(bool tc) {
|
||||
}
|
||||
|
||||
void i8272::set_data_input(uint8_t value) {
|
||||
}
|
||||
|
||||
uint8_t i8272::get_data_output() {
|
||||
return 0xff;
|
||||
}
|
||||
|
@ -13,22 +13,40 @@
|
||||
#include "../../Storage/Disk/Drive.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Intel {
|
||||
namespace i8272 {
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
virtual void set_dma_data_request(bool drq) {}
|
||||
virtual void set_interrupt(bool irq) {}
|
||||
};
|
||||
|
||||
class i8272: public Storage::Disk::MFMController {
|
||||
public:
|
||||
i8272(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
||||
i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
|
||||
|
||||
void run_for(Cycles);
|
||||
|
||||
void set_data_input(uint8_t value);
|
||||
uint8_t get_data_output();
|
||||
|
||||
void set_register(int address, uint8_t value);
|
||||
uint8_t get_register(int address);
|
||||
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||
|
||||
private:
|
||||
// The bus handler, for interrupt and DMA-driven usage.
|
||||
BusHandler &bus_handler_;
|
||||
std::unique_ptr<BusHandler> allocated_bus_handler_;
|
||||
|
||||
// Status registers.
|
||||
uint8_t main_status_;
|
||||
uint8_t status_[3];
|
||||
@ -36,6 +54,9 @@ class i8272: public Storage::Disk::MFMController {
|
||||
// A buffer for accumulating the incoming command, and one for accumulating the result.
|
||||
std::vector<uint8_t> command_;
|
||||
std::vector<uint8_t> result_stack_;
|
||||
uint8_t input_;
|
||||
bool has_input_;
|
||||
bool expects_input_;
|
||||
|
||||
// Event stream: the 8272-specific events, plus the current event state.
|
||||
enum class Event8272: int {
|
||||
@ -60,6 +81,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
Seeking,
|
||||
CompletedSeeking
|
||||
} phase;
|
||||
bool did_seek;
|
||||
bool seek_failed;
|
||||
|
||||
// Seeking: transient state.
|
||||
@ -82,6 +104,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
drive(new Storage::Disk::Drive),
|
||||
head_is_loaded{false, false} {};
|
||||
} drives_[4];
|
||||
int drives_seeking_;
|
||||
|
||||
// User-supplied parameters; as per the specify command.
|
||||
int step_rate_time_;
|
||||
@ -107,6 +130,6 @@ class i8272: public Storage::Disk::MFMController {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* i8272_hpp */
|
||||
|
@ -46,6 +46,7 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef __APPLE__
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
serial_dispatch_queue_ = nullptr;
|
||||
#else
|
||||
should_destruct_ = true;
|
||||
enqueue([](){});
|
||||
|
@ -438,12 +438,15 @@ struct KeyboardState {
|
||||
Wraps the 8272 so as to provide proper clocking and RPM counts, and just directly
|
||||
exposes motor control, applying the same value to all drives.
|
||||
*/
|
||||
class FDC: public Intel::i8272 {
|
||||
class FDC: public Intel::i8272::i8272 {
|
||||
private:
|
||||
Intel::i8272::BusHandler bus_handler_;
|
||||
|
||||
public:
|
||||
FDC() : i8272(Cycles(8000000), 16, 300) {}
|
||||
FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {}
|
||||
|
||||
void set_motor_on(bool on) {
|
||||
Intel::i8272::set_motor_on(on);
|
||||
Intel::i8272::i8272::set_motor_on(on);
|
||||
}
|
||||
};
|
||||
|
||||
@ -534,7 +537,7 @@ class ConcreteMachine:
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
z80_(*this),
|
||||
crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the memory accesses
|
||||
crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the CPU's memory accesses
|
||||
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
|
||||
crtc_bus_handler_(ram_, interrupt_timer_),
|
||||
i8255_(i8255_port_handler_),
|
||||
|
@ -21,12 +21,16 @@ static bool strcmp_insensitive(const char *a, const char *b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
return "run\"" + file.name + "\n";
|
||||
}
|
||||
|
||||
static void InspectDataCatalogue(
|
||||
const std::unique_ptr<Storage::Disk::CPM::Catalogue> &data_catalogue,
|
||||
StaticAnalyser::Target &target) {
|
||||
// If there's just one file, run that.
|
||||
if(data_catalogue->files.size() == 1) {
|
||||
target.loadingCommand = "run\"" + data_catalogue->files[0].name + "\n";
|
||||
target.loadingCommand = RunCommandFor(data_catalogue->files[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -61,7 +65,7 @@ static void InspectDataCatalogue(
|
||||
}
|
||||
if(basic_files == 1 || implicit_suffixed_files == 1) {
|
||||
size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
|
||||
target.loadingCommand = "run\"" + data_catalogue->files[selected_file].name + "\n";
|
||||
target.loadingCommand = RunCommandFor(data_catalogue->files[selected_file]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ void Controller::process_next_event()
|
||||
if(is_reading_) pll_->add_pulse();
|
||||
break;
|
||||
case Track::Event::IndexHole:
|
||||
printf("%p %d [/%d = %d]\n", this, cycles_since_index_hole_, clock_rate_multiplier_, cycles_since_index_hole_ / clock_rate_multiplier_);
|
||||
// printf("%p %d [/%d = %d]\n", this, cycles_since_index_hole_, clock_rate_multiplier_, cycles_since_index_hole_ / clock_rate_multiplier_);
|
||||
cycles_since_index_hole_ = 0;
|
||||
process_index_hole();
|
||||
break;
|
||||
|
@ -34,6 +34,7 @@ bool Drive::get_is_track_zero() {
|
||||
|
||||
void Drive::step(int direction) {
|
||||
head_position_ = std::max(head_position_ + direction, 0);
|
||||
printf("Head -> %d\n", head_position_);
|
||||
}
|
||||
|
||||
void Drive::set_head(unsigned int head) {
|
||||
@ -42,8 +43,7 @@ void Drive::set_head(unsigned int head) {
|
||||
|
||||
bool Drive::get_is_read_only() {
|
||||
if(disk_) return disk_->get_is_read_only();
|
||||
if(track_) return true;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Drive::get_is_ready() {
|
||||
|
@ -51,7 +51,7 @@ class Drive {
|
||||
void set_head(unsigned int head);
|
||||
|
||||
/*!
|
||||
@returns @c true if the inserted disk is read-only; @c false otherwise.
|
||||
@returns @c true if the inserted disk is read-only or no disk is inserted; @c false otherwise.
|
||||
*/
|
||||
bool get_is_read_only();
|
||||
|
||||
|
@ -120,8 +120,8 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
|
||||
size_t pre_address_mark_bytes,
|
||||
size_t post_address_mark_bytes, uint8_t post_address_mark_value,
|
||||
size_t pre_data_mark_bytes, size_t post_data_bytes,
|
||||
size_t inter_sector_gap,
|
||||
size_t pre_data_mark_bytes,
|
||||
size_t post_data_bytes, uint8_t post_data_value,
|
||||
size_t expected_track_bytes) {
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment.data.reserve(expected_track_bytes);
|
||||
@ -169,8 +169,7 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
}
|
||||
|
||||
// gap
|
||||
for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(0x00);
|
||||
for(size_t c = 0; c < inter_sector_gap; c++) shifter.add_byte(0x4e);
|
||||
for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(post_data_value);
|
||||
}
|
||||
|
||||
while(segment.data.size() < expected_track_bytes) shifter.add_byte(0x00);
|
||||
@ -199,11 +198,11 @@ const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0;
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
|
||||
return GetTrackWithSectors<FMEncoder>(
|
||||
sectors,
|
||||
16, 0x00,
|
||||
26, 0xff,
|
||||
6,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 0, sector_gap_filler_byte,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? 0 : 17, 14,
|
||||
0,
|
||||
11, 0xff,
|
||||
6,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 27, 0xff,
|
||||
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
|
||||
}
|
||||
|
||||
@ -212,9 +211,9 @@ std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSe
|
||||
sectors,
|
||||
50, 0x4e,
|
||||
12,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 22, sector_gap_filler_byte,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? 0 : 12, 18,
|
||||
32,
|
||||
22, 0x4e,
|
||||
12,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 54, 0xff,
|
||||
12500); // unintelligently: double the single-density bytes/rotation (or: 500kps @ 300 rpm)
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ unsigned int CPCDSK::get_head_count() {
|
||||
|
||||
bool CPCDSK::get_is_read_only() {
|
||||
// TODO: allow writing.
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> CPCDSK::get_uncached_track_at_position(unsigned int head, unsigned int position) {
|
||||
|
@ -153,3 +153,31 @@ void MFMController::process_input_bit(int value, unsigned int cycles_since_index
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MFMController::write_bit(int bit) {
|
||||
if(is_double_density_) {
|
||||
Controller::write_bit(!bit && !last_bit_);
|
||||
Controller::write_bit(!!bit);
|
||||
last_bit_ = bit;
|
||||
} else {
|
||||
Controller::write_bit(true);
|
||||
Controller::write_bit(!!bit);
|
||||
}
|
||||
}
|
||||
|
||||
void MFMController::write_byte(uint8_t byte) {
|
||||
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
|
||||
crc_generator_.add(byte);
|
||||
}
|
||||
|
||||
void MFMController::write_raw_short(uint16_t value) {
|
||||
for(int c = 0; c < 16; c++) {
|
||||
Controller::write_bit(!!((value << c)&0x8000));
|
||||
}
|
||||
}
|
||||
|
||||
void MFMController::write_crc() {
|
||||
uint16_t crc = get_crc_generator().get_value();
|
||||
write_byte(crc >> 8);
|
||||
write_byte(crc & 0xff);
|
||||
}
|
||||
|
@ -90,6 +90,29 @@ class MFMController: public Controller {
|
||||
*/
|
||||
virtual void posit_event(int type) = 0;
|
||||
|
||||
/*!
|
||||
Encodes @c bit according to the current single/double density mode and adds it
|
||||
to the controller's write buffer.
|
||||
*/
|
||||
void write_bit(int bit);
|
||||
|
||||
/*!
|
||||
Encodes @c byte according to the current single/double density mode and adds it
|
||||
to the controller's write buffer.
|
||||
*/
|
||||
void write_byte(uint8_t byte);
|
||||
|
||||
/*!
|
||||
Serialises @c value into the controller's write buffer without adjustment.
|
||||
*/
|
||||
void write_raw_short(uint16_t value);
|
||||
|
||||
/*!
|
||||
Gets the current value of the CRC generator and makes two calls to @c write_byte, to
|
||||
write first its higher-value byte and then its lower.
|
||||
*/
|
||||
void write_crc();
|
||||
|
||||
private:
|
||||
// Storage::Disk::Controller
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
||||
@ -108,6 +131,9 @@ class MFMController: public Controller {
|
||||
// output
|
||||
Token latest_token_;
|
||||
|
||||
// writing
|
||||
int last_bit_;
|
||||
|
||||
// CRC generator
|
||||
NumberTheory::CRC16 crc_generator_;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user