1
0
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:
Thomas Harte 2017-08-15 22:09:32 -04:00 committed by GitHub
commit 8a37a0ff2e
13 changed files with 299 additions and 125 deletions

View File

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

View File

@ -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,61 +293,75 @@ 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:
for(int c = 0; c < 4; c++) {
if(drives_[c].phase == Drive::Seeking) {
drives_[c].phase = Drive::NotSeeking;
drives_seeking_--;
}
}
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_--;
}
}
// 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;
default: goto invalid;
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;
}
// 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.
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,14 +409,27 @@ 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(!(command_[0]&0x20)) {
// SK is not set; set the error flag but read this sector before finishing.
SetControlMark();
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 {
// SK is set; skip this sector.
goto read_next_data;
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();
} else {
// SK is set; skip this sector.
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;
@ -379,12 +440,14 @@ void i8272::posit_event(int event_type) {
//
// TODO: consider DTL.
read_data_get_byte:
WAIT_FOR_EVENT(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);
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,20 +626,30 @@ 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?
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;
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;
distance_into_section_++;
if(distance_into_section_ < 4) {
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]);
}

View File

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

View File

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

View File

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

View File

@ -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_) {
Time time_found = track_->seek_to(track_time_now);
offset = track_time_now - time_found;
}
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;
}

View File

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

View File

@ -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);
@ -227,7 +227,7 @@ void MFMController::write_start_of_track() {
if(get_is_double_density()) {
write_n_bytes(80, 0x4e);
write_n_bytes(12, 0x00);
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMIndexSync);
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMIndexSync);
write_byte(Storage::Encodings::MFM::IndexAddressByte);
write_n_bytes(50, 0x4e);
} else {

View File

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

View File

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

View File

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

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

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