1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-30 04:50:08 +00:00
CLK/Machines/Amiga/Disk.cpp
Thomas Harte 47f36f08fb Switches to a synchronous audio state machine; renames advance -> advance_dma.
I can worry about how to just-in-time things once I better understand the hardware in general.
2021-11-13 15:53:41 -05:00

260 lines
6.7 KiB
C++

//
// Disk.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Chipset.hpp"
#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<int>(), 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<Storage::Disk::Disk> &disk, size_t drive) {
if(drive >= 4) return false;
get_drive(drive).set_disk(disk);
return true;
}
bool Chipset::insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks) {
bool inserted = false;
size_t target = 0;
for(const auto &disk: disks) {
inserted |= disk_controller_.insert(disk, target);
++target;
}
return inserted;
}