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:
parent
e3d9254555
commit
252650808d
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
144
Components/5380/TargetImplementation.hpp
Normal file
144
Components/5380/TargetImplementation.hpp
Normal 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;
|
||||
}
|
@ -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_;
|
||||
|
@ -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>";
|
||||
|
Loading…
Reference in New Issue
Block a user