From 2189e264c35084548c19fdeff886a659b3ddfc68 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Tue, 16 Apr 2019 02:30:40 +0200 Subject: [PATCH] Initial stab at MSCP implementation. Strives to be MSCP compliant but is not an emulation of the UDA50 controller. Currently works acceptably with RT-11, does not currently boot. Many holes in implementation. --- 10.02_devices/2_src/mscp_server.cpp | 721 +++++++++++++++++++++ 10.02_devices/2_src/mscp_server.hpp | 172 +++++ 10.02_devices/2_src/uda.cpp | 890 ++++++++++++++++++++++++++ 10.02_devices/2_src/uda.hpp | 162 +++++ 10.03_app_demo/2_src/makefile | 8 + 10.03_app_demo/2_src/menu_devices.cpp | 10 + 6 files changed, 1963 insertions(+) create mode 100644 10.02_devices/2_src/mscp_server.cpp create mode 100644 10.02_devices/2_src/mscp_server.hpp create mode 100644 10.02_devices/2_src/uda.cpp create mode 100644 10.02_devices/2_src/uda.hpp diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp new file mode 100644 index 0000000..3ed4fb2 --- /dev/null +++ b/10.02_devices/2_src/mscp_server.cpp @@ -0,0 +1,721 @@ + +#include +#include +#include + +using namespace std; + +#include "logger.hpp" +#include "utils.hpp" + +#include "uda.hpp" +#include "mscp_server.hpp" + +void* polling_worker( + void *context) +{ + mscp_server* server = reinterpret_cast(context); + + server->Poll(); + + return nullptr; +} + + +mscp_server::mscp_server( + uda_c *port) : + device_c(), + _hostTimeout(0), + _controllerFlags(0), + _abort_polling(false), + _pollState(PollingState::Wait), + polling_cond(PTHREAD_COND_INITIALIZER), + polling_mutex(PTHREAD_MUTEX_INITIALIZER), + _unitOnline(false), + _credits(INIT_CREDITS) +{ + // Alias the port pointer. We do not own the port, merely reference it. + _port = port; + + _diskBuffer.reset(new uint8_t[_diskBufferSize + 512]); // 16mb in-memory disk data + // + 1 block for write protect flag + + memset(reinterpret_cast(_diskBuffer.get()), 0x0, _diskBufferSize + 512); + + StartPollingThread(); +} + + +mscp_server::~mscp_server() +{ + AbortPollingThread(); +} + +void +mscp_server::StartPollingThread(void) +{ + _abort_polling = false; + _pollState = PollingState::Wait; + + // + // Initialize the polling thread and start it. + // It will wait to be woken to do actual work. + // + pthread_attr_t attribs; + pthread_attr_init(&attribs); + + int status = pthread_create( + &polling_pthread, + &attribs, + &polling_worker, + reinterpret_cast(this)); + + if (status != 0) + { + FATAL("Failed to start mscp server thread. Status 0x%x", status); + } + + DEBUG("Polling thread created."); +} + +void +mscp_server::AbortPollingThread(void) +{ + pthread_mutex_lock(&polling_mutex); + _abort_polling = true; + _pollState = PollingState::Wait; + pthread_cond_signal(&polling_cond); + pthread_mutex_unlock(&polling_mutex); + + pthread_cancel(polling_pthread); + + uint32_t status = pthread_join(polling_pthread, NULL); + + if (status != 0) + { + FATAL("Failed to join polling thread, status 0x%x", status); + } + + DEBUG("Polling thread aborted."); +} + +void +mscp_server::Poll(void) +{ + timeout_c timer; + + while(!_abort_polling) + { + // + // Wait to be awoken, then pull commands from the command ring + // + DEBUG("Sleeping until awoken."); + pthread_mutex_lock(&polling_mutex); + while (_pollState == PollingState::Wait) + { + pthread_cond_wait( + &polling_cond, + &polling_mutex); + } + + // Shouldn't happen but if it does we just return to the top. + if (_pollState == PollingState::InitRun) + { + _pollState = PollingState::Run; + } + + pthread_mutex_unlock(&polling_mutex); + + DEBUG("The sleeper awakes."); + + if (_abort_polling) + { + break; + } + + // + // Pull commands from the ring until the ring is empty, at which + // point we sleep until awoken again. + // + while(!_abort_polling && _pollState == PollingState::Run) + { + shared_ptr message(_port->GetNextCommand()); + + if (nullptr == message) + { + DEBUG("Empty command ring; sleeping."); + break; + } + + DEBUG("Message received."); + + // + // Handle the message. We dispatch on opcodes to the + // appropriate methods. These methods modify the message + // object in place; this message object is then posted back + // to the response ring. + // + ControlMessageHeader* header = + reinterpret_cast(message->Message); + + uint16_t *cmdbuf = reinterpret_cast(message->Message); + + DEBUG("Message opcode 0x%x rsvd 0x%x mod 0x%x", + header->Word3.Command.Opcode, + header->Word3.Command.Reserved, + header->Word3.Command.Modifiers); + + /* + for(int i=0;i<8;i++) + { + INFO("o%o", cmdbuf[i]); + } + */ + + uint32_t cmdStatus = 0; + + switch (header->Word3.Command.Opcode) + { + case Opcodes::GET_UNIT_STATUS: + cmdStatus = GetUnitStatus(message, header->UnitNumber, header->Word3.Command.Modifiers); + break; + + case Opcodes::ONLINE: + cmdStatus = Online(message, header->UnitNumber, header->Word3.Command.Modifiers); + break; + + case Opcodes::SET_CONTROLLER_CHARACTERISTICS: + cmdStatus = SetControllerCharacteristics(message); + break; + + case Opcodes::SET_UNIT_CHARACTERISTICS: + cmdStatus = SetUnitCharacteristics(message, header->UnitNumber, header->Word3.Command.Modifiers); + break; + + case Opcodes::READ: + cmdStatus = Read(message, header->UnitNumber, header->Word3.Command.Modifiers); + break; + + case Opcodes::WRITE: + cmdStatus = Write(message, header->UnitNumber, header->Word3.Command.Modifiers); + break; + + default: + FATAL("Unimplemented MSCP command 0x%x", header->Word3.Command.Opcode); + break; + } + + DEBUG("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); + + // + // Set the endcode and status bits + // + header->Word3.End.Status = GET_STATUS(cmdStatus); + header->Word3.End.Flags = GET_FLAGS(cmdStatus); + + // Set the End code properly -- for an Invalid Command response + // this is just the End code, for all others it's the End code + // or'd with the opcode. + if ((GET_STATUS(cmdStatus) & 0x1f) == Status::INVALID_COMMAND) + { + // Just the END code, no opcode + header->Word3.End.Endcode = Endcodes::END; + } + else + { + header->Word3.End.Endcode |= Endcodes::END; + } + + // + // TODO: credits, etc. + // + if (message->Word1.Info.MessageType == MessageTypes::Sequential && + header->Word3.End.Endcode & Endcodes::END) + { + // + // We steal the hack from simh: + // The controller gives all of its credits to the host, + // thereafter it supplies one credit for every response + // packet sent. + // + // Max 14 credits, also C++ is flaming garbage, thanks for replacing "min" + // with something so incredibly annoying to use. + // + uint32_t grantedCredits = min(_credits, static_cast(MAX_CREDITS)); + _credits -= grantedCredits; + message->Word1.Info.Credits = grantedCredits + 1; + } + + // + // Post the response to the port's response ring. + // + // TODO: is the retry approach appropriate or necessary? + for (int retry=0;retry<10;retry++) + { + if(_port->PostResponse(message)) + { + break; + } + timer.wait_us(200); + } + + // Hack: give interrupts time to settle before doing another transfer. + timer.wait_us(250); + + // + // Go around and pick up the next one. + // + } + + DEBUG("MSCP Polling thread going back to sleep."); + + pthread_mutex_lock(&polling_mutex); + if (_pollState == PollingState::InitRestart) + { + // Signal the Reset call that we're done so it can return + // and release the Host. + _pollState = PollingState::Wait; + pthread_cond_signal(&polling_cond); + } + else if (_pollState == PollingState::InitRun) + { + _pollState = PollingState::Run; + } + else + { + _pollState = PollingState::Wait; + } + pthread_mutex_unlock(&polling_mutex); + + } + DEBUG("MSCP Polling thread exiting."); +} + +uint32_t +mscp_server::GetUnitStatus( + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers) +{ + #pragma pack(push,1) + struct GetUnitStatusResponseParameters + { + uint16_t UnitFlags; + uint16_t MultiUnitCode; + uint32_t Reserved0; + uint64_t UnitIdentifier; + uint32_t MediaTypeIdentifier; + uint16_t Reserved1; + uint16_t ShadowUnit; + uint16_t GroupSize; + uint16_t TrackSize; + uint16_t Reserved2; + uint16_t CylinderSize; + uint32_t RCTStuff; + }; + #pragma pack(pop) + + if (unitNumber != 0) + { + return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum + } + + INFO("gusrp size %d", sizeof(GetUnitStatusResponseParameters)); + + // Adjust message length for response + message->MessageLength = sizeof(GetUnitStatusResponseParameters) + + HEADER_SIZE; + + GetUnitStatusResponseParameters* params = + reinterpret_cast( + GetParameterPointer(message)); + + params->UnitFlags = 0; // TODO: 0 for now, which is sane. + params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. + params->UnitIdentifier = UNIT_ID; // Unit #0 always for now + params->MediaTypeIdentifier = MEDIA_ID_RA80; // RA80 always for now + params->ShadowUnit = unitNumber; // Always equal to unit number + + // + // For group, and cylinder size we return 0 -- this is appropriate for the + // underlying storage (disk image on flash) since there are no physical tracks + // or cylinders to speak of (no seek times, etc.) + // + params->TrackSize = 1; // one block per track, per aa-l619a-tk. + params->GroupSize = 0; + params->CylinderSize = 0; + + // + // Since we do no bad block replacement (no bad blocks possible in a disk image file) + // the RCT size is one block as required for the volume write protect information. + // There are no replacement blocks, and no duplicate copies of + // the RCT are present. + // + params->RCTStuff = 0x01000001; + + if (_unitOnline) + { + return STATUS(Status::SUCCESS, 0); + } + else + { + return STATUS(Status::UNIT_AVAILABLE, 0); + } +} + +uint32_t +mscp_server::Online( + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers) +{ + #pragma pack(push,1) + struct OnlineParameters + { + uint16_t UnitFlags alignas(2); + uint16_t Reserved0 alignas(2); + uint32_t Reserved1; + uint32_t Reserved2; + uint32_t Reserved3; + uint32_t DeviceParameters; + uint32_t Reserved4; + }; + #pragma pack(pop) + + // + // TODO: Right now, ignoring all incoming parameters. + // With the exception of write-protection none of them really + // apply. + // We still need to flag errors if someone tries to set + // host-settable flags we can't support. + // + + // TODO: "The ONLINE command performs a SET UNIT CHARACTERISTICS + // operation after bringing a unit 'Unit-Online'" + // This code could be refactored w/th S_U_C handler. + // + #pragma pack(push,1) + struct OnlineResponseParameters + { + uint16_t UnitFlags alignas(2); + uint16_t MultiUnitCode alignas(2); + uint32_t Reserved0; + uint64_t UnitIdentifier; + uint32_t MediaTypeIdentifier; + uint32_t Reserved1; + uint32_t UnitSize; + uint32_t VolumeSerialNumber; + }; + #pragma pack(pop) + + if (unitNumber != 0) + { + return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- move to enum + } + + _unitOnline = true; + + // Adjust message length for response + message->MessageLength = sizeof(OnlineResponseParameters) + + HEADER_SIZE; + + OnlineResponseParameters* params = + reinterpret_cast( + GetParameterPointer(message)); + + params->UnitFlags = 0; // TODO: 0 for now, which is sane. + params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. + params->UnitIdentifier = UNIT_ID; // Unit #0 always for now + params->MediaTypeIdentifier = MEDIA_ID_RA80; // RA80 always for now + params->UnitSize = _diskBufferSize / 512; + params->VolumeSerialNumber = 0; // We report no serial + + return STATUS(Status::SUCCESS, 0); // TODO: subcode "Already Online" +} + +uint32_t +mscp_server::SetControllerCharacteristics( + shared_ptr message) +{ + #pragma pack(push,1) + struct SetControllerCharacteristicsParameters + { + uint16_t ControllerFlags; + uint16_t MSCPVersion; + uint16_t Reserved; + uint16_t HostTimeout; + uint64_t TimeAndDate; + }; + #pragma pack(pop) + + SetControllerCharacteristicsParameters* params = + reinterpret_cast( + GetParameterPointer(message)); + + // + // Check the version, if non-zero we must return an Invalid Command + // end message. + // + if (params->MSCPVersion != 0) + { + return STATUS(Status::INVALID_COMMAND, 0); // TODO: set sub-status + } + else + { + _hostTimeout = params->HostTimeout; + _controllerFlags = params->ControllerFlags; + + // At this time we ignore the time and date entirely. + + // Prepare the response message + params->ControllerFlags = _controllerFlags & 0xfe; // Mask off 576 byte sectors bit. + // it's read-only and we're a 512 + // byte sector shop here. + params->HostTimeout = 0xff; // Controller timeout: return the max value. + params->TimeAndDate = _port->GetControllerIdentifier(); // Controller ID + + return STATUS(Status::SUCCESS, 0); + } + +} + +uint32_t +mscp_server::SetUnitCharacteristics( + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers) +{ + #pragma pack(push,1) + struct SetUnitCharacteristicsParameters + { + uint16_t UnitFlags; + uint16_t Reserved0; + uint32_t Reserved1; + uint64_t Reserved2; + uint32_t DeviceDependent; + uint16_t Reserved3; + uint16_t Reserved4; + }; + #pragma pack(pop) + + // TODO: handle Set Write Protect modifier + + // Check unit + if (unitNumber != 0) + { + return STATUS(Status::UNIT_OFFLINE, 0); + } + + // TODO: mostly same as Online command: should share logic. + #pragma pack(push,1) + struct SetUnitCharacteristicsResponseParameters + { + uint16_t UnitFlags; + uint16_t MultiUnitCode; + uint32_t Reserved0; + uint64_t UnitIdentifier; + uint32_t MediaTypeIdentifier; + uint32_t Reserved1; + uint16_t ShadowUnit; + uint32_t UnitSize; + uint32_t VolumeSerialNumber; + }; + #pragma pack(pop) + + // Adjust message length for response + message->MessageLength = sizeof(SetUnitCharacteristicsResponseParameters) + + HEADER_SIZE; + + SetUnitCharacteristicsResponseParameters* params = + reinterpret_cast( + GetParameterPointer(message)); + + params->UnitFlags = 0; // TODO: 0 for now, which is sane. + params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. + params->UnitIdentifier = UNIT_ID; // Unit #0 always for now + params->MediaTypeIdentifier = MEDIA_ID_RA80; // RA80 always for now + params->UnitSize = _diskBufferSize / 512; + params->VolumeSerialNumber = 0; // We report no serial + + return STATUS(Status::SUCCESS, 0); +} + + +uint32_t +mscp_server::Read( + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers) +{ + #pragma pack(push,1) + struct ReadParameters + { + uint32_t ByteCount; + uint32_t BufferPhysicalAddress; // upper 8 bits are channel address for VAXen + uint32_t Unused0; + uint32_t Unused1; + uint32_t LBN; + }; + #pragma pack(pop) + + ReadParameters* params = + reinterpret_cast(GetParameterPointer(message)); + + INFO("MSCP READ unit %d pa o%o count %d lbn %d", + unitNumber, + params->BufferPhysicalAddress & 0x00ffffff, + params->ByteCount, + params->LBN); + + // Check unit + if (unitNumber != 0) + { + return STATUS(Status::UNIT_OFFLINE, 0); + } + + // TODO: Need to rectify reads/writes to RCT area more cleanly + // and enforce block size of 512 for RCT area. + + // Check LBN and byte count + if (params->LBN >= (_diskBufferSize + 512) / 512) + { + return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0); // TODO: set sub-code + } + + if (params->ByteCount > (((_diskBufferSize + 512) / 512) - params->LBN) * 512) + { + return STATUS(Status::INVALID_COMMAND + (0xc << 8), 0); // TODO: as above + } + + // + // OK: do the transfer to memory + // + _port->DMAWrite( + params->BufferPhysicalAddress & 0x00ffffff, + params->ByteCount, + _diskBuffer.get() + params->LBN * 512); + + + // Set parameters for response. + // We leave ByteCount as is (for now anyway) + // And set First Bad Block to 0. (This is unnecessary since we're + // not reporting a bad block, but we're doing it for completeness.) + params->LBN = 0; + + return STATUS(Status::SUCCESS,0); +} + +uint32_t +mscp_server::Write( + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers) +{ + #pragma pack(push,1) + struct WriteParameters + { + uint32_t ByteCount; + uint32_t BufferPhysicalAddress; // upper 8 bits are channel address for VAXen + uint32_t Unused0; + uint32_t Unused1; + uint32_t LBN; + }; + #pragma pack(pop) + + // TODO: Factor this code out (shared w/Read) + WriteParameters* params = + reinterpret_cast(GetParameterPointer(message)); + + INFO("MSCP WRITE unit %d pa o%o count %d lbn %d", + unitNumber, + params->BufferPhysicalAddress & 0x00ffffff, + params->ByteCount, + params->LBN); + + // Check unit + if (unitNumber != 0) + { + return STATUS(Status::UNIT_OFFLINE, 0); + } + + // Check LBN + if (params->LBN > (_diskBufferSize + 512) / 512) + { + return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0); // TODO: set sub-code + } + + // Check byte count + if (params->ByteCount > (((_diskBufferSize + 512) / 512) - params->LBN) * 512) + { + return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0); // TODO: as above + } + + // + // OK: do the transfer from the PDP-11 to a buffer + // + unique_ptr buffer(_port->DMARead( + params->BufferPhysicalAddress & 0x00ffffff, + params->ByteCount)); + + // Copy the buffer to our in-memory disk buffer + memcpy(_diskBuffer.get() + params->LBN * 512, buffer.get(), params->ByteCount); + + // Set parameters for response. + // We leave ByteCount as is (for now anyway) + // And set First Bad Block to 0. (This is unnecessary since we're + // not reporting a bad block, but we're doing it for completeness.) + params->LBN = 0; + + return STATUS(Status::SUCCESS,0); +} + +uint8_t* +mscp_server::GetParameterPointer( + shared_ptr message) +{ + return reinterpret_cast(message->Message)->Parameters; +} + +void +mscp_server::Reset(void) +{ + DEBUG("Aborting polling due to reset."); + + pthread_mutex_lock(&polling_mutex); + if (_pollState != PollingState::Wait) + { + _pollState = PollingState::InitRestart; + + while (_pollState != PollingState::Wait) + { + pthread_cond_wait( + &polling_cond, + &polling_mutex); + } + } + pthread_mutex_unlock(&polling_mutex); + + _credits = INIT_CREDITS; +} + + +void +mscp_server::InitPolling(void) +{ + // + // Wake the polling thread if not already awoken. + // + pthread_mutex_lock(&polling_mutex); + if (true) //!_continue_polling) + { + DEBUG("Waking polling thread."); + _pollState = PollingState::InitRun; + pthread_cond_signal(&polling_cond); + } + else + { + DEBUG("Polling already active."); + } + pthread_mutex_unlock(&polling_mutex); +} + diff --git a/10.02_devices/2_src/mscp_server.hpp b/10.02_devices/2_src/mscp_server.hpp new file mode 100644 index 0000000..f5d4b5b --- /dev/null +++ b/10.02_devices/2_src/mscp_server.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include +#include + +class uda_c; +class Message; + +// Builds a uint32_t containing the status, flags, and endcode for a response message, +// used to simplify returning the appropriate status bits from command functions. +#define STATUS(status, flags) ((flags) << 8) | ((status) << 16) + +#define GET_STATUS(status) (((status) >> 16) & 0xffff) +#define GET_FLAGS(status) (((status) >> 8) & 0xff) + +#define MAX_CREDITS 14 +#define INIT_CREDITS 32 + +#define MEDIA_ID_RA80 0x25641050 +#define UNIT_ID 0x1234567802020000 + +// TODO: Dependent on little-endian hardware +// +// ControlMessageHeader encapsulates the standard MSCP control +// message header: a 12-byte header followed by up to 36 bytes of +// parameters. +// +#pragma pack(push,1) +struct ControlMessageHeader +{ + uint32_t ReferenceNumber; + uint16_t Reserved; + uint16_t UnitNumber; + + union + { + struct + { + uint8_t Opcode : 8; + uint8_t Reserved : 8; + uint16_t Modifiers : 16; + } Command; + + struct + { + uint8_t Endcode : 8; + uint8_t Flags : 8; + uint16_t Status : 16; + } End; + } Word3; + + uint8_t Parameters[36]; +}; +#pragma pack(pop) + +// Size in bytes of the non-parameter portion of a ControlMessageHeader +#define HEADER_SIZE 12 + +enum Opcodes +{ + ABORT = 0x1, + ACCESS = 0x10, + AVAILABLE = 0x8, + COMPARE_HOST_DATA = 0x20, + DETERMINE_ACCESS_PATHS = 0x0b, + ERASE = 0x12, + GET_COMMAND_STATUS = 0x2, + GET_UNIT_STATUS = 0x3, + ONLINE = 0x9, + READ = 0x21, + REPLACE = 0x14, + SET_CONTROLLER_CHARACTERISTICS = 0x4, + SET_UNIT_CHARACTERISTICS = 0xa, + WRITE = 0x22 +}; + +enum Endcodes +{ + END = 0x80, + SERIOUS_EXCEPTION = 0x7, +}; + +enum Status +{ + SUCCESS = 0x0, + INVALID_COMMAND = 0x1, + COMMAND_ABORTED = 0x2, + UNIT_OFFLINE = 0x3, + UNIT_AVAILABLE = 0x4, + MEDIA_FORMAT_ERROR = 0x5, + WRITE_PROTECTED = 0x6, + COMPARE_ERROR = 0x7, + DATA_ERROR = 0x8, + HOST_BUFFER_ACCESS_ERROR = 0x9, + CONTROLLER_ERROR = 0xa, + DRIVE_ERROR = 0xb, + DIAGNOSTIC_MESSAGE = 0x1f +}; + +enum MessageTypes +{ + Sequential = 0, + Datagram = 1, + CreditNotification = 2, + Maintenance = 15, +}; + +// +// This inherits from device_c solely so the logging macros work. +// +class mscp_server : public device_c +{ +public: + mscp_server(uda_c *port); + ~mscp_server(); + +public: + void Reset(void); + void InitPolling(void); + void Poll(void); + +public: + void on_power_changed(void) override {} + void on_init_changed(void) override {} + void worker(void) override {} + bool on_param_changed(parameter_c *param) override { return true; } + +private: + uint32_t GetUnitStatus(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t Online(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t SetControllerCharacteristics(std::shared_ptr message); + uint32_t SetUnitCharacteristics(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t Read(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t Write(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + + uint8_t* GetParameterPointer(std::shared_ptr message); + +private: + void StartPollingThread(void); + void AbortPollingThread(void); + +private: + uint32_t _hostTimeout; + uint32_t _controllerFlags; + +private: + uda_c* _port; + + enum PollingState + { + Wait = 0, + Run, + InitRestart, + InitRun + }; + + bool _abort_polling; + PollingState _pollState; + + pthread_t polling_pthread; + pthread_cond_t polling_cond; + pthread_mutex_t polling_mutex; + + // Temporary: in-memory buffer for disk access + std::unique_ptr _diskBuffer; + uint32_t _diskBufferSize = 237212 * 512; // RA80 size + bool _unitOnline; + + // Credits available + uint32_t _credits; +}; + diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp new file mode 100644 index 0000000..f3adec5 --- /dev/null +++ b/10.02_devices/2_src/uda.cpp @@ -0,0 +1,890 @@ +#include +#include + +#include "unibus.h" +#include "unibusadapter.hpp" +#include "unibusdevice.hpp" +#include "storagecontroller.hpp" +#include "uda.hpp" + +uda_c::uda_c() : + storagecontroller_c(), + _sa(0), + _server(nullptr), + _ringBase(0), + _commandRingLength(0), + _responseRingLength(0), + _commandRingPointer(0), + _responseRingPointer(0), + _interruptVector(0), + _interruptEnable(false), + _initStep(InitializationStep::Uninitialized), + _next_step(false) +{ + name.value = "uda"; + type_name.value = "UDA50"; + log_label = "uda"; + + default_base_addr = 0772150; + default_intr_vector = 0154; + default_intr_level = 5; + + // The UDA50 controller has two registers. + register_count = 2; + + IP_reg = &(this->registers[0]); // @ base addr + strcpy(IP_reg->name, "IP"); + IP_reg->active_on_dati = true; + IP_reg->active_on_dato = true; + IP_reg->reset_value = 0; + IP_reg->writable_bits = 0xffff; + + SA_reg = &(this->registers[1]); // @ base addr + 2 + strcpy(SA_reg->name, "SA"); + SA_reg->active_on_dati = false; + SA_reg->active_on_dato = true; + SA_reg->reset_value = 0; + SA_reg->writable_bits = 0xffff; + + _server.reset(new mscp_server(this)); +} + +uda_c::~uda_c() +{ + +} + +void uda_c::Reset(void) +{ + DEBUG("UDA reset"); + + _server->Reset(); + + _sa = 0; + update_SA(); + + // Signal the worker to begin the initialization sequence. + StateTransition(InitializationStep::Uninitialized); + +} + +void uda_c::StateTransition(InitializationStep nextStep) +{ + pthread_mutex_lock(&on_after_register_access_mutex); + _initStep = nextStep; + _next_step = true; + pthread_cond_signal(&on_after_register_access_cond); + pthread_mutex_unlock(&on_after_register_access_mutex); +} + +void uda_c::worker(void) +{ + worker_init_realtime_priority(rt_device); + + timeout_c timeout; + + while (!worker_terminate) + { + // + // Wait to be awoken. + // + pthread_mutex_lock(&on_after_register_access_mutex); + while (!_next_step) + { + pthread_cond_wait( + &on_after_register_access_cond, + &on_after_register_access_mutex); + } + + _next_step = false; + pthread_mutex_unlock(&on_after_register_access_mutex); + + switch (_initStep) + { + case InitializationStep::Uninitialized: + INFO("Transition to Init state Uninitialized."); + + // SA should already be zero but we'll be extra sure here. + _sa = 0; + update_SA(); + + StateTransition(InitializationStep::Step1); + break; + + case InitializationStep::Step1: + // Wait 100uS, set SA. + timeout.wait_us(10); + + INFO("Transition to Init state S1."); + // + // S1 is set, all other bits zero. This indicates that we + // support a host-settable interrupt vector, that we do not + // implement enhanced diagnostics, and that no errors have + // occurred. + // + _sa = 0x0800; + update_SA(); + break; + + case InitializationStep::Step2: + // Wait 100uS, set SA. + timeout.wait_us(100); + + INFO("Transition to Init state S2."); + // update the SA read value for step 2: + // S2 is set, unibus port type (0), SA bits 15-8 written + // by the host in step 1. + _sa = 0x1000 | (_step1Value >> 8); + update_SA(); + + Interrupt(); + break; + + case InitializationStep::Step3: + // Wait 100uS, set SA. + timeout.wait_us(100); + + INFO("Transition to Init state S3."); + // Update the SA read value for step 3: + // S3 set, plus SA bits 7-0 written by the host in step 1. + _sa = 0x2000 | (_step1Value & 0xff); + update_SA(); + + Interrupt(); + break; + + case InitializationStep::Step4: + // Clear communications area, set SA + INFO("Clearing comm area at 0x%x.", _ringBase); + + // TODO: -6 and -8 are described; do these always get cleared or only + // on VAXen? ZUDJ diag only expects -2 and -4 to be cleared... + + for(uint32_t i = 0; + i < (_responseRingLength + _commandRingLength) * sizeof(Descriptor); + i += 2) + { + DMAWriteWord(_ringBase - 4 + i, 0x0); + } + + // + // Set the ownership bit on all descriptors in the response ring + // to indicate that the port owns them. + // + Descriptor blankDescriptor; + blankDescriptor.Word0.Word0 = 0; + blankDescriptor.Word1.Word1 = 0; + blankDescriptor.Word1.Fields.Ownership = 1; + + for(uint32_t i = 0; i < _responseRingLength; i++) + { + DMAWrite( + GetResponseDescriptorAddress(i), + sizeof(Descriptor), + reinterpret_cast(&blankDescriptor)); + } + + INFO("Transition to Init state S4."); + // Update the SA read value for step 4: + // Bits 7-0 indicating our control microcode version. + // _sa = 0x4063; //UDA50 + _sa = 0x4042; + update_SA(); + Interrupt(); + break; + + case InitializationStep::Complete: + INFO("Transition to Init state Complete. Initializing response ring."); + /* + // + // Set the ownership bit on all descriptors in the response ring + // to indicate that the port owns them. + // + Descriptor blankDescriptor; + blankDescriptor.Word0.Word0 = 0; + blankDescriptor.Word1.Word1 = 0; + blankDescriptor.Word1.Fields.Ownership = 1; + + for(uint32_t i = 0; i < _responseRingLength; i++) + { + DMAWrite( + GetResponseDescriptorAddress(i), + sizeof(Descriptor), + reinterpret_cast(&blankDescriptor)); + } */ + break; + + } + } +} + + +void +uda_c::on_after_register_access( + unibusdevice_register_t *device_reg, + uint8_t unibus_control +) +{ + switch (device_reg->index) + { + case 0: // IP + if (UNIBUS_CONTROL_DATO == unibus_control) + { + // "When written with any value, it causes a hard initialization + // of the port and the device controller." + DEBUG("Reset due to IP read"); + Reset(); + } + else + { + // "When read while the port is operating, it causes the controller + // to initiate polling..." + if (_initStep == InitializationStep::Complete) + { + DEBUG("Request to start polling."); + _server->InitPolling(); + } + } + break; + + case 1: // SA - write only + uint16_t value = SA_reg->active_dato_flipflops; + + switch (_initStep) + { + case InitializationStep::Uninitialized: + // Should not occur, we treat it like step1 here. + DEBUG("Write to SA in Uninitialized state."); + + case InitializationStep::Step1: + // Host writes the following: + // 15 13 11 10 8 7 6 0 + // +-+-+-----+-----+-+-------------+ + // |1|W|c rng|r rng|I| int vector | + // | |R| lng | lng |E|(address / 4)| + // +-+-+-----+-----+-+-------------+ + // WR = 1 tells the port to enter diagnostic wrap + // mode (which we ignore). + // + // c rng lng is the number of slots (32 bits each) + // in the command ring, expressed as a power of two. + // + // r rng lng is as above, but for the response ring. + // + // IE=1 means the host is requesting an interrupt + // at the end of the completion of init steps 1-3. + // + // int vector determines if interrupts will be generated + // by the port. If this field is non-zero, interupts will + // be generated during normal operation and, if IE=1, + // during initialization. + _step1Value = value; + intr_vector.value = _interruptVector = (value & 0x7f) << 2; + _interruptEnable = !!(value & 0x80); + _responseRingLength = (1 << ((value & 0x700) >> 8)); + _commandRingLength = (1 << ((value & 0x3800) >> 11)); + + DEBUG("Step1: 0x%x", value); + DEBUG("resp ring 0x%x", _responseRingLength); + DEBUG("cmd ring 0x%x", _commandRingLength); + + // Move to step 2. + StateTransition(InitializationStep::Step2); + break; + + case InitializationStep::Step2: + // Host writes the following: + // 15 1 0 + // +-----------------------------+-+ + // | ringbase low |P| + // | (address) |I| + // +-----------------------------+-+ + // ringbase low is the low-order portion of word + // [ringbase+0] of the communications area. This is a + // 16-bit byte address whose low-order bit is zero implicitly. + // + _ringBase = value & 0xfffe; + _purgeInterruptEnable = !!(value & 0x1); + + DEBUG("Step2: 0x%x", value); + // Move to step 3 and interrupt as necessary. + StateTransition(InitializationStep::Step3); + break; + + case InitializationStep::Step3: + // Host writes the following: + // 15 0 + // +-+-----------------------------+ + // |P| ringbase hi | + // |P| (address) | + // +-+-----------------------------+ + // PP = 1 means the host is requesting execution of + // purge and poll tests, which we ignore because we can. + // + // ringbase hi is the high-order portion of the address + // [ringbase+0]. + _ringBase |= ((value & 0x7fff) << 16); + + DEBUG("Step3: 0x%x", value); + // Move to step 4 and interrupt as necessary. + StateTransition(InitializationStep::Step4); + break; + + case InitializationStep::Step4: + // Host writes the following: + // 15 8 7 1 0 + // +---------------+-----------+-+-+ + // | reserved | burst |L|G| + // | | |F|O| + // +---------------+-----------+-+-+ + // burst is one less than the max. number of longwords + // the host is willing to allow per DMA transfer. + // If zero, the port uses its default burst count. + // + // LF=1 means that the host wants a "last fail" response + // packet when initialization is complete. + // + // GO=1 means that the controller should enter its functional + // microcode as soon as initialization completes. + // + // Note that if GO=0 when initialization completes, the port + // will continue to read SA until the host forces SA bit 0 to + // make the transition 0->1. + // + // There is no explicit interrupt at the end of Step 4. + // + // TODO: For now we ignore burst settings. + // We also ignore Last Fail report requests since we aren't + // supporting onboard diagnostics and there's nothing to + // report. + // + DEBUG("Step4: 0x%x", value); + if (value & 0x1) + { + // + // GO is set, move to the Complete state. The worker will + // start the controller running. + // + StateTransition(InitializationStep::Complete); + } + else + { + // GO unset, wait until it is. + } + break; + + case InitializationStep::Complete: + // "When zeroed by the host during both initialization and normal + // operation, it signals the port that the host has successfully + // completed a bus adapter purge in response to a port-initiated + // purge request. + // Unsure what this means at the moment. + break; + } + break; + + } +} + +void +uda_c::update_SA() +{ + set_register_dati_value( + SA_reg, + _sa, + "update_SA"); +} + +Message* +uda_c::GetNextCommand(void) +{ + timeout_c timer; + + // Grab the next descriptor being pointed to + uint32_t descriptorAddress = + GetCommandDescriptorAddress(_commandRingPointer); + + DEBUG("Next descriptor address is o%o", descriptorAddress); + + // Multiple retries on read, this is a workaround for attempting to do DMA + // while an interrupt is active. Need to find a better solution for this; + // likely at a lower level than this. + + std::unique_ptr cmdDescriptor; + for(int retry = 0 ; retry < 10; retry++) + { + cmdDescriptor.reset( + reinterpret_cast( + DMARead( + descriptorAddress, + sizeof(Descriptor)))); + + if (cmdDescriptor != nullptr) + { + break; + } + + timer.wait_us(200); + } + + // TODO: if NULL is returned after retry assume a bus error and handle it appropriately. + assert(cmdDescriptor != nullptr); + + // Check owner bit: if set, ownership has been passed to us, in which case + // we can attempt to pull the actual message from memory. + if (cmdDescriptor->Word1.Fields.Ownership) + { + bool doInterrupt = false; + + uint32_t messageAddress = + cmdDescriptor->Word0.EnvelopeLow | + (cmdDescriptor->Word1.Fields.EnvelopeHigh << 16); + + DEBUG("Next message address is o%o, flag %d", + messageAddress, cmdDescriptor->Word1.Fields.Flag); + + // + // Grab the message length; this is at messageAddress - 4 + // + bool success = false; + uint16_t messageLength = + DMAReadWord( + messageAddress - 4, + success); + + // + // TODO: sanity check message length (what is the max length we + // can expect to see?) + // + +// std::unique_ptr cmdMessage( +// reinterpret_cast( + uint16_t* data = reinterpret_cast( + DMARead( + messageAddress - 4, + messageLength + 4)); + /* + for(int i=0;i<(messageLength + 4) / 2; i++) + { + INFO("o%o", data[i]); + } + */ + + std::unique_ptr cmdMessage(reinterpret_cast(data)); + + + // + // Handle Ring Transitions (from full to not-full) and associated + // interrupts. + // If the previous entry in the ring is owned by the Port then that indicates + // that the ring was previously full (i.e. the descriptor we're now returning + // is the first free entry.) + // + if (cmdDescriptor->Word1.Fields.Flag) + { + // + // Flag is set, host is requesting a transition interrupt. + // Check the previous entry in the ring. + // + if (_commandRingLength == 1) + { + // Degenerate case: If the ring is of size 1 we always interrupt. + doInterrupt = true; + } + else + { + uint32_t previousDescriptorAddress = + GetCommandDescriptorAddress( + (_commandRingPointer - 1) % _commandRingLength); + + std::unique_ptr previousDescriptor( + reinterpret_cast( + DMARead( + previousDescriptorAddress, + sizeof(Descriptor)))); + + if (previousDescriptor->Word1.Fields.Ownership) + { + // We own the previous descriptor, so the ring was previously + // full. + doInterrupt = true; + } + } + } + + // + // Message retrieved; reset the Owner bit of the command descriptor, + // set the Flag bit (to indicate that we've processed it) + // and return a pointer to the message. + // + cmdDescriptor->Word1.Fields.Ownership = 0; + cmdDescriptor->Word1.Fields.Flag = 1; + DMAWrite( + descriptorAddress, + sizeof(Descriptor), + reinterpret_cast(cmdDescriptor.get())); + + // + // Move to the next descriptor in the ring for next time. + _commandRingPointer = (_commandRingPointer + 1) % _commandRingLength; + + // Post an interrupt as necessary. + if (doInterrupt) + { + DEBUG("Ring now empty, interrupting."); + // + // Set ring base - 4 to non-zero to indicate a transition. + // + DMAWriteWord( + _ringBase - 4, + 1); + + // + // Raise the interrupt + // + Interrupt(); + } + + return cmdMessage.release(); + } + + DEBUG("No descriptor found. 0x%x 0x%x", cmdDescriptor->Word0.Word0, cmdDescriptor->Word1.Word1); + + // No descriptor available. + return nullptr; +} + +bool +uda_c::PostResponse( + shared_ptr response +) +{ + bool res = false; + + // Grab the next descriptor. + uint32_t descriptorAddress = GetResponseDescriptorAddress(_responseRingPointer); + std::unique_ptr cmdDescriptor( + reinterpret_cast( + DMARead( + descriptorAddress, + sizeof(Descriptor)))); + + // TODO: if NULL is returned assume a bus error and handle it appropriately. + + // + // Check owner bit: if set, ownership has been passed to us, in which case + // we can use this descriptor and fill in the response buffer it points to. + // If not, we return false to indicate to the caller the need to try again later. + // + if (cmdDescriptor->Word1.Fields.Ownership) + { + bool doInterrupt = false; + + uint32_t messageAddress = + cmdDescriptor->Word0.EnvelopeLow | + (cmdDescriptor->Word1.Fields.EnvelopeHigh << 16); + + // + // Read the buffer length the host has allocated for this response; + // if it is shorter than the buffer we're writing then we will need to + // split the response into multiple responses. + // + // Message length is at messageAddress - 4 -- this is the size of the command + // not including the two header words. + // + bool success = false; + uint16_t messageLength = + DMAReadWord( + messageAddress - 4, + success); + + if (messageLength < response->MessageLength) + { + // TODO: handle this; for now eat flaming death. + FATAL("Response buffer %x > message length %x", response->MessageLength, messageLength); + } + else + { + // + // This will fit; simply copy the response message over the top + // of the buffer allocated on the host -- this updates the header fields + // as necessary and provides the actual response data to the host. + // + DMAWrite( + messageAddress - 4, + response->MessageLength + 4, + reinterpret_cast(response.get())); + } + + // + // Check if a transition from empty to non-empty occurred, interrupt if requested. + // + // TODO: factor this code out as it's basically identical to the code in GetNextCommand. + // + // If the previous entry in the ring is owned by the Port then that indicates + // that the ring was previously empty (i.e. the descriptor we're now returning + // is the first entry returned to the ring by the Port.) + // + if (cmdDescriptor->Word1.Fields.Flag) + { + // + // Flag is set, host is requesting a transition interrupt. + // Check the previous entry in the ring. + // + if (_responseRingLength == 1) + { + // Degenerate case: If the ring is of size 1 we always interrupt. + doInterrupt = true; + } + else + { + uint32_t previousDescriptorAddress = + GetResponseDescriptorAddress( + (_responseRingPointer - 1) % _responseRingLength); + + std::unique_ptr previousDescriptor( + reinterpret_cast( + DMARead( + previousDescriptorAddress, + sizeof(Descriptor)))); + + if (previousDescriptor->Word1.Fields.Ownership) + { + // We own the previous descriptor, so the ring was previously + // full. + doInterrupt = true; + } + } + } + + // + // Message posted; reset the Owner bit of the response descriptor, + // and set the Flag bit (to indicate that we've processed it). + // + cmdDescriptor->Word1.Fields.Ownership = 0; + cmdDescriptor->Word1.Fields.Flag = 1; + DMAWrite( + descriptorAddress, + sizeof(Descriptor), + reinterpret_cast(cmdDescriptor.get())); + + // Post an interrupt as necessary. + if (doInterrupt) + { + DEBUG("ring no longer empty, interrupting."); + // + // Set ring base - 4 to non-zero to indicate a transition. + // + DMAWriteWord( + _ringBase - 4, + 1); + + // + // Raise the interrupt + // + Interrupt(); + } + + res = true; + + // Move to the next descriptor in the ring for next time. + _responseRingPointer = (_responseRingPointer + 1) % _responseRingLength; + } + + return res; +} + +uint64_t +uda_c::GetControllerIdentifier() +{ + // TODO: make this not hardcoded + // ID 0x1234568, device class 1 (mass storage), model 2 (UDA50) + return 0x1234567801020000; +} + +void +uda_c::Interrupt(void) +{ + if (_interruptEnable && _interruptVector != 0) + { + interrupt(); + } +} + +void +uda_c::on_power_changed(void) +{ + storagecontroller_c::on_power_changed(); + + if (power_down) + { + DEBUG("Reset due to power change"); + Reset(); + } +} + +void +uda_c::on_init_changed(void) +{ + if (init_asserted) + { + DEBUG("Reset due to INIT"); + Reset(); + } + + storagecontroller_c::on_init_changed(); +} + +void +uda_c::on_drive_status_changed(storagedrive_c *drive) +{ + +} + +uint32_t +uda_c::GetCommandDescriptorAddress( + size_t index +) +{ + return _ringBase + _responseRingLength * sizeof(Descriptor) + + index * sizeof(Descriptor); +} + +uint32_t +uda_c::GetResponseDescriptorAddress( + size_t index +) +{ + return _ringBase + index * sizeof(Descriptor); +} + +/* + Write a single word to Unibus memory. Returns true + on success; if false is returned this is due to an NXM condition. +*/ +bool +uda_c::DMAWriteWord( + uint32_t address, + uint16_t word) +{ + return DMAWrite( + address, + sizeof(uint16_t), + reinterpret_cast(&word)); +} + +/* + Read a single word from Unibus memory. Returns the word read on success. + the success field indicates the success or failure of the read. +*/ +uint16_t +uda_c::DMAReadWord( + uint32_t address, + bool& success) +{ + uint8_t* buffer = DMARead( + address, + sizeof(uint16_t)); + + if (buffer) + { + success = true; + uint16_t retval = *reinterpret_cast(buffer); + delete[] buffer; + return retval; + } + else + { + success = false; + return 0; + } +} + + +/* + Write data from buffer to Unibus memory. Returns true + on success; if false is returned this is due to an NXM condition. +*/ +bool +uda_c::DMAWrite( + uint32_t address, + size_t lengthInBytes, + uint8_t* buffer) +{ + bool timeout = false; + timeout_c timer; + + assert((lengthInBytes % 2) == 0); + + unibusadapter->request_DMA( + this, + UNIBUS_CONTROL_DATO, + address, + reinterpret_cast(buffer), + lengthInBytes >> 1); + + // Wait for completion + while (true) + { + timer.wait_us(50); + uint32_t last_address = 0; + if (unibusadapter->complete_DMA( + this, + &last_address, + &timeout)) + { + break; + } + } + + return !timeout; +} + +/* + Read data from Unibus memory into the returned buffer. + Buffer returned is nullptr if memory could not be read. + Caller is responsible for freeing the buffer when done. +*/ +uint8_t* +uda_c::DMARead( + uint32_t address, + size_t lengthInBytes) +{ + assert((lengthInBytes % 2) == 0); + + uint16_t* buffer = new uint16_t[lengthInBytes >> 1]; + + assert(buffer); + + bool timeout = false; + timeout_c timer; + + unibusadapter->request_DMA( + this, + UNIBUS_CONTROL_DATI, + address, + buffer, + lengthInBytes >> 1); + + // Wait for completion + while (true) + { + timer.wait_us(50); + uint32_t last_address = 0; + if (unibusadapter->complete_DMA( + this, + &last_address, + &timeout)) + { + break; + } + } + + if (timeout) + { + delete[] buffer; + buffer = nullptr; + } + + return reinterpret_cast(buffer); +} diff --git a/10.02_devices/2_src/uda.hpp b/10.02_devices/2_src/uda.hpp new file mode 100644 index 0000000..4138162 --- /dev/null +++ b/10.02_devices/2_src/uda.hpp @@ -0,0 +1,162 @@ +/* + uda.hpp: MSCP controller port (UDA50) +*/ + +#pragma once + +#include +#include "utils.hpp" +#include "unibusdevice.hpp" +#include "storagecontroller.hpp" +#include "mscp_server.hpp" + +// TODO: this currently assumes a little-endian machine! +struct Message +{ + uint16_t MessageLength alignas(2); + + union + { + uint16_t Word1; + struct + { + uint16_t Credits : 4; + uint16_t MessageType : 4; + uint16_t ConnectionID : 8; + } Info; + } Word1 alignas(2); + + // 384 bytes is the minimum needed to support + // datagram messages. The underlying buffer will + // be allocated to cover whatever size needed. + uint8_t Message[384] alignas(2); +}; + + +/* + This implements the Transport layer for a Unibus MSCP controller. + + Logic for initialization, reset, and communcation with the MSCP Server + is implemented here. +*/ +class uda_c : public storagecontroller_c +{ +public: + uda_c(); + virtual ~uda_c(); + + void worker(void) override; + + void on_after_register_access( + unibusdevice_register_t *device_reg, + uint8_t unibus_control) override; + + void on_power_changed(void) override; + void on_init_changed(void) override; + + void on_drive_status_changed(storagedrive_c *drive) override; +public: + + // + // Returns the next command message from the command ring, if any. + // Returns NULL if the ring is empty. + // + Message* GetNextCommand(void); + + // + // Posts a response message to the response ring and memory + // if there is space. + // Returns FALSE if the ring is full. + bool PostResponse(std::shared_ptr response); + + uint64_t GetControllerIdentifier(void); + +private: + // TODO: consolidate these private/public groups here + void Reset(void); + void Interrupt(void); + + uint32_t GetCommandDescriptorAddress(size_t index); + uint32_t GetResponseDescriptorAddress(size_t index); + +public: + bool DMAWriteWord(uint32_t address, uint16_t word); + uint16_t DMAReadWord(uint32_t address, bool& success); + + bool DMAWrite(uint32_t address, size_t lengthInBytes, uint8_t* buffer); + uint8_t* DMARead(uint32_t address, size_t lengthInBytes); + +private: + void update_SA(void); + + // UDA50 registers: + unibusdevice_register_t *IP_reg; + unibusdevice_register_t *SA_reg; + + uint16_t _sa; + + std::unique_ptr _server; + + uint32_t _ringBase; + + // Lengths are in terms of slots (32 bits each) in the + // corresponding rings. + size_t _commandRingLength; + size_t _responseRingLength; + + // The current slot in the ring being accessed. + uint32_t _commandRingPointer; + uint32_t _responseRingPointer; + + // Interrupt vector -- if zero, no interrupts + // will be generated. + uint32_t _interruptVector; + + // Interrupt enable flag + bool _interruptEnable; + + // Purge interrupt enable flag + bool _purgeInterruptEnable; + + // Value written during step1, saved + // to make manipulation easier. + uint16_t _step1Value; + + enum InitializationStep + { + Uninitialized = 0, + Step1 = 1, + Step2 = 2, + Step3 = 4, + Step4 = 8, + Complete, + }; + + InitializationStep _initStep; + bool _next_step; + + void StateTransition(InitializationStep nextStep); + + // TODO: this currently assumes a little-endian machine! + struct Descriptor + { + union alignas(2) + { + uint16_t Word0; + uint16_t EnvelopeLow; + } Word0; + + union alignas(2) + { + uint16_t Word1; + struct + { + uint16_t EnvelopeHigh : 2; + uint16_t Reserved : 12; + uint16_t Flag : 1; + uint16_t Ownership : 1; + } Fields; + } Word1; + }; +}; + diff --git a/10.03_app_demo/2_src/makefile b/10.03_app_demo/2_src/makefile index f7f6704..8f54fda 100644 --- a/10.03_app_demo/2_src/makefile +++ b/10.03_app_demo/2_src/makefile @@ -91,6 +91,8 @@ OBJECTS = $(OBJDIR)/application.o \ $(OBJDIR)/rl11.o \ $(OBJDIR)/rk11.o \ $(OBJDIR)/rk05.o \ + $(OBJDIR)/uda.o \ + $(OBJDIR)/mscp_server.o \ $(OBJDIR)/storagedrive.o \ $(OBJDIR)/storagecontroller.o \ $(OBJDIR)/demo_io.o \ @@ -195,6 +197,12 @@ $(OBJDIR)/rk05.o : $(DEVICE_SRC_DIR)/rk05.cpp $(DEVICE_SRC_DIR)/rk05.hpp $(OBJDIR)/rk11.o : $(DEVICE_SRC_DIR)/rk11.cpp $(DEVICE_SRC_DIR)/rk11.hpp $(CC) $(CCFLAGS) $< -o $@ +$(OBJDIR)/uda.o : $(DEVICE_SRC_DIR)/uda.cpp $(DEVICE_SRC_DIR)/uda.hpp + $(CC) $(CCFLAGS) $< -o $@ + +$(OBJDIR)/mscp_server.o : $(DEVICE_SRC_DIR)/mscp_server.cpp $(DEVICE_SRC_DIR)/mscp_server.hpp + $(CC) $(CCFLAGS) $< -o $@ + $(OBJDIR)/storagedrive.o : $(BASE_SRC_DIR)/storagedrive.cpp $(BASE_SRC_DIR)/storagedrive.hpp $(CC) $(CCFLAGS) $< -o $@ diff --git a/10.03_app_demo/2_src/menu_devices.cpp b/10.03_app_demo/2_src/menu_devices.cpp index fb7dcd0..636d539 100644 --- a/10.03_app_demo/2_src/menu_devices.cpp +++ b/10.03_app_demo/2_src/menu_devices.cpp @@ -49,6 +49,7 @@ #include "demo_regs.hpp" #include "rl11.hpp" #include "rk11.hpp" +#include "uda.hpp" #include "cpu.hpp" @@ -84,6 +85,9 @@ void menus_c::menu_devices(void) { // create RK11 + drives rk11_c RK05; + + // Create UDA50 + uda_c UDA50; demo_io.install(); demo_io.worker_start(); @@ -98,6 +102,9 @@ void menus_c::menu_devices(void) { RK05.install(); RK05.worker_start(); + UDA50.install(); + UDA50.worker_start(); + cpu.install(); cpu.worker_start(); @@ -340,6 +347,9 @@ void menus_c::menu_devices(void) { RK05.worker_stop(); RK05.uninstall(); + UDA50.worker_stop(); + UDA50.uninstall(); + //demo_regs.worker_stop(); //demo_regs.uninstall();