mirror of
https://github.com/TomHarte/CLK.git
synced 2024-08-17 07:29:04 +00:00
403 lines
13 KiB
C++
403 lines
13 KiB
C++
//
|
|
// Target.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 17/08/2019.
|
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#ifndef SCSI_Target_hpp
|
|
#define SCSI_Target_hpp
|
|
|
|
#include "SCSI.hpp"
|
|
#include "../../../Outputs/Log.hpp"
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#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> &command, const std::vector<uint8_t> &received);
|
|
|
|
// For read and write commands.
|
|
struct ReadWrite {
|
|
uint32_t address, number_of_blocks;
|
|
};
|
|
ReadWrite read_write_specs() const;
|
|
|
|
// For inquiry commands.
|
|
size_t allocated_inquiry_bytes() const;
|
|
|
|
// For mode sense commands.
|
|
struct ModeSense {
|
|
bool exclude_block_descriptors = false;
|
|
enum class PageControlValues {
|
|
Current = 0,
|
|
Changeable = 1,
|
|
Default = 2,
|
|
Saved = 3
|
|
} page_control_values = PageControlValues::Current;
|
|
uint8_t page_code;
|
|
uint8_t subpage_code;
|
|
uint16_t allocated_bytes;
|
|
};
|
|
ModeSense mode_sense_specs() const;
|
|
|
|
struct ModeSelect {
|
|
bool content_is_vendor_specific = true;
|
|
bool revert_to_default = false;
|
|
bool save_pages = false;
|
|
uint16_t parameter_list_length = 0;
|
|
};
|
|
ModeSelect mode_select_specs() const;
|
|
|
|
struct ReadBuffer {
|
|
enum class Mode {
|
|
CombinedHeaderAndData = 0,
|
|
VendorSpecific = 1,
|
|
Data = 2,
|
|
Descriptor = 3,
|
|
Reserved = 4
|
|
} mode = Mode::CombinedHeaderAndData;
|
|
uint8_t buffer_id = 0;
|
|
uint32_t buffer_offset = 0, buffer_length = 0;
|
|
};
|
|
ReadBuffer read_buffer_specs() const;
|
|
|
|
const std::vector<uint8_t> &received_data() const {
|
|
return received_;
|
|
}
|
|
|
|
private:
|
|
uint32_t address() const;
|
|
uint16_t number_of_blocks() const;
|
|
const std::vector<uint8_t> &data_;
|
|
const std::vector<uint8_t> &received_;
|
|
};
|
|
|
|
/*!
|
|
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;
|
|
/*!
|
|
Terminates a SCSI command, sending the proper sequence of status and message phases.
|
|
*/
|
|
void terminate_command(Status status) {
|
|
send_status(status, [] (const Target::CommandState &, Target::Responder &responder) {
|
|
responder.send_message(Target::Responder::Message::CommandComplete, [] (const Target::CommandState &, Target::Responder &responder) {
|
|
responder.end_command();
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
/*!
|
|
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 &responder) {
|
|
/* "Returns zero status if addressed unit is powered on and ready. */
|
|
responder.terminate_command(Target::Responder::Status::Good);
|
|
return true;
|
|
}
|
|
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; }
|
|
|
|
/// Mode sense: the default implementation will call into the appropriate
|
|
/// structured getter.
|
|
bool mode_sense(const CommandState &state, Responder &responder) {
|
|
const auto specs = state.mode_sense_specs();
|
|
std::vector<uint8_t> response = {
|
|
specs.page_code,
|
|
uint8_t(specs.allocated_bytes)
|
|
};
|
|
switch(specs.page_code) {
|
|
default:
|
|
printf("Unknown mode sense page code %02x\n", specs.page_code);
|
|
response.resize(specs.allocated_bytes);
|
|
break;
|
|
|
|
case 0x30:
|
|
response.resize(34);
|
|
strcpy(reinterpret_cast<char *>(&response[14]), "APPLE COMPUTER, INC"); // This seems to be required to satisfy the Apple HD SC Utility.
|
|
break;
|
|
}
|
|
|
|
if(specs.allocated_bytes < response.size()) {
|
|
response.resize(specs.allocated_bytes);
|
|
}
|
|
responder.send_data(std::move(response), [] (const Target::CommandState &, Target::Responder &responder) {
|
|
responder.terminate_command(Target::Responder::Status::Good);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mode_select(const CommandState &state, Responder &responder) {
|
|
const auto specs = state.mode_select_specs();
|
|
|
|
responder.receive_data(specs.parameter_list_length, [] (const Target::CommandState &, Target::Responder &responder) {
|
|
// TODO: parse data according to current sense mode.
|
|
responder.terminate_command(Target::Responder::Status::Good);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Inquiry: the default implementation will call the structured version and
|
|
/// package appropriately.
|
|
struct Inquiry {
|
|
enum class DeviceType {
|
|
DirectAccess = 0,
|
|
SequentialAccess = 1,
|
|
Printer = 2,
|
|
Processor = 3,
|
|
WriteOnceMultipleRead = 4,
|
|
ReadOnlyDirectAccess = 5,
|
|
Scanner = 6,
|
|
OpticalMemory = 7,
|
|
MediumChanger = 8,
|
|
Communications = 9,
|
|
} device_type = DeviceType::DirectAccess;
|
|
bool is_removeable = false;
|
|
uint8_t iso_standard = 0, ecma_standard = 0, ansi_standard = 0;
|
|
bool supports_asynchronous_events = false;
|
|
bool supports_terminate_io_process = false;
|
|
bool supports_relative_addressing = false;
|
|
bool supports_synchronous_transfer = true;
|
|
bool supports_linked_commands = false;
|
|
bool supports_command_queing = false;
|
|
bool supports_soft_reset = false;
|
|
char vendor_identifier[9] = "";
|
|
char product_identifier[17] = "";
|
|
char product_revision_level[5] = "";
|
|
|
|
Inquiry(const char *vendor, const char *product, const char *revision) {
|
|
assert(strlen(vendor) <= 8);
|
|
assert(strlen(product) <= 16);
|
|
assert(strlen(revision) <= 4);
|
|
strcpy(vendor_identifier, vendor);
|
|
strcpy(product_identifier, product);
|
|
strcpy(product_revision_level, revision);
|
|
}
|
|
Inquiry() = default;
|
|
};
|
|
Inquiry inquiry_values() {
|
|
return Inquiry();
|
|
}
|
|
bool inquiry(const CommandState &state, Responder &responder) {
|
|
const Inquiry inq = inquiry_values();
|
|
|
|
// Set up the easy fields.
|
|
std::vector<uint8_t> response = {
|
|
uint8_t(inq.device_type),
|
|
uint8_t(inq.is_removeable ? 0x80 : 0x00),
|
|
uint8_t((inq.iso_standard << 5) | (inq.ecma_standard << 3) | (inq.ansi_standard)),
|
|
uint8_t((inq.supports_asynchronous_events ? 0x80 : 0x00) | (inq.supports_terminate_io_process ? 0x40 : 0x00) | 0x02),
|
|
32, /* Additional length: 36 - 4. */
|
|
0x00, /* Reserved. */
|
|
0x00, /* Reserved. */
|
|
uint8_t(
|
|
(inq.supports_relative_addressing ? 0x80 : 0x00) |
|
|
/* b6: supports 32-bit data; b5: supports 16-bit data. */
|
|
(inq.supports_synchronous_transfer ? 0x10 : 0x00) |
|
|
(inq.supports_linked_commands ? 0x08 : 0x00) |
|
|
/* b3: reserved. */
|
|
(inq.supports_command_queing ? 0x02 : 0x00) |
|
|
(inq.supports_soft_reset ? 0x01 : 0x00)
|
|
),
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Space for the vendor ID. */
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Space for the product ID. */
|
|
0x00, 0x00, 0x00, 0x00 /* Space for the revision level. */
|
|
};
|
|
|
|
auto copy_string = [] (uint8_t *destination, const char *source, size_t length) -> void {
|
|
// Determine length of source and copy in as much as possible.
|
|
const auto source_length = std::min(strlen(source), length);
|
|
memcpy(destination, source, source_length);
|
|
|
|
// Fill the rest with spaces.
|
|
memset(&destination[source_length], ' ', length - source_length);
|
|
};
|
|
copy_string(&response[8], inq.vendor_identifier, 8);
|
|
copy_string(&response[16], inq.product_identifier, 16);
|
|
copy_string(&response[32], inq.product_revision_level, 4);
|
|
|
|
// Truncate if requested.
|
|
const auto allocated_bytes = state.allocated_inquiry_bytes();
|
|
if(allocated_bytes < response.size()) {
|
|
response.resize(allocated_bytes);
|
|
}
|
|
|
|
responder.send_data(std::move(response), [] (const Target::CommandState &, Target::Responder &responder) {
|
|
responder.terminate_command(Target::Responder::Status::Good);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
/* 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; }
|
|
bool read_buffer(const CommandState &state, Responder &responder) {
|
|
// Since I have no idea what earthly function READ BUFFER is meant to allow,
|
|
// the default implementation just returns an empty buffer of the requested size.
|
|
const auto specs = state.read_buffer_specs();
|
|
responder.send_data(std::vector<uint8_t>(specs.buffer_length), [] (const Target::CommandState &, Target::Responder &responder) {
|
|
responder.terminate_command(Target::Responder::Status::Good);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
inline Executor *operator->() {
|
|
return &executor_;
|
|
}
|
|
|
|
private:
|
|
Executor executor_;
|
|
|
|
// Bus::Observer.
|
|
void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) 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,
|
|
ReceivingData,
|
|
SendingData,
|
|
SendingStatus,
|
|
SendingMessage
|
|
} phase_ = Phase::AwaitingSelection;
|
|
BusState bus_state_ = DefaultBusState;
|
|
|
|
void set_device_output(BusState state) {
|
|
expected_control_state_ = state & (Line::Control | Line::Input | Line::Message);
|
|
bus_.set_device_output(scsi_bus_device_id_, state);
|
|
}
|
|
BusState expected_control_state_ = DefaultBusState;
|
|
|
|
void begin_command(uint8_t first_byte);
|
|
std::vector<uint8_t> command_;
|
|
Status status_;
|
|
Message message_;
|
|
size_t command_pointer_ = 0;
|
|
bool dispatch_command();
|
|
|
|
std::vector<uint8_t> data_;
|
|
size_t data_pointer_ = 0;
|
|
|
|
continuation next_function_;
|
|
};
|
|
|
|
#include "TargetImplementation.hpp"
|
|
|
|
}
|
|
}
|
|
|
|
#endif /* SCSI_Target_hpp */
|