// // TargetImplementation.hpp // Clock Signal // // Created by Thomas Harte on 19/08/2019. // Copyright © 2019 Thomas Harte. All rights reserved. // #pragma once template Target::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 void Target::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." */ // Wait for deskew, at the very least. if(time_since_change < SCSI::DeskewDelay) return; // 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; } 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()) { if(!dispatch_command()) { // This is just a guess for now; I don't know how SCSI // devices are supposed to respond if they don't support // a command. terminate_command(Responder::Status::TaskAborted); } } } 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_; break; case 0: if(data_pointer_ == data_.size()) { next_function_(CommandState(command_, data_), *this); } else { bus_state_ |= Line::Request; } break; } set_device_output(bus_state_); break; case Phase::SendingData: case Phase::SendingStatus: case Phase::SendingMessage: switch(new_state & (Line::Request | Line::Acknowledge)) { case Line::Request | Line::Acknowledge: bus_state_ &= ~(Line::Request | 0xff); ++data_pointer_; break; case 0: if( (phase_ == Phase::SendingMessage && data_pointer_ == 1) || (phase_ == Phase::SendingStatus && data_pointer_ == 1) || (phase_ == Phase::SendingData && data_pointer_ == data_.size()) ) { next_function_(CommandState(command_, data_), *this); } else { bus_state_ |= Line::Request; bus_state_ &= ~0xff; switch(phase_) { case Phase::SendingData: bus_state_ |= data_[data_pointer_]; break; case Phase::SendingStatus: bus_state_ |= BusState(status_); break; default: case Phase::SendingMessage: bus_state_ |= BusState(message_); break; } } break; } set_device_output(bus_state_); break; } } template void Target::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; } } namespace { constexpr uint8_t G0(uint8_t opcode) { return 0x00 | opcode; } constexpr uint8_t G1(uint8_t opcode) { return 0x20 | opcode; } constexpr uint8_t G5(uint8_t opcode) { return 0xa0 | opcode; } } template bool Target::dispatch_command() { CommandState arguments(command_, data_); log_.info().append("---Command %02x---", command_[0]); 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(0x12): return executor_.inquiry(arguments, *this); case G0(0x15): return executor_.mode_select(arguments, *this); case G0(0x16): return executor_.reserve_unit(arguments, *this); case G0(0x17): return executor_.release_unit(arguments, *this); case G0(0x1a): return executor_.mode_sense(arguments, *this); case G0(0x1c): return executor_.read_diagnostic(arguments, *this); case G0(0x1d): return executor_.write_diagnostic(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 G1(0x1c): return executor_.read_buffer(arguments, *this); case G1(0x15): return executor_.mode_select(arguments, *this); case G5(0x09): return executor_.set_block_limits(arguments, *this); } return false; } template void Target::send_data(std::vector &&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; next_function_ = next; data_ = std::move(data); data_pointer_ = 0; set_device_output(bus_state_); } template void Target::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; next_function_ = next; data_.resize(length); data_pointer_ = 0; set_device_output(bus_state_); } template void Target::send_status(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; status_ = status; phase_ = Phase::SendingStatus; next_function_ = next; data_pointer_ = 0; set_device_output(bus_state_); } template void Target::send_message(Message message, continuation next) { // Message in phase: message, control and input set. bus_state_ |= Line::Message | Line::Control | Line::Input; message_ = message; phase_ = Phase::SendingMessage; next_function_ = next; data_pointer_ = 0; set_device_output(bus_state_); } template void Target::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_); log_.info().append("---Done---"); }