mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 03:32:32 +00:00
Merge pull request #197 from TomHarte/MoreFDC
Improves the 8272 and the whole disk infrastructure behind it
This commit is contained in:
commit
8a37a0ff2e
@ -502,7 +502,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
}
|
||||
|
||||
set_data_mode(DataMode::Writing);
|
||||
begin_writing();
|
||||
begin_writing(false);
|
||||
for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) {
|
||||
write_byte(0);
|
||||
}
|
||||
@ -694,7 +694,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
}
|
||||
|
||||
WAIT_FOR_EVENT(Event1770::IndexHoleTarget);
|
||||
begin_writing();
|
||||
begin_writing(true);
|
||||
index_hole_count_ = 0;
|
||||
|
||||
write_track_write_loop:
|
||||
|
@ -52,6 +52,29 @@ using namespace Intel::i8272;
|
||||
#define SetBadCylinder() (status_[2] |= 0x02)
|
||||
#define SetMissingDataAddressMark() (status_[2] |= 0x01)
|
||||
|
||||
namespace {
|
||||
const uint8_t CommandReadData = 0x06;
|
||||
const uint8_t CommandReadDeletedData = 0x0c;
|
||||
|
||||
const uint8_t CommandWriteData = 0x05;
|
||||
const uint8_t CommandWriteDeletedData = 0x09;
|
||||
|
||||
const uint8_t CommandReadTrack = 0x02;
|
||||
const uint8_t CommandReadID = 0x0a;
|
||||
const uint8_t CommandFormatTrack = 0x0d;
|
||||
|
||||
const uint8_t CommandScanLow = 0x11;
|
||||
const uint8_t CommandScanLowOrEqual = 0x19;
|
||||
const uint8_t CommandScanHighOrEqual = 0x1d;
|
||||
|
||||
const uint8_t CommandRecalibrate = 0x07;
|
||||
const uint8_t CommandSeek = 0x0f;
|
||||
|
||||
const uint8_t CommandSenseInterruptStatus = 0x08;
|
||||
const uint8_t CommandSpecify = 0x03;
|
||||
const uint8_t CommandSenseDriveStatus = 0x04;
|
||||
}
|
||||
|
||||
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),
|
||||
@ -170,25 +193,28 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
#define CONCAT(x, y) PASTE(x, y)
|
||||
|
||||
#define FIND_HEADER() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||
if(event_type == (int)Event::IndexHole) index_hole_limit_--; \
|
||||
if(event_type == (int)Event::IndexHole) { index_hole_limit_--; } \
|
||||
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
|
||||
\
|
||||
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
|
||||
CONCAT(header_found, __LINE__): 0;\
|
||||
|
||||
#define FIND_DATA() \
|
||||
set_data_mode(DataMode::Scanning); \
|
||||
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
|
||||
if(event_type == (int)Event::Token && get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) goto CONCAT(find_data, __LINE__);
|
||||
if(event_type == (int)Event::Token) { \
|
||||
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
|
||||
}
|
||||
|
||||
#define READ_HEADER() \
|
||||
distance_into_section_ = 0; \
|
||||
set_data_mode(Reading); \
|
||||
set_data_mode(DataMode::Reading); \
|
||||
CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||
header_[distance_into_section_] = get_latest_token().byte_value; \
|
||||
distance_into_section_++; \
|
||||
if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \
|
||||
set_data_mode(Scanning);
|
||||
|
||||
#define SET_DRIVE_HEAD_MFM() \
|
||||
active_drive_ = command_[1]&3; \
|
||||
@ -198,6 +224,12 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
set_is_double_density(command_[0] & 0x40); \
|
||||
invalidate_track();
|
||||
|
||||
#define WAIT_FOR_BYTES(n) \
|
||||
distance_into_section_ = 0; \
|
||||
CONCAT(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token); \
|
||||
if(get_latest_token().type == Token::Byte) distance_into_section_++; \
|
||||
if(distance_into_section_ < (n)) goto CONCAT(wait_bytes, __LINE__);
|
||||
|
||||
#define LOAD_HEAD() \
|
||||
if(!drives_[active_drive_].head_is_loaded[active_head_]) { \
|
||||
drives_[active_drive_].head_is_loaded[active_head_] = true; \
|
||||
@ -218,6 +250,7 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
}
|
||||
|
||||
void i8272::posit_event(int event_type) {
|
||||
if(event_type == (int)Event::IndexHole) index_hole_count_++;
|
||||
if(!(interesting_event_mask_ & event_type)) return;
|
||||
interesting_event_mask_ &= ~event_type;
|
||||
|
||||
@ -260,44 +293,62 @@ void i8272::posit_event(int event_type) {
|
||||
|
||||
// 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
|
||||
case CommandReadData:
|
||||
case CommandReadDeletedData:
|
||||
case CommandWriteData:
|
||||
case CommandWriteDeletedData:
|
||||
case CommandReadTrack:
|
||||
case CommandReadID:
|
||||
case CommandFormatTrack:
|
||||
case CommandScanLow:
|
||||
case CommandScanLowOrEqual:
|
||||
case CommandScanHighOrEqual:
|
||||
is_access_command_ = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
is_access_command_ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_access_command_) {
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(drives_[c].phase == Drive::Seeking) {
|
||||
drives_[c].phase = Drive::NotSeeking;
|
||||
drives_seeking_--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Jump to the proper place.
|
||||
switch(command_[0] & 0x1f) {
|
||||
case 0x06: // read data
|
||||
case 0x0c: // read deleted data
|
||||
case CommandReadData:
|
||||
case CommandReadDeletedData:
|
||||
goto read_data;
|
||||
|
||||
case 0x05: // write data
|
||||
case 0x09: // write deleted data
|
||||
case CommandWriteData:
|
||||
case CommandWriteDeletedData:
|
||||
goto write_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 CommandReadTrack: goto read_track;
|
||||
case CommandReadID: goto read_id;
|
||||
case CommandFormatTrack: goto format_track;
|
||||
|
||||
case CommandScanLow: goto scan_low;
|
||||
case CommandScanLowOrEqual: goto scan_low_or_equal;
|
||||
case CommandScanHighOrEqual: goto scan_high_or_equal;
|
||||
|
||||
case CommandRecalibrate: goto recalibrate;
|
||||
case CommandSeek: goto seek;
|
||||
|
||||
case CommandSenseInterruptStatus: goto sense_interrupt_status;
|
||||
case CommandSpecify: goto specify;
|
||||
case CommandSenseDriveStatus: goto sense_drive_status;
|
||||
|
||||
default: goto invalid;
|
||||
}
|
||||
@ -306,15 +357,11 @@ void i8272::posit_event(int event_type) {
|
||||
// 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.
|
||||
LOAD_HEAD();
|
||||
|
||||
// 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.
|
||||
index_hole_limit_ = 2;
|
||||
set_data_mode(DataMode::Scanning);
|
||||
// printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_);
|
||||
find_next_sector:
|
||||
FIND_HEADER();
|
||||
@ -322,38 +369,39 @@ void i8272::posit_event(int event_type) {
|
||||
// Two index holes have passed wihout finding the header sought.
|
||||
// printf("Not found\n");
|
||||
SetNoData();
|
||||
goto abort_read_write;
|
||||
goto abort;
|
||||
}
|
||||
index_hole_count_ = 0;
|
||||
// printf("Header\n");
|
||||
READ_HEADER();
|
||||
if(index_hole_count_) {
|
||||
// This implies an index hole was sighted within the header. Error out.
|
||||
SetEndOfCylinder();
|
||||
goto abort;
|
||||
}
|
||||
if(get_crc_generator().get_value()) {
|
||||
// This implies a CRC error in the header; mark as such but continue.
|
||||
SetDataError();
|
||||
}
|
||||
// printf("Considering %02x %02x %02x %02x\n", header_[0], header_[1], header_[2], header_[3]);
|
||||
// printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
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
|
||||
// printf("Proceeding\n");
|
||||
switch(command_[0] & 0x1f) {
|
||||
case 0x06: // read data
|
||||
case 0x0c: // read deleted data
|
||||
case CommandReadData:
|
||||
case CommandReadDeletedData:
|
||||
goto read_data_found_header;
|
||||
|
||||
case 0x05: // write data
|
||||
case 0x09: // write deleted data
|
||||
case CommandWriteData: // write data
|
||||
case CommandWriteDeletedData: // 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();
|
||||
printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
||||
read_next_data:
|
||||
goto read_write_find_header;
|
||||
|
||||
@ -361,7 +409,14 @@ void i8272::posit_event(int event_type) {
|
||||
// flag doesn't match the sort the command was looking for.
|
||||
read_data_found_header:
|
||||
FIND_DATA();
|
||||
if((get_latest_token().type == Token::Data) != ((command_[0]&0xf) == 0x6)) {
|
||||
if(event_type == (int)Event::Token) {
|
||||
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
||||
// Something other than a data mark came next — impliedly an ID or index mark.
|
||||
SetMissingAddressMark();
|
||||
SetMissingDataAddressMark();
|
||||
goto abort; // TODO: or read_next_data?
|
||||
} else {
|
||||
if((get_latest_token().type == Token::Data) != ((command_[0] & 0x1f) == CommandReadData)) {
|
||||
if(!(command_[0]&0x20)) {
|
||||
// SK is not set; set the error flag but read this sector before finishing.
|
||||
SetControlMark();
|
||||
@ -370,6 +425,12 @@ void i8272::posit_event(int event_type) {
|
||||
goto read_next_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// An index hole appeared before the data mark.
|
||||
SetEndOfCylinder();
|
||||
goto abort; // TODO: or read_next_data?
|
||||
}
|
||||
|
||||
distance_into_section_ = 0;
|
||||
set_data_mode(Reading);
|
||||
@ -379,12 +440,14 @@ void i8272::posit_event(int event_type) {
|
||||
//
|
||||
// TODO: consider DTL.
|
||||
read_data_get_byte:
|
||||
WAIT_FOR_EVENT(Event::Token);
|
||||
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
|
||||
if(event_type == (int)Event::Token) {
|
||||
result_stack_.push_back(get_latest_token().byte_value);
|
||||
distance_into_section_++;
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole);
|
||||
}
|
||||
switch(event_type) {
|
||||
case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal.
|
||||
ResetDataRequest();
|
||||
@ -392,9 +455,11 @@ void i8272::posit_event(int event_type) {
|
||||
break;
|
||||
case (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived
|
||||
SetOverrun();
|
||||
goto abort_read_write;
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::IndexHole:
|
||||
SetEndOfCylinder();
|
||||
goto abort;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -405,7 +470,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_write;
|
||||
goto abort;
|
||||
}
|
||||
|
||||
// check whether that's it: either the final requested sector has been read, or because
|
||||
@ -419,26 +484,24 @@ void i8272::posit_event(int event_type) {
|
||||
goto post_st012chrn;
|
||||
|
||||
write_data:
|
||||
printf("Write [deleted] data\n");
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
|
||||
|
||||
if(drives_[active_drive_].drive->get_is_read_only()) {
|
||||
SetNotWriteable();
|
||||
goto abort_read_write;
|
||||
goto abort;
|
||||
}
|
||||
|
||||
write_next_data:
|
||||
goto read_write_find_header;
|
||||
|
||||
write_data_found_header:
|
||||
begin_writing();
|
||||
WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11);
|
||||
begin_writing(true);
|
||||
|
||||
write_id_data_joiner((command_[0] & 0x1f) == 0x09);
|
||||
write_id_data_joiner((command_[0] & 0x1f) == CommandWriteDeletedData, true);
|
||||
|
||||
SetDataDirectionFromProcessor();
|
||||
SetDataRequest();
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
|
||||
@ -447,7 +510,7 @@ void i8272::posit_event(int event_type) {
|
||||
if(!has_input_) {
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort_read_write;
|
||||
goto abort;
|
||||
}
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
@ -457,19 +520,23 @@ void i8272::posit_event(int event_type) {
|
||||
goto write_loop;
|
||||
}
|
||||
|
||||
printf("Wrote %d bytes\n", distance_into_section_);
|
||||
write_crc();
|
||||
expects_input_ = false;
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
end_writing();
|
||||
|
||||
if(sector_ != command_[6]) {
|
||||
sector_++;
|
||||
goto write_next_data;
|
||||
}
|
||||
|
||||
goto post_st012chrn;
|
||||
|
||||
// Performs the read ID command.
|
||||
read_id:
|
||||
// Establishes the drive and head being addressed, and whether in double density mode.
|
||||
printf("Read ID\n");
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
LOAD_HEAD();
|
||||
printf("Read ID [%02x %02x]\n", command_[0], command_[1]);
|
||||
|
||||
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
|
||||
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
|
||||
@ -477,8 +544,8 @@ void i8272::posit_event(int event_type) {
|
||||
read_id_find_next_sector:
|
||||
FIND_HEADER();
|
||||
if(!index_hole_limit_) {
|
||||
SetNoData();
|
||||
goto abort_read_write;
|
||||
SetMissingAddressMark();
|
||||
goto abort;
|
||||
}
|
||||
READ_HEADER();
|
||||
|
||||
@ -493,8 +560,6 @@ void i8272::posit_event(int event_type) {
|
||||
// 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);
|
||||
@ -508,7 +573,7 @@ void i8272::posit_event(int event_type) {
|
||||
if(!index_hole_limit_) {
|
||||
if(!sector_) {
|
||||
SetMissingAddressMark();
|
||||
goto abort_read_write;
|
||||
goto abort;
|
||||
} else {
|
||||
goto post_st012chrn;
|
||||
}
|
||||
@ -536,18 +601,15 @@ void i8272::posit_event(int event_type) {
|
||||
// Performs format [/write] track.
|
||||
format_track:
|
||||
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;
|
||||
goto abort;
|
||||
}
|
||||
|
||||
LOAD_HEAD();
|
||||
|
||||
// Wait for the index hole.
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
begin_writing();
|
||||
index_hole_count_ = 0;
|
||||
begin_writing(true);
|
||||
|
||||
// Write start-of-track.
|
||||
write_start_of_track();
|
||||
@ -564,8 +626,14 @@ void i8272::posit_event(int event_type) {
|
||||
expects_input_ = true;
|
||||
distance_into_section_ = 0;
|
||||
format_track_write_header:
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
// TODO: overrun?
|
||||
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
|
||||
switch(event_type) {
|
||||
case (int)Event::IndexHole:
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::DataWritten:
|
||||
header_[distance_into_section_] = input_;
|
||||
write_byte(input_);
|
||||
has_input_ = false;
|
||||
@ -574,10 +642,14 @@ void i8272::posit_event(int event_type) {
|
||||
SetDataRequest();
|
||||
goto format_track_write_header;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
|
||||
write_crc();
|
||||
|
||||
// Write the sector body.
|
||||
write_id_data_joiner(false);
|
||||
write_id_data_joiner(false, false);
|
||||
write_n_bytes(128 << command_[2], command_[5]);
|
||||
write_crc();
|
||||
|
||||
@ -586,7 +658,7 @@ void i8272::posit_event(int event_type) {
|
||||
|
||||
// Consider repeating.
|
||||
sector_++;
|
||||
if(sector_ < command_[3])
|
||||
if(sector_ < command_[3] && !index_hole_count_)
|
||||
goto format_track_write_sector;
|
||||
|
||||
// Otherwise, pad out to the index hole.
|
||||
@ -717,6 +789,11 @@ void i8272::posit_event(int event_type) {
|
||||
result_stack_.push_back(0x80);
|
||||
goto post_result;
|
||||
|
||||
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
||||
abort:
|
||||
SetAbnormalTermination();
|
||||
goto post_st012chrn;
|
||||
|
||||
// Posts ST0, ST1, ST2, C, H, R and N as a result phase.
|
||||
post_st012chrn:
|
||||
SCHEDULE_HEAD_UNLOAD();
|
||||
@ -735,7 +812,7 @@ 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);
|
||||
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
|
||||
for(size_t c = 0; c < result_stack_.size(); c++) {
|
||||
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
void posit_event(int type);
|
||||
int interesting_event_mask_;
|
||||
int resume_point_;
|
||||
bool is_access_command_;
|
||||
|
||||
// The counter used for ::Timer events.
|
||||
int delay_time_;
|
||||
@ -117,7 +118,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
// Transient storage and counters used while reading the disk.
|
||||
uint8_t header_[6];
|
||||
int distance_into_section_;
|
||||
int index_hole_limit_;
|
||||
int index_hole_count_, index_hole_limit_;
|
||||
|
||||
// Keeps track of the drive and head in use during commands.
|
||||
int active_drive_;
|
||||
|
@ -121,6 +121,7 @@
|
||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; };
|
||||
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */; };
|
||||
4BACC5B11F3DFF7C0037C015 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */; };
|
||||
4BAD9B961F43D7E900724854 /* UnformattedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */; };
|
||||
4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; };
|
||||
4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; };
|
||||
4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; };
|
||||
@ -673,6 +674,8 @@
|
||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = "<group>"; };
|
||||
4BACC5AF1F3DFF7C0037C015 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = AmstradCPC/CharacterMapper.cpp; sourceTree = "<group>"; };
|
||||
4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = AmstradCPC/CharacterMapper.hpp; sourceTree = "<group>"; };
|
||||
4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UnformattedTrack.cpp; sourceTree = "<group>"; };
|
||||
4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = UnformattedTrack.hpp; sourceTree = "<group>"; };
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = "<group>"; };
|
||||
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
|
||||
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
|
||||
@ -1552,6 +1555,7 @@
|
||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */,
|
||||
4B121F961E060CF000BFDA12 /* PCMSegment.cpp */,
|
||||
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
|
||||
4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */,
|
||||
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */,
|
||||
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */,
|
||||
@ -1560,6 +1564,7 @@
|
||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */,
|
||||
4B121F971E060CF000BFDA12 /* PCMSegment.hpp */,
|
||||
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
|
||||
4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */,
|
||||
4BB697CF1D4BA44900248BDF /* Encodings */,
|
||||
4BAB62B21D327F7E00DF5BA0 /* Formats */,
|
||||
4B3FE75F1F3CF6BA00448EE4 /* Parsers */,
|
||||
@ -2709,6 +2714,7 @@
|
||||
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
|
||||
4B95FA9D1F11893B0008E395 /* ZX8081OptionsPanel.swift in Sources */,
|
||||
4BAD9B961F43D7E900724854 /* UnformattedTrack.cpp in Sources */,
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
|
||||
4B8378DC1F336631005CA9E4 /* CharacterMapper.cpp in Sources */,
|
||||
4B8378E51F3378C4005CA9E4 /* CharacterMapper.cpp in Sources */,
|
||||
|
@ -28,6 +28,9 @@ void Disk::set_track_at_position(unsigned int head, unsigned int position, const
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> Disk::get_track_at_position(unsigned int head, unsigned int position) {
|
||||
if(head >= get_head_count()) return nullptr;
|
||||
if(position >= get_head_position_count()) return nullptr;
|
||||
|
||||
int address = get_id_for_track_at_position(head, position);
|
||||
std::map<int, std::shared_ptr<Track>>::iterator cached_track = cached_tracks_.find(address);
|
||||
if(cached_track != cached_tracks_.end()) return cached_track->second;
|
||||
|
@ -7,7 +7,9 @@
|
||||
//
|
||||
|
||||
#include "DiskController.hpp"
|
||||
#include "UnformattedTrack.hpp"
|
||||
#include "../../NumberTheory/Factors.hpp"
|
||||
#include <cassert>
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
@ -29,13 +31,17 @@ Controller::Controller(Cycles clock_rate, int clock_rate_multiplier, int revolut
|
||||
|
||||
void Controller::setup_track() {
|
||||
track_ = drive_->get_track();
|
||||
if(!track_) {
|
||||
track_.reset(new UnformattedTrack);
|
||||
}
|
||||
|
||||
Time offset;
|
||||
Time track_time_now = get_time_into_track();
|
||||
if(track_) {
|
||||
assert(track_time_now >= Time(0) && current_event_.length <= Time(1));
|
||||
|
||||
Time time_found = track_->seek_to(track_time_now);
|
||||
assert(time_found >= Time(0) && time_found <= track_time_now);
|
||||
offset = track_time_now - time_found;
|
||||
}
|
||||
|
||||
get_next_event(offset);
|
||||
}
|
||||
@ -93,7 +99,9 @@ void Controller::get_next_event(const Time &duration_already_passed) {
|
||||
|
||||
// divide interval, which is in terms of a single rotation of the disk, by rotation speed to
|
||||
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
|
||||
set_next_event_time_interval((current_event_.length - duration_already_passed) * rotational_multiplier_);
|
||||
assert(current_event_.length <= Time(1) && current_event_.length >= Time(0));
|
||||
Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_;
|
||||
set_next_event_time_interval(interval);
|
||||
}
|
||||
|
||||
void Controller::process_next_event()
|
||||
@ -121,8 +129,9 @@ Storage::Time Controller::get_time_into_track() {
|
||||
|
||||
#pragma mark - Writing
|
||||
|
||||
void Controller::begin_writing() {
|
||||
void Controller::begin_writing(bool clamp_to_index_hole) {
|
||||
is_reading_ = false;
|
||||
clamp_writing_to_index_hole_ = clamp_to_index_hole;
|
||||
|
||||
write_segment_.length_of_a_bit = bit_length_ / rotational_multiplier_;
|
||||
write_segment_.data.clear();
|
||||
@ -150,7 +159,8 @@ void Controller::end_writing() {
|
||||
patched_track_.reset(new PCMPatchedTrack(track_));
|
||||
}
|
||||
}
|
||||
patched_track_->add_segment(write_start_time_, write_segment_);
|
||||
patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_);
|
||||
cycles_since_index_hole_ %= 8000000 * clock_rate_multiplier_;
|
||||
invalidate_track(); // TEMPORARY: to force a seek
|
||||
}
|
||||
|
||||
@ -205,8 +215,7 @@ bool Controller::get_motor_on() {
|
||||
}
|
||||
|
||||
void Controller::set_drive(std::shared_ptr<Drive> drive) {
|
||||
if(drive_ != drive)
|
||||
{
|
||||
if(drive_ != drive) {
|
||||
invalidate_track();
|
||||
drive_ = drive;
|
||||
}
|
||||
|
@ -72,8 +72,11 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
@c write_bit. They will be written with the length set via @c set_expected_bit_length.
|
||||
It is acceptable to supply a backlog of bits. Flux transition events will not be reported
|
||||
while writing.
|
||||
|
||||
@param clamp_to_index_hole If @c true then writing will automatically be truncated by
|
||||
the index hole. Writing will continue over the index hole otherwise.
|
||||
*/
|
||||
void begin_writing();
|
||||
void begin_writing(bool clamp_to_index_hole);
|
||||
|
||||
/*!
|
||||
Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing.
|
||||
@ -129,6 +132,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
bool motor_is_on_;
|
||||
|
||||
bool is_reading_;
|
||||
bool clamp_writing_to_index_hole_;
|
||||
std::shared_ptr<PCMPatchedTrack> patched_track_;
|
||||
PCMSegment write_segment_;
|
||||
Time write_start_time_;
|
||||
|
@ -199,15 +199,15 @@ void MFMController::write_id_joiner() {
|
||||
}
|
||||
}
|
||||
|
||||
void MFMController::write_id_data_joiner(bool is_deleted) {
|
||||
void MFMController::write_id_data_joiner(bool is_deleted, bool skip_first_gap) {
|
||||
if(get_is_double_density()) {
|
||||
write_n_bytes(22, 0x4e);
|
||||
if(!skip_first_gap) write_n_bytes(22, 0x4e);
|
||||
write_n_bytes(12, 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(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
} else {
|
||||
write_n_bytes(11, 0xff);
|
||||
if(!skip_first_gap) write_n_bytes(11, 0xff);
|
||||
write_n_bytes(6, 0x00);
|
||||
get_crc_generator().reset();
|
||||
get_crc_generator().add(is_deleted ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
|
||||
|
@ -125,11 +125,12 @@ class MFMController: public Controller {
|
||||
void write_id_joiner();
|
||||
|
||||
/*!
|
||||
Writes everything that should, per the spec, appear after the ID's CRC, up to and
|
||||
Writes at most what should, per the spec, appear after the ID's CRC, up to and
|
||||
including the mark that indicates the beginning of data, appropriately seeding
|
||||
the CRC generator.
|
||||
the CRC generator; if @c skip_first_gap is set then the initial gap after the
|
||||
CRC isn't written.
|
||||
*/
|
||||
void write_id_data_joiner(bool is_deleted);
|
||||
void write_id_data_joiner(bool is_deleted, bool skip_first_gap);
|
||||
|
||||
/*!
|
||||
Writes the gap expected after a sector's data CRC and before the beginning of the
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include "PCMPatchedTrack.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
PCMPatchedTrack::PCMPatchedTrack(std::shared_ptr<Track> underlying_track) :
|
||||
@ -29,20 +31,25 @@ Track *PCMPatchedTrack::clone() {
|
||||
return new PCMPatchedTrack(*this);
|
||||
}
|
||||
|
||||
void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment) {
|
||||
void PCMPatchedTrack::add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole) {
|
||||
std::shared_ptr<PCMSegmentEventSource> event_source(new PCMSegmentEventSource(segment));
|
||||
|
||||
Time zero(0);
|
||||
Time one(1);
|
||||
Time end_time = start_time + event_source->get_length();
|
||||
if(clamp_to_index_hole && end_time > one) {
|
||||
end_time = one;
|
||||
}
|
||||
Period insertion_period(start_time, end_time, zero, event_source);
|
||||
|
||||
// the new segment may wrap around, so divide it up into track-length parts if required
|
||||
Time one = Time(1);
|
||||
assert(insertion_period.start_time <= one);
|
||||
while(insertion_period.end_time > one) {
|
||||
Time next_end_time = insertion_period.end_time - one;
|
||||
insertion_period.end_time = one;
|
||||
insert_period(insertion_period);
|
||||
|
||||
insertion_period.segment_start_time += one;
|
||||
insertion_period.start_time = zero;
|
||||
insertion_period.end_time = next_end_time;
|
||||
}
|
||||
@ -127,8 +134,7 @@ void PCMPatchedTrack::insert_period(const Period &period) {
|
||||
}
|
||||
}
|
||||
|
||||
Track::Event PCMPatchedTrack::get_next_event()
|
||||
{
|
||||
Track::Event PCMPatchedTrack::get_next_event() {
|
||||
const Time one(1);
|
||||
const Time zero(0);
|
||||
Time extra_time(0);
|
||||
@ -184,13 +190,19 @@ Track::Event PCMPatchedTrack::get_next_event()
|
||||
Storage::Time PCMPatchedTrack::seek_to(const Time &time_since_index_hole) {
|
||||
// start at the beginning and continue while segments end before reaching the time sought
|
||||
active_period_ = periods_.begin();
|
||||
while(active_period_->end_time < time_since_index_hole) active_period_++;
|
||||
while(active_period_->end_time < time_since_index_hole) {
|
||||
assert(active_period_ != periods_.end());
|
||||
active_period_++;
|
||||
}
|
||||
|
||||
// allow whatever storage represents the period found to perform its seek
|
||||
// allow whatever storage represents the period found to perform its seek; calculation for periods
|
||||
// with an event source is, in effect: seek_to(offset_into_segment + distance_into_period) - offset_into_segment.
|
||||
if(active_period_->event_source)
|
||||
current_time_ = active_period_->event_source->seek_to(time_since_index_hole - active_period_->start_time) + active_period_->start_time;
|
||||
current_time_ = active_period_->event_source->seek_to(active_period_->segment_start_time + time_since_index_hole - active_period_->start_time) + active_period_->start_time - active_period_->segment_start_time;
|
||||
else
|
||||
current_time_ = underlying_track_->seek_to(time_since_index_hole);
|
||||
|
||||
assert(current_time_ <= time_since_index_hole);
|
||||
return current_time_;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
#ifndef PCMPatchedTrack_hpp
|
||||
#define PCMPatchedTrack_hpp
|
||||
|
||||
#include "PCMTrack.hpp"
|
||||
#include "Disk.hpp"
|
||||
#include "PCMSegment.hpp"
|
||||
|
||||
namespace Storage {
|
||||
@ -34,8 +34,13 @@ class PCMPatchedTrack: public Track {
|
||||
/*!
|
||||
Replaces whatever is currently on the track from @c start_position to @c start_position + segment length
|
||||
with the contents of @c segment.
|
||||
|
||||
@param start_time The time at which this segment begins. Must be in the range [0, 1).
|
||||
@param segment The PCM segment to add.
|
||||
@param clamp_to_index_hole If @c true then the new segment will be truncated if it overruns the index hole;
|
||||
it will otherwise write over the index hole and continue.
|
||||
*/
|
||||
void add_segment(const Time &start_time, const PCMSegment &segment);
|
||||
void add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole);
|
||||
|
||||
// To satisfy Storage::Disk::Track
|
||||
Event get_next_event();
|
||||
|
26
Storage/Disk/UnformattedTrack.cpp
Normal file
26
Storage/Disk/UnformattedTrack.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// UnformattedTrack.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "UnformattedTrack.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
Track::Event UnformattedTrack::get_next_event() {
|
||||
Track::Event event;
|
||||
event.type = Event::IndexHole;
|
||||
event.length = Time(1);
|
||||
return event;
|
||||
}
|
||||
|
||||
Storage::Time UnformattedTrack::seek_to(const Time &time_since_index_hole) {
|
||||
return Time(0);
|
||||
}
|
||||
|
||||
Track *UnformattedTrack::clone() {
|
||||
return new UnformattedTrack;
|
||||
}
|
30
Storage/Disk/UnformattedTrack.hpp
Normal file
30
Storage/Disk/UnformattedTrack.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// UnformattedTrack.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/08/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef UnformattedTrack_hpp
|
||||
#define UnformattedTrack_hpp
|
||||
|
||||
#include "Disk.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
A subclass of @c Track with no contents. Just an index hole.
|
||||
*/
|
||||
class UnformattedTrack: public Track {
|
||||
public:
|
||||
Event get_next_event();
|
||||
Time seek_to(const Time &time_since_index_hole);
|
||||
Track *clone();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* UnformattedTrack_hpp */
|
Loading…
Reference in New Issue
Block a user