From 0e0c789b0261c9561a2291541bd58491a5dafd4a Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Sat, 17 Aug 2019 23:43:42 -0400
Subject: [PATCH] Starts attempting to introduce a direct access device.

Without having access to the SCSI-1 standard, a lot of this is guesswork.
---
 Components/5380/DirectAccessDevice.cpp        | 57 +++++++++++++++++++
 Components/5380/DirectAccessDevice.hpp        | 44 ++++++++++++++
 Components/5380/SCSI.cpp                      | 22 ++++---
 Components/5380/SCSI.hpp                      | 10 +++-
 Components/5380/ncr5380.cpp                   | 40 ++++++++-----
 Components/5380/ncr5380.hpp                   |  7 ++-
 .../Clock Signal.xcodeproj/project.pbxproj    |  6 ++
 7 files changed, 164 insertions(+), 22 deletions(-)
 create mode 100644 Components/5380/DirectAccessDevice.cpp
 create mode 100644 Components/5380/DirectAccessDevice.hpp

diff --git a/Components/5380/DirectAccessDevice.cpp b/Components/5380/DirectAccessDevice.cpp
new file mode 100644
index 000000000..6d776836e
--- /dev/null
+++ b/Components/5380/DirectAccessDevice.cpp
@@ -0,0 +1,57 @@
+//
+//  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;
+
+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);
+}
+
+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."
+	*/
+
+	switch(state_) {
+		case State::Inactive:
+			if(
+				(new_state & scsi_id_mask_) &&
+				((new_state & (Line::SelectTarget | Line::Busy | Line::Input)) == Line::SelectTarget)
+			) {
+				state_ = State::Selected;
+				bus_state_ |= Line::Busy | Line::Request;
+				bus_.set_device_output(scsi_bus_device_id_, bus_state_);
+			}
+		break;
+
+		case State::Selected:
+			switch(new_state & (Line::Request | Line::Acknowledge)) {
+				case Line::Request | Line::Acknowledge:
+					bus_state_ &= ~Line::Request;
+					printf("Got %02x maybe?\n", bus_state_ & 0xff);
+				break;
+
+				case Line::Acknowledge:
+				case 0:
+					bus_state_ |= Line::Request;
+				break;
+
+				default: break;
+			}
+			bus_.set_device_output(scsi_bus_device_id_, bus_state_);
+		break;
+	}
+}
diff --git a/Components/5380/DirectAccessDevice.hpp b/Components/5380/DirectAccessDevice.hpp
new file mode 100644
index 000000000..e92d486ce
--- /dev/null
+++ b/Components/5380/DirectAccessDevice.hpp
@@ -0,0 +1,44 @@
+//
+//  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"
+
+namespace SCSI {
+
+/*!
+	Models a SCSI direct access device, ordinarily some sort of
+	hard drive.
+*/
+class DirectAccessDevice: public Bus::Observer {
+	public:
+		/*!
+			Instantiates a direct access device attached to @c bus,
+			with SCSI ID @c scsi_id — a number in the range 0 to 7.
+		*/
+		DirectAccessDevice(Bus &bus, int scsi_id);
+
+	private:
+		void scsi_bus_did_change(Bus *, BusState new_state) final;
+
+		Bus &bus_;
+		const BusState scsi_id_mask_;
+		const size_t scsi_bus_device_id_;
+
+		enum class State {
+			Inactive,
+			Selected
+		} state_ = State::Inactive;
+		BusState bus_state_ = DefaultBusState;
+};
+
+}
+
+#endif /* DirectAccessDevice_hpp */
diff --git a/Components/5380/SCSI.cpp b/Components/5380/SCSI.cpp
index 1ae837b05..a6ba9a0a0 100644
--- a/Components/5380/SCSI.cpp
+++ b/Components/5380/SCSI.cpp
@@ -17,19 +17,27 @@ size_t Bus::add_device() {
 }
 
 void Bus::set_device_output(size_t device, BusState output) {
-	printf("%08x output\n", output);
+	if(device_states_[device] == output) return;
 	device_states_[device] = output;
-	state_is_valid_ = false;
-}
 
-BusState Bus::get_state() {
-	if(!state_is_valid_) return state_;
-
-	state_is_valid_ = true;
+	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);
+}
diff --git a/Components/5380/SCSI.hpp b/Components/5380/SCSI.hpp
index 69d087a6e..9e36fd586 100644
--- a/Components/5380/SCSI.hpp
+++ b/Components/5380/SCSI.hpp
@@ -70,10 +70,18 @@ class 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;
-		bool state_is_valid_ = false;
+		std::vector<Observer *> observers_;
 };
 
 }
diff --git a/Components/5380/ncr5380.cpp b/Components/5380/ncr5380.cpp
index 82fcbe75e..6f40ee9fb 100644
--- a/Components/5380/ncr5380.cpp
+++ b/Components/5380/ncr5380.cpp
@@ -12,7 +12,9 @@
 
 using namespace NCR::NCR5380;
 
-NCR5380::NCR5380(int clock_rate) : clock_rate_(clock_rate) {
+NCR5380::NCR5380(int clock_rate) :
+	device_(bus_, 6),
+	clock_rate_(clock_rate) {
 	device_id_ = bus_.add_device();
 }
 
@@ -28,16 +30,17 @@ void NCR5380::write(int address, uint8_t value) {
 			LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
 			initiator_command_ = value;
 
-			SCSI::BusState mask = SCSI::DefaultBusState;
-			if(value & 0x80) mask |= Line::Reset;
-			test_mode_ = value & 0x40;
+			bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
+			if(value & 0x80) bus_output_ |= Line::Reset;
+			if(value & 0x10) bus_output_ |= Line::Acknowledge;
+			if(value & 0x08) bus_output_ |= Line::Busy;
+			if(value & 0x04) bus_output_ |= Line::SelectTarget;
+			if(value & 0x02) bus_output_ |= Line::Attention;
+
 			/* bit 5 = differential enable if this were a 5381 */
-			if(value & 0x10) mask |= Line::Acknowledge;
-			if(value & 0x08) mask |= Line::Busy;
-			if(value & 0x04) mask |= Line::SelectTarget;
-			if(value & 0x02) mask |= Line::Attention;
+
+			test_mode_ = value & 0x40;
 			assert_data_bus_ = value & 0x01;
-			bus_output_ = (bus_output_ & ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention)) | mask;
 		} break;
 
 		case 2:
@@ -74,9 +77,14 @@ void NCR5380::write(int address, uint8_t value) {
 			}
 		break;
 
-		case 3:
+		case 3: {
 			LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
-		break;
+			bus_output_ &= ~(Line::Request | Line::Message | Line::Control | Line::Input);
+			if(value & 0x08) bus_output_ |= Line::Request;
+			if(value & 0x04) bus_output_ |= Line::Message;
+			if(value & 0x02) bus_output_ |= Line::Control;
+			if(value & 0x01) bus_output_ |= Line::Input;
+		} break;
 
 		case 4:
 			LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
@@ -133,9 +141,15 @@ uint8_t NCR5380::read(int address) {
 			LOG("[SCSI 2] Get mode");
 		return mode_;
 
-		case 3:
+		case 3: {
 			LOG("[SCSI 3] Get target command");
-		return 0xff;
+			const auto bus_state = bus_.get_state();
+			return
+				((bus_state & SCSI::Line::Request)			? 0x08 : 0x00) |
+				((bus_state & SCSI::Line::Message)			? 0x04 : 0x00) |
+				((bus_state & SCSI::Line::Control)			? 0x02 : 0x00) |
+				((bus_state & SCSI::Line::Input)			? 0x01 : 0x00);
+		}
 
 		case 4: {
 			LOG("[SCSI 4] Get current bus state");
diff --git a/Components/5380/ncr5380.hpp b/Components/5380/ncr5380.hpp
index 57ce6510a..b9b551a7b 100644
--- a/Components/5380/ncr5380.hpp
+++ b/Components/5380/ncr5380.hpp
@@ -12,6 +12,7 @@
 #include <cstdint>
 
 #include "SCSI.hpp"
+#include "DirectAccessDevice.hpp"
 #include "../../ClockReceiver/ClockReceiver.hpp"
 #include "../../ClockReceiver/ClockingHintSource.hpp"
 
@@ -52,8 +53,12 @@ class NCR5380 final: public ClockingHint::Source {
 		ClockingHint::Preference preferred_clocking() final;
 
 	private:
-		const int clock_rate_;
+		// TEMPORARY. For development expediency, the 5380 owns its own
+		// SCSI bus and direct access device. These will be moved out.
 		SCSI::Bus bus_;
+		SCSI::DirectAccessDevice device_;
+
+		const int clock_rate_;
 		size_t device_id_;
 
 		SCSI::BusState bus_output_ = SCSI::DefaultBusState;
diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj
index e95f436e2..58c2dc5f7 100644
--- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
+++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
@@ -619,6 +619,7 @@
 		4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */; };
 		4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
 		4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
+		4BB8F5272308E5A50015C2A6 /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB8F5252308E5A50015C2A6 /* DirectAccessDevice.cpp */; };
 		4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
 		4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
 		4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; };
@@ -1407,6 +1408,8 @@
 		4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock_SignalUITests.swift; sourceTree = "<group>"; };
 		4BB73EC31B587A5100552FC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = "<group>"; };
+		4BB8F5252308E5A50015C2A6 /* DirectAccessDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DirectAccessDevice.cpp; sourceTree = "<group>"; };
+		4BB8F5262308E5A50015C2A6 /* DirectAccessDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DirectAccessDevice.hpp; sourceTree = "<group>"; };
 		4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; };
 		4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMediaTarget.hpp; sourceTree = "<group>"; };
 		4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
@@ -3333,6 +3336,8 @@
 				4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */,
 				4B89BCFA23024BB500EA0782 /* SCSI.cpp */,
 				4B89BCFB23024BB500EA0782 /* SCSI.hpp */,
+				4BB8F5252308E5A50015C2A6 /* DirectAccessDevice.cpp */,
+				4BB8F5262308E5A50015C2A6 /* DirectAccessDevice.hpp */,
 			);
 			path = 5380;
 			sourceTree = "<group>";
@@ -4143,6 +4148,7 @@
 				4BCE0060227D39AB000CA200 /* Video.cpp in Sources */,
 				4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
 				4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
+				4BB8F5272308E5A50015C2A6 /* DirectAccessDevice.cpp in Sources */,
 				4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,
 				4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
 				4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */,