1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-04 18:29:40 +00:00
CLK/Storage/MassStorage/SCSI/TargetImplementation.hpp
Thomas Harte 310c722cc0 Starts a transition to bus-level knowledge of SCSI-specific bus timing thresholds.
The idea being that bus attachees need not all count time for themselves. They can be very plain finite state machines.

New semantics are not yet implemented within the Bus. The plan is to do that, remove the internal counting of time within the NCR, then adjust the Target to be more explicitly stateful.
2019-08-31 21:44:22 -04:00

245 lines
7.9 KiB
C++

//
// TargetImplementation.hpp
// Clock Signal
//
// Created by Thomas Harte on 19/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
template <typename Executor> Target<Executor>::Target(Bus &bus, int scsi_id) :
bus_(bus),
scsi_id_mask_(BusState(1 << scsi_id)),
scsi_bus_device_id_(bus.add_device()) {
bus.add_observer(this);
}
template <typename Executor> void Target<Executor>::scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) {
/*
"The target determines that it is selected when the SEL# signal
and its SCSI ID bit are active and the BSY# and I#/O signals
are false. It then asserts the signal within a selection abort
time."
*/
// A reset always takes precedence over anything else ongoing.
if(new_state & Line::Reset) {
phase_ = Phase::AwaitingSelection;
bus_state_ = DefaultBusState;
set_device_output(bus_state_);
return;
}
// Check for an unexpected change of SCSI state.
/* if((phase_ > Phase::Command) && (new_state & (Line::Control | Line::Input | Line::Message)) != expected_control_state_) {
phase_ = Phase::AwaitingSelection;
bus_state_ = DefaultBusState;
set_device_output(bus_state_);
return;
}*/
switch(phase_) {
/*
While awaiting selection the SCSI target is passively watching the bus waiting for its ID
to be set during a target selection. It will segue automatically from there to the command
phase regardless of its executor.
*/
case Phase::AwaitingSelection:
if(
(new_state & scsi_id_mask_) &&
((new_state & (Line::SelectTarget | Line::Busy | Line::Input)) == Line::SelectTarget)
) {
phase_ = Phase::Command;
command_.resize(0);
command_pointer_ = 0;
bus_state_ |= Line::Busy; // Initiate the command phase: request a command byte.
set_device_output(bus_state_);
}
break;
/*
In the command phase, the target will stream an appropriate number of bytes for the command
it is being offered, before giving the executor a chance to handle the command. If the target
supports this command, it becomes responsible for the appropriate next phase transition. If it
reports that it doesn't support that command, a suitable response is automatically dispatched.
*/
case Phase::Command:
// Wait for select to be disabled before beginning the control phase proper.
if((new_state & Line::SelectTarget)) return;
bus_state_ |= Line::Control;
switch(new_state & (Line::Request | Line::Acknowledge)) {
// If request and acknowledge are both enabled, grab a byte and cancel the request.
case Line::Request | Line::Acknowledge:
bus_state_ &= ~Line::Request;
if(command_.empty()) {
begin_command(uint8_t(new_state));
// TODO: if(command_.empty()) signal_error_somehow();
} else {
command_[command_pointer_] = uint8_t(new_state);
++command_pointer_;
if(command_pointer_ == command_.size()) {
dispatch_command();
// TODO: if(!dispatch_command()) signal_error_somehow();
}
}
break;
// The reset of request has caused the initiator to reset acknowledge, so it is now
// safe to request the next byte.
case 0:
bus_state_ |= Line::Request;
break;
default: break;
}
set_device_output(bus_state_);
break;
case Phase::ReceivingData:
switch(new_state & (Line::Request | Line::Acknowledge)) {
case Line::Request | Line::Acknowledge:
bus_state_ &= ~Line::Request;
data_[data_pointer_] = uint8_t(new_state);
++data_pointer_;
if(data_pointer_ == data_.size()) {
next_function_(CommandState(command_), *this);
}
break;
case 0:
bus_state_ |= Line::Request;
break;
}
set_device_output(bus_state_);
break;
case Phase::SendingData:
switch(new_state & (Line::Request | Line::Acknowledge)) {
case Line::Request | Line::Acknowledge:
bus_state_ &= ~(Line::Request | 0xff);
++data_pointer_;
printf("DP: %zu\n", data_pointer_);
if(data_pointer_ == data_.size()) {
next_function_(CommandState(command_), *this);
}
break;
case 0:
bus_state_ |= Line::Request;
bus_state_ = (bus_state_ & ~0xff) | data_[data_pointer_];
break;
}
set_device_output(bus_state_);
break;
}
}
template <typename Executor> void Target<Executor>::begin_command(uint8_t first_byte) {
// The logic below is valid for SCSI-1. TODO: other SCSIs.
switch(first_byte >> 5) {
default: break;
case 0: command_.resize(6); break; // Group 0 commands: 6 bytes long.
case 1: command_.resize(10); break; // Group 1 commands: 10 bytes long.
case 5: command_.resize(12); break; // Group 5 commands: 12 bytes long.
}
// Store the first byte if it was recognised.
if(!command_.empty()) {
command_[0] = first_byte;
command_pointer_ = 1;
}
}
template <typename Executor> bool Target<Executor>::dispatch_command() {
CommandState arguments(command_);
#define G0(x) x
#define G1(x) (0x20|x)
#define G5(x) (0xa0|x)
switch(command_[0]) {
default: return false;
case G0(0x00): return executor_.test_unit_ready(arguments, *this);
case G0(0x01): return executor_.rezero_unit(arguments, *this);
case G0(0x03): return executor_.request_sense(arguments, *this);
case G0(0x04): return executor_.format_unit(arguments, *this);
case G0(0x08): return executor_.read(arguments, *this);
case G0(0x0a): return executor_.write(arguments, *this);
case G0(0x0b): return executor_.seek(arguments, *this);
case G0(0x16): return executor_.reserve_unit(arguments, *this);
case G0(0x17): return executor_.release_unit(arguments, *this);
case G0(0x1c): return executor_.read_diagnostic(arguments, *this);
case G0(0x1d): return executor_.write_diagnostic(arguments, *this);
case G0(0x12): return executor_.inquiry(arguments, *this);
case G1(0x05): return executor_.read_capacity(arguments, *this);
case G1(0x08): return executor_.read(arguments, *this);
case G1(0x0a): return executor_.write(arguments, *this);
case G1(0x0e): return executor_.write_and_verify(arguments, *this);
case G1(0x0f): return executor_.verify(arguments, *this);
case G1(0x11): return executor_.search_data_equal(arguments, *this);
case G1(0x10): return executor_.search_data_high(arguments, *this);
case G1(0x12): return executor_.search_data_low(arguments, *this);
case G5(0x09): return executor_.set_block_limits(arguments, *this);
}
#undef G0
#undef G1
#undef G5
return false;
}
template <typename Executor> void Target<Executor>::send_data(std::vector<uint8_t> &&data, continuation next) {
// Data out phase: control and message all reset, input set.
bus_state_ &= ~(Line::Control | Line::Input | Line::Message);
bus_state_ |= Line::Input;
phase_ = Phase::SendingData;
data_ = std::move(data);
data_pointer_ = 0;
set_device_output(bus_state_);
}
template <typename Executor> void Target<Executor>::receive_data(size_t length, continuation next) {
// Data out phase: control, input and message all reset.
bus_state_ &= ~(Line::Control | Line::Input | Line::Message);
phase_ = Phase::ReceivingData;
data_.resize(length);
data_pointer_ = 0;
set_device_output(bus_state_);
}
template <typename Executor> void Target<Executor>::send_status(Status, continuation next) {
// Status phase: message reset, control and input set.
bus_state_ &= ~(Line::Control | Line::Input | Line::Message);
bus_state_ |= Line::Input | Line::Control;
phase_ = Phase::SendingStatus;
set_device_output(bus_state_);
}
template <typename Executor> void Target<Executor>::send_message(Message, continuation next) {
// Message out phase: message and control set, input reset.
bus_state_ &= ~(Line::Control | Line::Input | Line::Message);
bus_state_ |= Line::Message | Line::Control;
phase_ = Phase::SendingMessage;
set_device_output(bus_state_);
}
template <typename Executor> void Target<Executor>::end_command() {
// TODO: was this a linked command?
// Release all bus lines and return to awaiting selection.
phase_ = Phase::AwaitingSelection;
bus_state_ = DefaultBusState;
set_device_output(bus_state_);
}