1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-04 18:29:40 +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:
Thomas Harte 2017-08-14 14:35:45 -04:00 committed by GitHub
commit d7bed958b3
13 changed files with 495 additions and 157 deletions

View File

@ -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_;

View File

@ -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;
}

View File

@ -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 */

View File

@ -46,6 +46,7 @@ AsyncTaskQueue::AsyncTaskQueue()
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
dispatch_release(serial_dispatch_queue_);
serial_dispatch_queue_ = nullptr;
#else
should_destruct_ = true;
enqueue([](){});

View File

@ -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_),

View File

@ -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;
}

View File

@ -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;

View File

@ -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() {

View File

@ -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();

View File

@ -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> &sectors, 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)
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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_;
};