1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-05 08:26:28 +00:00

Starts pushing towards figuring out a proper infrastructure for mass storage.

This commit is contained in:
Thomas Harte
2019-08-21 23:22:58 -04:00
parent 8e274ec5d0
commit faec516a2c
10 changed files with 117 additions and 20 deletions

View File

@@ -1,41 +0,0 @@
//
// DirectAccessDevice.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "DirectAccessDevice.hpp"
using namespace SCSI::Target;
CommandState::CommandState(const std::vector<uint8_t> &data) : data_(data) {}
uint32_t CommandState::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 CommandState::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

@@ -1,172 +0,0 @@
//
// DirectAccessDevice.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef DirectAccessDevice_hpp
#define DirectAccessDevice_hpp
#include "SCSI.hpp"
#include <functional>
namespace SCSI {
namespace Target {
/*!
Encapsulates the arguments supplied for a target SCSI command during
the command phase plus any other data read since then.
*/
class CommandState {
public:
CommandState(const std::vector<uint8_t> &data);
uint32_t address();
uint16_t number_of_blocks();
private:
const std::vector<uint8_t> &data_;
};
/*!
A Responder is supplied both (i) to the initial call-in to an Executor; and
(ii) to all continuations provided by that Executor. It allows the next
set of bus interactions to be dictated.
*/
struct Responder {
using continuation = std::function<void(const CommandState &, Responder &)>;
enum class Status {
Good = 0x00,
CheckCondition = 0x02,
ConditionMet = 0x04,
Busy = 0x08,
Intermediate = 0x10,
IntermediateConditionMet = 0x14,
ReservationConflict = 0x18,
CommandTerminated = 0x22,
TaskSetFull = 0x28,
ACAActive = 0x30,
TaskAborted = 0x40
};
enum class Message {
CommandComplete = 0x00
};
/*!
Causes the SCSI device to send @c data to the initiator and
call @c next when done.
*/
virtual void send_data(std::vector<uint8_t> &&data, continuation next) = 0;
/*!
Causes the SCSI device to receive @c length bytes from the initiator and
call @c next when done. The bytes will be accessible via the CommandInput object.
*/
virtual void receive_data(size_t length, continuation next) = 0;
/*!
Communicates the supplied status to the initiator.
*/
virtual void send_status(Status, continuation next) = 0;
/*!
Communicates the supplied message to the initiator.
*/
virtual void send_message(Message, continuation next) = 0;
/*!
Ends the SCSI command.
*/
virtual void end_command() = 0;
};
/*!
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 CommandState &, Responder &) { return false; }
bool rezero_unit(const CommandState &, Responder &) { return false; }
bool request_sense(const CommandState &, Responder &) { return false; }
bool format_unit(const CommandState &, Responder &) { return false; }
bool seek(const CommandState &, Responder &) { return false; }
bool reserve_unit(const CommandState &, Responder &) { return false; }
bool release_unit(const CommandState &, Responder &) { return false; }
bool read_diagnostic(const CommandState &, Responder &) { return false; }
bool write_diagnostic(const CommandState &, Responder &) { return false; }
bool inquiry(const CommandState &, Responder &) { return false; }
/* Group 0/1 commands. */
bool read(const CommandState &, Responder &) { return false; }
bool write(const CommandState &, Responder &) { return false; }
/* Group 1 commands. */
bool read_capacity(const CommandState &, Responder &) { return false; }
bool write_and_verify(const CommandState &, Responder &) { return false; }
bool verify(const CommandState &, Responder &) { return false; }
bool search_data_equal(const CommandState &, Responder &) { return false; }
bool search_data_high(const CommandState &, Responder &) { return false; }
bool search_data_low(const CommandState &, Responder &) { return false; }
/* Group 5 commands. */
bool set_block_limits(const CommandState &, Responder &) { return false; }
};
/*!
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 Responder {
public:
/*!
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.
*/
Target(Bus &bus, int scsi_id);
Executor executor;
private:
// Bus::Observer.
void scsi_bus_did_change(Bus *, BusState new_state) final;
// Responder
void send_data(std::vector<uint8_t> &&data, continuation next) final;
void receive_data(size_t length, continuation next) final;
void send_status(Status, continuation next) final;
void send_message(Message, continuation next) final;
void end_command() final;
// Instance storage.
Bus &bus_;
const BusState scsi_id_mask_;
const size_t scsi_bus_device_id_;
enum class Phase {
AwaitingSelection,
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

@@ -1,43 +0,0 @@
//
// SCSI.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "SCSI.hpp"
using namespace SCSI;
size_t Bus::add_device() {
const auto slot = device_states_.size();
device_states_.push_back(DefaultBusState);
return slot;
}
void Bus::set_device_output(size_t device, BusState output) {
if(device_states_[device] == output) return;
device_states_[device] = output;
const auto previous_state = state_;
state_ = DefaultBusState;
for(auto state: device_states_) {
state_ |= state;
}
if(state_ == previous_state) return;
printf("SCSI bus: %08x\n", state_);
for(auto &observer: observers_) {
observer->scsi_bus_did_change(this, state_);
}
}
BusState Bus::get_state() {
return state_;
}
void Bus::add_observer(Observer *observer) {
observers_.push_back(observer);
}

View File

@@ -1,89 +0,0 @@
//
// SCSI.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef SCSI_hpp
#define SCSI_hpp
#include <limits>
#include <vector>
namespace SCSI {
typedef int BusState;
static const BusState DefaultBusState = 0;
/*!
SCSI bus state is encoded entirely within an int.
Bits correlate mostly but not exactly to the real SCSI bus.
TODO: validate levels below. The bus uses open collector logic,
so active low needs to be respected.
*/
enum Line: BusState {
/// Provides the value currently on the data lines.
Data = 0xff,
/// Parity of the data lines.
Parity = 1 << 8,
/// Set if the SEL line is currently selecting a target.
/// Reset if it is selecting an initiator.
SelectTarget = 1 << 9,
/// Set to indicate an attention condition. Reset otherwise.
Attention = 1 << 10,
/// Set if control is on the bus. Reset if data is on the bus.
Control = 1 << 11,
/// Set if the bus is busy. Reset otherwise.
Busy = 1 << 12,
/// Set if acknowledging a data transfer request. Reset otherwise.
Acknowledge = 1 << 13,
/// Set if a bus reset is being requested. Reset otherwise.
Reset = 1 << 14,
/// Set if data is currently an input to the initiator. Reset if it is an output.
Input = 1 << 15,
/// Set during the message phase. Reset otherwise.
Message = 1 << 16,
/// Set if requesting a data transfer. Reset otherwise.
Request = 1 << 17,
};
class Bus {
public:
/*!
Adds a device to the bus, returning the index it should use
to refer to itself in subsequent calls to set_device_output.
*/
size_t add_device();
/*!
Sets the current output for @c device.
*/
void set_device_output(size_t device, BusState output);
/*!
@returns the current state of the bus.
*/
BusState get_state();
struct Observer {
virtual void scsi_bus_did_change(Bus *, BusState new_state) = 0;
};
/*!
Adds an observer.
*/
void add_observer(Observer *);
private:
std::vector<BusState> device_states_;
BusState state_ = DefaultBusState;
std::vector<Observer *> observers_;
};
}
#endif /* SCSI_hpp */

View File

@@ -1,150 +0,0 @@
//
// 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() {
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) {}
template <typename Executor> void Target<Executor>::receive_data(size_t length, continuation next) {}
template <typename Executor> void Target<Executor>::send_status(Status, continuation next) {}
template <typename Executor> void Target<Executor>::send_message(Message, continuation next) {}
template <typename Executor> void Target<Executor>::end_command() {}

View File

@@ -11,8 +11,8 @@
#include <cstdint>
#include "SCSI.hpp"
#include "DirectAccessDevice.hpp"
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
#include "../../Storage/MassStorage/SCSI/Target.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"