diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 1fe909a51..79cff13f4 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -140,9 +140,7 @@ template 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 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_; diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 285ec755d..eb23cf694 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -7,10 +7,11 @@ // #include "i8272.hpp" +#include "../../Storage/Disk/Encodings/MFM.hpp" #include -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 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; +} diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 137e56b3f..61729874a 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -13,22 +13,40 @@ #include "../../Storage/Disk/Drive.hpp" #include +#include #include 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 disk, int drive); private: + // The bus handler, for interrupt and DMA-driven usage. + BusHandler &bus_handler_; + std::unique_ptr 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 command_; std::vector 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 */ diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 6edd4055b..4202cd275 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -46,6 +46,7 @@ AsyncTaskQueue::AsyncTaskQueue() AsyncTaskQueue::~AsyncTaskQueue() { #ifdef __APPLE__ dispatch_release(serial_dispatch_queue_); + serial_dispatch_queue_ = nullptr; #else should_destruct_ = true; enqueue([](){}); diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 328c233c3..0c7b71fc2 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -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_), diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index da34bfabe..8d76afe68 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -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 &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; } diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 5429da94f..c07eea966 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -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; diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 1911d7916..b8124dc90 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -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() { diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index c67f5d1b6..d65cb5435 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -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(); diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index aeb47404a..3808baff2 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -120,8 +120,8 @@ template std::shared_ptr 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 std::shared_ptr } // 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::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { return GetTrackWithSectors( 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::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) } diff --git a/Storage/Disk/Formats/CPCDSK.cpp b/Storage/Disk/Formats/CPCDSK.cpp index f1a9b1570..ba57c539b 100644 --- a/Storage/Disk/Formats/CPCDSK.cpp +++ b/Storage/Disk/Formats/CPCDSK.cpp @@ -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 CPCDSK::get_uncached_track_at_position(unsigned int head, unsigned int position) { diff --git a/Storage/Disk/MFMDiskController.cpp b/Storage/Disk/MFMDiskController.cpp index d9663f398..51f4ac3b2 100644 --- a/Storage/Disk/MFMDiskController.cpp +++ b/Storage/Disk/MFMDiskController.cpp @@ -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); +} diff --git a/Storage/Disk/MFMDiskController.hpp b/Storage/Disk/MFMDiskController.hpp index e32e697a2..4ace6665e 100644 --- a/Storage/Disk/MFMDiskController.hpp +++ b/Storage/Disk/MFMDiskController.hpp @@ -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_; };