// // Disk.cpp // Clock Signal // // Created by Thomas Harte on 02/11/2021. // Copyright © 2021 Thomas Harte. All rights reserved. // #include "Chipset.hpp" #ifndef NDEBUG #define NDEBUG #endif #define LOG_PREFIX "[Disk] " #include "../../Outputs/Log.hpp" using namespace Amiga; // MARK: - Disk DMA. void Chipset::DiskDMA::enqueue(uint16_t value, bool matches_sync) { if(matches_sync && state_ == State::WaitingForSync) { state_ = State::Reading; return; } if(state_ == State::Reading) { buffer_[buffer_write_ & 3] = value; if(buffer_write_ == buffer_read_ + 4) { ++buffer_read_; } ++buffer_write_; } } void Chipset::DiskDMA::set_control(uint16_t control) { sync_with_word_ = control & 0x400; } void Chipset::DiskDMA::set_length(uint16_t value) { if(value == last_set_length_) { dma_enable_ = value & 0x8000; write_ = value & 0x4000; length_ = value & 0x3fff; buffer_read_ = buffer_write_ = 0; if(dma_enable_) { LOG("Disk DMA " << (write_ ? "write" : "read") << " of " << length_ << " to " << PADHEX(8) << pointer_[0]); } state_ = sync_with_word_ ? State::WaitingForSync : State::Reading; } last_set_length_ = value; } bool Chipset::DiskDMA::advance_dma() { if(!dma_enable_) return false; if(!write_) { if(length_ && buffer_read_ != buffer_write_) { ram_[pointer_[0] & ram_mask_] = buffer_[buffer_read_ & 3]; ++pointer_[0]; ++buffer_read_; --length_; if(!length_) { chipset_.posit_interrupt(InterruptFlag::DiskBlock); state_ = State::Inactive; } return true; } } return false; } // MARK: - Disk Controller. Chipset::DiskController::DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia) : Storage::Disk::Controller(clock_rate), chipset_(chipset), disk_dma_(disk_dma), cia_(cia) { // Add four drives. for(int c = 0; c < 4; c++) { emplace_drive(clock_rate.as(), 300, 2, Storage::Disk::Drive::ReadyType::IBMRDY); } } void Chipset::DiskController::process_input_bit(int value) { data_ = uint16_t((data_ << 1) | value); ++bit_count_; const bool sync_matches = data_ == sync_word_; if(sync_matches) { chipset_.posit_interrupt(InterruptFlag::DiskSyncMatch); if(sync_with_word_) { bit_count_ = 0; } } if(!(bit_count_ & 15)) { disk_dma_.enqueue(data_, sync_matches); } } void Chipset::DiskController::set_sync_word(uint16_t value) { LOG("Set disk sync word to " << PADHEX(4) << value); sync_word_ = value; } void Chipset::DiskController::set_control(uint16_t control) { // b13 and b14: precompensation length specifier // b12: 0 => GCR precompensation; 1 => MFM. // b10: 1 => enable use of word sync; 0 => disable. // b9: 1 => sync on MSB (Disk II style, presumably?); 0 => don't. // b8: 1 => 2µs per bit; 0 => 4µs. sync_with_word_ = control & 0x400; Storage::Time bit_length; bit_length.length = 1; bit_length.clock_rate = (control & 0x100) ? 500000 : 250000; set_expected_bit_length(bit_length); LOG((sync_with_word_ ? "Will" : "Won't") << " sync with word; bit length is " << ((control & 0x100) ? "short" : "long")); } void Chipset::DiskController::process_index_hole() { // Pulse the CIA flag input. // // TODO: rectify once drives do an actual index pulse, with length. cia_.set_flag_input(true); cia_.set_flag_input(false); // Resync word output. Experimental!! bit_count_ = 0; } void Chipset::DiskController::set_mtr_sel_side_dir_step(uint8_t value) { // b7: /MTR // b6: /SEL3 // b5: /SEL2 // b4: /SEL1 // b3: /SEL0 // b2: /SIDE // b1: DIR // b0: /STEP // Select active drive. set_drive(((value >> 3) & 0x0f) ^ 0x0f); // "[The MTR] signal is nonstandard on the Amiga system. // Each drive will latch the motor signal at the time its // select signal turns on." — The Hardware Reference Manual. const auto difference = int(previous_select_ ^ value); previous_select_ = value; // Check for changes in the SEL line per drive. const bool motor_on = !(value & 0x80); const int side = (value & 0x04) ? 0 : 1; const bool did_step = difference & value & 0x01; const auto direction = Storage::Disk::HeadPosition( (value & 0x02) ? -1 : 1 ); for(int c = 0; c < 4; c++) { auto &drive = get_drive(size_t(c)); const int select_mask = 0x08 << c; const bool is_selected = !(value & select_mask); // Both the motor state and the ID shifter are affected upon // changes in drive selection only. if(difference & select_mask) { // If transitioning to inactive, shift the drive ID value; // if transitioning to active, possibly reset the drive // ID and definitely latch the new motor state. if(!is_selected) { drive_ids_[c] <<= 1; LOG("Shifted drive ID shift register for drive " << +c << " to " << PADHEX(4) << std::bitset<16>{drive_ids_[c]}); } else { // Motor transition on -> off => reload register. if(!motor_on && drive.get_motor_on()) { // NB: // 0xffff'ffff = 3.5" drive; // 0x5555'5555 = 5.25" drive; // 0x0000'0000 = no drive. drive_ids_[c] = 0xffff'ffff; LOG("Reloaded drive ID shift register for drive " << +c); } // Also latch the new motor state. drive.set_motor_on(motor_on); } } // Set the new side. drive.set_head(side); // Possibly step. if(did_step && is_selected) { LOG("Stepped drive " << +c << " by " << std::dec << +direction.as_int()); drive.step(direction); } } } uint8_t Chipset::DiskController::get_rdy_trk0_wpro_chng() { // b5: /RDY // b4: /TRK0 // b3: /WPRO // b2: /CHNG // My interpretation: // // RDY isn't RDY, it's a shift value as described above, combined with the motor state. // CHNG is what is normally RDY. const uint32_t combined_id = ((previous_select_ & 0x40) ? 0 : drive_ids_[3]) | ((previous_select_ & 0x20) ? 0 : drive_ids_[2]) | ((previous_select_ & 0x10) ? 0 : drive_ids_[1]) | ((previous_select_ & 0x08) ? 0 : drive_ids_[0]); auto &drive = get_drive(); const uint8_t active_high = ((combined_id & 0x8000) >> 10) | (drive.get_motor_on() ? 0x20 : 0x00) | (drive.get_is_ready() ? 0x00 : 0x04) | (drive.get_is_track_zero() ? 0x10 : 0x00) | (drive.get_is_read_only() ? 0x08 : 0x00); return ~active_high; } void Chipset::DiskController::set_activity_observer(Activity::Observer *observer) { for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) { drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true); }); } bool Chipset::DiskController::insert(const std::shared_ptr &disk, size_t drive) { if(drive >= 4) return false; get_drive(drive).set_disk(disk); return true; } bool Chipset::insert(const std::vector> &disks) { bool inserted = false; size_t target = 0; for(const auto &disk: disks) { inserted |= disk_controller_.insert(disk, target); ++target; } return inserted; }