mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +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 */
 |