// // DMAController.cpp // Clock Signal // // Created by Thomas Harte on 26/10/2019. // Copyright © 2019 Thomas Harte. All rights reserved. // #include "DMAController.hpp" #include using namespace Atari::ST; namespace { enum Control: uint16_t { Direction = 0x100, DRQSource = 0x80, SectorCountSelect = 0x10, CPUTarget = 0x08 }; } DMAController::DMAController() { fdc_.set_delegate(this); fdc_.set_clocking_hint_observer(this); } uint16_t DMAController::read(int address) { switch(address & 7) { // Reserved. default: break; // Disk controller or sector count. case 2: if(control_ & Control::SectorCountSelect) { return uint16_t((byte_count_ + 511) >> 9); // Assumed here: the count is of sectors remaining, i.e. it decrements // only when a sector is complete. } else { if(control_ & Control::CPUTarget) { return 0xffff; } else { return 0xff00 | fdc_.get_register(control_ >> 1); } } break; // DMA status. case 3: // TODO: should DRQ come from the HDC if that mode is selected? return 0xfff8 | (error_ ? 0 : 1) | (byte_count_ ? 2 : 0) | (fdc_.get_data_request_line() ? 4 : 0); // DMA addressing. case 4: return uint16_t(0xff00 | ((address_ >> 16) & 0xff)); case 5: return uint16_t(0xff00 | ((address_ >> 8) & 0xff)); case 6: return uint16_t(0xff00 | ((address_ >> 0) & 0xff)); } return 0xffff; } void DMAController::write(int address, uint16_t value) { switch(address & 7) { // Reserved. default: break; // Disk controller or sector count. case 2: if(control_ & Control::SectorCountSelect) { byte_count_ = (value & 0xff) << 9; // The computer provides a sector count; that times 512 is a byte count. // TODO: if this is a write-mode DMA operation, try to fill both buffers, ASAP. } else { if(control_ & Control::CPUTarget) { // TODO: HDC. } else { fdc_.set_register(control_ >> 1, uint8_t(value)); } } break; // DMA control; meaning is: // // b0: unused // b1, b2 = address lines for FDC access. // b3 = 1 => CPU HDC access; 0 => CPU FDC access. // b4 = 1 => sector count access; 0 => [F/H]DC access. // b5: unused. // b6 = officially, 1 => DMA off; 0 => DMA on. Ignored in real hardware. // b7 = 1 => FDC DRQs being observed; 0 => HDC access DRQs being observed. // b8 = 1 => DMA is writing to [F/H]DC; 0 => DMA is reading. Changing value resets DMA state. // // All other bits: undefined. case 3: // Check for a DMA state reset. if((control_^value) & Control::Direction) { bytes_received_ = active_buffer_ = 0; error_ = false; byte_count_ = 0; } control_ = value; break; // DMA addressing. case 4: address_ = int((address_ & 0x00ffff) | ((value & 0xff) << 16)); break; case 5: address_ = int((address_ & 0xff00ff) | ((value & 0xff) << 8)); break; case 6: address_ = int((address_ & 0xffff00) | ((value & 0xfe) << 0)); break; // Lowest bit: discarded. } } void DMAController::set_floppy_drive_selection(bool drive1, bool drive2, bool side2) { fdc_.set_floppy_drive_selection(drive1, drive2, side2); } void DMAController::set_floppy_disk(std::shared_ptr disk, size_t drive) { fdc_.drives_[drive]->set_disk(disk); } void DMAController::run_for(HalfCycles duration) { running_time_ += duration; fdc_.run_for(duration.flush()); } void DMAController::wd1770_did_change_output(WD::WD1770 *) { // Check for a change in interrupt state. const bool old_interrupt_line = interrupt_line_; interrupt_line_ = fdc_.get_interrupt_request_line(); if(delegate_ && interrupt_line_ != old_interrupt_line) { delegate_->dma_controller_did_change_output(this); } // Check for a change in DRQ state, if it's the FDC that is currently being watched. if(byte_count_ && fdc_.get_data_request_line() && (control_ & Control::DRQSource)) { --byte_count_; if(control_ & Control::Direction) { // TODO: DMA is supposed to be helping with a write. } else { // DMA is enabling a read. // Read from the data register into the active buffer. if(bytes_received_ < 16) { buffer_[active_buffer_].contents[bytes_received_] = fdc_.get_register(3); ++bytes_received_; } if(bytes_received_ == 16) { // Mark buffer as full. buffer_[active_buffer_].is_full = true; // Move to the next if it is empty; if it isn't, note a DMA error. const auto next_buffer = active_buffer_ ^ 1; error_ |= buffer_[next_buffer].is_full; if(!buffer_[next_buffer].is_full) { bytes_received_ = 0; active_buffer_ = next_buffer; } // Set bus request. if(!bus_request_line_) { bus_request_line_ = true; if(delegate_) delegate_->dma_controller_did_change_output(this); } } } } } int DMAController::bus_grant(uint16_t *ram, size_t size) { // Being granted the bus negates the request. bus_request_line_ = false; if(delegate_) delegate_->dma_controller_did_change_output(this); if(control_ & Control::Direction) { // TODO: writes. return 0; } else { // Check that the older buffer is full; stop if not. if(!buffer_[active_buffer_ ^ 1].is_full) return 0; for(int c = 0; c < 8; ++c) { ram[size_t(address_ >> 1) & (size - 1)] = uint16_t( (buffer_[active_buffer_ ^ 1].contents[(c << 1) + 0] << 8) | (buffer_[active_buffer_ ^ 1].contents[(c << 1) + 1] << 0) ); address_ += 2; } buffer_[active_buffer_ ^ 1].is_full = false; // Check that the newer buffer is full; stop if not. if(!buffer_[active_buffer_ ].is_full) return 8; for(int c = 0; c < 8; ++c) { ram[size_t(address_ >> 1) & (size - 1)] = uint16_t( (buffer_[active_buffer_].contents[(c << 1) + 0] << 8) | (buffer_[active_buffer_].contents[(c << 1) + 1] << 0) ); address_ += 2; } buffer_[active_buffer_].is_full = false; // Both buffers were full, so unblock reading. bytes_received_ = 0; return 16; } } void DMAController::set_delegate(Delegate *delegate) { delegate_ = delegate; } bool DMAController::get_interrupt_line() { return interrupt_line_; } bool DMAController::get_bus_request_line() { return bus_request_line_; } void DMAController::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) { update_clocking_observer(); } ClockingHint::Preference DMAController::preferred_clocking() { return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime; } void DMAController::set_activity_observer(Activity::Observer *observer) { fdc_.drives_[0]->set_activity_observer(observer, "Internal", true); fdc_.drives_[1]->set_activity_observer(observer, "External", true); }