1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Starts seeking to unbind SCSI bus logic and command performance.

This commit is contained in:
Thomas Harte 2019-08-19 22:47:01 -04:00
parent e3d9254555
commit 252650808d
5 changed files with 249 additions and 71 deletions

View File

@ -8,71 +8,34 @@
#include "DirectAccessDevice.hpp"
using namespace SCSI;
using namespace SCSI::Target;
DirectAccessDevice::DirectAccessDevice(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);
}
CommandArguments::CommandArguments(const std::vector<uint8_t> &data) : data_(data) {}
void DirectAccessDevice::scsi_bus_did_change(Bus *, BusState new_state) {
/*
"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;
bus_.set_device_output(scsi_bus_device_id_, bus_state_);
return;
}
switch(phase_) {
case Phase::AwaitingSelection:
if(
(new_state & scsi_id_mask_) &&
((new_state & (Line::SelectTarget | Line::Busy | Line::Input)) == Line::SelectTarget)
) {
printf("Selected\n");
phase_ = Phase::Command;
bus_state_ |= Line::Busy; // Initiate the command phase: request a command byte.
bus_.set_device_output(scsi_bus_device_id_, bus_state_);
} else {
if(!(new_state & scsi_id_mask_)) printf("No ID mask\n");
else printf("Not SEL|~BSY|~IO");
}
break;
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;
printf("Got %02x maybe?\n", new_state & 0xff);
// TODO: is the command phase over?
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;
}
bus_.set_device_output(scsi_bus_device_id_, bus_state_);
break;
uint32_t CommandArguments::address() {
switch(data_.size()) {
default: return 0;
case 6:
return
(uint32_t(data_[1]) << 16) |
(uint32_t(data_[2]) << 8) |
uint32_t(data_[3]);
case 10:
case 12:
return
(uint32_t(data_[1]) << 24) |
(uint32_t(data_[2]) << 16) |
(uint32_t(data_[3]) << 8) |
uint32_t(data_[4]);
}
}
uint16_t CommandArguments::number_of_blocks() {
switch(data_.size()) {
default: return 0;
case 6:
return uint16_t(data_[4]);
case 10:
return uint16_t((data_[7] << 8) | data_[8]);
}
}

View File

@ -12,18 +12,79 @@
#include "SCSI.hpp"
namespace SCSI {
namespace Target {
/*!
Models a SCSI direct access device, ordinarily some sort of
hard drive.
Encapsulates the arguments supplied for a target SCSI command during
the command phase. An instance of TargetCommandArguments will be
supplied to the target whenever a function is called.
*/
class DirectAccessDevice: public Bus::Observer {
class CommandArguments {
public:
CommandArguments(const std::vector<uint8_t> &data);
uint32_t address();
uint16_t number_of_blocks();
private:
const std::vector<uint8_t> &data_;
};
/*!
Executors contain device-specific logic; when the target has completed
the command phase it will call the appropriate method on its executor,
supplying it with the command's arguments.
If you implement a method, you should push a result and return @c true.
Return @c false if you do not implement a method (or, just inherit from
the basic executor below, and don't implement anything you don't support).
*/
struct Executor {
/* Group 0 commands. */
bool test_unit_ready(const CommandArguments &) { return false; }
bool rezero_unit(const CommandArguments &) { return false; }
bool request_sense(const CommandArguments &) { return false; }
bool format_unit(const CommandArguments &) { return false; }
bool seek(const CommandArguments &) { return false; }
bool reserve_unit(const CommandArguments &) { return false; }
bool release_unit(const CommandArguments &) { return false; }
bool read_diagnostic(const CommandArguments &) { return false; }
bool write_diagnostic(const CommandArguments &) { return false; }
bool inquiry(const CommandArguments &) { return false; }
/* Group 0/1 commands. */
bool read(const CommandArguments &) { return false; }
bool write(const CommandArguments &) { return false; }
/* Group 1 commands. */
bool read_capacity(const CommandArguments &) { return false; }
bool write_and_verify(const CommandArguments &) { return false; }
bool verify(const CommandArguments &) { return false; }
bool search_data_equal(const CommandArguments &) { return false; }
bool search_data_high(const CommandArguments &) { return false; }
bool search_data_low(const CommandArguments &) { return false; }
/* Group 5 commands. */
bool set_block_limits(const CommandArguments &) { return false; }
void reset_block_limits(const CommandArguments &) {}
};
/*!
A template for any SCSI target; provides the necessary bus glue to
receive and respond to commands. Specific targets should be implemented
as Executors.
*/
template <typename Executor> class Target: public Bus::Observer {
public:
/*!
Instantiates a direct access device attached to @c bus,
Instantiates a target attached to @c bus,
with SCSI ID @c scsi_id a number in the range 0 to 7.
Received commands will be handed to the Executor to perform.
*/
DirectAccessDevice(Bus &bus, int scsi_id);
Target(Bus &bus, int scsi_id);
Executor executor;
private:
void scsi_bus_did_change(Bus *, BusState new_state) final;
@ -37,8 +98,16 @@ class DirectAccessDevice: public Bus::Observer {
Command
} phase_ = Phase::AwaitingSelection;
BusState bus_state_ = DefaultBusState;
void begin_command(uint8_t first_byte);
std::vector<uint8_t> command_;
size_t command_pointer_ = 0;
bool dispatch_command();
};
#import "TargetImplementation.hpp"
}
}
#endif /* DirectAccessDevice_hpp */

View File

@ -0,0 +1,144 @@
//
// 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) {
/*
"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;
bus_.set_device_output(scsi_bus_device_id_, bus_state_);
return;
}
switch(phase_) {
case Phase::AwaitingSelection:
if(
(new_state & scsi_id_mask_) &&
((new_state & (Line::SelectTarget | Line::Busy | Line::Input)) == Line::SelectTarget)
) {
printf("Selected\n");
phase_ = Phase::Command;
bus_state_ |= Line::Busy; // Initiate the command phase: request a command byte.
bus_.set_device_output(scsi_bus_device_id_, bus_state_);
} else {
if(!(new_state & scsi_id_mask_)) printf("No ID mask\n");
else printf("Not SEL|~BSY|~IO");
}
break;
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;
}
bus_.set_device_output(scsi_bus_device_id_, 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() {
CommandArguments 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);
case G0(0x01): return executor.rezero_unit(arguments);
case G0(0x03): return executor.request_sense(arguments);
case G0(0x04): return executor.format_unit(arguments);
case G0(0x08): return executor.read(arguments);
case G0(0x0a): return executor.write(arguments);
case G0(0x0b): return executor.seek(arguments);
case G0(0x16): return executor.reserve_unit(arguments);
case G0(0x17): return executor.release_unit(arguments);
case G0(0x1c): return executor.read_diagnostic(arguments);
case G0(0x1d): return executor.write_diagnostic(arguments);
case G0(0x12): return executor.inquiry(arguments);
case G1(0x05): return executor.read_capacity(arguments);
case G1(0x08): return executor.read(arguments);
case G1(0x0a): return executor.write(arguments);
case G1(0x0e): return executor.write_and_verify(arguments);
case G1(0x0f): return executor.verify(arguments);
case G1(0x11): return executor.search_data_equal(arguments);
case G1(0x10): return executor.search_data_high(arguments);
case G1(0x12): return executor.search_data_low(arguments);
case G5(0x09): return executor.set_block_limits(arguments);
}
#undef G0
#undef G1
#undef G5
return false;
}

View File

@ -54,9 +54,9 @@ class NCR5380 final: public ClockingHint::Source {
private:
// TEMPORARY. For development expediency, the 5380 owns its own
// SCSI bus and direct access device. These will be moved out.
// SCSI bus and target. These will be moved out.
SCSI::Bus bus_;
SCSI::DirectAccessDevice device_;
SCSI::Target::Target<SCSI::Target::Executor> device_;
const int clock_rate_;
size_t device_id_;

View File

@ -1501,6 +1501,7 @@
4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231720532CC0006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE64626230B939B00C36C50 /* TargetImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetImplementation.hpp; sourceTree = "<group>"; };
4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; };
4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; };
@ -3338,6 +3339,7 @@
4B89BCFB23024BB500EA0782 /* SCSI.hpp */,
4BB8F5252308E5A50015C2A6 /* DirectAccessDevice.cpp */,
4BB8F5262308E5A50015C2A6 /* DirectAccessDevice.hpp */,
4BE64626230B939B00C36C50 /* TargetImplementation.hpp */,
);
path = 5380;
sourceTree = "<group>";