From 2189e264c35084548c19fdeff886a659b3ddfc68 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Tue, 16 Apr 2019 02:30:40 +0200 Subject: [PATCH 01/18] 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(); From e0aabf2197ba5c74d9403b8c3ea81160b1c1d82e Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Wed, 24 Apr 2019 20:29:33 +0200 Subject: [PATCH 02/18] Changes to MSCP implementation with tweaks to PRU1 code to allow operation on 11/84 under 2.11BSD. 2.11BSD boots and works well enough to recompile itself. --- 10.01_base/2_src/arm/unibusadapter.cpp | 7 +- 10.01_base/2_src/arm/unibusadapter.hpp | 2 +- 10.01_base/2_src/pru1/pru1_statemachine_dma.c | 4 +- 10.02_devices/2_src/mscp_server.cpp | 180 +++++++----- 10.02_devices/2_src/mscp_server.hpp | 16 +- 10.02_devices/2_src/uda.cpp | 258 +++++++++++------- 10.02_devices/2_src/uda.hpp | 29 +- 7 files changed, 301 insertions(+), 195 deletions(-) diff --git a/10.01_base/2_src/arm/unibusadapter.cpp b/10.01_base/2_src/arm/unibusadapter.cpp index 7a48224..d97314e 100644 --- a/10.01_base/2_src/arm/unibusadapter.cpp +++ b/10.01_base/2_src/arm/unibusadapter.cpp @@ -465,12 +465,12 @@ bool unibusadapter_c::request_INTR_active(const char *error_info) { // request a DMA cycle. // unibus_control = UNIBUS_CONTROL_DATI or _DATO -void unibusadapter_c::request_DMA(unibusdevice_c *device, uint8_t unibus_control, +bool unibusadapter_c::request_DMA(unibusdevice_c *device, uint8_t unibus_control, uint32_t unibus_addr, uint16_t *buffer, unsigned wordcount) { // TODO: if another DMA or INTR is active: put request in queue UNUSED(device); if (request_DMA_active(__func__) || request_INTR_active(__func__)) - return; + return false; mailbox->dma.startaddr = unibus_addr; mailbox->dma.control = unibus_control; @@ -490,7 +490,8 @@ void unibusadapter_c::request_DMA(unibusdevice_c *device, uint8_t unibus_control // start! mailbox->arm2pru_req = ARM2PRU_DMA; - // PRU now changes state + // PRU now changes state + return true; } void unibusadapter_c::request_INTR(unibusdevice_c *device, unsigned level, unsigned vector) { diff --git a/10.01_base/2_src/arm/unibusadapter.hpp b/10.01_base/2_src/arm/unibusadapter.hpp index 5519e85..674adab 100644 --- a/10.01_base/2_src/arm/unibusadapter.hpp +++ b/10.01_base/2_src/arm/unibusadapter.hpp @@ -66,7 +66,7 @@ public: bool request_DMA_active(const char *error_info) ; bool request_INTR_active(const char *error_info) ; - void request_DMA(unibusdevice_c *device, uint8_t unibus_control, uint32_t unibus_addr, + bool request_DMA(unibusdevice_c *device, uint8_t unibus_control, uint32_t unibus_addr, uint16_t *buffer, unsigned wordcount); void request_INTR(unibusdevice_c *device, unsigned level, unsigned vector); bool complete_DMA(unibusdevice_c *device, uint32_t *unibus_end_addr, bool *error); diff --git a/10.01_base/2_src/pru1/pru1_statemachine_dma.c b/10.01_base/2_src/pru1/pru1_statemachine_dma.c index abf8c9a..2c7f380 100644 --- a/10.01_base/2_src/pru1/pru1_statemachine_dma.c +++ b/10.01_base/2_src/pru1/pru1_statemachine_dma.c @@ -140,7 +140,7 @@ static uint8_t sm_dma_state_1() { // prev SSYN & DATA may be still on bus, disturbes DATA while (buslatches_get(4) & BIT(5)) ; // wait for SSYN inactive - __delay_cycles(NANOSECS(150) - 10); + __delay_cycles(NANOSECS(350) - 10); // assume 10 cycles for buslatches_get and address test // ADDR, CONTROL (and DATA) stable since 150ns, set MSYN @@ -190,7 +190,7 @@ static uint8_t sm_dma_state_1() { buslatches_setbits(4, 0x3f, tmpval); // wait 150ns after MSYN, no distance to SSYN required - __delay_cycles(NANOSECS(150) - 10); + __delay_cycles(NANOSECS(350) - 10); // assume 10 cycles for buslatches_get and address test // ADDR, CONTROL (and DATA) stable since 150ns, set MSYN next diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index 3ed4fb2..dca257a 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -8,8 +8,9 @@ using namespace std; #include "logger.hpp" #include "utils.hpp" -#include "uda.hpp" +#include "mscp_drive.hpp" #include "mscp_server.hpp" +#include "uda.hpp" void* polling_worker( void *context) @@ -31,17 +32,11 @@ mscp_server::mscp_server( _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(); } @@ -102,6 +97,8 @@ mscp_server::AbortPollingThread(void) void mscp_server::Poll(void) { + worker_init_realtime_priority(rt_device); + timeout_c timer; while(!_abort_polling) @@ -109,7 +106,6 @@ mscp_server::Poll(void) // // Wait to be awoken, then pull commands from the command ring // - DEBUG("Sleeping until awoken."); pthread_mutex_lock(&polling_mutex); while (_pollState == PollingState::Wait) { @@ -125,8 +121,9 @@ mscp_server::Poll(void) } pthread_mutex_unlock(&polling_mutex); + + //timer.wait_us(100); - DEBUG("The sleeper awakes."); if (_abort_polling) { @@ -137,15 +134,16 @@ mscp_server::Poll(void) // Pull commands from the ring until the ring is empty, at which // point we sleep until awoken again. // - while(!_abort_polling && _pollState == PollingState::Run) + while(!_abort_polling && _pollState != PollingState::InitRestart) { - shared_ptr message(_port->GetNextCommand()); + shared_ptr message(_port->GetNextCommand()); if (nullptr == message) { DEBUG("Empty command ring; sleeping."); break; } + DEBUG("Message received."); @@ -158,19 +156,13 @@ mscp_server::Poll(void) ControlMessageHeader* header = reinterpret_cast(message->Message); - uint16_t *cmdbuf = reinterpret_cast(message->Message); - - DEBUG("Message opcode 0x%x rsvd 0x%x mod 0x%x", + DEBUG("Message opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 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]); - } - */ + header->Word3.Command.Modifiers, + header->UnitNumber, + header->Reserved, + header->ReferenceNumber); uint32_t cmdStatus = 0; @@ -226,9 +218,6 @@ mscp_server::Poll(void) header->Word3.End.Endcode |= Endcodes::END; } - // - // TODO: credits, etc. - // if (message->Word1.Info.MessageType == MessageTypes::Sequential && header->Word3.End.Endcode & Endcodes::END) { @@ -241,37 +230,38 @@ mscp_server::Poll(void) // 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)); + uint8_t grantedCredits = min(_credits, static_cast(MAX_CREDITS)); _credits -= grantedCredits; message->Word1.Info.Credits = grantedCredits + 1; + DEBUG("granted credits %d", grantedCredits + 1); } + else + { + message->Word1.Info.Credits = 0; + } + + timer.wait_us(250); // // 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)) + if(!_port->PostResponse(message.get())) { - break; + FATAL("no room at the inn."); } - timer.wait_us(200); - } // Hack: give interrupts time to settle before doing another transfer. - timer.wait_us(250); + timer.wait_us(2500); // // 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) { + DEBUG("MSCP Polling thread reset."); // Signal the Reset call that we're done so it can return // and release the Host. _pollState = PollingState::Wait; @@ -315,13 +305,14 @@ mscp_server::GetUnitStatus( }; #pragma pack(pop) - if (unitNumber != 0) + mscp_drive_c* drive = GetDrive(unitNumber); + + if (nullptr == drive || + !drive->IsAvailable()) { 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; @@ -332,8 +323,8 @@ mscp_server::GetUnitStatus( 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->UnitIdentifier = drive->GetUnitID(); + params->MediaTypeIdentifier = drive->GetMediaID(); params->ShadowUnit = unitNumber; // Always equal to unit number // @@ -353,7 +344,7 @@ mscp_server::GetUnitStatus( // params->RCTStuff = 0x01000001; - if (_unitOnline) + if (drive->IsOnline()) { return STATUS(Status::SUCCESS, 0); } @@ -397,8 +388,8 @@ mscp_server::Online( #pragma pack(push,1) struct OnlineResponseParameters { - uint16_t UnitFlags alignas(2); - uint16_t MultiUnitCode alignas(2); + uint16_t UnitFlags; + uint16_t MultiUnitCode; uint32_t Reserved0; uint64_t UnitIdentifier; uint32_t MediaTypeIdentifier; @@ -408,12 +399,15 @@ mscp_server::Online( }; #pragma pack(pop) - if (unitNumber != 0) + mscp_drive_c* drive = GetDrive(unitNumber); + + if (nullptr == drive || + !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- move to enum + return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum } - - _unitOnline = true; + + drive->SetOnline(); // Adjust message length for response message->MessageLength = sizeof(OnlineResponseParameters) + @@ -425,11 +419,11 @@ mscp_server::Online( 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->UnitIdentifier = drive->GetUnitID(); + params->MediaTypeIdentifier = drive->GetMediaID(); + params->UnitSize = drive->GetBlockCount(); params->VolumeSerialNumber = 0; // We report no serial - + return STATUS(Status::SUCCESS, 0); // TODO: subcode "Already Online" } @@ -440,10 +434,10 @@ mscp_server::SetControllerCharacteristics( #pragma pack(push,1) struct SetControllerCharacteristicsParameters { + uint16_t MSCPVersion; uint16_t ControllerFlags; - uint16_t MSCPVersion; - uint16_t Reserved; uint16_t HostTimeout; + uint16_t Reserved; uint64_t TimeAndDate; }; #pragma pack(pop) @@ -500,10 +494,13 @@ mscp_server::SetUnitCharacteristics( // TODO: handle Set Write Protect modifier + mscp_drive_c* drive = GetDrive(unitNumber); + // Check unit - if (unitNumber != 0) + if (nullptr == drive || + !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 0); + return STATUS(Status::UNIT_OFFLINE, 3); } // TODO: mostly same as Online command: should share logic. @@ -532,9 +529,9 @@ mscp_server::SetUnitCharacteristics( 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->UnitIdentifier = drive->GetUnitID(); + params->MediaTypeIdentifier = drive->GetMediaID(); + params->UnitSize = drive->GetBlockCount(); params->VolumeSerialNumber = 0; // We report no serial return STATUS(Status::SUCCESS, 0); @@ -561,28 +558,31 @@ mscp_server::Read( ReadParameters* params = reinterpret_cast(GetParameterPointer(message)); - INFO("MSCP READ unit %d pa o%o count %d lbn %d", + DEBUG("MSCP READ unit %d pa o%o count %d lbn %d", unitNumber, params->BufferPhysicalAddress & 0x00ffffff, params->ByteCount, params->LBN); + mscp_drive_c* drive = GetDrive(unitNumber); + // Check unit - if (unitNumber != 0) + if (nullptr == drive || + !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 0); + return STATUS(Status::UNIT_OFFLINE, 3); } // 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) + if (params->LBN >= drive->GetBlockCount() + 1) // + 1 for RCT write protect flag { return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0); // TODO: set sub-code } - if (params->ByteCount > (((_diskBufferSize + 512) / 512) - params->LBN) * 512) + if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize()) { return STATUS(Status::INVALID_COMMAND + (0xc << 8), 0); // TODO: as above } @@ -590,12 +590,18 @@ mscp_server::Read( // // OK: do the transfer to memory // + unique_ptr diskBuffer(drive->Read(params->LBN, params->ByteCount)); + _port->DMAWrite( params->BufferPhysicalAddress & 0x00ffffff, params->ByteCount, - _diskBuffer.get() + params->LBN * 512); + diskBuffer.get()); + // Adjust message length for response + message->MessageLength = sizeof(ReadParameters) + + HEADER_SIZE; + // 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 @@ -626,26 +632,29 @@ mscp_server::Write( WriteParameters* params = reinterpret_cast(GetParameterPointer(message)); - INFO("MSCP WRITE unit %d pa o%o count %d lbn %d", + DEBUG("MSCP WRITE unit %d pa o%o count %d lbn %d", unitNumber, params->BufferPhysicalAddress & 0x00ffffff, params->ByteCount, params->LBN); + mscp_drive_c* drive = GetDrive(unitNumber); + // Check unit - if (unitNumber != 0) + if (nullptr == drive || + !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 0); + return STATUS(Status::UNIT_OFFLINE, 3); } // Check LBN - if (params->LBN > (_diskBufferSize + 512) / 512) + if (params->LBN > drive->GetBlockCount() + 1) // + 1 for RCT { return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0); // TODO: set sub-code } // Check byte count - if (params->ByteCount > (((_diskBufferSize + 512) / 512) - params->LBN) * 512) + if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize()) { return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0); // TODO: as above } @@ -653,12 +662,18 @@ mscp_server::Write( // // OK: do the transfer from the PDP-11 to a buffer // - unique_ptr buffer(_port->DMARead( + unique_ptr memBuffer(_port->DMARead( params->BufferPhysicalAddress & 0x00ffffff, + params->ByteCount, params->ByteCount)); - // Copy the buffer to our in-memory disk buffer - memcpy(_diskBuffer.get() + params->LBN * 512, buffer.get(), params->ByteCount); + drive->Write(params->LBN, + params->ByteCount, + memBuffer.get()); + + // Adjust message length for response + message->MessageLength = sizeof(WriteParameters) + + HEADER_SIZE; // Set parameters for response. // We leave ByteCount as is (for now anyway) @@ -676,6 +691,19 @@ mscp_server::GetParameterPointer( return reinterpret_cast(message->Message)->Parameters; } +mscp_drive_c* +mscp_server::GetDrive( + uint32_t unitNumber) +{ + mscp_drive_c* drive = nullptr; + if (unitNumber < _port->GetDriveCount()) + { + drive = _port->GetDrive(unitNumber); + } + + return drive; +} + void mscp_server::Reset(void) { @@ -696,6 +724,12 @@ mscp_server::Reset(void) pthread_mutex_unlock(&polling_mutex); _credits = INIT_CREDITS; + + // Release all drives + for (int i=0;i<_port->GetDriveCount();i++) + { + GetDrive(i)->SetOffline(); + } } diff --git a/10.02_devices/2_src/mscp_server.hpp b/10.02_devices/2_src/mscp_server.hpp index f5d4b5b..d861eb6 100644 --- a/10.02_devices/2_src/mscp_server.hpp +++ b/10.02_devices/2_src/mscp_server.hpp @@ -5,6 +5,7 @@ class uda_c; class Message; +class mscp_drive_c; // 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. @@ -16,9 +17,6 @@ class Message; #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 @@ -29,8 +27,8 @@ class Message; struct ControlMessageHeader { uint32_t ReferenceNumber; - uint16_t Reserved; uint16_t UnitNumber; + uint16_t Reserved; union { @@ -49,7 +47,7 @@ struct ControlMessageHeader } End; } Word3; - uint8_t Parameters[36]; + uint8_t Parameters[512]; }; #pragma pack(pop) @@ -134,6 +132,7 @@ private: uint32_t Write(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); uint8_t* GetParameterPointer(std::shared_ptr message); + mscp_drive_c* GetDrive(uint32_t unitNumber); private: void StartPollingThread(void); @@ -161,12 +160,7 @@ private: 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; + uint8_t _credits; }; diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index f3adec5..2bc9a09 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -5,6 +5,7 @@ #include "unibusadapter.hpp" #include "unibusdevice.hpp" #include "storagecontroller.hpp" +#include "mscp_drive.hpp" #include "uda.hpp" uda_c::uda_c() : @@ -47,28 +48,60 @@ uda_c::uda_c() : SA_reg->writable_bits = 0xffff; _server.reset(new mscp_server(this)); + + // + // Initialize drives. We support up to eight attached drives. + // + drivecount = 8; + for (uint32_t i=0; iunitno.value = i; + drive->name.value = name.value + std::to_string(i); + drive->log_label = drive->name.value; + drive->parent = this; + storagedrives.push_back(drive); + } } uda_c::~uda_c() { + for(uint32_t i=0; iReset(); - _sa = 0; update_SA(); // Signal the worker to begin the initialization sequence. StateTransition(InitializationStep::Uninitialized); - + + _server->Reset(); } -void uda_c::StateTransition(InitializationStep nextStep) +uint32_t uda_c::GetDriveCount(void) +{ + return drivecount; +} + +mscp_drive_c* uda_c::GetDrive( + uint32_t driveNumber) +{ + assert(driveNumber < drivecount); + + return dynamic_cast(storagedrives[driveNumber]); +} + +void uda_c::StateTransition( + InitializationStep nextStep) { pthread_mutex_lock(&on_after_register_access_mutex); _initStep = nextStep; @@ -99,6 +132,8 @@ void uda_c::worker(void) _next_step = false; pthread_mutex_unlock(&on_after_register_access_mutex); + // INFO("Awoken."); + switch (_initStep) { case InitializationStep::Uninitialized: @@ -156,12 +191,13 @@ void uda_c::worker(void) case InitializationStep::Step4: // Clear communications area, set SA INFO("Clearing comm area at 0x%x.", _ringBase); - + INFO("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength); + // 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 < (_responseRingLength + _commandRingLength) * sizeof(Descriptor) + 8; i += 2) { DMAWriteWord(_ringBase - 4 + i, 0x0); @@ -171,6 +207,8 @@ void uda_c::worker(void) // 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; @@ -195,11 +233,14 @@ void uda_c::worker(void) case InitializationStep::Complete: INFO("Transition to Init state Complete. Initializing response ring."); - /* + _sa = 0x0; + update_SA(); + // // 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; @@ -232,7 +273,7 @@ uda_c::on_after_register_access( { // "When written with any value, it causes a hard initialization // of the port and the device controller." - DEBUG("Reset due to IP read"); + // INFO("Reset due to IP read"); Reset(); } else @@ -241,7 +282,7 @@ uda_c::on_after_register_access( // to initiate polling..." if (_initStep == InitializationStep::Complete) { - DEBUG("Request to start polling."); + //INFO("Request to start polling."); _server->InitPolling(); } } @@ -254,7 +295,7 @@ uda_c::on_after_register_access( { case InitializationStep::Uninitialized: // Should not occur, we treat it like step1 here. - DEBUG("Write to SA in Uninitialized state."); + INFO("Write to SA in Uninitialized state."); case InitializationStep::Step1: // Host writes the following: @@ -358,7 +399,7 @@ uda_c::on_after_register_access( // supporting onboard diagnostics and there's nothing to // report. // - DEBUG("Step4: 0x%x", value); + INFO("Step4: 0x%x", value); if (value & 0x1) { // @@ -404,28 +445,18 @@ uda_c::GetNextCommand(void) uint32_t descriptorAddress = GetCommandDescriptorAddress(_commandRingPointer); - DEBUG("Next descriptor address is o%o", descriptorAddress); + DEBUG("Next descriptor (ring ptr 0x%x) address is o%o", + _commandRingPointer, + 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( + std::unique_ptr cmdDescriptor( reinterpret_cast( DMARead( descriptorAddress, + sizeof(Descriptor), 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); @@ -451,27 +482,19 @@ uda_c::GetNextCommand(void) DMAReadWord( messageAddress - 4, success); - + + // INFO("Message length 0x%x", messageLength); + // // 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( + std::unique_ptr cmdMessage( + 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)); - + messageLength + 4, + sizeof(Message)))); // // Handle Ring Transitions (from full to not-full) and associated @@ -501,6 +524,7 @@ uda_c::GetNextCommand(void) reinterpret_cast( DMARead( previousDescriptorAddress, + sizeof(Descriptor), sizeof(Descriptor)))); if (previousDescriptor->Word1.Fields.Ownership) @@ -531,13 +555,12 @@ uda_c::GetNextCommand(void) // 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); + 0xff); // // Raise the interrupt @@ -556,7 +579,7 @@ uda_c::GetNextCommand(void) bool uda_c::PostResponse( - shared_ptr response + Message* response ) { bool res = false; @@ -567,6 +590,7 @@ uda_c::PostResponse( reinterpret_cast( DMARead( descriptorAddress, + sizeof(Descriptor), sizeof(Descriptor)))); // TODO: if NULL is returned assume a bus error and handle it appropriately. @@ -598,12 +622,19 @@ uda_c::PostResponse( messageAddress - 4, success); + DEBUG("response address o%o length o%o", messageAddress, response->MessageLength); + + if (reinterpret_cast(response)[0] == 0) + { + INFO("Writing zero length response!"); + } + if (messageLength < response->MessageLength) { // TODO: handle this; for now eat flaming death. - FATAL("Response buffer %x > message length %x", response->MessageLength, messageLength); + INFO("Response buffer %x > message length %x", response->MessageLength, messageLength); } - else + // else { // // This will fit; simply copy the response message over the top @@ -613,7 +644,7 @@ uda_c::PostResponse( DMAWrite( messageAddress - 4, response->MessageLength + 4, - reinterpret_cast(response.get())); + reinterpret_cast(response)); } // @@ -646,6 +677,7 @@ uda_c::PostResponse( reinterpret_cast( DMARead( previousDescriptorAddress, + sizeof(Descriptor), sizeof(Descriptor)))); if (previousDescriptor->Word1.Fields.Ownership) @@ -671,13 +703,13 @@ uda_c::PostResponse( // Post an interrupt as necessary. if (doInterrupt) { - DEBUG("ring no longer empty, interrupting."); + // INFO("Response ring no longer empty, interrupting."); // - // Set ring base - 4 to non-zero to indicate a transition. + // Set ring base - 2 to non-zero to indicate a transition. // DMAWriteWord( - _ringBase - 4, - 1); + _ringBase - 2, + 0xff); // // Raise the interrupt @@ -784,6 +816,7 @@ uda_c::DMAReadWord( { uint8_t* buffer = DMARead( address, + sizeof(uint16_t), sizeof(uint16_t)); if (buffer) @@ -814,30 +847,48 @@ uda_c::DMAWrite( bool timeout = false; timeout_c timer; - assert((lengthInBytes % 2) == 0); + assert ((lengthInBytes % 2) == 0); - unibusadapter->request_DMA( - this, - UNIBUS_CONTROL_DATO, - address, - reinterpret_cast(buffer), - lengthInBytes >> 1); - - // Wait for completion - while (true) + // Retry the transfer to work around lower-level DMA issues + while(true) { - timer.wait_us(50); - uint32_t last_address = 0; - if (unibusadapter->complete_DMA( - this, - &last_address, - &timeout)) + if(!unibusadapter->request_DMA_active("uda r") && + !unibusadapter->request_INTR_active("uda w")) { - break; + unibusadapter->request_DMA( + this, + UNIBUS_CONTROL_DATO, + address, + reinterpret_cast(buffer), + lengthInBytes >> 1); + + // Wait for completion + uint32_t last_address = 0; + while(!unibusadapter->complete_DMA( + this, + &last_address, + &timeout)) + { + timer.wait_us(50); + } + + if (!timeout) + { + // Success! + // timer.wait_us(250); // also a hack + return true; + } + else + { + INFO(" DMA WRITE FAILED, RETRYING."); + } } + + // Try again + timer.wait_us(100); } - return !timeout; + return false; } /* @@ -848,43 +899,62 @@ uda_c::DMAWrite( uint8_t* uda_c::DMARead( uint32_t address, - size_t lengthInBytes) + size_t lengthInBytes, + size_t bufferSize) { + assert (bufferSize >= lengthInBytes); assert((lengthInBytes % 2) == 0); - uint16_t* buffer = new uint16_t[lengthInBytes >> 1]; + uint16_t* buffer = new uint16_t[bufferSize >> 1]; assert(buffer); + memset(reinterpret_cast(buffer), 0xc3, bufferSize); + bool timeout = false; timeout_c timer; - unibusadapter->request_DMA( - this, - UNIBUS_CONTROL_DATI, - address, - buffer, - lengthInBytes >> 1); - - // Wait for completion - while (true) + // We retry the transfer to work around lower-level DMA issues + while(true) { - timer.wait_us(50); - uint32_t last_address = 0; - if (unibusadapter->complete_DMA( - this, - &last_address, - &timeout)) + timeout = false; + + if(!unibusadapter->request_DMA_active("uda r") && + !unibusadapter->request_INTR_active("uda w")) { - break; + unibusadapter->request_DMA( + this, + UNIBUS_CONTROL_DATI, + address, + buffer, + lengthInBytes >> 1); + + uint32_t last_address = 0; + // Wait for completion + while (!unibusadapter->complete_DMA( + this, + &last_address, + &timeout)) + { + timer.wait_us(50); + } + + if (!timeout) + { + // success! + // timer.wait_us(250); + break; + } + else + { + INFO("DMA READ FAILED (addr o%o length o%o, lastaddr o%o RETRYING", address, lengthInBytes, last_address); + } } + + // Try again + timer.wait_us(100); } - if (timeout) - { - delete[] buffer; - buffer = nullptr; - } - - return reinterpret_cast(buffer); + // timer.wait_us(250); + return reinterpret_cast(buffer); } diff --git a/10.02_devices/2_src/uda.hpp b/10.02_devices/2_src/uda.hpp index 4138162..41ac76f 100644 --- a/10.02_devices/2_src/uda.hpp +++ b/10.02_devices/2_src/uda.hpp @@ -9,29 +9,31 @@ #include "unibusdevice.hpp" #include "storagecontroller.hpp" #include "mscp_server.hpp" +#include "mscp_drive.hpp" // TODO: this currently assumes a little-endian machine! +#pragma pack(push,1) struct Message { - uint16_t MessageLength alignas(2); + uint16_t MessageLength; union { - uint16_t Word1; struct { uint16_t Credits : 4; uint16_t MessageType : 4; uint16_t ConnectionID : 8; } Info; - } Word1 alignas(2); + uint16_t Word1; + } Word1; // 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); + uint8_t Message[sizeof(ControlMessageHeader)]; }; - +#pragma pack(pop) /* This implements the Transport layer for a Unibus MSCP controller. @@ -67,9 +69,12 @@ public: // 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); + bool PostResponse(Message* response); uint64_t GetControllerIdentifier(void); + + uint32_t GetDriveCount(void); + mscp_drive_c* GetDrive(uint32_t driveNumber); private: // TODO: consolidate these private/public groups here @@ -84,7 +89,7 @@ public: 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); + uint8_t* DMARead(uint32_t address, size_t lengthInBytes, size_t bufferSize); private: void update_SA(void); @@ -95,7 +100,7 @@ private: uint16_t _sa; - std::unique_ptr _server; + std::shared_ptr _server; uint32_t _ringBase; @@ -138,15 +143,16 @@ private: void StateTransition(InitializationStep nextStep); // TODO: this currently assumes a little-endian machine! + #pragma pack(push,1) struct Descriptor { - union alignas(2) + union { uint16_t Word0; uint16_t EnvelopeLow; } Word0; - union alignas(2) + union { uint16_t Word1; struct @@ -157,6 +163,7 @@ private: uint16_t Ownership : 1; } Fields; } Word1; - }; + }; + #pragma pack(pop) }; From 8eff2a4e105710421af2c655f36613af12965a3e Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Sat, 4 May 2019 03:30:26 +0200 Subject: [PATCH 03/18] Rewrote lower-level DMA and IRQ handling: DMA and IRQ requests are now queued and will run to completion on their own without help from the device code (just call request_DMA and when it returns the DMA transfer is complete.) Fixed request_DMA to chunk DMA transfers larger than 1024 bytes to avoid overrunning the mailbox's shared memory. Fixed concurrency issues with DMA requests -- a race condition could cause DMA request data to get clobbered. RT-11 now boots, MSCP behavior is now very reliable. --- 10.01_base/2_src/arm/unibusadapter.cpp | 365 +++++++++++++++++++------ 10.01_base/2_src/arm/unibusadapter.hpp | 79 +++++- 10.01_base/2_src/arm/unibusdevice.cpp | 2 +- 10.02_devices/2_src/mscp_server.cpp | 105 ++++--- 10.02_devices/2_src/mscp_server.hpp | 12 +- 10.02_devices/2_src/rk11.cpp | 28 +- 10.02_devices/2_src/rl11.cpp | 16 +- 10.02_devices/2_src/uda.cpp | 141 +++------- 10.03_app_demo/2_src/makefile | 4 + 10.03_app_demo/2_src/menu_devices.cpp | 30 +- 10 files changed, 484 insertions(+), 298 deletions(-) diff --git a/10.01_base/2_src/arm/unibusadapter.cpp b/10.01_base/2_src/arm/unibusadapter.cpp index d97314e..fe136de 100644 --- a/10.01_base/2_src/arm/unibusadapter.cpp +++ b/10.01_base/2_src/arm/unibusadapter.cpp @@ -50,6 +50,7 @@ #include #include #include +#include // TEST //#include // sleep() @@ -66,11 +67,58 @@ using namespace std; #include "iopageregister.h" #include "unibusadapter.hpp" +dma_request_c::dma_request_c( + uint8_t unibus_control, + uint32_t unibus_addr, + uint16_t* buffer, + uint32_t wordcount) : + _unibus_control(unibus_control), + _unibus_addr(unibus_addr), + _unibus_end_addr(0), + _buffer(buffer), + _wordcount(wordcount), + _isComplete(false), + _success(false) +{ + +} + +dma_request_c::~dma_request_c() +{ + +} + +irq_request_c::irq_request_c( + unsigned level, + unsigned vector) : + _level(level), + _vector(vector) +{ + +} + +irq_request_c::~irq_request_c() +{ + +} + +void* bus_worker( + void *context) +{ + unibusadapter_c* bus = reinterpret_cast(context); + bus->dma_worker(); + return nullptr; +} + unibusadapter_c *unibusadapter; // another Singleton // is registered in device_c.list ... order of static constructor calls ??? unibusadapter_c::unibusadapter_c() : - device_c() { + device_c(), + _busWakeup_cond(PTHREAD_COND_INITIALIZER), + _requestFinished_cond(PTHREAD_COND_INITIALIZER), + _busWorker_mutex(PTHREAD_MUTEX_INITIALIZER) + { unsigned i; log_label = "UNAPT"; @@ -82,14 +130,33 @@ unibusadapter_c::unibusadapter_c() : line_INIT = false; line_DCLO = false; + // + // Start bus worker thread + // + pthread_attr_t attribs; + pthread_attr_init(&attribs); + + int status = pthread_create( + &_busWorker_pthread, + &attribs, + &bus_worker, + reinterpret_cast(this)); + + if (status != 0) + { + FATAL("Failed to start unibus worker thread. Status 0x%x", status); + } } + bool unibusadapter_c::on_param_changed(parameter_c *param) { UNUSED(param); return true ; } -void unibusadapter_c::on_power_changed(void) { +void unibusadapter_c::on_power_changed(void) +{ + } void unibusadapter_c::on_init_changed(void) { @@ -106,6 +173,14 @@ void unibusadapter_c::worker_init_event() { device->init_asserted = line_INIT; device->on_init_changed(); } + + // Clear bus request queues + /* + pthread_mutex_lock(&_busWorker_mutex); + _dmaRequests.clear(); + _irqRequests.clear(); + pthread_mutex_unlock(&_busWorker_mutex); + */ } void unibusadapter_c::worker_power_event() { @@ -118,6 +193,14 @@ void unibusadapter_c::worker_power_event() { device->power_down = line_DCLO; device->on_power_changed(); } + + // Clear bus request queues + /* + pthread_mutex_lock(&_busWorker_mutex); + _dmaRequests.clear(); + _irqRequests.clear(); + pthread_mutex_unlock(&_busWorker_mutex); + */ } // process DATI/DATO access to active device registers @@ -463,111 +546,213 @@ bool unibusadapter_c::request_INTR_active(const char *error_info) { return false; } -// request a DMA cycle. +// Invoke a DMA transfer. // unibus_control = UNIBUS_CONTROL_DATI or _DATO -bool unibusadapter_c::request_DMA(unibusdevice_c *device, uint8_t unibus_control, - uint32_t unibus_addr, uint16_t *buffer, unsigned wordcount) { -// TODO: if another DMA or INTR is active: put request in queue - UNUSED(device); - if (request_DMA_active(__func__) || request_INTR_active(__func__)) - return false; +bool unibusadapter_c::request_DMA( + uint8_t unibus_control, + uint32_t unibus_addr, + uint16_t *buffer, + uint32_t wordcount) { - mailbox->dma.startaddr = unibus_addr; - mailbox->dma.control = unibus_control; - mailbox->dma.wordcount = wordcount; + // + // Acquire bus mutex; append new request to queue. + // bus worker will wake and service the request in due time. + // + dma_request_c request( + unibus_control, + unibus_addr, + buffer, + wordcount); - // save params of current transaction - cur_DMA_unibus_control = unibus_control; - cur_DMA_buffer = buffer; - cur_DMA_wordcount = wordcount; + pthread_mutex_lock(&_busWorker_mutex); + _dmaRequests.push(&request); + pthread_cond_signal(&_busWakeup_cond); + pthread_mutex_unlock(&_busWorker_mutex); - if (unibus_control == UNIBUS_CONTROL_DATO) { - // copy data into mailbox->DMA buffer - memcpy((void*) mailbox->dma.words, buffer, 2 * wordcount); - } DEBUG("DMA start: %s @ %06o, len=%d", unibus->control2text(unibus_control), unibus_addr, wordcount); - // start! - mailbox->arm2pru_req = ARM2PRU_DMA; - // PRU now changes state - return true; -} - -void unibusadapter_c::request_INTR(unibusdevice_c *device, unsigned level, unsigned vector) { -// TODO: if another DMA or INTR is active: put request in queue - UNUSED(device); - - // it is not an error if the INTR (at same level) is still pending - // a device may re-raise its interrupt, an interrupt may remain pending for years. - if (request_DMA_active(__func__)) - return; - if (request_INTR_active(NULL)) - return; - - switch (level) { - case 4: - mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B4; - break; - case 5: - mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B5; - break; - case 6: - mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B6; - break; - case 7: - mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B7; - break; - default: - ERROR("Request_INTR(): Illegal priority %u, aborting", level); - return; + // + // Wait for request to finish. + // + pthread_mutex_lock(&_busWorker_mutex); + while (!request.IsComplete()) + { + pthread_cond_wait(&_requestFinished_cond, &_busWorker_mutex); } - mailbox->intr.vector = vector; - - // start! - mailbox->arm2pru_req = ARM2PRU_INTR; - // PRU now changes state + pthread_mutex_unlock(&_busWorker_mutex); + return request.GetSuccess(); } -// device wants to know state of its requests -// also checks for completion if the single current DMA or INTR. -// to be called by device.worker() -// result: false = not yet finished, true = complete, -// error: return NXM status -bool unibusadapter_c::complete_DMA(unibusdevice_c *device, uint32_t *unibus_end_addr, -bool *error) { -// TODO: access correct request in queue - UNUSED(device); +void unibusadapter_c::dma_worker() +{ - // rely on RL11 to check for completion and sorting DMA/INTR requests. - if (request_DMA_active(NULL)) - return false; + while(true) + { + dma_request_c* dmaReq = nullptr; + irq_request_c irqReq(0,0); + + // + // Wait for the next request. + // + pthread_mutex_lock(&_busWorker_mutex); + while(_dmaRequests.empty() && _irqRequests.empty()) + { + pthread_cond_wait( + &_busWakeup_cond, + &_busWorker_mutex); + } - if (cur_DMA_unibus_control == UNIBUS_CONTROL_DATI) { - // data were read - // copy result cur_DMA_wordcount from mailbox->DMA bufuffer to cur_DMA_buffer - memcpy(cur_DMA_buffer, (void *) mailbox->dma.words, 2 * cur_DMA_wordcount); + // + // We have a request: prioritize IRQ over DMA, dequeue from the requisite + // queue and get to work. + // + if (!_irqRequests.empty()) + { + irq_request_c const& req = _irqRequests.front(); + irqReq = req; + _irqRequests.pop(); + } + else + { + dmaReq = _dmaRequests.front(); + _dmaRequests.pop(); + } + pthread_mutex_unlock(&_busWorker_mutex); + + + // Sanity check: Should be no active DMA requests on the PRU. + assert (!request_DMA_active(nullptr)); + + // If there's an IRQ still active, wait for it to finish. + // TODO: find a way to avoid having to do this. + timeout_c timer; + while (request_INTR_active(nullptr)) + { + timer.wait_us(50); + } + + if (dmaReq) + { + // We do the DMA transfer in chunks so we can handle arbitrary buffer sizes. + // (the PRU mailbox has limited space available.) + // Configure the DMA transfer. + + uint32_t maxTransferSize = 512; + + uint32_t wordCount = dmaReq->GetWordCount(); + uint32_t unibusAddr = dmaReq->GetUnibusAddr(); + uint32_t bufferOffset = 0; + + while (wordCount > 0) + { + uint32_t chunkSize = std::min(maxTransferSize, wordCount); + + mailbox->dma.startaddr = unibusAddr + bufferOffset * 2; + mailbox->dma.control = dmaReq->GetUnibusControl(); + mailbox->dma.wordcount = chunkSize; + + // Copy outgoing data into maibox DMA buffer + if (dmaReq->GetUnibusControl() == UNIBUS_CONTROL_DATO) + { + memcpy( + (void*)mailbox->dma.words, + dmaReq->GetBuffer() + bufferOffset, + 2 * chunkSize); + } + + // + // Start the PRU: + mailbox->arm2pru_req = ARM2PRU_DMA; + + // + // Wait for the transfer to complete. + // TODO: we're polling the mailbox; is there a more efficient way to do this? + timeout_c timeout; + while (request_DMA_active(NULL)) + { + timeout.wait_us(50); + } + + if (dmaReq->GetUnibusControl() == UNIBUS_CONTROL_DATI) + { + // Copy data read from mailbox to user's buffer. + memcpy( + dmaReq->GetBuffer() + bufferOffset, + (void *)mailbox->dma.words, + 2 * chunkSize); + } + + wordCount -= chunkSize; + bufferOffset += chunkSize; + } + + dmaReq->SetUnibusEndAddr(mailbox->dma.cur_addr); + dmaReq->SetSuccess(mailbox->dma.cur_status == DMA_STATE_READY); + + assert(dmaReq->GetUnibusAddr() + dmaReq->GetWordCount() * 2 == mailbox->dma.cur_addr + 2); + + // + // Signal that the request is complete. + // + pthread_mutex_lock(&_busWorker_mutex); + dmaReq->SetComplete(); + pthread_cond_signal(&_requestFinished_cond); + pthread_mutex_unlock(&_busWorker_mutex); + } + else + { + // Handle interrupt request + switch(irqReq.GetInterruptLevel()) + { + case 4: + mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B4; + break; + + case 5: + mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B5; + break; + + case 6: + mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B6; + break; + + case 7: + mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B7; + break; + + default: + ERROR("Request_INTR(): Illegal priority %u, aborting", irqReq.GetInterruptLevel()); + return; + } + + mailbox->intr.vector = irqReq.GetVector(); + + // start! + mailbox->arm2pru_req = ARM2PRU_INTR; + // PRU now changes state + } } - - *unibus_end_addr = mailbox->dma.cur_addr; - - *error = mailbox->dma.cur_status != DMA_STATE_READY; - DEBUG("DMA ready: %s @ %06o..%06o, wordcount %d, data=%06o, %06o, ... %s", - unibus->control2text(mailbox->dma.control), mailbox->dma.startaddr, - mailbox->dma.cur_addr, mailbox->dma.wordcount, mailbox->dma.words[0], - mailbox->dma.words[1], *error ? "TIMEOUT" : "OK"); - - return true; } -// result: false = not yet finished, true = complete, -bool unibusadapter_c::complete_INTR(unibusdevice_c *device) { -// TODO: access correct request in queue - UNUSED(device); +void unibusadapter_c::request_INTR(uint32_t level, uint32_t vector) { + // + // Acquire bus mutex; append new request to queue. + // bus worker will wake and service the request in due time. + // + irq_request_c request( + level, + vector); - // rely on RL11 to check for completion and sorting DMA/INTR requests. - return request_INTR_active(NULL); + pthread_mutex_lock(&_busWorker_mutex); + _irqRequests.push(request); + pthread_cond_signal(&_busWakeup_cond); + pthread_mutex_unlock(&_busWorker_mutex); + + // + // And we're done. + // } // debugging: print PRU sharead regsster map diff --git a/10.01_base/2_src/arm/unibusadapter.hpp b/10.01_base/2_src/arm/unibusadapter.hpp index 674adab..5de5821 100644 --- a/10.01_base/2_src/arm/unibusadapter.hpp +++ b/10.01_base/2_src/arm/unibusadapter.hpp @@ -28,17 +28,67 @@ #define _UNIBUSADAPTER_HPP_ #include +#include #include "iopageregister.h" #include "unibusdevice.hpp" +class dma_request_c +{ +public: + dma_request_c( + uint8_t unibus_control, + uint32_t unibus_addr, + uint16_t *buffer, + uint32_t wordcount); + + ~dma_request_c(); + + uint8_t GetUnibusControl() { return _unibus_control; } + uint32_t GetUnibusAddr() { return _unibus_addr; } + uint16_t* GetBuffer() { return _buffer; } + uint32_t GetWordCount() { return _wordcount; } + uint32_t GetUnibusEndAddr() { return _unibus_end_addr; } + void SetUnibusEndAddr(uint32_t end) { _unibus_end_addr = end; } + + bool IsComplete() { return _isComplete; } + bool GetSuccess() { return _success; } + + void SetComplete() { _isComplete = true; } + void SetSuccess(bool success) { _success = success; } + +private: + + uint8_t _unibus_control; + uint32_t _unibus_addr; + uint32_t _unibus_end_addr; + uint16_t* _buffer; + uint32_t _wordcount; + + bool _isComplete; + bool _success; +}; + +class irq_request_c +{ +public: + irq_request_c( + uint32_t level, + uint32_t vector); + + ~irq_request_c(); + + uint32_t GetInterruptLevel() { return _level; } + uint32_t GetVector() { return _vector; } + +private: + uint32_t _level; + uint32_t _vector; +}; + + // is a device_c. need a thread (but no params) class unibusadapter_c: public device_c { -private: - // save params of current DMA transaction - volatile uint8_t cur_DMA_unibus_control; // DATI? DATO? - uint16_t *cur_DMA_buffer; - volatile unsigned cur_DMA_wordcount; public: unibusadapter_c(); @@ -59,21 +109,28 @@ public: void worker_power_event(void) ; void worker_deviceregister_event(void) ; void worker(void) override; // background worker function - + void dma_worker(void); // background DMA worker + bool register_device(unibusdevice_c& device); void unregister_device(unibusdevice_c& device); bool request_DMA_active(const char *error_info) ; bool request_INTR_active(const char *error_info) ; - bool request_DMA(unibusdevice_c *device, uint8_t unibus_control, uint32_t unibus_addr, - uint16_t *buffer, unsigned wordcount); - void request_INTR(unibusdevice_c *device, unsigned level, unsigned vector); - bool complete_DMA(unibusdevice_c *device, uint32_t *unibus_end_addr, bool *error); - bool complete_INTR(unibusdevice_c *device); + bool request_DMA(uint8_t unibus_control, uint32_t unibus_addr, + uint16_t *buffer, uint32_t wordcount); + void request_INTR(uint32_t level, uint32_t vector); void print_shared_register_map(void); +private: + + std::queue _dmaRequests; + std::queue _irqRequests; + pthread_t _busWorker_pthread; + pthread_cond_t _busWakeup_cond; + pthread_cond_t _requestFinished_cond; + pthread_mutex_t _busWorker_mutex; }; extern unibusadapter_c *unibusadapter; // another Singleton diff --git a/10.01_base/2_src/arm/unibusdevice.cpp b/10.01_base/2_src/arm/unibusdevice.cpp index 9083323..8c1ab9f 100644 --- a/10.01_base/2_src/arm/unibusdevice.cpp +++ b/10.01_base/2_src/arm/unibusdevice.cpp @@ -135,7 +135,7 @@ void unibusdevice_c::reset_unibus_registers() { // set an UNIBUS interrupt condition with intr_vector and intr_level void unibusdevice_c::interrupt(void) { // delegate to unibusadapter_c - unibusadapter->request_INTR(this, intr_level.value, intr_vector.value); + unibusadapter->request_INTR(intr_level.value, intr_vector.value); // WARNING("unibusdevice_c::interrupt() TODO: generated interrupt!"); } diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index dca257a..3f2a506 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -122,7 +122,7 @@ mscp_server::Poll(void) pthread_mutex_unlock(&polling_mutex); - //timer.wait_us(100); + // timer.wait_us(100); if (_abort_polling) @@ -156,7 +156,8 @@ mscp_server::Poll(void) ControlMessageHeader* header = reinterpret_cast(message->Message); - DEBUG("Message opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", + DEBUG("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", + message->MessageLength, header->Word3.Command.Opcode, header->Word3.Command.Reserved, header->Word3.Command.Modifiers, @@ -240,7 +241,7 @@ mscp_server::Poll(void) message->Word1.Info.Credits = 0; } - timer.wait_us(250); + //timer.wait_us(250); // // Post the response to the port's response ring. @@ -251,7 +252,7 @@ mscp_server::Poll(void) } // Hack: give interrupts time to settle before doing another transfer. - timer.wait_us(2500); + //timer.wait_us(2500); // // Go around and pick up the next one. @@ -295,28 +296,32 @@ mscp_server::GetUnitStatus( uint32_t Reserved0; uint64_t UnitIdentifier; uint32_t MediaTypeIdentifier; - uint16_t Reserved1; uint16_t ShadowUnit; - uint16_t GroupSize; + uint16_t Reserved1; uint16_t TrackSize; - uint16_t Reserved2; + uint16_t GroupSize; uint16_t CylinderSize; + uint16_t Reserved2; uint32_t RCTStuff; }; #pragma pack(pop) + INFO("MSCP GET UNIT STATUS drive %d", unitNumber); + + // Adjust message length for response + message->MessageLength = sizeof(GetUnitStatusResponseParameters) + + HEADER_SIZE; + + mscp_drive_c* drive = GetDrive(unitNumber); if (nullptr == drive || !drive->IsAvailable()) { + INFO("Returning UNIT OFFLINE"); return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum } - // Adjust message length for response - message->MessageLength = sizeof(GetUnitStatusResponseParameters) + - HEADER_SIZE; - GetUnitStatusResponseParameters* params = reinterpret_cast( GetParameterPointer(message)); @@ -333,8 +338,8 @@ mscp_server::GetUnitStatus( // 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; + params->GroupSize = 1; + params->CylinderSize = 1; // // Since we do no bad block replacement (no bad blocks possible in a disk image file) @@ -343,7 +348,7 @@ mscp_server::GetUnitStatus( // the RCT are present. // params->RCTStuff = 0x01000001; - + if (drive->IsOnline()) { return STATUS(Status::SUCCESS, 0); @@ -399,20 +404,25 @@ mscp_server::Online( }; #pragma pack(pop) + INFO("MSCP ONLINE drive %d", unitNumber); + + // Adjust message length for response + message->MessageLength = sizeof(OnlineResponseParameters) + + HEADER_SIZE; + mscp_drive_c* drive = GetDrive(unitNumber); if (nullptr == drive || !drive->IsAvailable()) { + INFO("Returning UNIT OFFLINE"); return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum } - + + bool alreadyOnline = drive->IsOnline(); + drive->SetOnline(); - // Adjust message length for response - message->MessageLength = sizeof(OnlineResponseParameters) + - HEADER_SIZE; - OnlineResponseParameters* params = reinterpret_cast( GetParameterPointer(message)); @@ -422,9 +432,12 @@ mscp_server::Online( params->UnitIdentifier = drive->GetUnitID(); params->MediaTypeIdentifier = drive->GetMediaID(); params->UnitSize = drive->GetBlockCount(); - params->VolumeSerialNumber = 0; // We report no serial - - return STATUS(Status::SUCCESS, 0); // TODO: subcode "Already Online" + params->VolumeSerialNumber = 1; // We report no serial + params->Reserved0 = 0; + params->Reserved1 = 0; + + return STATUS(Status::SUCCESS | + (alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0); } uint32_t @@ -446,6 +459,11 @@ mscp_server::SetControllerCharacteristics( reinterpret_cast( GetParameterPointer(message)); + INFO("MSCP SET CONTROLLER CHARACTERISTICS"); + + // Adjust message length for response + message->MessageLength = sizeof(SetControllerCharacteristicsParameters) + + HEADER_SIZE; // // Check the version, if non-zero we must return an Invalid Command // end message. @@ -494,14 +512,7 @@ mscp_server::SetUnitCharacteristics( // TODO: handle Set Write Protect modifier - mscp_drive_c* drive = GetDrive(unitNumber); - - // Check unit - if (nullptr == drive || - !drive->IsAvailable()) - { - return STATUS(Status::UNIT_OFFLINE, 3); - } + INFO("MSCP SET UNIT CHARACTERISTICS drive %d", unitNumber); // TODO: mostly same as Online command: should share logic. #pragma pack(push,1) @@ -523,6 +534,15 @@ mscp_server::SetUnitCharacteristics( message->MessageLength = sizeof(SetUnitCharacteristicsResponseParameters) + HEADER_SIZE; + mscp_drive_c* drive = GetDrive(unitNumber); + // Check unit + if (nullptr == drive || + !drive->IsAvailable()) + { + INFO("Returning UNIT OFFLINE"); + return STATUS(Status::UNIT_OFFLINE, 3); + } + SetUnitCharacteristicsResponseParameters* params = reinterpret_cast( GetParameterPointer(message)); @@ -558,12 +578,17 @@ mscp_server::Read( ReadParameters* params = reinterpret_cast(GetParameterPointer(message)); - DEBUG("MSCP READ unit %d pa o%o count %d lbn %d", + DEBUG("MSCP READ unit %d chan o%o pa o%o count %d lbn %d", unitNumber, + params->BufferPhysicalAddress >> 24, params->BufferPhysicalAddress & 0x00ffffff, params->ByteCount, params->LBN); + // Adjust message length for response + message->MessageLength = sizeof(ReadParameters) + + HEADER_SIZE; + mscp_drive_c* drive = GetDrive(unitNumber); // Check unit @@ -597,17 +622,12 @@ mscp_server::Read( params->ByteCount, diskBuffer.get()); - - // Adjust message length for response - message->MessageLength = sizeof(ReadParameters) + - HEADER_SIZE; - // 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); } @@ -632,12 +652,17 @@ mscp_server::Write( WriteParameters* params = reinterpret_cast(GetParameterPointer(message)); - DEBUG("MSCP WRITE unit %d pa o%o count %d lbn %d", + DEBUG("MSCP WRITE unit %d chan o%o pa o%o count %d lbn %d", unitNumber, + params->BufferPhysicalAddress >> 24, params->BufferPhysicalAddress & 0x00ffffff, params->ByteCount, params->LBN); + // Adjust message length for response + message->MessageLength = sizeof(WriteParameters) + + HEADER_SIZE; + mscp_drive_c* drive = GetDrive(unitNumber); // Check unit @@ -671,10 +696,6 @@ mscp_server::Write( params->ByteCount, memBuffer.get()); - // Adjust message length for response - message->MessageLength = sizeof(WriteParameters) + - HEADER_SIZE; - // 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 diff --git a/10.02_devices/2_src/mscp_server.hpp b/10.02_devices/2_src/mscp_server.hpp index d861eb6..571bfe1 100644 --- a/10.02_devices/2_src/mscp_server.hpp +++ b/10.02_devices/2_src/mscp_server.hpp @@ -15,7 +15,7 @@ class mscp_drive_c; #define GET_FLAGS(status) (((status) >> 8) & 0xff) #define MAX_CREDITS 14 -#define INIT_CREDITS 32 +#define INIT_CREDITS 1 // TODO: Dependent on little-endian hardware // @@ -95,6 +95,16 @@ enum Status DIAGNOSTIC_MESSAGE = 0x1f }; +enum SuccessSubcodes +{ + NORMAL = 0x0, + SPIN_DOWN_IGNORED = 0x20, + STILL_CONNECTED = 0x40, + DUPLICATE_UNIT_NUMBER = 0x80, + ALREADY_ONLINE = 0x100, + STILL_ONLINE = 0x200, +}; + enum MessageTypes { Sequential = 0, diff --git a/10.02_devices/2_src/rk11.cpp b/10.02_devices/2_src/rk11.cpp index 659d062..e454eb9 100755 --- a/10.02_devices/2_src/rk11.cpp +++ b/10.02_devices/2_src/rk11.cpp @@ -88,6 +88,8 @@ rk11_c::rk11_c() : RKDB_reg->reset_value = 0; RKDB_reg->writable_bits = 0x0000; // read only + _rkda_drive = 0; + // // Drive configuration: up to eight drives. // @@ -127,8 +129,7 @@ void rk11_c::dma_transfer(DMARequest &request) { // Write FROM buffer TO unibus memory, IBA on: // We only need to write the last word in the buffer to memory. - unibusadapter->request_DMA( - this, + request.timeout = !unibusadapter->request_DMA( UNIBUS_CONTROL_DATO, request.address, request.buffer + request.count - 1, @@ -139,8 +140,7 @@ void rk11_c::dma_transfer(DMARequest &request) // Read FROM unibus memory TO buffer, IBA on: // We read a single word from the unibus and fill the // entire buffer with this value. - unibusadapter->request_DMA( - this, + request.timeout = !unibusadapter->request_DMA( UNIBUS_CONTROL_DATI, request.address, request.buffer, @@ -153,8 +153,7 @@ void rk11_c::dma_transfer(DMARequest &request) if (request.write) { // Write FROM buffer TO unibus memory - unibusadapter->request_DMA( - this, + request.timeout = !unibusadapter->request_DMA( UNIBUS_CONTROL_DATO, request.address, request.buffer, @@ -163,8 +162,7 @@ void rk11_c::dma_transfer(DMARequest &request) else { // Read FROM unibus memory TO buffer - unibusadapter->request_DMA( - this, + request.timeout = !unibusadapter->request_DMA( UNIBUS_CONTROL_DATI, request.address, request.buffer, @@ -172,20 +170,6 @@ void rk11_c::dma_transfer(DMARequest &request) } } - // And wait for completion. - while(true) - { - timeout.wait_us(50); // Stolen from RL11 - uint32_t last_address = 0; - if (unibusadapter->complete_DMA( - this, - &last_address, - &request.timeout)) - { - break; - } - } - // If an IBA DMA read from memory, we need to fill the request buffer // with the single word returned from memory by the DMA operation. if (request.iba && !request.write) diff --git a/10.02_devices/2_src/rl11.cpp b/10.02_devices/2_src/rl11.cpp index 54ad1b9..8346ad4 100644 --- a/10.02_devices/2_src/rl11.cpp +++ b/10.02_devices/2_src/rl11.cpp @@ -718,7 +718,7 @@ void RL11_c::state_readwrite() { //logger.debug_hexdump(LC_RL, "Read data between disk access and DMA", // (uint8_t *) silo, sizeof(silo), NULL); // start DMA transmission of SILO into memory - unibusadapter->request_DMA(this, UNIBUS_CONTROL_DATO, unibus_address, silo, + error_dma_timeout = !unibusadapter->request_DMA(UNIBUS_CONTROL_DATO, unibus_address, silo, dma_wordcount); } else if (function_code == CMD_WRITE_CHECK) { // read sector data to compare with sector data @@ -726,26 +726,20 @@ void RL11_c::state_readwrite() { // logger.debug_hexdump(LC_RL, "Read data between disk access and DMA", // (uint8_t *) silo, sizeof(silo), NULL); // start DMA transmission of memory to compare with SILO - unibusadapter->request_DMA(this, UNIBUS_CONTROL_DATI, unibus_address, silo_compare, + error_dma_timeout = !unibusadapter->request_DMA(UNIBUS_CONTROL_DATI, unibus_address, silo_compare, dma_wordcount); } else if (function_code == CMD_WRITE_DATA) { // start DMA transmission of memory into SILO - unibusadapter->request_DMA(this, UNIBUS_CONTROL_DATI, unibus_address, silo, + error_dma_timeout = !unibusadapter->request_DMA(UNIBUS_CONTROL_DATI, unibus_address, silo, dma_wordcount); } change_state(RL11_STATE_RW_WAIT_DMA); break; case RL11_STATE_RW_WAIT_DMA: - // wait for DMA to complete, start read of next sector + // Complete DMA, move to next sector - // data late DLT does never happen - if (!unibusadapter->complete_DMA(this, &unibus_address, (bool *) &error_dma_timeout)) { - timeout.wait_us(50); // 50us, but is more because of granularity - break; // DMA still in progress - } - - unibus_address += 2; // was last address, is now next to fill + unibus_address += dma_wordcount * 2; // if timeout: addr AFTER illegal address (verified) update_unibus_address(unibus_address); // set addr msb to cs diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index 2bc9a09..60c1665 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -148,7 +148,7 @@ void uda_c::worker(void) case InitializationStep::Step1: // Wait 100uS, set SA. - timeout.wait_us(10); + timeout.wait_us(100); INFO("Transition to Init state S1."); // @@ -163,44 +163,42 @@ void uda_c::worker(void) case InitializationStep::Step2: // Wait 100uS, set SA. - timeout.wait_us(100); + timeout.wait_ms(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); + _sa = 0x1000 | ((_step1Value >> 8) & 0xff); update_SA(); - Interrupt(); break; case InitializationStep::Step3: // Wait 100uS, set SA. - timeout.wait_us(100); + timeout.wait_ms(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); INFO("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength); // 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) + 8; + i < (_responseRingLength + _commandRingLength) * sizeof(Descriptor) + 4; i += 2) { - DMAWriteWord(_ringBase - 4 + i, 0x0); + DMAWriteWord(_ringBase + i - 4, 0x0); } // @@ -208,7 +206,6 @@ void uda_c::worker(void) // to indicate that the port owns them. // - Descriptor blankDescriptor; blankDescriptor.Word0.Word0 = 0; blankDescriptor.Word1.Word1 = 0; @@ -220,7 +217,7 @@ void uda_c::worker(void) GetResponseDescriptorAddress(i), sizeof(Descriptor), reinterpret_cast(&blankDescriptor)); - } + } INFO("Transition to Init state S4."); // Update the SA read value for step 4: @@ -233,9 +230,9 @@ void uda_c::worker(void) case InitializationStep::Complete: INFO("Transition to Init state Complete. Initializing response ring."); - _sa = 0x0; - update_SA(); - + //_sa = 0x0; + //update_SA(); + // // Set the ownership bit on all descriptors in the response ring // to indicate that the port owns them. @@ -282,7 +279,7 @@ uda_c::on_after_register_access( // to initiate polling..." if (_initStep == InitializationStep::Complete) { - //INFO("Request to start polling."); + INFO("Request to start polling."); _server->InitPolling(); } } @@ -325,9 +322,9 @@ uda_c::on_after_register_access( _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); + INFO("Step1: 0x%x", value); + INFO("resp ring 0x%x", _responseRingLength); + INFO("cmd ring 0x%x", _commandRingLength); // Move to step 2. StateTransition(InitializationStep::Step2); @@ -347,7 +344,7 @@ uda_c::on_after_register_access( _ringBase = value & 0xfffe; _purgeInterruptEnable = !!(value & 0x1); - DEBUG("Step2: 0x%x", value); + INFO("Step2: 0x%x", value); // Move to step 3 and interrupt as necessary. StateTransition(InitializationStep::Step3); break; @@ -366,7 +363,7 @@ uda_c::on_after_register_access( // [ringbase+0]. _ringBase |= ((value & 0x7fff) << 16); - DEBUG("Step3: 0x%x", value); + INFO("Step3: 0x%x", value); // Move to step 4 and interrupt as necessary. StateTransition(InitializationStep::Step4); break; @@ -407,6 +404,10 @@ uda_c::on_after_register_access( // start the controller running. // StateTransition(InitializationStep::Complete); + // The VMS bootstrap expects SA to be zero IMMEDIATELY + // after completion. + _sa = 0; + update_SA(); } else { @@ -560,7 +561,7 @@ uda_c::GetNextCommand(void) // DMAWriteWord( _ringBase - 4, - 0xff); + 0xffff); // // Raise the interrupt @@ -709,7 +710,7 @@ uda_c::PostResponse( // DMAWriteWord( _ringBase - 2, - 0xff); + 0xffff); // // Raise the interrupt @@ -844,51 +845,13 @@ uda_c::DMAWrite( size_t lengthInBytes, uint8_t* buffer) { - bool timeout = false; - timeout_c timer; - assert ((lengthInBytes % 2) == 0); - // Retry the transfer to work around lower-level DMA issues - while(true) - { - if(!unibusadapter->request_DMA_active("uda r") && - !unibusadapter->request_INTR_active("uda w")) - { - unibusadapter->request_DMA( - this, + return unibusadapter->request_DMA( UNIBUS_CONTROL_DATO, address, reinterpret_cast(buffer), lengthInBytes >> 1); - - // Wait for completion - uint32_t last_address = 0; - while(!unibusadapter->complete_DMA( - this, - &last_address, - &timeout)) - { - timer.wait_us(50); - } - - if (!timeout) - { - // Success! - // timer.wait_us(250); // also a hack - return true; - } - else - { - INFO(" DMA WRITE FAILED, RETRYING."); - } - } - - // Try again - timer.wait_us(100); - } - - return false; } /* @@ -911,50 +874,18 @@ uda_c::DMARead( memset(reinterpret_cast(buffer), 0xc3, bufferSize); - bool timeout = false; - timeout_c timer; + bool success = unibusadapter->request_DMA( + UNIBUS_CONTROL_DATI, + address, + buffer, + lengthInBytes >> 1); - // We retry the transfer to work around lower-level DMA issues - while(true) - { - timeout = false; - - if(!unibusadapter->request_DMA_active("uda r") && - !unibusadapter->request_INTR_active("uda w")) - { - unibusadapter->request_DMA( - this, - UNIBUS_CONTROL_DATI, - address, - buffer, - lengthInBytes >> 1); - - uint32_t last_address = 0; - // Wait for completion - while (!unibusadapter->complete_DMA( - this, - &last_address, - &timeout)) - { - timer.wait_us(50); - } - - if (!timeout) - { - // success! - // timer.wait_us(250); - break; - } - else - { - INFO("DMA READ FAILED (addr o%o length o%o, lastaddr o%o RETRYING", address, lengthInBytes, last_address); - } - } - - // Try again - timer.wait_us(100); + if (success) + { + return reinterpret_cast(buffer); + } + else + { + return nullptr; } - - // timer.wait_us(250); - return reinterpret_cast(buffer); } diff --git a/10.03_app_demo/2_src/makefile b/10.03_app_demo/2_src/makefile index 8f54fda..2a39f9a 100644 --- a/10.03_app_demo/2_src/makefile +++ b/10.03_app_demo/2_src/makefile @@ -93,6 +93,7 @@ OBJECTS = $(OBJDIR)/application.o \ $(OBJDIR)/rk05.o \ $(OBJDIR)/uda.o \ $(OBJDIR)/mscp_server.o \ + $(OBJDIR)/mscp_drive.o \ $(OBJDIR)/storagedrive.o \ $(OBJDIR)/storagecontroller.o \ $(OBJDIR)/demo_io.o \ @@ -203,6 +204,9 @@ $(OBJDIR)/uda.o : $(DEVICE_SRC_DIR)/uda.cpp $(DEVICE_SRC_DIR)/uda.hpp $(OBJDIR)/mscp_server.o : $(DEVICE_SRC_DIR)/mscp_server.cpp $(DEVICE_SRC_DIR)/mscp_server.hpp $(CC) $(CCFLAGS) $< -o $@ +$(OBJDIR)/mscp_drive.o : $(DEVICE_SRC_DIR)/mscp_drive.cpp $(DEVICE_SRC_DIR)/mscp_drive.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 636d539..4078925 100644 --- a/10.03_app_demo/2_src/menu_devices.cpp +++ b/10.03_app_demo/2_src/menu_devices.cpp @@ -72,10 +72,10 @@ void menus_c::menu_devices(void) { unibus->emulation_logic_start(); // PRU is active UNIBUS node // 2 demo controller - demo_io_c demo_io; + //demo_io_c demo_io; //demo_regs_c demo_regs; // mem at 160000: RT11 crashes? - cpu_c cpu; + // cpu_c cpu; // create RL11 + drives RL11_c RL11; // instantiates also 4 RL01/02 drives @@ -89,8 +89,8 @@ void menus_c::menu_devices(void) { // Create UDA50 uda_c UDA50; - demo_io.install(); - demo_io.worker_start(); + // demo_io.install(); + //demo_io.worker_start(); //demo_regs.install(); //demo_regs.worker_start(); @@ -105,8 +105,8 @@ void menus_c::menu_devices(void) { UDA50.install(); UDA50.worker_start(); - cpu.install(); - cpu.worker_start(); + // cpu.install(); + // cpu.worker_start(); while (!ready) { @@ -337,24 +337,24 @@ void menus_c::menu_devices(void) { cout << "Error : " << e.what() << "\n"; } } // ready - cpu.worker_stop(); - cpu.uninstall(); + // cpu.worker_stop(); + // cpu.uninstall(); - RL11.worker_stop(); - RL11.disconnect_from_panel(); - RL11.uninstall(); + //RL11.worker_stop(); + //RL11.disconnect_from_panel(); + //RL11.uninstall(); RK05.worker_stop(); RK05.uninstall(); - UDA50.worker_stop(); - UDA50.uninstall(); + // UDA50.worker_stop(); + // UDA50.uninstall(); //demo_regs.worker_stop(); //demo_regs.uninstall(); - demo_io.worker_stop(); - demo_io.uninstall(); + // demo_io.worker_stop(); + // demo_io.uninstall(); if (unibus->arbitration_active) unibus->emulation_logic_stop(); // undo From be3b6d57eda319f9ca3724d57b8abcaaa4f46aa5 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Mon, 6 May 2019 19:28:20 +0200 Subject: [PATCH 04/18] Implemented AVAILABLE, ERASE, DETERMINE ACCESS PATHS commands. Tweaks to interrupt queueing (request_INTR now blocks until the interrupt actually gets signaled on the unibus). OpenVMS 7.3 now boots on the VAX. --- 10.01_base/2_src/arm/unibusadapter.cpp | 81 ++++++--- 10.01_base/2_src/arm/unibusadapter.hpp | 6 +- 10.02_devices/2_src/mscp_server.cpp | 220 +++++++++++++++++++++---- 10.02_devices/2_src/mscp_server.hpp | 5 +- 10.02_devices/2_src/uda.cpp | 25 +-- 10.03_app_demo/2_src/menu_devices.cpp | 16 +- 6 files changed, 272 insertions(+), 81 deletions(-) diff --git a/10.01_base/2_src/arm/unibusadapter.cpp b/10.01_base/2_src/arm/unibusadapter.cpp index fe136de..7d62e9d 100644 --- a/10.01_base/2_src/arm/unibusadapter.cpp +++ b/10.01_base/2_src/arm/unibusadapter.cpp @@ -92,7 +92,8 @@ irq_request_c::irq_request_c( unsigned level, unsigned vector) : _level(level), - _vector(vector) + _vector(vector), + _isComplete(false) { } @@ -174,13 +175,13 @@ void unibusadapter_c::worker_init_event() { device->on_init_changed(); } + INFO("clearing due to INIT empty %d", _irqRequests.empty()); + // Clear bus request queues - /* pthread_mutex_lock(&_busWorker_mutex); - _dmaRequests.clear(); - _irqRequests.clear(); + while (!_dmaRequests.empty()) _dmaRequests.pop(); + while (!_irqRequests.empty()) _irqRequests.pop(); pthread_mutex_unlock(&_busWorker_mutex); - */ } void unibusadapter_c::worker_power_event() { @@ -194,13 +195,13 @@ void unibusadapter_c::worker_power_event() { device->on_power_changed(); } + INFO("clearing due to power empty %d", _irqRequests.empty()); + // Clear bus request queues - /* pthread_mutex_lock(&_busWorker_mutex); - _dmaRequests.clear(); - _irqRequests.clear(); - pthread_mutex_unlock(&_busWorker_mutex); - */ + while (!_dmaRequests.empty()) _dmaRequests.pop(); + while (!_irqRequests.empty()) _irqRequests.pop(); + pthread_mutex_unlock(&_busWorker_mutex); } // process DATI/DATO access to active device registers @@ -587,11 +588,11 @@ bool unibusadapter_c::request_DMA( void unibusadapter_c::dma_worker() { - + //worker_init_realtime_priority(rt_device); while(true) { dma_request_c* dmaReq = nullptr; - irq_request_c irqReq(0,0); + irq_request_c* irqReq = nullptr; // // Wait for the next request. @@ -610,8 +611,7 @@ void unibusadapter_c::dma_worker() // if (!_irqRequests.empty()) { - irq_request_c const& req = _irqRequests.front(); - irqReq = req; + irqReq = _irqRequests.front(); _irqRequests.pop(); } else @@ -622,16 +622,18 @@ void unibusadapter_c::dma_worker() pthread_mutex_unlock(&_busWorker_mutex); - // Sanity check: Should be no active DMA requests on the PRU. - assert (!request_DMA_active(nullptr)); + // Sanity check: Should be no active DMA or interrupt requests on the PRU. + assert (!request_DMA_active(nullptr) && !request_INTR_active(nullptr)); + /* // If there's an IRQ still active, wait for it to finish. // TODO: find a way to avoid having to do this. timeout_c timer; while (request_INTR_active(nullptr)) { + INFO("intr active"); timer.wait_us(50); - } + } */ if (dmaReq) { @@ -670,7 +672,7 @@ void unibusadapter_c::dma_worker() // Wait for the transfer to complete. // TODO: we're polling the mailbox; is there a more efficient way to do this? timeout_c timeout; - while (request_DMA_active(NULL)) + while (request_DMA_active(nullptr)) { timeout.wait_us(50); } @@ -691,8 +693,14 @@ void unibusadapter_c::dma_worker() dmaReq->SetUnibusEndAddr(mailbox->dma.cur_addr); dmaReq->SetSuccess(mailbox->dma.cur_status == DMA_STATE_READY); - assert(dmaReq->GetUnibusAddr() + dmaReq->GetWordCount() * 2 == mailbox->dma.cur_addr + 2); + if(dmaReq->GetUnibusAddr() + dmaReq->GetWordCount() * 2 != mailbox->dma.cur_addr + 2) + { + FATAL("PRU end addr 0x%x, expected 0x%x", + mailbox->dma.cur_addr + 2, + dmaReq->GetUnibusAddr() + dmaReq->GetWordCount() * 2); + } + // // Signal that the request is complete. // @@ -704,7 +712,7 @@ void unibusadapter_c::dma_worker() else { // Handle interrupt request - switch(irqReq.GetInterruptLevel()) + switch(irqReq->GetInterruptLevel()) { case 4: mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B4; @@ -723,15 +731,30 @@ void unibusadapter_c::dma_worker() break; default: - ERROR("Request_INTR(): Illegal priority %u, aborting", irqReq.GetInterruptLevel()); + ERROR("Request_INTR(): Illegal priority %u, aborting", irqReq->GetInterruptLevel()); return; } - - mailbox->intr.vector = irqReq.GetVector(); + + mailbox->intr.vector = irqReq->GetVector(); // start! mailbox->arm2pru_req = ARM2PRU_INTR; // PRU now changes state + + // Signal that the request has been raised. + pthread_mutex_lock(&_busWorker_mutex); + irqReq->SetComplete(); + pthread_cond_signal(&_requestFinished_cond); + pthread_mutex_unlock(&_busWorker_mutex); + + // Wait for the transfer to complete. + // TODO: we're polling the mailbox; is there a more efficient way to + // do this? (as w/dma) + timeout_c timeout; + while(request_INTR_active(nullptr)) + { + timeout.wait_us(50); + } } } } @@ -746,10 +769,20 @@ void unibusadapter_c::request_INTR(uint32_t level, uint32_t vector) { vector); pthread_mutex_lock(&_busWorker_mutex); - _irqRequests.push(request); + _irqRequests.push(&request); pthread_cond_signal(&_busWakeup_cond); pthread_mutex_unlock(&_busWorker_mutex); + // + // Wait for request to finish. + // + pthread_mutex_lock(&_busWorker_mutex); + while (!request.IsComplete()) + { + pthread_cond_wait(&_requestFinished_cond, &_busWorker_mutex); + } + pthread_mutex_unlock(&_busWorker_mutex); + // // And we're done. // diff --git a/10.01_base/2_src/arm/unibusadapter.hpp b/10.01_base/2_src/arm/unibusadapter.hpp index 5de5821..d902c1b 100644 --- a/10.01_base/2_src/arm/unibusadapter.hpp +++ b/10.01_base/2_src/arm/unibusadapter.hpp @@ -80,10 +80,14 @@ public: uint32_t GetInterruptLevel() { return _level; } uint32_t GetVector() { return _vector; } + bool IsComplete() { return _isComplete; } + + void SetComplete() { _isComplete = true; } private: uint32_t _level; uint32_t _vector; + bool _isComplete; }; @@ -126,7 +130,7 @@ public: private: std::queue _dmaRequests; - std::queue _irqRequests; + std::queue _irqRequests; pthread_t _busWorker_pthread; pthread_cond_t _busWakeup_cond; pthread_cond_t _requestFinished_cond; diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index 3f2a506..a256422 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -156,7 +156,7 @@ mscp_server::Poll(void) ControlMessageHeader* header = reinterpret_cast(message->Message); - DEBUG("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", + INFO("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", message->MessageLength, header->Word3.Command.Opcode, header->Word3.Command.Reserved, @@ -169,6 +169,18 @@ mscp_server::Poll(void) switch (header->Word3.Command.Opcode) { + case Opcodes::AVAILABLE: + cmdStatus = Available(message, header->UnitNumber, header->Word3.Command.Modifiers); + break; + + case Opcodes::DETERMINE_ACCESS_PATHS: + cmdStatus = DetermineAccessPaths(message, header->UnitNumber); + break; + + case Opcodes::ERASE: + cmdStatus = Erase(message, header->UnitNumber, header->Word3.Command.Modifiers); + break; + case Opcodes::GET_UNIT_STATUS: cmdStatus = GetUnitStatus(message, header->UnitNumber, header->Word3.Command.Modifiers); break; @@ -198,7 +210,7 @@ mscp_server::Poll(void) break; } - DEBUG("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); + INFO("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); // // Set the endcode and status bits @@ -241,18 +253,15 @@ mscp_server::Poll(void) message->Word1.Info.Credits = 0; } - //timer.wait_us(250); // // Post the response to the port's response ring. // - if(!_port->PostResponse(message.get())) - { - FATAL("no room at the inn."); - } + if(!_port->PostResponse(message.get())) + { + FATAL("no room at the inn."); + } - // Hack: give interrupts time to settle before doing another transfer. - //timer.wait_us(2500); // // Go around and pick up the next one. @@ -282,6 +291,116 @@ mscp_server::Poll(void) DEBUG("MSCP Polling thread exiting."); } +uint32_t +mscp_server::Available( + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers) +{ + // Message has no message-specific data. + // We don't do much with this now... + // Just set the specified drive as Available if appropriate. + // We do nothing with the spin-down modifier. + + INFO("MSCP AVAILABLE"); + + mscp_drive_c* drive = GetDrive(unitNumber); + + if (nullptr == drive || + !drive->IsAvailable()) + { + return STATUS(Status::UNIT_OFFLINE, 0x3, 0); + } + + drive->SetOffline(); + + return STATUS(Status::SUCCESS, 0x40, 0); // still connected +} + +uint32_t +mscp_server::DetermineAccessPaths( + shared_ptr message, + uint16_t unitNumber) +{ + INFO("MSCP DETERMINE ACCESS PATHS drive %d", unitNumber); + + // "This command must be treated as a no-op that always succeeds + // if the unit is incapable of being connected to more than one + // controller." That's us! + + return STATUS(Status::SUCCESS, 0, 0); +} + +uint32_t +mscp_server::Erase( + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers) +{ + #pragma pack(push,1) + struct EraseParameters + { + uint32_t ByteCount; + uint32_t Unused0; + uint32_t Unused1; + uint32_t Unused2; + uint32_t LBN; + }; + #pragma pack(pop) + + // TODO: Factor this code out (shared w/Read and Write) + EraseParameters* params = + reinterpret_cast(GetParameterPointer(message)); + + INFO ("MSCP ERASE unit %d chan count %d lbn %d", + unitNumber, + params->ByteCount, + params->LBN); + + // Adjust message length for response + message->MessageLength = sizeof(EraseParameters) + + HEADER_SIZE; + + mscp_drive_c* drive = GetDrive(unitNumber); + + // Check unit + if (nullptr == drive || + !drive->IsAvailable()) + { + return STATUS(Status::UNIT_OFFLINE, 0x3, 0); + } + + // Check LBN + if (params->LBN > drive->GetBlockCount() + 1) // + 1 for RCT + { + return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code + } + + // Check byte count + if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize()) + { + return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0, 0); // TODO: as above + } + + // + // OK: do the transfer from a zero'd out buffer to disk + // + unique_ptr memBuffer(new uint8_t[params->ByteCount]); + memset(reinterpret_cast(memBuffer.get()), 0, params->ByteCount); + + drive->Write(params->LBN, + params->ByteCount, + memBuffer.get()); + + // 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, 0); +} + uint32_t mscp_server::GetUnitStatus( shared_ptr message, @@ -311,26 +430,55 @@ mscp_server::GetUnitStatus( // Adjust message length for response message->MessageLength = sizeof(GetUnitStatusResponseParameters) + HEADER_SIZE; - + + + ControlMessageHeader* header = + reinterpret_cast(message->Message); + + if (modifiers & 0x1) + { + // Next Unit modifier: return the next known unit >= unitNumber. + // Unless unitNumber is greater than the number of drives we support + // we just return the unit specified by unitNumber. + if (unitNumber >= _port->GetDriveCount()) + { + // In this case we act as if drive 0 was queried. + unitNumber = 0; + header->UnitNumber = 0; + } + } mscp_drive_c* drive = GetDrive(unitNumber); - if (nullptr == drive || - !drive->IsAvailable()) - { - INFO("Returning UNIT OFFLINE"); - return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum - } - GetUnitStatusResponseParameters* params = reinterpret_cast( GetParameterPointer(message)); + if (nullptr == drive) + { + // No such drive + params->UnitIdentifier = 0; + params->ShadowUnit = 0; + return STATUS(Status::UNIT_OFFLINE, 0x3, 0); // offline; unknown unit. + } + + if(!drive->IsAvailable()) + { + // Known drive, but offline. + params->UnitIdentifier = 0; + params->ShadowUnit = 0; + + return STATUS(Status::UNIT_OFFLINE, 0x23, 0); // offline; no volume available + } + + params->Reserved0 = 0; + params->Reserved1 = 0; + params->Reserved2 = 0; params->UnitFlags = 0; // TODO: 0 for now, which is sane. params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. params->UnitIdentifier = drive->GetUnitID(); params->MediaTypeIdentifier = drive->GetMediaID(); - params->ShadowUnit = unitNumber; // Always equal to unit number + params->ShadowUnit = 0; // Always equal to unit number // // For group, and cylinder size we return 0 -- this is appropriate for the @@ -351,11 +499,11 @@ mscp_server::GetUnitStatus( if (drive->IsOnline()) { - return STATUS(Status::SUCCESS, 0); + return STATUS(Status::SUCCESS, 0, 0); } else { - return STATUS(Status::UNIT_AVAILABLE, 0); + return STATUS(Status::UNIT_AVAILABLE, 0, 0); } } @@ -416,7 +564,7 @@ mscp_server::Online( !drive->IsAvailable()) { INFO("Returning UNIT OFFLINE"); - return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum + return STATUS(Status::UNIT_OFFLINE, 0x3, 0); // unknown -- todo move to enum } bool alreadyOnline = drive->IsOnline(); @@ -437,7 +585,7 @@ mscp_server::Online( params->Reserved1 = 0; return STATUS(Status::SUCCESS | - (alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0); + (alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0, 0); } uint32_t @@ -470,7 +618,7 @@ mscp_server::SetControllerCharacteristics( // if (params->MSCPVersion != 0) { - return STATUS(Status::INVALID_COMMAND, 0); // TODO: set sub-status + return STATUS(Status::INVALID_COMMAND, 0, 0); // TODO: set sub-status } else { @@ -486,7 +634,7 @@ mscp_server::SetControllerCharacteristics( params->HostTimeout = 0xff; // Controller timeout: return the max value. params->TimeAndDate = _port->GetControllerIdentifier(); // Controller ID - return STATUS(Status::SUCCESS, 0); + return STATUS(Status::SUCCESS, 0, 0); } } @@ -540,7 +688,7 @@ mscp_server::SetUnitCharacteristics( !drive->IsAvailable()) { INFO("Returning UNIT OFFLINE"); - return STATUS(Status::UNIT_OFFLINE, 3); + return STATUS(Status::UNIT_OFFLINE, 0x3, 0); } SetUnitCharacteristicsResponseParameters* params = @@ -554,7 +702,7 @@ mscp_server::SetUnitCharacteristics( params->UnitSize = drive->GetBlockCount(); params->VolumeSerialNumber = 0; // We report no serial - return STATUS(Status::SUCCESS, 0); + return STATUS(Status::SUCCESS, 0, 0); } @@ -578,7 +726,7 @@ mscp_server::Read( ReadParameters* params = reinterpret_cast(GetParameterPointer(message)); - DEBUG("MSCP READ unit %d chan o%o pa o%o count %d lbn %d", + INFO ("MSCP READ unit %d chan o%o pa o%o count %d lbn %d", unitNumber, params->BufferPhysicalAddress >> 24, params->BufferPhysicalAddress & 0x00ffffff, @@ -595,7 +743,7 @@ mscp_server::Read( if (nullptr == drive || !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 3); + return STATUS(Status::UNIT_OFFLINE, 0x3, 0); } // TODO: Need to rectify reads/writes to RCT area more cleanly @@ -604,12 +752,12 @@ mscp_server::Read( // Check LBN and byte count if (params->LBN >= drive->GetBlockCount() + 1) // + 1 for RCT write protect flag { - return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0); // TODO: set sub-code + return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code } if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize()) { - return STATUS(Status::INVALID_COMMAND + (0xc << 8), 0); // TODO: as above + return STATUS(Status::INVALID_COMMAND + (0xc << 8), 0, 0); // TODO: as above } // @@ -628,7 +776,7 @@ mscp_server::Read( // not reporting a bad block, but we're doing it for completeness.) params->LBN = 0; - return STATUS(Status::SUCCESS,0); + return STATUS(Status::SUCCESS, 0, 0); } uint32_t @@ -652,7 +800,7 @@ mscp_server::Write( WriteParameters* params = reinterpret_cast(GetParameterPointer(message)); - DEBUG("MSCP WRITE unit %d chan o%o pa o%o count %d lbn %d", + INFO ("MSCP WRITE unit %d chan o%o pa o%o count %d lbn %d", unitNumber, params->BufferPhysicalAddress >> 24, params->BufferPhysicalAddress & 0x00ffffff, @@ -669,19 +817,19 @@ mscp_server::Write( if (nullptr == drive || !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 3); + return STATUS(Status::UNIT_OFFLINE, 0x3, 0); } // Check LBN if (params->LBN > drive->GetBlockCount() + 1) // + 1 for RCT { - return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0); // TODO: set sub-code + return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code } // Check byte count if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize()) { - return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0); // TODO: as above + return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0, 0); // TODO: as above } // @@ -702,7 +850,7 @@ mscp_server::Write( // not reporting a bad block, but we're doing it for completeness.) params->LBN = 0; - return STATUS(Status::SUCCESS,0); + return STATUS(Status::SUCCESS, 0, 0); } uint8_t* diff --git a/10.02_devices/2_src/mscp_server.hpp b/10.02_devices/2_src/mscp_server.hpp index 571bfe1..9110ea4 100644 --- a/10.02_devices/2_src/mscp_server.hpp +++ b/10.02_devices/2_src/mscp_server.hpp @@ -9,7 +9,7 @@ class mscp_drive_c; // 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 STATUS(status, modifier, flags) ((flags) << 8) | (((status) | ((modifier) * 0x20)) << 16) #define GET_STATUS(status) (((status) >> 16) & 0xffff) #define GET_FLAGS(status) (((status) >> 8) & 0xff) @@ -134,6 +134,9 @@ public: bool on_param_changed(parameter_c *param) override { return true; } private: + uint32_t Available(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t DetermineAccessPaths(std::shared_ptr message, uint16_t unitNumber); + uint32_t Erase(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); 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); diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index 60c1665..d635399 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -148,7 +148,7 @@ void uda_c::worker(void) case InitializationStep::Step1: // Wait 100uS, set SA. - timeout.wait_us(100); + timeout.wait_ms(100); INFO("Transition to Init state S1."); // @@ -162,10 +162,8 @@ void uda_c::worker(void) break; case InitializationStep::Step2: - // Wait 100uS, set SA. - timeout.wait_ms(100); - INFO("Transition to Init state S2."); + timeout.wait_ms(1000); // 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. @@ -176,7 +174,7 @@ void uda_c::worker(void) case InitializationStep::Step3: // Wait 100uS, set SA. - timeout.wait_ms(100); + timeout.wait_ms(1000); INFO("Transition to Init state S3."); // Update the SA read value for step 3: @@ -187,6 +185,7 @@ void uda_c::worker(void) break; case InitializationStep::Step4: + timeout.wait_ms(100); // Clear communications area, set SA INFO("Clearing comm area at 0x%x.", _ringBase); @@ -195,10 +194,10 @@ void uda_c::worker(void) // 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) + 4; + i < (_responseRingLength + _commandRingLength) * sizeof(Descriptor) + 6; i += 2) { - DMAWriteWord(_ringBase + i - 4, 0x0); + DMAWriteWord(_ringBase + i - 6, 0x0); } // @@ -225,7 +224,7 @@ void uda_c::worker(void) // _sa = 0x4063; //UDA50 _sa = 0x4042; update_SA(); - Interrupt(); + Interrupt(); break; case InitializationStep::Complete: @@ -317,7 +316,8 @@ uda_c::on_after_register_access( // be generated during normal operation and, if IE=1, // during initialization. _step1Value = value; - intr_vector.value = _interruptVector = (value & 0x7f) << 2; + + intr_vector.value = _interruptVector = ((value & 0x7f) << 2); _interruptEnable = !!(value & 0x80); _responseRingLength = (1 << ((value & 0x700) >> 8)); _commandRingLength = (1 << ((value & 0x3800) >> 11)); @@ -325,6 +325,8 @@ uda_c::on_after_register_access( INFO("Step1: 0x%x", value); INFO("resp ring 0x%x", _responseRingLength); INFO("cmd ring 0x%x", _commandRingLength); + INFO("vector 0x%x", _interruptVector); + INFO("ie %d", _interruptEnable); // Move to step 2. StateTransition(InitializationStep::Step2); @@ -344,7 +346,7 @@ uda_c::on_after_register_access( _ringBase = value & 0xfffe; _purgeInterruptEnable = !!(value & 0x1); - INFO("Step2: 0x%x", value); + INFO("Step2: rb 0x%x pi %d", _ringBase, _purgeInterruptEnable); // Move to step 3 and interrupt as necessary. StateTransition(InitializationStep::Step3); break; @@ -363,7 +365,7 @@ uda_c::on_after_register_access( // [ringbase+0]. _ringBase |= ((value & 0x7fff) << 16); - INFO("Step3: 0x%x", value); + INFO("Step3: ringbase 0x%x", _ringBase); // Move to step 4 and interrupt as necessary. StateTransition(InitializationStep::Step4); break; @@ -740,6 +742,7 @@ uda_c::Interrupt(void) { if (_interruptEnable && _interruptVector != 0) { + INFO("interrupt"); interrupt(); } } diff --git a/10.03_app_demo/2_src/menu_devices.cpp b/10.03_app_demo/2_src/menu_devices.cpp index 4078925..ea3f341 100644 --- a/10.03_app_demo/2_src/menu_devices.cpp +++ b/10.03_app_demo/2_src/menu_devices.cpp @@ -78,7 +78,7 @@ void menus_c::menu_devices(void) { // cpu_c cpu; // create RL11 + drives - RL11_c RL11; // instantiates also 4 RL01/02 drives + // RL11_c RL11; // instantiates also 4 RL01/02 drives cur_device = NULL; paneldriver->reset(); // reset I2C, restart worker() @@ -95,9 +95,9 @@ void menus_c::menu_devices(void) { //demo_regs.install(); //demo_regs.worker_start(); - RL11.install(); - RL11.connect_to_panel(); - RL11.worker_start(); + //RL11.install(); + //RL11.connect_to_panel(); + //RL11.worker_start(); RK05.install(); RK05.worker_start(); @@ -344,11 +344,11 @@ void menus_c::menu_devices(void) { //RL11.disconnect_from_panel(); //RL11.uninstall(); - RK05.worker_stop(); - RK05.uninstall(); + // RK05.worker_stop(); + // RK05.uninstall(); - // UDA50.worker_stop(); - // UDA50.uninstall(); + UDA50.worker_stop(); + UDA50.uninstall(); //demo_regs.worker_stop(); //demo_regs.uninstall(); From 3204e65499969feeee532f2848cdb52336db66f6 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Tue, 7 May 2019 03:20:58 +0200 Subject: [PATCH 05/18] Fixed UDA reset behavior, a few things were not getting re-initialized. Adjusted timings. 2.11bsd boots/runs without apparent issues on 11/84. Tested RT-11 on 11/84 and 11/05, mini-unix on 11/05. --- 10.02_devices/2_src/mscp_drive.cpp | 192 ++++++++++++++++++++++++++ 10.02_devices/2_src/mscp_drive.hpp | 89 ++++++++++++ 10.02_devices/2_src/mscp_server.cpp | 24 ++-- 10.02_devices/2_src/uda.cpp | 27 +++- 10.03_app_demo/2_src/menu_devices.cpp | 18 +-- 5 files changed, 321 insertions(+), 29 deletions(-) create mode 100644 10.02_devices/2_src/mscp_drive.cpp create mode 100644 10.02_devices/2_src/mscp_drive.hpp diff --git a/10.02_devices/2_src/mscp_drive.cpp b/10.02_devices/2_src/mscp_drive.cpp new file mode 100644 index 0000000..47fe260 --- /dev/null +++ b/10.02_devices/2_src/mscp_drive.cpp @@ -0,0 +1,192 @@ +/* + mscp_drive.cpp: Implementation of MSCP disks. +*/ + +#include + +using namespace std; + +#include "logger.hpp" +#include "utils.hpp" +#include "mscp_drive.hpp" +#include "mscp_server.hpp" + +mscp_drive_c::mscp_drive_c( + storagecontroller_c *controller, + uint32_t driveNumber) : + storagedrive_c(controller) +{ + log_label = "MSCPD"; + SetDriveType("RA81"); + SetOffline(); + + // Calculate the unit's ID: + // drive number in upper 32 bits, class/model in lower. + _unitID = (static_cast(0xffffffff) << 32) | 0x02020000; +} + +mscp_drive_c::~mscp_drive_c() +{ + if (file_is_open()) + { + file_close(); + } +} + +uint32_t mscp_drive_c::GetBlockSize() +{ + // + // For the time being this is always 512 bytes. + // + return 512; +} + +uint32_t mscp_drive_c::GetBlockCount() +{ + // TODO: need to be able to handle drives of arbitrary size, not just + // DEC-branded units. + return _driveInfo.BlockCount; +} + +uint32_t mscp_drive_c::GetMediaID() +{ + return _driveInfo.MediaID; +} + +uint64_t mscp_drive_c::GetUnitID() +{ + return _unitID; +} + +bool mscp_drive_c::IsAvailable() +{ + return file_is_open(); +} + +bool mscp_drive_c::IsOnline() +{ + return _online; +} + +void mscp_drive_c::SetOnline() +{ + _online = true; + + // + // Once online, the drive's type and image cannot be changed until + // the drive is offline. + // + type_name.readonly = true; + image_filepath.readonly = true; +} + +void mscp_drive_c::SetOffline() +{ + _online = false; + type_name.readonly = false; + image_filepath.readonly = false; +} + +// +// Writes the specified number of bytes from the provided buffer, +// starting at the specified logical block. +// +void mscp_drive_c::Write( + uint32_t blockNumber, + size_t lengthInBytes, + uint8_t* buffer) +{ + file_write( + buffer, + blockNumber * GetBlockSize(), + lengthInBytes); +} + +// +// Reads the specifed number of bytes starting at the specified logical +// block. Returns a pointer to a buffer containing the data read. +// Caller is responsible for freeing this buffer. +// +uint8_t* mscp_drive_c::Read( + uint32_t blockNumber, + size_t lengthInBytes) +{ + uint8_t* buffer = new uint8_t[lengthInBytes]; + + assert(nullptr != buffer); + + file_read( + buffer, + blockNumber * GetBlockSize(), + lengthInBytes); + + return buffer; +} + +bool mscp_drive_c::on_param_changed( + parameter_c *param) +{ + if (&type_name == param) + { + return SetDriveType(type_name.new_value.c_str()); + } + else if (&image_filepath == param) + { + // + // Try to open the image file. + // + if (file_open(image_filepath.new_value, true)) + { + image_filepath.value = image_filepath.new_value; + return true; + } + + // + // TODO: if file is a nonstandard size? + } + + return false; +} + +bool mscp_drive_c::SetDriveType(const char* typeName) +{ + // + // Search through drive data table for name, + // and if valid, set the type appropriately. + // + int index = 0; + while (g_driveTable[index].BlockCount != 0) + { + if (!strcasecmp(typeName, g_driveTable[index].TypeName)) + { + _driveInfo = g_driveTable[index]; + type_name.value = _driveInfo.TypeName; + capacity.value = GetBlockCount() * GetBlockSize(); + return true; + } + + index++; + } + + // Not found + return false; +} + +void mscp_drive_c::worker(void) +{ + // Nothing to do here at the moment. +} + +void mscp_drive_c::on_power_changed(void) +{ + // Take the drive offline due to power change + SetOffline(); +} + +void mscp_drive_c::on_init_changed(void) +{ + // Take the drive offline due to reset + SetOffline(); +} + + diff --git a/10.02_devices/2_src/mscp_drive.hpp b/10.02_devices/2_src/mscp_drive.hpp new file mode 100644 index 0000000..8bb856a --- /dev/null +++ b/10.02_devices/2_src/mscp_drive.hpp @@ -0,0 +1,89 @@ +/* + mscp_drive.hpp: Implementation of MSCP drive, used with MSCP controller. +*/ + +#pragma once + +#include +#include +#include "parameter.hpp" +#include "storagedrive.hpp" + +// +// Implements the backing store for MSCP disk images +// +class mscp_drive_c : public storagedrive_c +{ +public: + mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber); + ~mscp_drive_c(void); + + uint32_t GetBlockSize(void); + uint32_t GetBlockCount(void); + uint32_t GetMediaID(void); + uint64_t GetUnitID(void); + + void SetOnline(void); + void SetOffline(void); + bool IsOnline(void); + bool IsAvailable(void); + + void Write( + uint32_t blockNumber, + size_t lengthInBytes, + uint8_t* buffer); + + uint8_t* Read( + uint32_t blockNumber, + size_t lengthInBytes); + +public: + bool on_param_changed(parameter_c *param) override; + void on_power_changed(void) override; + void on_init_changed(void) override; + + void worker(void) override; + +private: + + struct DriveInfo + { + char TypeName[16]; + size_t BlockCount; + uint32_t MediaID; + bool Removable; + bool ReadOnly; + }; + + DriveInfo g_driveTable[23] + { + { "RX50", 800, 0x25658032, true, false }, + { "RX33", 2400, 0x25658021, true, false }, + { "RD51", 21600, 0x25644033, false, false }, + { "RD31", 41560, 0x2564401f, false, false }, + { "RC25", 50902, 0x20643019, true, false }, + { "RC25F", 50902, 0x20643319, true, false }, + { "RD52", 60480, 0x25644034, false, false }, + { "RD32", 83236, 0x25641047, false, false }, + { "RD53", 138672, 0x25644035, false, false }, + { "RA80", 237212, 0x20643019, false, false }, + { "RD54", 311200, 0x25644036, false, false }, + { "RA60", 400176, 0x22a4103c, true, false }, + { "RA70", 547041, 0x20643019, false, false }, + { "RA81", 891072, 0x25641051, false, false }, + { "RA82", 1216665, 0x25641052, false, false }, + { "RA71", 1367310, 0x25641047, false, false }, + { "RRD40", 1331200, 0x25652228, true, true }, + { "RA72", 1953300, 0x25641048, false, false }, + { "RA90", 2376153, 0x2564105a, false, false }, + { "RA92", 2940951, 0x2564105c, false, false }, + { "RA73", 3920490, 0x25641049, false, false }, + { "JD90", 2376153, 0x2564105d, false, false }, + { "", 0, 0, false, false } + }; + + bool SetDriveType(const char* typeName); + DriveInfo _driveInfo; + bool _online; + uint64_t _unitID; +}; diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index a256422..b10b47a 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -156,7 +156,7 @@ mscp_server::Poll(void) ControlMessageHeader* header = reinterpret_cast(message->Message); - INFO("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", + DEBUG("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", message->MessageLength, header->Word3.Command.Opcode, header->Word3.Command.Reserved, @@ -210,7 +210,7 @@ mscp_server::Poll(void) break; } - INFO("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); + DEBUG("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); // // Set the endcode and status bits @@ -302,7 +302,7 @@ mscp_server::Available( // Just set the specified drive as Available if appropriate. // We do nothing with the spin-down modifier. - INFO("MSCP AVAILABLE"); + DEBUG("MSCP AVAILABLE"); mscp_drive_c* drive = GetDrive(unitNumber); @@ -322,7 +322,7 @@ mscp_server::DetermineAccessPaths( shared_ptr message, uint16_t unitNumber) { - INFO("MSCP DETERMINE ACCESS PATHS drive %d", unitNumber); + DEBUG("MSCP DETERMINE ACCESS PATHS drive %d", unitNumber); // "This command must be treated as a no-op that always succeeds // if the unit is incapable of being connected to more than one @@ -352,7 +352,7 @@ mscp_server::Erase( EraseParameters* params = reinterpret_cast(GetParameterPointer(message)); - INFO ("MSCP ERASE unit %d chan count %d lbn %d", + DEBUG("MSCP ERASE unit %d chan count %d lbn %d", unitNumber, params->ByteCount, params->LBN); @@ -425,7 +425,7 @@ mscp_server::GetUnitStatus( }; #pragma pack(pop) - INFO("MSCP GET UNIT STATUS drive %d", unitNumber); + DEBUG("MSCP GET UNIT STATUS drive %d", unitNumber); // Adjust message length for response message->MessageLength = sizeof(GetUnitStatusResponseParameters) + @@ -552,7 +552,7 @@ mscp_server::Online( }; #pragma pack(pop) - INFO("MSCP ONLINE drive %d", unitNumber); + DEBUG("MSCP ONLINE drive %d", unitNumber); // Adjust message length for response message->MessageLength = sizeof(OnlineResponseParameters) + @@ -563,7 +563,6 @@ mscp_server::Online( if (nullptr == drive || !drive->IsAvailable()) { - INFO("Returning UNIT OFFLINE"); return STATUS(Status::UNIT_OFFLINE, 0x3, 0); // unknown -- todo move to enum } @@ -607,7 +606,7 @@ mscp_server::SetControllerCharacteristics( reinterpret_cast( GetParameterPointer(message)); - INFO("MSCP SET CONTROLLER CHARACTERISTICS"); + DEBUG("MSCP SET CONTROLLER CHARACTERISTICS"); // Adjust message length for response message->MessageLength = sizeof(SetControllerCharacteristicsParameters) + @@ -660,7 +659,7 @@ mscp_server::SetUnitCharacteristics( // TODO: handle Set Write Protect modifier - INFO("MSCP SET UNIT CHARACTERISTICS drive %d", unitNumber); + DEBUG("MSCP SET UNIT CHARACTERISTICS drive %d", unitNumber); // TODO: mostly same as Online command: should share logic. #pragma pack(push,1) @@ -687,7 +686,6 @@ mscp_server::SetUnitCharacteristics( if (nullptr == drive || !drive->IsAvailable()) { - INFO("Returning UNIT OFFLINE"); return STATUS(Status::UNIT_OFFLINE, 0x3, 0); } @@ -726,7 +724,7 @@ mscp_server::Read( ReadParameters* params = reinterpret_cast(GetParameterPointer(message)); - INFO ("MSCP READ unit %d chan o%o pa o%o count %d lbn %d", + DEBUG("MSCP READ unit %d chan o%o pa o%o count %d lbn %d", unitNumber, params->BufferPhysicalAddress >> 24, params->BufferPhysicalAddress & 0x00ffffff, @@ -800,7 +798,7 @@ mscp_server::Write( WriteParameters* params = reinterpret_cast(GetParameterPointer(message)); - INFO ("MSCP WRITE unit %d chan o%o pa o%o count %d lbn %d", + DEBUG("MSCP WRITE unit %d chan o%o pa o%o count %d lbn %d", unitNumber, params->BufferPhysicalAddress >> 24, params->BufferPhysicalAddress & 0x00ffffff, diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index d635399..446b917 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -19,6 +19,8 @@ uda_c::uda_c() : _responseRingPointer(0), _interruptVector(0), _interruptEnable(false), + _purgeInterruptEnable(false), + _step1Value(0), _initStep(InitializationStep::Uninitialized), _next_step(false) { @@ -81,6 +83,18 @@ void uda_c::Reset(void) _sa = 0; update_SA(); + _ringBase = 0; + _commandRingLength = 0; + _responseRingLength = 0; + _commandRingPointer = 0; + _responseRingPointer = 0; + _interruptVector = 0; + intr_vector.value = 0; + _interruptEnable = false; + _purgeInterruptEnable = false; + _next_step = false; + + // Signal the worker to begin the initialization sequence. StateTransition(InitializationStep::Uninitialized); @@ -148,7 +162,7 @@ void uda_c::worker(void) case InitializationStep::Step1: // Wait 100uS, set SA. - timeout.wait_ms(100); + timeout.wait_us(1000); INFO("Transition to Init state S1."); // @@ -163,7 +177,7 @@ void uda_c::worker(void) case InitializationStep::Step2: INFO("Transition to Init state S2."); - timeout.wait_ms(1000); + timeout.wait_us(1000); // 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. @@ -174,7 +188,7 @@ void uda_c::worker(void) case InitializationStep::Step3: // Wait 100uS, set SA. - timeout.wait_ms(1000); + timeout.wait_us(1000); INFO("Transition to Init state S3."); // Update the SA read value for step 3: @@ -185,7 +199,7 @@ void uda_c::worker(void) break; case InitializationStep::Step4: - timeout.wait_ms(100); + timeout.wait_us(100); // Clear communications area, set SA INFO("Clearing comm area at 0x%x.", _ringBase); @@ -269,7 +283,7 @@ uda_c::on_after_register_access( { // "When written with any value, it causes a hard initialization // of the port and the device controller." - // INFO("Reset due to IP read"); + DEBUG("Reset due to IP read"); Reset(); } else @@ -278,7 +292,7 @@ uda_c::on_after_register_access( // to initiate polling..." if (_initStep == InitializationStep::Complete) { - INFO("Request to start polling."); + DEBUG("Request to start polling."); _server->InitPolling(); } } @@ -742,7 +756,6 @@ uda_c::Interrupt(void) { if (_interruptEnable && _interruptVector != 0) { - INFO("interrupt"); interrupt(); } } diff --git a/10.03_app_demo/2_src/menu_devices.cpp b/10.03_app_demo/2_src/menu_devices.cpp index ea3f341..74d401f 100644 --- a/10.03_app_demo/2_src/menu_devices.cpp +++ b/10.03_app_demo/2_src/menu_devices.cpp @@ -78,7 +78,7 @@ void menus_c::menu_devices(void) { // cpu_c cpu; // create RL11 + drives - // RL11_c RL11; // instantiates also 4 RL01/02 drives + RL11_c RL11; // instantiates also 4 RL01/02 drives cur_device = NULL; paneldriver->reset(); // reset I2C, restart worker() @@ -95,9 +95,9 @@ void menus_c::menu_devices(void) { //demo_regs.install(); //demo_regs.worker_start(); - //RL11.install(); - //RL11.connect_to_panel(); - //RL11.worker_start(); + RL11.install(); + RL11.connect_to_panel(); + RL11.worker_start(); RK05.install(); RK05.worker_start(); @@ -340,12 +340,12 @@ void menus_c::menu_devices(void) { // cpu.worker_stop(); // cpu.uninstall(); - //RL11.worker_stop(); - //RL11.disconnect_from_panel(); - //RL11.uninstall(); + RL11.worker_stop(); + RL11.disconnect_from_panel(); + RL11.uninstall(); - // RK05.worker_stop(); - // RK05.uninstall(); + RK05.worker_stop(); + RK05.uninstall(); UDA50.worker_stop(); UDA50.uninstall(); From a00f0592dc5460fbbeebdbc413755dfc23c453c8 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Tue, 7 May 2019 19:03:49 +0200 Subject: [PATCH 06/18] Added small workaround for (possible) bug in VMS secondary bootstrap; improved reset behavior. 4.3bsd still panics during uda bringup. --- 10.01_base/2_src/arm/unibusadapter.cpp | 59 ++++++++++++++------------ 10.01_base/2_src/arm/unibusadapter.hpp | 1 + 10.02_devices/2_src/mscp_server.cpp | 11 +++-- 10.02_devices/2_src/uda.cpp | 13 +++--- 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/10.01_base/2_src/arm/unibusadapter.cpp b/10.01_base/2_src/arm/unibusadapter.cpp index 7d62e9d..563f31b 100644 --- a/10.01_base/2_src/arm/unibusadapter.cpp +++ b/10.01_base/2_src/arm/unibusadapter.cpp @@ -175,13 +175,9 @@ void unibusadapter_c::worker_init_event() { device->on_init_changed(); } - INFO("clearing due to INIT empty %d", _irqRequests.empty()); // Clear bus request queues - pthread_mutex_lock(&_busWorker_mutex); - while (!_dmaRequests.empty()) _dmaRequests.pop(); - while (!_irqRequests.empty()) _irqRequests.pop(); - pthread_mutex_unlock(&_busWorker_mutex); + rundown_bus_requests(); } void unibusadapter_c::worker_power_event() { @@ -195,13 +191,8 @@ void unibusadapter_c::worker_power_event() { device->on_power_changed(); } - INFO("clearing due to power empty %d", _irqRequests.empty()); - // Clear bus request queues - pthread_mutex_lock(&_busWorker_mutex); - while (!_dmaRequests.empty()) _dmaRequests.pop(); - while (!_irqRequests.empty()) _irqRequests.pop(); - pthread_mutex_unlock(&_busWorker_mutex); + rundown_bus_requests(); } // process DATI/DATO access to active device registers @@ -625,16 +616,6 @@ void unibusadapter_c::dma_worker() // Sanity check: Should be no active DMA or interrupt requests on the PRU. assert (!request_DMA_active(nullptr) && !request_INTR_active(nullptr)); - /* - // If there's an IRQ still active, wait for it to finish. - // TODO: find a way to avoid having to do this. - timeout_c timer; - while (request_INTR_active(nullptr)) - { - INFO("intr active"); - timer.wait_us(50); - } */ - if (dmaReq) { // We do the DMA transfer in chunks so we can handle arbitrary buffer sizes. @@ -693,14 +674,9 @@ void unibusadapter_c::dma_worker() dmaReq->SetUnibusEndAddr(mailbox->dma.cur_addr); dmaReq->SetSuccess(mailbox->dma.cur_status == DMA_STATE_READY); - if(dmaReq->GetUnibusAddr() + dmaReq->GetWordCount() * 2 != mailbox->dma.cur_addr + 2) + assert(dmaReq->GetUnibusAddr() + dmaReq->GetWordCount() * 2 == + mailbox->dma.cur_addr + 2); - { - FATAL("PRU end addr 0x%x, expected 0x%x", - mailbox->dma.cur_addr + 2, - dmaReq->GetUnibusAddr() + dmaReq->GetWordCount() * 2); - } - // // Signal that the request is complete. // @@ -759,6 +735,33 @@ void unibusadapter_c::dma_worker() } } +void unibusadapter_c::rundown_bus_requests() +{ + // + // Cancel all pending DMA and IRQ requests, freeing threads waiting + // on completion. + // + pthread_mutex_lock(&_busWorker_mutex); + while (!_dmaRequests.empty()) + { + dma_request_c* dmaReq = _dmaRequests.front(); + dmaReq->SetSuccess(false); + dmaReq->SetComplete(); + pthread_cond_signal(&_requestFinished_cond); + _dmaRequests.pop(); + } + while (!_irqRequests.empty()) + { + irq_request_c* irqReq = _irqRequests.front(); + irqReq->SetComplete(); + pthread_cond_signal(&_requestFinished_cond); + _irqRequests.pop(); + } + pthread_mutex_unlock(&_busWorker_mutex); + +} + + void unibusadapter_c::request_INTR(uint32_t level, uint32_t vector) { // // Acquire bus mutex; append new request to queue. diff --git a/10.01_base/2_src/arm/unibusadapter.hpp b/10.01_base/2_src/arm/unibusadapter.hpp index d902c1b..a6c45a4 100644 --- a/10.01_base/2_src/arm/unibusadapter.hpp +++ b/10.01_base/2_src/arm/unibusadapter.hpp @@ -124,6 +124,7 @@ public: bool request_DMA(uint8_t unibus_control, uint32_t unibus_addr, uint16_t *buffer, uint32_t wordcount); void request_INTR(uint32_t level, uint32_t vector); + void rundown_bus_requests(void); void print_shared_register_map(void); diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index b10b47a..3bf63af 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -122,9 +122,6 @@ mscp_server::Poll(void) pthread_mutex_unlock(&polling_mutex); - // timer.wait_us(100); - - if (_abort_polling) { break; @@ -262,6 +259,14 @@ mscp_server::Poll(void) FATAL("no room at the inn."); } + // + // This delay works around an issue in the VMS bootstrap -- unsure of the + // exact cause, it appears to be a race condition exacerbated by the speed + // at which we're able to process commands in the ring buffer (we are much + // faster than any real MSCP device ever was.) + // This is likely a hack, we may be papering over a bug somewhere. + // + timer.wait_us(100); // // Go around and pick up the next one. diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index 446b917..cf49b26 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -78,10 +78,7 @@ uda_c::~uda_c() void uda_c::Reset(void) { - DEBUG("UDA reset"); - - _sa = 0; - update_SA(); + INFO("UDA reset"); _ringBase = 0; _commandRingLength = 0; @@ -94,11 +91,13 @@ void uda_c::Reset(void) _purgeInterruptEnable = false; _next_step = false; + _server->Reset(); // Signal the worker to begin the initialization sequence. StateTransition(InitializationStep::Uninitialized); - _server->Reset(); + _sa = 0; + update_SA(); } uint32_t uda_c::GetDriveCount(void) @@ -126,7 +125,7 @@ void uda_c::StateTransition( void uda_c::worker(void) { - worker_init_realtime_priority(rt_device); + worker_init_realtime_priority(rt_max); timeout_c timeout; @@ -862,6 +861,7 @@ uda_c::DMAWrite( uint8_t* buffer) { assert ((lengthInBytes % 2) == 0); + assert (address < 0x40000); return unibusadapter->request_DMA( UNIBUS_CONTROL_DATO, @@ -883,6 +883,7 @@ uda_c::DMARead( { assert (bufferSize >= lengthInBytes); assert((lengthInBytes % 2) == 0); + assert (address < 0x40000); uint16_t* buffer = new uint16_t[bufferSize >> 1]; From c6958e16607608b111ddd70cc6ddd5bf7c1b2baa Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Wed, 8 May 2019 05:34:40 +0200 Subject: [PATCH 07/18] Implemented the last few unimplemented MSCP commands; as yet untested with real PDP-11/VAX code (have yet to find a case that uses them.) General code cleanup/refactoring. Added header comments. Added "use image size" parameter for MSCP disks -- block count derived from image file size rather than DEC drive geometry; allows for arbitrarily large disks (up to 2TB, theoretically.) --- 10.01_base/2_src/arm/storagedrive.cpp | 6 + 10.01_base/2_src/arm/storagedrive.hpp | 1 + 10.02_devices/2_src/mscp_drive.cpp | 88 ++++- 10.02_devices/2_src/mscp_drive.hpp | 29 +- 10.02_devices/2_src/mscp_server.cpp | 443 +++++++++++++++++--------- 10.02_devices/2_src/mscp_server.hpp | 6 + 10.02_devices/2_src/uda.cpp | 11 + 7 files changed, 430 insertions(+), 154 deletions(-) diff --git a/10.01_base/2_src/arm/storagedrive.cpp b/10.01_base/2_src/arm/storagedrive.cpp index 9c261c5..92b1e0d 100644 --- a/10.01_base/2_src/arm/storagedrive.cpp +++ b/10.01_base/2_src/arm/storagedrive.cpp @@ -145,6 +145,12 @@ void storagedrive_c::file_write(uint8_t *buffer, uint64_t position, unsigned len f.flush(); } +uint64_t storagedrive_c::file_size(void) +{ + f.seekp(0, ios::end); + return f.tellp(); +} + void storagedrive_c::file_close(void) { assert(file_is_open()); f.close(); diff --git a/10.01_base/2_src/arm/storagedrive.hpp b/10.01_base/2_src/arm/storagedrive.hpp index fae8267..4c63287 100644 --- a/10.01_base/2_src/arm/storagedrive.hpp +++ b/10.01_base/2_src/arm/storagedrive.hpp @@ -67,6 +67,7 @@ public: bool file_is_open(void); void file_read(uint8_t *buffer, uint64_t position, unsigned len); void file_write(uint8_t *buffer, uint64_t position, unsigned len); + uint64_t file_size(void); void file_close(void); storagedrive_c(storagecontroller_c *controller); diff --git a/10.02_devices/2_src/mscp_drive.cpp b/10.02_devices/2_src/mscp_drive.cpp index 47fe260..03e17ab 100644 --- a/10.02_devices/2_src/mscp_drive.cpp +++ b/10.02_devices/2_src/mscp_drive.cpp @@ -3,6 +3,7 @@ */ #include +#include using namespace std; @@ -14,7 +15,8 @@ using namespace std; mscp_drive_c::mscp_drive_c( storagecontroller_c *controller, uint32_t driveNumber) : - storagedrive_c(controller) + storagedrive_c(controller), + _useImageSize(false) { log_label = "MSCPD"; SetDriveType("RA81"); @@ -22,7 +24,11 @@ mscp_drive_c::mscp_drive_c( // Calculate the unit's ID: // drive number in upper 32 bits, class/model in lower. - _unitID = (static_cast(0xffffffff) << 32) | 0x02020000; + _unitID = (static_cast(driveNumber) << 32) | 0x02020000; + + // Initialize the RCT area + _rctData.reset(new uint8_t[GetBlockSize()]); + memset(reinterpret_cast(_rctData.get()), 0, GetBlockSize()); } mscp_drive_c::~mscp_drive_c() @@ -43,9 +49,27 @@ uint32_t mscp_drive_c::GetBlockSize() uint32_t mscp_drive_c::GetBlockCount() { - // TODO: need to be able to handle drives of arbitrary size, not just - // DEC-branded units. - return _driveInfo.BlockCount; + if (_useImageSize) + { + // Return the image size / Block size (rounding down). + return file_size() / GetBlockSize(); + } + else + { + // + // Use the size defined by the drive type. + // + return _driveInfo.BlockCount; + } +} + +uint32_t mscp_drive_c::GetRCTBlockCount() +{ + // + // We provide only a single RCT block, required by the MSCP spec for the volume + // write-protect flags. + // + return 1; } uint32_t mscp_drive_c::GetMediaID() @@ -123,6 +147,50 @@ uint8_t* mscp_drive_c::Read( return buffer; } +// +// Writes a single block's worth of data from the provided buffer into the +// RCT area at the specified RCT block. Buffer must be at least as large +// as the disk's block size. +// +void mscp_drive_c::WriteRCTBlock( + uint32_t rctBlockNumber, + uint8_t* buffer) +{ + assert (rctBlockNumber < GetRCTBlockCount()); + + memcpy( + reinterpret_cast(_rctData.get() + rctBlockNumber * GetBlockSize()), + reinterpret_cast(buffer), + GetBlockSize()); +} + +// +// Reads a single block's worth of data from the RCT area (at the specified +// block offset). Returns a pointer to a buffer containing the data read. +// Caller is responsible for freeing this buffer. +// +uint8_t* mscp_drive_c::ReadRCTBlock( + uint32_t rctBlockNumber) +{ + assert (rctBlockNumber < GetRCTBlockCount()); + + uint8_t* buffer = new uint8_t[GetBlockSize()]; + assert (nullptr != buffer); + + memcpy( + reinterpret_cast(buffer), + reinterpret_cast(_rctData.get() + rctBlockNumber * GetBlockSize()), + GetBlockSize()); + + return buffer; +} + +void mscp_drive_c::UpdateCapacity() +{ + capacity.value = + GetBlockCount() * GetBlockSize(); +} + bool mscp_drive_c::on_param_changed( parameter_c *param) { @@ -138,12 +206,20 @@ bool mscp_drive_c::on_param_changed( if (file_open(image_filepath.new_value, true)) { image_filepath.value = image_filepath.new_value; + UpdateCapacity(); return true; } // // TODO: if file is a nonstandard size? } + else if (&use_image_size == param) + { + _useImageSize = use_image_size.new_value; + use_image_size.value = use_image_size.new_value; + UpdateCapacity(); + return true; + } return false; } @@ -161,7 +237,7 @@ bool mscp_drive_c::SetDriveType(const char* typeName) { _driveInfo = g_driveTable[index]; type_name.value = _driveInfo.TypeName; - capacity.value = GetBlockCount() * GetBlockSize(); + UpdateCapacity(); return true; } diff --git a/10.02_devices/2_src/mscp_drive.hpp b/10.02_devices/2_src/mscp_drive.hpp index 8bb856a..318dab5 100644 --- a/10.02_devices/2_src/mscp_drive.hpp +++ b/10.02_devices/2_src/mscp_drive.hpp @@ -20,6 +20,7 @@ public: uint32_t GetBlockSize(void); uint32_t GetBlockCount(void); + uint32_t GetRCTBlockCount(void); uint32_t GetMediaID(void); uint64_t GetUnitID(void); @@ -35,7 +36,14 @@ public: uint8_t* Read( uint32_t blockNumber, - size_t lengthInBytes); + size_t lengthInBytes); + + void WriteRCTBlock( + uint32_t rctBlockNumber, + uint8_t* buffer); + + uint8_t* ReadRCTBlock( + uint32_t rctBlockNumber); public: bool on_param_changed(parameter_c *param) override; @@ -44,6 +52,10 @@ public: void worker(void) override; +public: + parameter_bool_c use_image_size = parameter_bool_c( + this, "useimagesize", "uis", false, "Determine unit size from image file instead of drive type"); + private: struct DriveInfo @@ -55,7 +67,7 @@ private: bool ReadOnly; }; - DriveInfo g_driveTable[23] + DriveInfo g_driveTable[22] { { "RX50", 800, 0x25658032, true, false }, { "RX33", 2400, 0x25658021, true, false }, @@ -78,12 +90,23 @@ private: { "RA90", 2376153, 0x2564105a, false, false }, { "RA92", 2940951, 0x2564105c, false, false }, { "RA73", 3920490, 0x25641049, false, false }, - { "JD90", 2376153, 0x2564105d, false, false }, { "", 0, 0, false, false } }; bool SetDriveType(const char* typeName); + void UpdateCapacity(void); DriveInfo _driveInfo; bool _online; uint64_t _unitID; + bool _useImageSize; + + // + // RCT ("Replacement and Caching Table") data: + // At this time we provide the minimum required by the MSCP spec, + // a single block used to provide the volume write-protect flags. + // We do not require additional RCT space because the underlying media + // (an image file) doesn't have bad sectors. + // This data is not persisted to disk as it is unnecessary. + // + unique_ptr _rctData; }; diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index 3bf63af..a95d06b 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -1,7 +1,38 @@ +/* + mscp_server.cpp: Implementation a simple MSCP server. + + This provides an implementation of the Minimal MSCP subset outlined + in AA-L619A-TK (Chapter 6). It takes a few liberties and errs on + the side of implementation simplicity. + + In particular: + All commands are executed sequentially, as they appear in the + command ring. This includes any commands in the "Immediate" + category. Technically this is incorrect: Immediate commands + should execute as soon as possible, before any other commands. + In practice I have yet to find code that cares. + + This simplifies the implementation significantly, and apart + from maintaining fealty to the MSCP spec for Immediate commands, + there's no good reason to make it more complex: real MSCP + controllers (like the original UDA50) would resequence commands + to allow optimal throughput across multiple units, etc. On the + unibone, the underlying storage and the execution speed of the + processor is orders of magnitude faster, so even a brute-force + braindead implementation like this can saturate the Unibus. + + TODO: + Some commands aren't checked as thoroughly for errors as they could be, + and at this time NXM (attempts to address non-existent memory) are + almost completely unhandled. + + +*/ #include #include #include +#include using namespace std; @@ -163,11 +194,24 @@ mscp_server::Poll(void) header->ReferenceNumber); uint32_t cmdStatus = 0; + uint16_t modifiers = header->Word3.Command.Modifiers; switch (header->Word3.Command.Opcode) { + case Opcodes::ABORT: + cmdStatus = Abort(message); + break; + + case Opcodes::ACCESS: + cmdStatus = Access(message, header->UnitNumber); + break; + case Opcodes::AVAILABLE: - cmdStatus = Available(message, header->UnitNumber, header->Word3.Command.Modifiers); + cmdStatus = Available(message, header->UnitNumber, modifiers); + break; + + case Opcodes::COMPARE_HOST_DATA: + cmdStatus = CompareHostData(message, header->UnitNumber); break; case Opcodes::DETERMINE_ACCESS_PATHS: @@ -175,15 +219,27 @@ mscp_server::Poll(void) break; case Opcodes::ERASE: - cmdStatus = Erase(message, header->UnitNumber, header->Word3.Command.Modifiers); + cmdStatus = Erase(message, header->UnitNumber, modifiers); + break; + + case Opcodes::GET_COMMAND_STATUS: + cmdStatus = GetCommandStatus(message); break; case Opcodes::GET_UNIT_STATUS: - cmdStatus = GetUnitStatus(message, header->UnitNumber, header->Word3.Command.Modifiers); + cmdStatus = GetUnitStatus(message, header->UnitNumber, modifiers); break; case Opcodes::ONLINE: - cmdStatus = Online(message, header->UnitNumber, header->Word3.Command.Modifiers); + cmdStatus = Online(message, header->UnitNumber, modifiers); + break; + + case Opcodes::READ: + cmdStatus = Read(message, header->UnitNumber, modifiers); + break; + + case Opcodes::REPLACE: + cmdStatus = Replace(message, header->UnitNumber); break; case Opcodes::SET_CONTROLLER_CHARACTERISTICS: @@ -191,15 +247,11 @@ mscp_server::Poll(void) 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); + cmdStatus = SetUnitCharacteristics(message, header->UnitNumber, modifiers); break; case Opcodes::WRITE: - cmdStatus = Write(message, header->UnitNumber, header->Word3.Command.Modifiers); + cmdStatus = Write(message, header->UnitNumber, modifiers); break; default: @@ -260,13 +312,14 @@ mscp_server::Poll(void) } // - // This delay works around an issue in the VMS bootstrap -- unsure of the + // This delay works around an issue in various bootstraps -- unsure of the // exact cause, it appears to be a race condition exacerbated by the speed // at which we're able to process commands in the ring buffer (we are much - // faster than any real MSCP device ever was.) + // faster than any real MSCP device ever was) and the liberties that bootstrap + // code takes with the MSCP spec to save code space. // This is likely a hack, we may be papering over a bug somewhere. // - timer.wait_us(100); + timer.wait_us(2000); // // Go around and pick up the next one. @@ -296,6 +349,25 @@ mscp_server::Poll(void) DEBUG("MSCP Polling thread exiting."); } +uint32_t +mscp_server::Abort( + shared_ptr message) +{ + INFO("MSCP ABORT"); + + // + // Since we do not reorder messages and in fact pick up and execute + // them one at a time, sequentially as they appear in the ring buffer, + // by the time we've gotten this command, the command it's referring + // to is long gone. + // This is legal behavior and it's legal for us to ignore ABORT in this + // case. + // + // We just return SUCCESS here. + return STATUS(Status::SUCCESS, 0, 0); +} + + uint32_t mscp_server::Available( shared_ptr message, @@ -322,6 +394,33 @@ mscp_server::Available( return STATUS(Status::SUCCESS, 0x40, 0); // still connected } +uint32_t +mscp_server::Access( + shared_ptr message, + uint16_t unitNumber) +{ + INFO("MSCP ACCESS"); + + return DoDiskTransfer( + Opcodes::ACCESS, + message, + unitNumber, + 0); +} + +uint32_t +mscp_server::CompareHostData( + shared_ptr message, + uint16_t unitNumber) +{ + INFO("MSCP COMPARE HOST DATA"); + return DoDiskTransfer( + Opcodes::COMPARE_HOST_DATA, + message, + unitNumber, + 0); +} + uint32_t mscp_server::DetermineAccessPaths( shared_ptr message, @@ -342,66 +441,40 @@ mscp_server::Erase( uint16_t unitNumber, uint16_t modifiers) { + return DoDiskTransfer( + Opcodes::ERASE, + message, + unitNumber, + modifiers); +} + +uint32_t +mscp_server::GetCommandStatus( + shared_ptr message) +{ + INFO("MSCP GET COMMAND STATUS"); + #pragma pack(push,1) - struct EraseParameters + struct GetCommandStatusResponseParameters { - uint32_t ByteCount; - uint32_t Unused0; - uint32_t Unused1; - uint32_t Unused2; - uint32_t LBN; + uint32_t OutstandingReferenceNumber; + uint32_t CommandStatus; }; #pragma pack(pop) - // TODO: Factor this code out (shared w/Read and Write) - EraseParameters* params = - reinterpret_cast(GetParameterPointer(message)); + message->MessageLength = sizeof(GetCommandStatusResponseParameters) + + HEADER_SIZE; - DEBUG("MSCP ERASE unit %d chan count %d lbn %d", - unitNumber, - params->ByteCount, - params->LBN); - - // Adjust message length for response - message->MessageLength = sizeof(EraseParameters) + - HEADER_SIZE; - - mscp_drive_c* drive = GetDrive(unitNumber); - - // Check unit - if (nullptr == drive || - !drive->IsAvailable()) - { - return STATUS(Status::UNIT_OFFLINE, 0x3, 0); - } - - // Check LBN - if (params->LBN > drive->GetBlockCount() + 1) // + 1 for RCT - { - return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code - } - - // Check byte count - if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize()) - { - return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0, 0); // TODO: as above - } + + GetCommandStatusResponseParameters* params = + reinterpret_cast( + GetParameterPointer(message)); // - // OK: do the transfer from a zero'd out buffer to disk + // This will always return zero; as with the ABORT command, at this + // point the command being referenced has already been executed. // - unique_ptr memBuffer(new uint8_t[params->ByteCount]); - memset(reinterpret_cast(memBuffer.get()), 0, params->ByteCount); - - drive->Write(params->LBN, - params->ByteCount, - memBuffer.get()); - - // 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; + params->CommandStatus = 0; return STATUS(Status::SUCCESS, 0, 0); } @@ -592,6 +665,31 @@ mscp_server::Online( (alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0, 0); } +uint32_t +mscp_server::Replace( + shared_ptr message, + uint16_t unitNumber) +{ + INFO("MSCP REPLACE"); + // + // We treat this as a success for valid units as we do no block replacement at all. + // Best just to smile and nod. We could be more vigilant and check LBNs, etc... + // + message->MessageLength = HEADER_SIZE; + + mscp_drive_c* drive = GetDrive(unitNumber); + + if (nullptr == drive || + !drive->IsAvailable()) + { + return STATUS(Status::UNIT_OFFLINE, 0x3, 0); // unknown -- todo move to enum + } + else + { + return STATUS(Status::SUCCESS, 0, 0); + } +} + uint32_t mscp_server::SetControllerCharacteristics( shared_ptr message) @@ -715,71 +813,11 @@ mscp_server::Read( 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)); - - DEBUG("MSCP READ unit %d chan o%o pa o%o count %d lbn %d", + return DoDiskTransfer( + Opcodes::READ, + message, unitNumber, - params->BufferPhysicalAddress >> 24, - params->BufferPhysicalAddress & 0x00ffffff, - params->ByteCount, - params->LBN); - - // Adjust message length for response - message->MessageLength = sizeof(ReadParameters) + - HEADER_SIZE; - - mscp_drive_c* drive = GetDrive(unitNumber); - - // Check unit - if (nullptr == drive || - !drive->IsAvailable()) - { - return STATUS(Status::UNIT_OFFLINE, 0x3, 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 >= drive->GetBlockCount() + 1) // + 1 for RCT write protect flag - { - return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code - } - - if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize()) - { - return STATUS(Status::INVALID_COMMAND + (0xc << 8), 0, 0); // TODO: as above - } - - // - // OK: do the transfer to memory - // - unique_ptr diskBuffer(drive->Read(params->LBN, params->ByteCount)); - - _port->DMAWrite( - params->BufferPhysicalAddress & 0x00ffffff, - params->ByteCount, - diskBuffer.get()); - - // 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, 0); + modifiers); } uint32_t @@ -787,9 +825,23 @@ mscp_server::Write( shared_ptr message, uint16_t unitNumber, uint16_t modifiers) +{ + return DoDiskTransfer( + Opcodes::WRITE, + message, + unitNumber, + modifiers); +} + +uint32_t +mscp_server::DoDiskTransfer( + uint16_t operation, + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers) { #pragma pack(push,1) - struct WriteParameters + struct ReadWriteEraseParameters { uint32_t ByteCount; uint32_t BufferPhysicalAddress; // upper 8 bits are channel address for VAXen @@ -799,11 +851,11 @@ mscp_server::Write( }; #pragma pack(pop) - // TODO: Factor this code out (shared w/Read) - WriteParameters* params = - reinterpret_cast(GetParameterPointer(message)); + ReadWriteEraseParameters* params = + reinterpret_cast(GetParameterPointer(message)); - DEBUG("MSCP WRITE unit %d chan o%o pa o%o count %d lbn %d", + DEBUG("MSCP RWE 0x%x unit %d chan o%o pa o%o count %d lbn %d", + operation, unitNumber, params->BufferPhysicalAddress >> 24, params->BufferPhysicalAddress & 0x00ffffff, @@ -811,7 +863,7 @@ mscp_server::Write( params->LBN); // Adjust message length for response - message->MessageLength = sizeof(WriteParameters) + + message->MessageLength = sizeof(ReadWriteEraseParameters) + HEADER_SIZE; mscp_drive_c* drive = GetDrive(unitNumber); @@ -823,29 +875,130 @@ mscp_server::Write( return STATUS(Status::UNIT_OFFLINE, 0x3, 0); } - // Check LBN - if (params->LBN > drive->GetBlockCount() + 1) // + 1 for RCT + // Are we accessing the RCT area? + bool rctAccess = params->LBN >= drive->GetBlockCount(); + uint32_t rctBlockNumber = params->LBN - drive->GetBlockCount(); + + // Check that the LBN is valid + if (params->LBN >= drive->GetBlockCount() + drive->GetRCTBlockCount()) { return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code } - // Check byte count - if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize()) + // Check byte count: + if (params->ByteCount > ((drive->GetBlockCount() + drive->GetRCTBlockCount()) - params->LBN) * drive->GetBlockSize()) { return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0, 0); // TODO: as above } + // If this is an RCT access, byte count must equal the block size. + if (rctAccess && params->ByteCount != drive->GetBlockSize()) + { + return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0, 0); // TODO: again + } + // // OK: do the transfer from the PDP-11 to a buffer // - unique_ptr memBuffer(_port->DMARead( - params->BufferPhysicalAddress & 0x00ffffff, - params->ByteCount, - params->ByteCount)); + switch (operation) + { + case Opcodes::ACCESS: + // We don't need to actually do any sort of transfer; ACCESS merely checks + // That the data can be read -- we checked the LBN, etc. above and we + // will never encounter a read error, so there's nothing left to do. + break; - drive->Write(params->LBN, - params->ByteCount, - memBuffer.get()); + case Opcodes::COMPARE_HOST_DATA: + { + // Read the data in from disk, read the data in from memory, and compare. + unique_ptr diskBuffer; + + if (rctAccess) + { + diskBuffer.reset(drive->ReadRCTBlock(rctBlockNumber)); + } + else + { + diskBuffer.reset(drive->Read(params->LBN, params->ByteCount)); + } + + unique_ptr memBuffer(_port->DMARead( + params->BufferPhysicalAddress & 0x00ffffff, + params->ByteCount, + params->ByteCount)); + + if (!memcmp(diskBuffer.get(), memBuffer.get(), params->ByteCount)) + { + // TODO: maybe not do an early return, make code not as ugly? Hm. + return STATUS(Status::COMPARE_ERROR, 0, 0); + } + } + + case Opcodes::ERASE: + { + unique_ptr memBuffer(new uint8_t[params->ByteCount]); + memset(reinterpret_cast(memBuffer.get()), 0, params->ByteCount); + + if (rctAccess) + { + drive->WriteRCTBlock(rctBlockNumber, + memBuffer.get()); + } + else + { + drive->Write(params->LBN, + params->ByteCount, + memBuffer.get()); + } + } + break; + + case Opcodes::READ: + { + unique_ptr diskBuffer; + + if (rctAccess) + { + diskBuffer.reset(drive->ReadRCTBlock(rctBlockNumber)); + } + else + { + diskBuffer.reset(drive->Read(params->LBN, params->ByteCount)); + } + + _port->DMAWrite( + params->BufferPhysicalAddress & 0x00ffffff, + params->ByteCount, + diskBuffer.get()); + } + break; + + case Opcodes::WRITE: + { + unique_ptr memBuffer(_port->DMARead( + params->BufferPhysicalAddress & 0x00ffffff, + params->ByteCount, + params->ByteCount)); + + if (rctAccess) + { + drive->WriteRCTBlock(rctBlockNumber, + memBuffer.get()); + } + else + { + drive->Write(params->LBN, + params->ByteCount, + memBuffer.get()); + } + } + break; + + default: + // Should never happen. + assert(false); + break; + } // Set parameters for response. // We leave ByteCount as is (for now anyway) diff --git a/10.02_devices/2_src/mscp_server.hpp b/10.02_devices/2_src/mscp_server.hpp index 9110ea4..73373a0 100644 --- a/10.02_devices/2_src/mscp_server.hpp +++ b/10.02_devices/2_src/mscp_server.hpp @@ -134,16 +134,22 @@ public: bool on_param_changed(parameter_c *param) override { return true; } private: + uint32_t Abort(std::shared_ptr message); + uint32_t Access(std::shared_ptr message, uint16_t unitNumber); uint32_t Available(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t CompareHostData(std::shared_ptr message, uint16_t unitNumber); uint32_t DetermineAccessPaths(std::shared_ptr message, uint16_t unitNumber); uint32_t Erase(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t GetCommandStatus(std::shared_ptr message); 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 Replace(std::shared_ptr message, uint16_t unitNumber); uint32_t Write(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t DoDiskTransfer(uint16_t operation, std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); uint8_t* GetParameterPointer(std::shared_ptr message); mscp_drive_c* GetDrive(uint32_t unitNumber); diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index cf49b26..3bf85fd 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -1,3 +1,14 @@ +/* + uda.cpp: Implementation of the MSCP port (unibus interface). + + This provides logic for the UDA50's SA and IP registers, + the four-step initialization handshake, DMA transfers to and + from the Unibus, and the command/response ring protocols. + + At this time it acts as the port for an MSCP controller. + It would be trivial to extend this to TMSCP at a future date. +*/ + #include #include From 22be88832c65a1aa63823fec57e6f94efa6cfd1a Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Thu, 9 May 2019 02:00:05 +0200 Subject: [PATCH 08/18] Tweaks to MSCP initialization, some cleanup. --- 10.02_devices/2_src/mscp_server.cpp | 9 ++-- 10.02_devices/2_src/uda.cpp | 81 +++++++++-------------------- 10.02_devices/2_src/uda.hpp | 11 ++-- 3 files changed, 34 insertions(+), 67 deletions(-) diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index a95d06b..54ce9de 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -184,7 +184,7 @@ mscp_server::Poll(void) ControlMessageHeader* header = reinterpret_cast(message->Message); - DEBUG("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", + INFO ("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", message->MessageLength, header->Word3.Command.Opcode, header->Word3.Command.Reserved, @@ -542,7 +542,7 @@ mscp_server::GetUnitStatus( if(!drive->IsAvailable()) { - // Known drive, but offline. + // Known drive, but not available at this time. params->UnitIdentifier = 0; params->ShadowUnit = 0; @@ -556,7 +556,7 @@ mscp_server::GetUnitStatus( params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. params->UnitIdentifier = drive->GetUnitID(); params->MediaTypeIdentifier = drive->GetMediaID(); - params->ShadowUnit = 0; // Always equal to unit number + params->ShadowUnit = unitNumber; // Always equal to unit number // // For group, and cylinder size we return 0 -- this is appropriate for the @@ -709,7 +709,7 @@ mscp_server::SetControllerCharacteristics( reinterpret_cast( GetParameterPointer(message)); - DEBUG("MSCP SET CONTROLLER CHARACTERISTICS"); + INFO ("MSCP SET CONTROLLER CHARACTERISTICS"); // Adjust message length for response message->MessageLength = sizeof(SetControllerCharacteristicsParameters) + @@ -724,6 +724,7 @@ mscp_server::SetControllerCharacteristics( } else { + INFO("version 0x%x controller flags 0x%x", params->MSCPVersion, params->ControllerFlags); _hostTimeout = params->HostTimeout; _controllerFlags = params->ControllerFlags; diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index 3bf85fd..392f05c 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -21,7 +21,6 @@ uda_c::uda_c() : storagecontroller_c(), - _sa(0), _server(nullptr), _ringBase(0), _commandRingLength(0), @@ -92,8 +91,8 @@ void uda_c::Reset(void) INFO("UDA reset"); _ringBase = 0; - _commandRingLength = 0; - _responseRingLength = 0; + //_commandRingLength = 0; + //_responseRingLength = 0; _commandRingPointer = 0; _responseRingPointer = 0; _interruptVector = 0; @@ -106,9 +105,7 @@ void uda_c::Reset(void) // Signal the worker to begin the initialization sequence. StateTransition(InitializationStep::Uninitialized); - - _sa = 0; - update_SA(); + update_SA(0x0); } uint32_t uda_c::GetDriveCount(void) @@ -164,15 +161,15 @@ void uda_c::worker(void) INFO("Transition to Init state Uninitialized."); // SA should already be zero but we'll be extra sure here. - _sa = 0; - update_SA(); + update_SA(0x0); + timeout.wait_ms(500); StateTransition(InitializationStep::Step1); break; case InitializationStep::Step1: // Wait 100uS, set SA. - timeout.wait_us(1000); + timeout.wait_ms(500); INFO("Transition to Init state S1."); // @@ -181,30 +178,27 @@ void uda_c::worker(void) // implement enhanced diagnostics, and that no errors have // occurred. // - _sa = 0x0800; - update_SA(); + update_SA(0x0800); break; case InitializationStep::Step2: INFO("Transition to Init state S2."); - timeout.wait_us(1000); + timeout.wait_ms(500); // 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) & 0xff); - update_SA(); + update_SA(0x1000 | ((_step1Value >> 8) & 0xff)); Interrupt(); break; case InitializationStep::Step3: // Wait 100uS, set SA. - timeout.wait_us(1000); + timeout.wait_ms(500); 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(); + update_SA(0x2000 | (_step1Value & 0xff)); Interrupt(); break; @@ -212,23 +206,23 @@ void uda_c::worker(void) timeout.wait_us(100); // Clear communications area, set SA - INFO("Clearing comm area at 0x%x.", _ringBase); + INFO("Clearing comm area at 0x%x. Purge header: %d", _ringBase, _purgeInterruptEnable); INFO("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength); - // 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) + 6; - i += 2) { - DMAWriteWord(_ringBase + i - 6, 0x0); + int headerSize = _purgeInterruptEnable ? 8 : 4; + for(uint32_t i = 0; + i < (_responseRingLength + _commandRingLength) * sizeof(Descriptor) + headerSize; + i += 2) + { + DMAWriteWord(_ringBase + i - headerSize, 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; @@ -242,37 +236,15 @@ void uda_c::worker(void) reinterpret_cast(&blankDescriptor)); } - INFO("Transition to Init state S4."); + INFO("Transition to Init state S4, comm area initialized."); // Update the SA read value for step 4: // Bits 7-0 indicating our control microcode version. - // _sa = 0x4063; //UDA50 - _sa = 0x4042; - update_SA(); - Interrupt(); + update_SA(0x4063); // UDA50 ID, makes RSTS happy + Interrupt(); break; case InitializationStep::Complete: - INFO("Transition to Init state Complete. Initializing response ring."); - //_sa = 0x0; - //update_SA(); - - // - // 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("Initialization complete."); break; } @@ -432,8 +404,7 @@ uda_c::on_after_register_access( StateTransition(InitializationStep::Complete); // The VMS bootstrap expects SA to be zero IMMEDIATELY // after completion. - _sa = 0; - update_SA(); + update_SA(0x0); } else { @@ -455,11 +426,11 @@ uda_c::on_after_register_access( } void -uda_c::update_SA() +uda_c::update_SA(uint16_t value) { set_register_dati_value( SA_reg, - _sa, + value, "update_SA"); } diff --git a/10.02_devices/2_src/uda.hpp b/10.02_devices/2_src/uda.hpp index 41ac76f..f33ff83 100644 --- a/10.02_devices/2_src/uda.hpp +++ b/10.02_devices/2_src/uda.hpp @@ -28,9 +28,6 @@ struct Message uint16_t Word1; } Word1; - // 384 bytes is the minimum needed to support - // datagram messages. The underlying buffer will - // be allocated to cover whatever size needed. uint8_t Message[sizeof(ControlMessageHeader)]; }; #pragma pack(pop) @@ -92,14 +89,12 @@ public: uint8_t* DMARead(uint32_t address, size_t lengthInBytes, size_t bufferSize); private: - void update_SA(void); + void update_SA(uint16_t value); // UDA50 registers: unibusdevice_register_t *IP_reg; unibusdevice_register_t *SA_reg; - uint16_t _sa; - std::shared_ptr _server; uint32_t _ringBase; @@ -137,8 +132,8 @@ private: Complete, }; - InitializationStep _initStep; - bool _next_step; + volatile InitializationStep _initStep; + volatile bool _next_step; void StateTransition(InitializationStep nextStep); From bb546db52a8fe3bc8cd221faa05055d80aa6989e Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Thu, 9 May 2019 07:26:55 +0200 Subject: [PATCH 09/18] Workaround for low-level DMA issue; give up waiting for DMA transfers if enough time passes. This works around an as-yet-unexplained PRU bug. --- 10.01_base/2_src/arm/unibusadapter.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/10.01_base/2_src/arm/unibusadapter.cpp b/10.01_base/2_src/arm/unibusadapter.cpp index 563f31b..641216d 100644 --- a/10.01_base/2_src/arm/unibusadapter.cpp +++ b/10.01_base/2_src/arm/unibusadapter.cpp @@ -652,10 +652,25 @@ void unibusadapter_c::dma_worker() // // Wait for the transfer to complete. // TODO: we're polling the mailbox; is there a more efficient way to do this? - timeout_c timeout; - while (request_DMA_active(nullptr)) + timeout_c timeout; + int retries = 0; + while (request_DMA_active(nullptr) && retries < 10000) { timeout.wait_us(50); + retries++; + } + + // + // TODO: this should not be necessary. There are rare occasions + // where it appears that the PRU dma transfer is interrupted + // but never clears the DMA active flag, so we hang in the loop above + // forever. + // Nothing to do in that case but give up. + // And log the issue. Should get to the root of this.. + // + if (retries == 10000) + { + INFO("dma timeout"); } if (dmaReq->GetUnibusControl() == UNIBUS_CONTROL_DATI) From 464049f0b6858b1c06b9ae5f7fa786a40387c268 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Sat, 11 May 2019 02:10:12 +0200 Subject: [PATCH 10/18] Changed reset behavior; now done asynchronously. (Was taking too long, especially during resets due to IP regsister read). RSX-11M now boots and runs. --- 10.02_devices/2_src/mscp_drive.cpp | 2 +- 10.02_devices/2_src/mscp_server.cpp | 6 +- 10.02_devices/2_src/uda.cpp | 87 +++++++++++++---------------- 3 files changed, 42 insertions(+), 53 deletions(-) diff --git a/10.02_devices/2_src/mscp_drive.cpp b/10.02_devices/2_src/mscp_drive.cpp index 03e17ab..d8b4498 100644 --- a/10.02_devices/2_src/mscp_drive.cpp +++ b/10.02_devices/2_src/mscp_drive.cpp @@ -24,7 +24,7 @@ mscp_drive_c::mscp_drive_c( // Calculate the unit's ID: // drive number in upper 32 bits, class/model in lower. - _unitID = (static_cast(driveNumber) << 32) | 0x02020000; + _unitID = (static_cast(driveNumber + 1) << 32) | 0x02020000; // Initialize the RCT area _rctData.reset(new uint8_t[GetBlockSize()]); diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index 54ce9de..8472a9f 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -184,7 +184,7 @@ mscp_server::Poll(void) ControlMessageHeader* header = reinterpret_cast(message->Message); - INFO ("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", + DEBUG("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", message->MessageLength, header->Word3.Command.Opcode, header->Word3.Command.Reserved, @@ -302,7 +302,6 @@ mscp_server::Poll(void) message->Word1.Info.Credits = 0; } - // // Post the response to the port's response ring. // @@ -709,7 +708,7 @@ mscp_server::SetControllerCharacteristics( reinterpret_cast( GetParameterPointer(message)); - INFO ("MSCP SET CONTROLLER CHARACTERISTICS"); + DEBUG("MSCP SET CONTROLLER CHARACTERISTICS"); // Adjust message length for response message->MessageLength = sizeof(SetControllerCharacteristicsParameters) + @@ -724,7 +723,6 @@ mscp_server::SetControllerCharacteristics( } else { - INFO("version 0x%x controller flags 0x%x", params->MSCPVersion, params->ControllerFlags); _hostTimeout = params->HostTimeout; _controllerFlags = params->ControllerFlags; diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index 392f05c..7e7b9dc 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -88,24 +88,19 @@ uda_c::~uda_c() void uda_c::Reset(void) { - INFO("UDA reset"); + DEBUG("UDA reset"); + + _server->Reset(); _ringBase = 0; - //_commandRingLength = 0; - //_responseRingLength = 0; + _commandRingLength = 0; + _responseRingLength = 0; _commandRingPointer = 0; _responseRingPointer = 0; _interruptVector = 0; intr_vector.value = 0; _interruptEnable = false; _purgeInterruptEnable = false; - _next_step = false; - - _server->Reset(); - - // Signal the worker to begin the initialization sequence. - StateTransition(InitializationStep::Uninitialized); - update_SA(0x0); } uint32_t uda_c::GetDriveCount(void) @@ -133,7 +128,7 @@ void uda_c::StateTransition( void uda_c::worker(void) { - worker_init_realtime_priority(rt_max); + worker_init_realtime_priority(rt_device); timeout_c timeout; @@ -153,25 +148,24 @@ void uda_c::worker(void) _next_step = false; pthread_mutex_unlock(&on_after_register_access_mutex); - // INFO("Awoken."); - switch (_initStep) { case InitializationStep::Uninitialized: - INFO("Transition to Init state Uninitialized."); - + DEBUG("Transition to Init state Uninitialized."); // SA should already be zero but we'll be extra sure here. update_SA(0x0); - timeout.wait_ms(500); + // Reset the controller: This may take some time as we must + // wait for the MSCP server to wrap up its current workitem. + Reset(); StateTransition(InitializationStep::Step1); break; case InitializationStep::Step1: // Wait 100uS, set SA. - timeout.wait_ms(500); + timeout.wait_us(500); - INFO("Transition to Init state S1."); + DEBUG("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 @@ -182,8 +176,8 @@ void uda_c::worker(void) break; case InitializationStep::Step2: - INFO("Transition to Init state S2."); - timeout.wait_ms(500); + DEBUG("Transition to Init state S2."); + timeout.wait_us(500); // 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. @@ -193,9 +187,9 @@ void uda_c::worker(void) case InitializationStep::Step3: // Wait 100uS, set SA. - timeout.wait_ms(500); + timeout.wait_us(500); - INFO("Transition to Init state S3."); + DEBUG("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. update_SA(0x2000 | (_step1Value & 0xff)); @@ -206,8 +200,8 @@ void uda_c::worker(void) timeout.wait_us(100); // Clear communications area, set SA - INFO("Clearing comm area at 0x%x. Purge header: %d", _ringBase, _purgeInterruptEnable); - INFO("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength); + DEBUG("Clearing comm area at 0x%x. Purge header: %d", _ringBase, _purgeInterruptEnable); + DEBUG("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength); { int headerSize = _purgeInterruptEnable ? 8 : 4; @@ -236,7 +230,7 @@ void uda_c::worker(void) reinterpret_cast(&blankDescriptor)); } - INFO("Transition to Init state S4, comm area initialized."); + DEBUG("Transition to Init state S4, comm area initialized."); // Update the SA read value for step 4: // Bits 7-0 indicating our control microcode version. update_SA(0x4063); // UDA50 ID, makes RSTS happy @@ -244,9 +238,8 @@ void uda_c::worker(void) break; case InitializationStep::Complete: - INFO("Initialization complete."); + DEBUG("Initialization complete."); break; - } } } @@ -266,7 +259,7 @@ uda_c::on_after_register_access( // "When written with any value, it causes a hard initialization // of the port and the device controller." DEBUG("Reset due to IP read"); - Reset(); + StateTransition(InitializationStep::Uninitialized); } else { @@ -287,7 +280,7 @@ uda_c::on_after_register_access( { case InitializationStep::Uninitialized: // Should not occur, we treat it like step1 here. - INFO("Write to SA in Uninitialized state."); + DEBUG("Write to SA in Uninitialized state."); case InitializationStep::Step1: // Host writes the following: @@ -318,11 +311,11 @@ uda_c::on_after_register_access( _responseRingLength = (1 << ((value & 0x700) >> 8)); _commandRingLength = (1 << ((value & 0x3800) >> 11)); - INFO("Step1: 0x%x", value); - INFO("resp ring 0x%x", _responseRingLength); - INFO("cmd ring 0x%x", _commandRingLength); - INFO("vector 0x%x", _interruptVector); - INFO("ie %d", _interruptEnable); + DEBUG("Step1: 0x%x", value); + DEBUG("resp ring 0x%x", _responseRingLength); + DEBUG("cmd ring 0x%x", _commandRingLength); + DEBUG("vector 0x%x", _interruptVector); + DEBUG("ie %d", _interruptEnable); // Move to step 2. StateTransition(InitializationStep::Step2); @@ -342,7 +335,7 @@ uda_c::on_after_register_access( _ringBase = value & 0xfffe; _purgeInterruptEnable = !!(value & 0x1); - INFO("Step2: rb 0x%x pi %d", _ringBase, _purgeInterruptEnable); + DEBUG("Step2: rb 0x%x pi %d", _ringBase, _purgeInterruptEnable); // Move to step 3 and interrupt as necessary. StateTransition(InitializationStep::Step3); break; @@ -361,7 +354,7 @@ uda_c::on_after_register_access( // [ringbase+0]. _ringBase |= ((value & 0x7fff) << 16); - INFO("Step3: ringbase 0x%x", _ringBase); + DEBUG("Step3: ringbase 0x%x", _ringBase); // Move to step 4 and interrupt as necessary. StateTransition(InitializationStep::Step4); break; @@ -394,7 +387,7 @@ uda_c::on_after_register_access( // supporting onboard diagnostics and there's nothing to // report. // - INFO("Step4: 0x%x", value); + DEBUG("Step4: 0x%x", value); if (value & 0x1) { // @@ -417,7 +410,7 @@ uda_c::on_after_register_access( // 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. + // We don't deal with bus adapter purges, yet. break; } break; @@ -481,8 +474,6 @@ uda_c::GetNextCommand(void) messageAddress - 4, success); - // INFO("Message length 0x%x", messageLength); - // // TODO: sanity check message length (what is the max length we // can expect to see?) @@ -558,7 +549,7 @@ uda_c::GetNextCommand(void) // DMAWriteWord( _ringBase - 4, - 0xffff); + 0x1); // // Raise the interrupt @@ -624,13 +615,13 @@ uda_c::PostResponse( if (reinterpret_cast(response)[0] == 0) { - INFO("Writing zero length response!"); + ERROR("Writing zero length response!"); } if (messageLength < response->MessageLength) { - // TODO: handle this; for now eat flaming death. - INFO("Response buffer %x > message length %x", response->MessageLength, messageLength); + // TODO: A lot of bootstraps appear to set up response buffers of length 0... + ERROR("Response buffer %x > message length %x", response->MessageLength, messageLength); } // else { @@ -701,13 +692,13 @@ uda_c::PostResponse( // Post an interrupt as necessary. if (doInterrupt) { - // INFO("Response ring no longer empty, interrupting."); + DEBUG("Response ring no longer empty, interrupting."); // // Set ring base - 2 to non-zero to indicate a transition. // DMAWriteWord( _ringBase - 2, - 0xffff); + 0x1); // // Raise the interrupt @@ -749,7 +740,7 @@ uda_c::on_power_changed(void) if (power_down) { DEBUG("Reset due to power change"); - Reset(); + StateTransition(InitializationStep::Uninitialized); } } @@ -759,7 +750,7 @@ uda_c::on_init_changed(void) if (init_asserted) { DEBUG("Reset due to INIT"); - Reset(); + StateTransition(InitializationStep::Uninitialized); } storagecontroller_c::on_init_changed(); From fc312df0bc7de9496fd1a2adefb9187b2fa69d17 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Tue, 14 May 2019 03:14:50 +0200 Subject: [PATCH 11/18] Cleaned up a few things: - Removed delay in mscp server polling loop, which as I suspected was papering over an issue. We now pull all messages from the command ring at once and save them locally. When processing completes, the polling loop goes back to sleep. This jibes with host code expectations of the port. No more delay necessary to avoid race conditions. - Cleaned up RCT/RBN information so that this can be dynamically configured in the future, should the need arise --- 10.02_devices/2_src/mscp_drive.cpp | 22 ++++++- 10.02_devices/2_src/mscp_drive.hpp | 3 + 10.02_devices/2_src/mscp_server.cpp | 96 ++++++++++++++++------------- 10.02_devices/2_src/mscp_server.hpp | 10 +++ 4 files changed, 86 insertions(+), 45 deletions(-) diff --git a/10.02_devices/2_src/mscp_drive.cpp b/10.02_devices/2_src/mscp_drive.cpp index d8b4498..d66a41c 100644 --- a/10.02_devices/2_src/mscp_drive.cpp +++ b/10.02_devices/2_src/mscp_drive.cpp @@ -24,7 +24,7 @@ mscp_drive_c::mscp_drive_c( // Calculate the unit's ID: // drive number in upper 32 bits, class/model in lower. - _unitID = (static_cast(driveNumber + 1) << 32) | 0x02020000; + _unitID = (static_cast(driveNumber + 1) << 32) | 0xffffffff; // Initialize the RCT area _rctData.reset(new uint8_t[GetBlockSize()]); @@ -66,8 +66,9 @@ uint32_t mscp_drive_c::GetBlockCount() uint32_t mscp_drive_c::GetRCTBlockCount() { // - // We provide only a single RCT block, required by the MSCP spec for the volume - // write-protect flags. + // We provide only a single RCT block. Per the latest MSCP spec no RCT is required, + // however several operating systems appear to expect at least one block be present + // for volume write-protect flags. // return 1; } @@ -82,6 +83,21 @@ uint64_t mscp_drive_c::GetUnitID() return _unitID; } +uint16_t mscp_drive_c::GetRCTSize() +{ + return 1; +} + +uint8_t mscp_drive_c::GetRBNs() +{ + return 0; +} + +uint8_t mscp_drive_c::GetRCTCopies() +{ + return 1; +} + bool mscp_drive_c::IsAvailable() { return file_is_open(); diff --git a/10.02_devices/2_src/mscp_drive.hpp b/10.02_devices/2_src/mscp_drive.hpp index 318dab5..32fca80 100644 --- a/10.02_devices/2_src/mscp_drive.hpp +++ b/10.02_devices/2_src/mscp_drive.hpp @@ -23,6 +23,9 @@ public: uint32_t GetRCTBlockCount(void); uint32_t GetMediaID(void); uint64_t GetUnitID(void); + uint16_t GetRCTSize(void); + uint8_t GetRBNs(void); + uint8_t GetRCTCopies(void); void SetOnline(void); void SetOffline(void); diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index 8472a9f..88f1d8b 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -33,6 +33,7 @@ #include #include #include +#include using namespace std; @@ -130,8 +131,6 @@ mscp_server::Poll(void) { worker_init_realtime_priority(rt_device); - timeout_c timer; - while(!_abort_polling) { // @@ -159,21 +158,33 @@ mscp_server::Poll(void) } // - // Pull commands from the ring until the ring is empty, at which - // point we sleep until awoken again. + // Read all commands from the ring into a queue; then execute them. // - while(!_abort_polling && _pollState != PollingState::InitRestart) + std::queue> messages; + + while (!_abort_polling && _pollState != PollingState::InitRestart) { - shared_ptr message(_port->GetNextCommand()); + shared_ptr message(_port->GetNextCommand()); if (nullptr == message) { - DEBUG("Empty command ring; sleeping."); + DEBUG("End of command ring; %d messages to be executed."); break; } - - DEBUG("Message received."); + messages.push(message); + } + + // + // Pull commands from the queue until it is empty, at which + // point we sleep until awoken again. + // + while(!messages.empty() && !_abort_polling && _pollState != PollingState::InitRestart) + { + shared_ptr message(messages.front()); + messages.pop(); + + DEBUG("Processing message."); // // Handle the message. We dispatch on opcodes to the @@ -184,7 +195,7 @@ mscp_server::Poll(void) ControlMessageHeader* header = reinterpret_cast(message->Message); - DEBUG("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", + INFO ("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", message->MessageLength, header->Word3.Command.Opcode, header->Word3.Command.Reserved, @@ -259,7 +270,7 @@ mscp_server::Poll(void) break; } - DEBUG("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); + INFO ("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); // // Set the endcode and status bits @@ -307,19 +318,9 @@ mscp_server::Poll(void) // if(!_port->PostResponse(message.get())) { - FATAL("no room at the inn."); + FATAL("Unexpected: no room in response ring."); } - // - // This delay works around an issue in various bootstraps -- unsure of the - // exact cause, it appears to be a race condition exacerbated by the speed - // at which we're able to process commands in the ring buffer (we are much - // faster than any real MSCP device ever was) and the liberties that bootstrap - // code takes with the MSCP spec to save code space. - // This is likely a hack, we may be papering over a bug somewhere. - // - timer.wait_us(2000); - // // Go around and pick up the next one. // @@ -359,7 +360,7 @@ mscp_server::Abort( // them one at a time, sequentially as they appear in the ring buffer, // by the time we've gotten this command, the command it's referring // to is long gone. - // This is legal behavior and it's legal for us to ignore ABORT in this + // This is semi-legal behavior and it's legal for us to ignore ABORT in this // case. // // We just return SUCCESS here. @@ -385,7 +386,7 @@ mscp_server::Available( if (nullptr == drive || !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 0x3, 0); + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); } drive->SetOffline(); @@ -487,8 +488,8 @@ mscp_server::GetUnitStatus( #pragma pack(push,1) struct GetUnitStatusResponseParameters { - uint16_t UnitFlags; uint16_t MultiUnitCode; + uint16_t UnitFlags; uint32_t Reserved0; uint64_t UnitIdentifier; uint32_t MediaTypeIdentifier; @@ -498,11 +499,13 @@ mscp_server::GetUnitStatus( uint16_t GroupSize; uint16_t CylinderSize; uint16_t Reserved2; - uint32_t RCTStuff; + uint16_t RCTSize; + uint8_t RBNs; + uint8_t Copies; }; #pragma pack(pop) - DEBUG("MSCP GET UNIT STATUS drive %d", unitNumber); + INFO ("MSCP GET UNIT STATUS drive %d", unitNumber); // Adjust message length for response message->MessageLength = sizeof(GetUnitStatusResponseParameters) + @@ -536,7 +539,7 @@ mscp_server::GetUnitStatus( // No such drive params->UnitIdentifier = 0; params->ShadowUnit = 0; - return STATUS(Status::UNIT_OFFLINE, 0x3, 0); // offline; unknown unit. + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); } if(!drive->IsAvailable()) @@ -545,7 +548,7 @@ mscp_server::GetUnitStatus( params->UnitIdentifier = 0; params->ShadowUnit = 0; - return STATUS(Status::UNIT_OFFLINE, 0x23, 0); // offline; no volume available + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::NO_VOLUME, 0); // offline; no volume available } params->Reserved0 = 0; @@ -557,14 +560,19 @@ mscp_server::GetUnitStatus( params->MediaTypeIdentifier = drive->GetMediaID(); 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.) + // From the MSCP spec: "As stated above, the host area of a disk is structured as a + // vector of logical blocks. From a performance viewpoint, however, + // it is more appropriate to view the host area as a four + // dimensional hyper-cube." + // This has nothing whatsoever to do with what's going on here but it makes me snicker + // every time I read it so I'm including it. + // Let's relay some information about our data-tesseract: + // Since our underlying storage is an image file on flash memory, we don't need to be concerned + // about seek times, so the below is appropriate: // params->TrackSize = 1; // one block per track, per aa-l619a-tk. - params->GroupSize = 1; - params->CylinderSize = 1; + params->GroupSize = 1; // one cylinder per group + params->CylinderSize = 1; // one sector per cylinder // // Since we do no bad block replacement (no bad blocks possible in a disk image file) @@ -572,7 +580,9 @@ mscp_server::GetUnitStatus( // There are no replacement blocks, and no duplicate copies of // the RCT are present. // - params->RCTStuff = 0x01000001; + params->RCTSize = drive->GetRCTSize(); + params->RBNs = drive->GetRBNs(); + params->Copies = drive->GetRCTCopies(); if (drive->IsOnline()) { @@ -640,7 +650,7 @@ mscp_server::Online( if (nullptr == drive || !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 0x3, 0); // unknown -- todo move to enum + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); } bool alreadyOnline = drive->IsOnline(); @@ -681,7 +691,7 @@ mscp_server::Replace( if (nullptr == drive || !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 0x3, 0); // unknown -- todo move to enum + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); } else { @@ -729,10 +739,11 @@ mscp_server::SetControllerCharacteristics( // At this time we ignore the time and date entirely. // Prepare the response message + params->Reserved = 0; 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->HostTimeout = 0x10; // Controller timeout: return the max value. params->TimeAndDate = _port->GetControllerIdentifier(); // Controller ID return STATUS(Status::SUCCESS, 0, 0); @@ -788,7 +799,7 @@ mscp_server::SetUnitCharacteristics( if (nullptr == drive || !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 0x3, 0); + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); } SetUnitCharacteristicsResponseParameters* params = @@ -853,9 +864,10 @@ mscp_server::DoDiskTransfer( ReadWriteEraseParameters* params = reinterpret_cast(GetParameterPointer(message)); - DEBUG("MSCP RWE 0x%x unit %d chan o%o pa o%o count %d lbn %d", + INFO ("MSCP RWE 0x%x unit %d mod 0x%x chan o%o pa o%o count %d lbn %d", operation, unitNumber, + modifiers, params->BufferPhysicalAddress >> 24, params->BufferPhysicalAddress & 0x00ffffff, params->ByteCount, @@ -871,7 +883,7 @@ mscp_server::DoDiskTransfer( if (nullptr == drive || !drive->IsAvailable()) { - return STATUS(Status::UNIT_OFFLINE, 0x3, 0); + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); } // Are we accessing the RCT area? diff --git a/10.02_devices/2_src/mscp_server.hpp b/10.02_devices/2_src/mscp_server.hpp index 73373a0..3b7bd81 100644 --- a/10.02_devices/2_src/mscp_server.hpp +++ b/10.02_devices/2_src/mscp_server.hpp @@ -105,6 +105,16 @@ enum SuccessSubcodes STILL_ONLINE = 0x200, }; +enum UnitOfflineSubcodes +{ + UNIT_UNKNOWN = 0x0, + NO_VOLUME = 0x1, + UNIT_INOPERATIVE = 0x2, + DUPLICATE_UNIT = 0x4, + UNIT_DISABLED = 0x8, + EXCLUSIVE = 0x10, +}; + enum MessageTypes { Sequential = 0, From 398c54ee3fb63ed8c98af0b2ec5ecc5534750fa4 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Fri, 17 May 2019 00:47:11 +0200 Subject: [PATCH 12/18] Fixed Unit and Controller ID fields (word order was scrambled) and removed hacked-in constant values for same. Made RCT table size dynamic based on the drive type. The above allow RSTS/E to boot! Huzzah! --- 10.02_devices/2_src/mscp_drive.cpp | 39 ++++++++------- 10.02_devices/2_src/mscp_drive.hpp | 60 +++++++++++++---------- 10.02_devices/2_src/mscp_server.cpp | 74 +++++++++++++++++++---------- 10.02_devices/2_src/uda.cpp | 12 +++-- 10.02_devices/2_src/uda.hpp | 3 +- 5 files changed, 117 insertions(+), 71 deletions(-) diff --git a/10.02_devices/2_src/mscp_drive.cpp b/10.02_devices/2_src/mscp_drive.cpp index d66a41c..9bb257b 100644 --- a/10.02_devices/2_src/mscp_drive.cpp +++ b/10.02_devices/2_src/mscp_drive.cpp @@ -23,12 +23,7 @@ mscp_drive_c::mscp_drive_c( SetOffline(); // Calculate the unit's ID: - // drive number in upper 32 bits, class/model in lower. - _unitID = (static_cast(driveNumber + 1) << 32) | 0xffffffff; - - // Initialize the RCT area - _rctData.reset(new uint8_t[GetBlockSize()]); - memset(reinterpret_cast(_rctData.get()), 0, GetBlockSize()); + _unitIDDeviceNumber = driveNumber + 1; } mscp_drive_c::~mscp_drive_c() @@ -65,12 +60,7 @@ uint32_t mscp_drive_c::GetBlockCount() uint32_t mscp_drive_c::GetRCTBlockCount() { - // - // We provide only a single RCT block. Per the latest MSCP spec no RCT is required, - // however several operating systems appear to expect at least one block be present - // for volume write-protect flags. - // - return 1; + return _driveInfo.RCTSize * GetRCTCopies(); } uint32_t mscp_drive_c::GetMediaID() @@ -78,14 +68,19 @@ uint32_t mscp_drive_c::GetMediaID() return _driveInfo.MediaID; } -uint64_t mscp_drive_c::GetUnitID() +uint32_t mscp_drive_c::GetUnitIDDeviceNumber() { - return _unitID; + return _unitIDDeviceNumber; +} + +uint16_t mscp_drive_c::GetUnitIDClassModel() +{ + return _unitIDClassModel; } uint16_t mscp_drive_c::GetRCTSize() { - return 1; + return _driveInfo.RCTSize; } uint8_t mscp_drive_c::GetRBNs() @@ -207,6 +202,17 @@ void mscp_drive_c::UpdateCapacity() GetBlockCount() * GetBlockSize(); } +void mscp_drive_c::UpdateMetadata() +{ + _unitIDClassModel = 0x0200 | _driveInfo.Model; + + // Initialize the RCT area + size_t rctSize = _driveInfo.RCTSize * GetBlockSize(); + _rctData.reset(new uint8_t[rctSize]); + assert(_rctData != nullptr); + memset(reinterpret_cast(_rctData.get()), 0, rctSize); +} + bool mscp_drive_c::on_param_changed( parameter_c *param) { @@ -253,7 +259,8 @@ bool mscp_drive_c::SetDriveType(const char* typeName) { _driveInfo = g_driveTable[index]; type_name.value = _driveInfo.TypeName; - UpdateCapacity(); + UpdateCapacity(); + UpdateMetadata(); return true; } diff --git a/10.02_devices/2_src/mscp_drive.hpp b/10.02_devices/2_src/mscp_drive.hpp index 32fca80..a54cc24 100644 --- a/10.02_devices/2_src/mscp_drive.hpp +++ b/10.02_devices/2_src/mscp_drive.hpp @@ -22,7 +22,8 @@ public: uint32_t GetBlockCount(void); uint32_t GetRCTBlockCount(void); uint32_t GetMediaID(void); - uint64_t GetUnitID(void); + uint32_t GetUnitIDDeviceNumber(void); + uint16_t GetUnitIDClassModel(void); uint16_t GetRCTSize(void); uint8_t GetRBNs(void); uint8_t GetRCTCopies(void); @@ -66,41 +67,50 @@ private: char TypeName[16]; size_t BlockCount; uint32_t MediaID; + uint8_t Model; + uint16_t RCTSize; bool Removable; bool ReadOnly; }; - DriveInfo g_driveTable[22] + // + // TODO: add a lot more drive types. + // Does it make sense to support drive types not native to Unibus machines (SCSI types, for example?) + // Need to add a ClassID table entry in that eventuality... + // Also TODO: RCTSize parameters taken from SIMH rq source; how valid are these? + DriveInfo g_driveTable[21] { - { "RX50", 800, 0x25658032, true, false }, - { "RX33", 2400, 0x25658021, true, false }, - { "RD51", 21600, 0x25644033, false, false }, - { "RD31", 41560, 0x2564401f, false, false }, - { "RC25", 50902, 0x20643019, true, false }, - { "RC25F", 50902, 0x20643319, true, false }, - { "RD52", 60480, 0x25644034, false, false }, - { "RD32", 83236, 0x25641047, false, false }, - { "RD53", 138672, 0x25644035, false, false }, - { "RA80", 237212, 0x20643019, false, false }, - { "RD54", 311200, 0x25644036, false, false }, - { "RA60", 400176, 0x22a4103c, true, false }, - { "RA70", 547041, 0x20643019, false, false }, - { "RA81", 891072, 0x25641051, false, false }, - { "RA82", 1216665, 0x25641052, false, false }, - { "RA71", 1367310, 0x25641047, false, false }, - { "RRD40", 1331200, 0x25652228, true, true }, - { "RA72", 1953300, 0x25641048, false, false }, - { "RA90", 2376153, 0x2564105a, false, false }, - { "RA92", 2940951, 0x2564105c, false, false }, - { "RA73", 3920490, 0x25641049, false, false }, - { "", 0, 0, false, false } +// Name Blocks MediaID Model RCTSize Removable ReadOnly + { "RX50", 800, 0x25658032, 7, 0, true, false }, + { "RX33", 2400, 0x25658021, 10, 0, true, false }, + { "RD51", 21600, 0x25644033, 6, 36, false, false }, + { "RD31", 41560, 0x2564401f, 12, 3, false, false }, + { "RC25", 50902, 0x20643019, 2, 0, true, false }, + { "RC25F", 50902, 0x20643319, 3, 0, true, false }, + { "RD52", 60480, 0x25644034, 8, 4, false, false }, + { "RD32", 83236, 0x25641047, 15, 4, false, false }, + { "RD53", 138672, 0x25644035, 9, 5, false, false }, + { "RA80", 237212, 0x20643019, 1, 0, false, false }, + { "RD54", 311200, 0x25644036, 13, 7, false, false }, + { "RA60", 400176, 0x22a4103c, 4, 1008, true, false }, + { "RA70", 547041, 0x20643019, 18, 198, false, false }, + { "RA81", 891072, 0x25641051, 5, 2856, false, false }, + { "RA82", 1216665, 0x25641052, 11, 3420, false, false }, + { "RA71", 1367310, 0x25641047, 40, 1428, false, false }, + { "RA72", 1953300, 0x25641048, 37, 2040, false, false }, + { "RA90", 2376153, 0x2564105a, 19, 1794, false, false }, + { "RA92", 2940951, 0x2564105c, 29, 949, false, false }, + { "RA73", 3920490, 0x25641049, 47, 198, false, false }, + { "", 0, 0, 0, 0, false, false } }; bool SetDriveType(const char* typeName); void UpdateCapacity(void); + void UpdateMetadata(void); DriveInfo _driveInfo; bool _online; - uint64_t _unitID; + uint32_t _unitIDDeviceNumber; + uint16_t _unitIDClassModel; bool _useImageSize; // diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index 88f1d8b..052fca4 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -195,7 +195,7 @@ mscp_server::Poll(void) ControlMessageHeader* header = reinterpret_cast(message->Message); - INFO ("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", + DEBUG("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x", message->MessageLength, header->Word3.Command.Opcode, header->Word3.Command.Reserved, @@ -270,7 +270,7 @@ mscp_server::Poll(void) break; } - INFO ("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); + DEBUG("cmd 0x%x st 0x%x fl 0x%x", cmdStatus, GET_STATUS(cmdStatus), GET_FLAGS(cmdStatus)); // // Set the endcode and status bits @@ -491,7 +491,9 @@ mscp_server::GetUnitStatus( uint16_t MultiUnitCode; uint16_t UnitFlags; uint32_t Reserved0; - uint64_t UnitIdentifier; + uint32_t UnitIdDeviceNumber; + uint16_t UnitIdUnused; + uint16_t UnitIdClassModel; uint32_t MediaTypeIdentifier; uint16_t ShadowUnit; uint16_t Reserved1; @@ -505,7 +507,7 @@ mscp_server::GetUnitStatus( }; #pragma pack(pop) - INFO ("MSCP GET UNIT STATUS drive %d", unitNumber); + DEBUG("MSCP GET UNIT STATUS drive %d", unitNumber); // Adjust message length for response message->MessageLength = sizeof(GetUnitStatusResponseParameters) + @@ -534,29 +536,24 @@ mscp_server::GetUnitStatus( reinterpret_cast( GetParameterPointer(message)); - if (nullptr == drive) + if (nullptr == drive || !drive->IsAvailable()) { - // No such drive - params->UnitIdentifier = 0; + // No such drive or drive image not loaded. + params->UnitIdDeviceNumber = 0; + params->UnitIdClassModel = 0; + params->UnitIdUnused = 0; params->ShadowUnit = 0; return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); } - if(!drive->IsAvailable()) - { - // Known drive, but not available at this time. - params->UnitIdentifier = 0; - params->ShadowUnit = 0; - - return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::NO_VOLUME, 0); // offline; no volume available - } - params->Reserved0 = 0; params->Reserved1 = 0; params->Reserved2 = 0; params->UnitFlags = 0; // TODO: 0 for now, which is sane. params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. - params->UnitIdentifier = drive->GetUnitID(); + params->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber(); + params->UnitIdClassModel = drive->GetUnitIDClassModel(); + params->UnitIdUnused = 0; params->MediaTypeIdentifier = drive->GetMediaID(); params->ShadowUnit = unitNumber; // Always equal to unit number @@ -631,7 +628,9 @@ mscp_server::Online( uint16_t UnitFlags; uint16_t MultiUnitCode; uint32_t Reserved0; - uint64_t UnitIdentifier; + uint32_t UnitIdDeviceNumber; + uint16_t UnitIdUnused; + uint16_t UnitIdClassModel; uint32_t MediaTypeIdentifier; uint32_t Reserved1; uint32_t UnitSize; @@ -663,7 +662,9 @@ mscp_server::Online( params->UnitFlags = 0; // TODO: 0 for now, which is sane. params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. - params->UnitIdentifier = drive->GetUnitID(); + params->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber(); + params->UnitIdClassModel = drive->GetUnitIDClassModel(); + params->UnitIdUnused = 0; params->MediaTypeIdentifier = drive->GetMediaID(); params->UnitSize = drive->GetBlockCount(); params->VolumeSerialNumber = 1; // We report no serial @@ -710,7 +711,16 @@ mscp_server::SetControllerCharacteristics( uint16_t ControllerFlags; uint16_t HostTimeout; uint16_t Reserved; - uint64_t TimeAndDate; + union + { + uint64_t TimeAndDate; + struct + { + uint32_t UniqueDeviceNumber; + uint16_t Unused; + uint16_t ClassModel; + } ControllerId; + } w; }; #pragma pack(pop) @@ -743,8 +753,10 @@ mscp_server::SetControllerCharacteristics( 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 = 0x10; // Controller timeout: return the max value. - params->TimeAndDate = _port->GetControllerIdentifier(); // Controller ID + params->HostTimeout = 0xff; // Controller timeout: return the max value. + params->w.ControllerId.UniqueDeviceNumber = _port->GetControllerIdentifier(); + params->w.ControllerId.ClassModel = _port->GetControllerClassModel(); + params->w.ControllerId.Unused = 0; return STATUS(Status::SUCCESS, 0, 0); } @@ -781,7 +793,9 @@ mscp_server::SetUnitCharacteristics( uint16_t UnitFlags; uint16_t MultiUnitCode; uint32_t Reserved0; - uint64_t UnitIdentifier; + uint32_t UnitIdDeviceNumber; + uint16_t UnitIdUnused; + uint16_t UnitIdClassModel; uint32_t MediaTypeIdentifier; uint32_t Reserved1; uint16_t ShadowUnit; @@ -808,7 +822,9 @@ mscp_server::SetUnitCharacteristics( params->UnitFlags = 0; // TODO: 0 for now, which is sane. params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. - params->UnitIdentifier = drive->GetUnitID(); + params->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber(); + params->UnitIdClassModel = drive->GetUnitIDClassModel(); + params->UnitIdUnused = 0; params->MediaTypeIdentifier = drive->GetMediaID(); params->UnitSize = drive->GetBlockCount(); params->VolumeSerialNumber = 0; // We report no serial @@ -864,7 +880,7 @@ mscp_server::DoDiskTransfer( ReadWriteEraseParameters* params = reinterpret_cast(GetParameterPointer(message)); - INFO ("MSCP RWE 0x%x unit %d mod 0x%x chan o%o pa o%o count %d lbn %d", + DEBUG("MSCP RWE 0x%x unit %d mod 0x%x chan o%o pa o%o count %d lbn %d", operation, unitNumber, modifiers, @@ -886,6 +902,11 @@ mscp_server::DoDiskTransfer( return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); } + if (!drive->IsOnline()) + { + return STATUS(Status::UNIT_AVAILABLE, 0, 0); + } + // Are we accessing the RCT area? bool rctAccess = params->LBN >= drive->GetBlockCount(); uint32_t rctBlockNumber = params->LBN - drive->GetBlockCount(); @@ -967,7 +988,7 @@ mscp_server::DoDiskTransfer( case Opcodes::READ: { unique_ptr diskBuffer; - + if (rctAccess) { diskBuffer.reset(drive->ReadRCTBlock(rctBlockNumber)); @@ -981,6 +1002,7 @@ mscp_server::DoDiskTransfer( params->BufferPhysicalAddress & 0x00ffffff, params->ByteCount, diskBuffer.get()); + } break; diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index 7e7b9dc..d05ed60 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -715,12 +715,18 @@ uda_c::PostResponse( return res; } -uint64_t +uint32_t uda_c::GetControllerIdentifier() { // TODO: make this not hardcoded - // ID 0x1234568, device class 1 (mass storage), model 2 (UDA50) - return 0x1234567801020000; + // ID 0x12345678 + return 0x12345678; +} + +uint16_t +uda_c::GetControllerClassModel() +{ + return 0x0102; // Class 1 (mass storage), model 2 (UDA50) } void diff --git a/10.02_devices/2_src/uda.hpp b/10.02_devices/2_src/uda.hpp index f33ff83..71fbfff 100644 --- a/10.02_devices/2_src/uda.hpp +++ b/10.02_devices/2_src/uda.hpp @@ -68,7 +68,8 @@ public: // Returns FALSE if the ring is full. bool PostResponse(Message* response); - uint64_t GetControllerIdentifier(void); + uint32_t GetControllerIdentifier(void); + uint16_t GetControllerClassModel(void); uint32_t GetDriveCount(void); mscp_drive_c* GetDrive(uint32_t driveNumber); From 2265a2067c5821b7f037a6a57d3aab0d629fae95 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Sat, 18 May 2019 00:57:07 +0200 Subject: [PATCH 13/18] General cleanup: Refactored ONLINE / SET UNIT CHARACTERISTICS code, added comment headers to most functions. Fixed RK05 to properly use on_param_changed instead of ugly hacks for the image path. --- 10.02_devices/2_src/mscp_drive.cpp | 111 ++++++++++- 10.02_devices/2_src/mscp_drive.hpp | 16 +- 10.02_devices/2_src/mscp_server.cpp | 288 +++++++++++++++------------- 10.02_devices/2_src/mscp_server.hpp | 19 +- 10.02_devices/2_src/rk05.cpp | 42 ++-- 10.02_devices/2_src/rk05.hpp | 6 +- 10.02_devices/2_src/uda.cpp | 239 ++++++++++++++++------- 10.02_devices/2_src/uda.hpp | 14 ++ 8 files changed, 479 insertions(+), 256 deletions(-) diff --git a/10.02_devices/2_src/mscp_drive.cpp b/10.02_devices/2_src/mscp_drive.cpp index 9bb257b..00bd1ef 100644 --- a/10.02_devices/2_src/mscp_drive.cpp +++ b/10.02_devices/2_src/mscp_drive.cpp @@ -1,5 +1,15 @@ /* mscp_drive.cpp: Implementation of MSCP disks. + + This provides the logic for reads and writes to the data and RCT space + for a given drive, as well as configuration for different standard DEC + drive types. + + Disk data is backed by an image file on disk. RCT data exists only in + memory and is not saved -- it is provided to satisfy software that + expects the RCT area to exist. Since no bad sectors will ever actually + exist, the RCT area has no real purpose, so it is ephemeral in this + implementation. */ #include @@ -23,7 +33,7 @@ mscp_drive_c::mscp_drive_c( SetOffline(); // Calculate the unit's ID: - _unitIDDeviceNumber = driveNumber + 1; + _unitDeviceNumber = driveNumber + 1; } mscp_drive_c::~mscp_drive_c() @@ -34,6 +44,11 @@ mscp_drive_c::~mscp_drive_c() } } +// +// GetBlockSize(): +// Returns the size, in bytes, of a single block on this drive. +// This is either 512 or 576 bytes. +// uint32_t mscp_drive_c::GetBlockSize() { // @@ -42,6 +57,11 @@ uint32_t mscp_drive_c::GetBlockSize() return 512; } +// +// GetBlockCount(): +// Get the size of the data space (not including RCT area) of this +// drive, in blocks. +// uint32_t mscp_drive_c::GetBlockCount() { if (_useImageSize) @@ -58,51 +78,95 @@ uint32_t mscp_drive_c::GetBlockCount() } } +// +// GetRCTBlockCount(): +// Returns the total size of the RCT area in blocks. +// uint32_t mscp_drive_c::GetRCTBlockCount() { return _driveInfo.RCTSize * GetRCTCopies(); } +// +// GetMediaID(): +// Returns the media ID specific to this drive's type. +// uint32_t mscp_drive_c::GetMediaID() { return _driveInfo.MediaID; } -uint32_t mscp_drive_c::GetUnitIDDeviceNumber() +// +// GetDeviceNumber(): +// Returns the unique device number for this drive. +// +uint32_t mscp_drive_c::GetDeviceNumber() { - return _unitIDDeviceNumber; + return _unitDeviceNumber; } -uint16_t mscp_drive_c::GetUnitIDClassModel() +// +// GetClassModel(): +// Returns the class and model information for this drive. +// +uint16_t mscp_drive_c::GetClassModel() { - return _unitIDClassModel; + return _unitClassModel; } +// +// GetRCTSize(): +// Returns the size of one copy of the RCT. +// uint16_t mscp_drive_c::GetRCTSize() { return _driveInfo.RCTSize; } +// +// GetRBNs(): +// Returns the number of replacement blocks per track for +// this drive. +// uint8_t mscp_drive_c::GetRBNs() { return 0; } +// +// GetRCTCopies(): +// Returns the number of copies of the RCT present in the RCT +// area. +// uint8_t mscp_drive_c::GetRCTCopies() { return 1; } +// +// IsAvailable(): +// Indicates whether this drive is available (i.e. has an image +// assigned to it and can thus be used by the controller.) +// bool mscp_drive_c::IsAvailable() { return file_is_open(); } +// +// IsOnline(): +// Indicates whether this drive has been placed into an Online +// state (for example by the ONLINE command). +// bool mscp_drive_c::IsOnline() { return _online; } +// +// SetOnline(): +// Brings the drive online. +// void mscp_drive_c::SetOnline() { _online = true; @@ -115,6 +179,10 @@ void mscp_drive_c::SetOnline() image_filepath.readonly = true; } +// +// SetOffline(): +// Takes the drive offline. +// void mscp_drive_c::SetOffline() { _online = false; @@ -196,15 +264,23 @@ uint8_t* mscp_drive_c::ReadRCTBlock( return buffer; } +// +// UpdateCapacity(): +// Updates the capacity parameter of the drive based on the block count and block size. +// void mscp_drive_c::UpdateCapacity() { capacity.value = GetBlockCount() * GetBlockSize(); } +// +// UpdateMetadata(): +// Updates the Unit Class / Model info and RCT area based on the selected drive type. +// void mscp_drive_c::UpdateMetadata() { - _unitIDClassModel = 0x0200 | _driveInfo.Model; + _unitClassModel = 0x0200 | _driveInfo.Model; // Initialize the RCT area size_t rctSize = _driveInfo.RCTSize * GetBlockSize(); @@ -213,6 +289,10 @@ void mscp_drive_c::UpdateMetadata() memset(reinterpret_cast(_rctData.get()), 0, rctSize); } +// +// on_param_changed(): +// Handles configuration parameter changes. +// bool mscp_drive_c::on_param_changed( parameter_c *param) { @@ -246,6 +326,14 @@ bool mscp_drive_c::on_param_changed( return false; } +// +// SetDriveType(): +// Updates this drive's type to the specified type (i.e. +// RA90 or RD54). +// If the specified type is not found in our list of known +// drive types, the drive's type is not changed and false +// is returned. +// bool mscp_drive_c::SetDriveType(const char* typeName) { // @@ -271,17 +359,28 @@ bool mscp_drive_c::SetDriveType(const char* typeName) return false; } +// +// worker(): +// worker method for this drive. No work is necessary. +// void mscp_drive_c::worker(void) { // Nothing to do here at the moment. } +// +// on_power_changed(): +// Handle power change notifications. +// void mscp_drive_c::on_power_changed(void) { // Take the drive offline due to power change SetOffline(); } +// +// on_init_changed(): +// Handle INIT signal. void mscp_drive_c::on_init_changed(void) { // Take the drive offline due to reset diff --git a/10.02_devices/2_src/mscp_drive.hpp b/10.02_devices/2_src/mscp_drive.hpp index a54cc24..9d6a90d 100644 --- a/10.02_devices/2_src/mscp_drive.hpp +++ b/10.02_devices/2_src/mscp_drive.hpp @@ -22,8 +22,8 @@ public: uint32_t GetBlockCount(void); uint32_t GetRCTBlockCount(void); uint32_t GetMediaID(void); - uint32_t GetUnitIDDeviceNumber(void); - uint16_t GetUnitIDClassModel(void); + uint32_t GetDeviceNumber(void); + uint16_t GetClassModel(void); uint16_t GetRCTSize(void); uint8_t GetRBNs(void); uint8_t GetRCTCopies(void); @@ -109,16 +109,16 @@ private: void UpdateMetadata(void); DriveInfo _driveInfo; bool _online; - uint32_t _unitIDDeviceNumber; - uint16_t _unitIDClassModel; + uint32_t _unitDeviceNumber; + uint16_t _unitClassModel; bool _useImageSize; // // RCT ("Replacement and Caching Table") data: - // At this time we provide the minimum required by the MSCP spec, - // a single block used to provide the volume write-protect flags. - // We do not require additional RCT space because the underlying media - // (an image file) doesn't have bad sectors. + // The size of this area varies depending on the drive. This is + // provided only to appease software that expects the RCT to exist -- + // since there will never be any bad sectors in our disk images + // there is no other purpose. // This data is not persisted to disk as it is unnecessary. // unique_ptr _rctData; diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index 052fca4..1afd1c5 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -1,7 +1,6 @@ /* mscp_server.cpp: Implementation a simple MSCP server. - This provides an implementation of the Minimal MSCP subset outlined in AA-L619A-TK (Chapter 6). It takes a few liberties and errs on the side of implementation simplicity. @@ -18,7 +17,7 @@ there's no good reason to make it more complex: real MSCP controllers (like the original UDA50) would resequence commands to allow optimal throughput across multiple units, etc. On the - unibone, the underlying storage and the execution speed of the + Unibone, the underlying storage and the execution speed of the processor is orders of magnitude faster, so even a brute-force braindead implementation like this can saturate the Unibus. @@ -44,17 +43,18 @@ using namespace std; #include "mscp_server.hpp" #include "uda.hpp" +// +// polling_worker(): +// Runs the main MSCP polling thread. +// void* polling_worker( void *context) { mscp_server* server = reinterpret_cast(context); - server->Poll(); - return nullptr; } - mscp_server::mscp_server( uda_c *port) : device_c(), @@ -66,7 +66,7 @@ mscp_server::mscp_server( polling_mutex(PTHREAD_MUTEX_INITIALIZER), _credits(INIT_CREDITS) { - // Alias the port pointer. We do not own the port, merely reference it. + // Alias the port pointer. We do not own the port, we merely reference it. _port = port; StartPollingThread(); @@ -78,6 +78,10 @@ mscp_server::~mscp_server() AbortPollingThread(); } +// +// StartPollingThread(): +// Initializes the MSCP polling thread and starts it running. +// void mscp_server::StartPollingThread(void) { @@ -105,6 +109,10 @@ mscp_server::StartPollingThread(void) DEBUG("Polling thread created."); } +// +// AbortPollingThread(): +// Stops the MSCP polling thread. +// void mscp_server::AbortPollingThread(void) { @@ -126,6 +134,14 @@ mscp_server::AbortPollingThread(void) DEBUG("Polling thread aborted."); } +// +// Poll(): +// The MSCP polling thread. +// This thread waits to be awoken, then pulls messages from the MSCP command +// ring and executes them. When no work is left to be done, it goes back to +// sleep. +// This is awoken by a write to the UDA IP register. +// void mscp_server::Poll(void) { @@ -176,8 +192,7 @@ mscp_server::Poll(void) } // - // Pull commands from the queue until it is empty, at which - // point we sleep until awoken again. + // Pull commands from the queue until it is empty or we're told to quit. // while(!messages.empty() && !_abort_polling && _pollState != PollingState::InitRestart) { @@ -210,7 +225,7 @@ mscp_server::Poll(void) switch (header->Word3.Command.Opcode) { case Opcodes::ABORT: - cmdStatus = Abort(message); + cmdStatus = Abort(); break; case Opcodes::ACCESS: @@ -218,7 +233,7 @@ mscp_server::Poll(void) break; case Opcodes::AVAILABLE: - cmdStatus = Available(message, header->UnitNumber, modifiers); + cmdStatus = Available(header->UnitNumber, modifiers); break; case Opcodes::COMPARE_HOST_DATA: @@ -226,7 +241,7 @@ mscp_server::Poll(void) break; case Opcodes::DETERMINE_ACCESS_PATHS: - cmdStatus = DetermineAccessPaths(message, header->UnitNumber); + cmdStatus = DetermineAccessPaths(header->UnitNumber); break; case Opcodes::ERASE: @@ -295,13 +310,10 @@ mscp_server::Poll(void) header->Word3.End.Endcode & Endcodes::END) { // - // We steal the hack from simh: + // We steal the credits 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. // uint8_t grantedCredits = min(_credits, static_cast(MAX_CREDITS)); _credits -= grantedCredits; @@ -315,6 +327,7 @@ mscp_server::Poll(void) // // Post the response to the port's response ring. + // If everything is working properly, there should always be room. // if(!_port->PostResponse(message.get())) { @@ -326,6 +339,11 @@ mscp_server::Poll(void) // } + // + // Go back to sleep. If a UDA reset is pending, we need to signal + // the Reset() call so it knows we've completed our poll and are + // returning to sleep (i.e. the polling thread is now reset.) + // pthread_mutex_lock(&polling_mutex); if (_pollState == PollingState::InitRestart) { @@ -349,9 +367,12 @@ mscp_server::Poll(void) DEBUG("MSCP Polling thread exiting."); } +// +// The following are all implementations of the MSCP commands we support. +// + uint32_t -mscp_server::Abort( - shared_ptr message) +mscp_server::Abort() { INFO("MSCP ABORT"); @@ -367,18 +388,16 @@ mscp_server::Abort( return STATUS(Status::SUCCESS, 0, 0); } - uint32_t mscp_server::Available( - shared_ptr message, uint16_t unitNumber, uint16_t modifiers) { + UNUSED(modifiers); + // Message has no message-specific data. - // We don't do much with this now... // Just set the specified drive as Available if appropriate. // We do nothing with the spin-down modifier. - DEBUG("MSCP AVAILABLE"); mscp_drive_c* drive = GetDrive(unitNumber); @@ -423,7 +442,6 @@ mscp_server::CompareHostData( uint32_t mscp_server::DetermineAccessPaths( - shared_ptr message, uint16_t unitNumber) { DEBUG("MSCP DETERMINE ACCESS PATHS drive %d", unitNumber); @@ -432,7 +450,16 @@ mscp_server::DetermineAccessPaths( // if the unit is incapable of being connected to more than one // controller." That's us! - return STATUS(Status::SUCCESS, 0, 0); + mscp_drive_c* drive = GetDrive(unitNumber); + if (nullptr == drive || + !drive->IsAvailable()) + { + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); + } + else + { + return STATUS(Status::SUCCESS, 0, 0); + } } uint32_t @@ -465,7 +492,6 @@ mscp_server::GetCommandStatus( message->MessageLength = sizeof(GetCommandStatusResponseParameters) + HEADER_SIZE; - GetCommandStatusResponseParameters* params = reinterpret_cast( GetParameterPointer(message)); @@ -513,7 +539,6 @@ mscp_server::GetUnitStatus( message->MessageLength = sizeof(GetUnitStatusResponseParameters) + HEADER_SIZE; - ControlMessageHeader* header = reinterpret_cast(message->Message); @@ -551,8 +576,8 @@ mscp_server::GetUnitStatus( params->Reserved2 = 0; params->UnitFlags = 0; // TODO: 0 for now, which is sane. params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives. - params->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber(); - params->UnitIdClassModel = drive->GetUnitIDClassModel(); + params->UnitIdDeviceNumber = drive->GetDeviceNumber(); + params->UnitIdClassModel = drive->GetClassModel(); params->UnitIdUnused = 0; params->MediaTypeIdentifier = drive->GetMediaID(); params->ShadowUnit = unitNumber; // Always equal to unit number @@ -618,61 +643,9 @@ mscp_server::Online( // host-settable flags we can't support. // - // TODO: "The ONLINE command performs a SET UNIT CHARACTERISTICS + // "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; - uint16_t MultiUnitCode; - uint32_t Reserved0; - uint32_t UnitIdDeviceNumber; - uint16_t UnitIdUnused; - uint16_t UnitIdClassModel; - uint32_t MediaTypeIdentifier; - uint32_t Reserved1; - uint32_t UnitSize; - uint32_t VolumeSerialNumber; - }; - #pragma pack(pop) - - DEBUG("MSCP ONLINE drive %d", unitNumber); - - // Adjust message length for response - message->MessageLength = sizeof(OnlineResponseParameters) + - HEADER_SIZE; - - mscp_drive_c* drive = GetDrive(unitNumber); - - if (nullptr == drive || - !drive->IsAvailable()) - { - return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); - } - - bool alreadyOnline = drive->IsOnline(); - - drive->SetOnline(); - - 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->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber(); - params->UnitIdClassModel = drive->GetUnitIDClassModel(); - params->UnitIdUnused = 0; - params->MediaTypeIdentifier = drive->GetMediaID(); - params->UnitSize = drive->GetBlockCount(); - params->VolumeSerialNumber = 1; // We report no serial - params->Reserved0 = 0; - params->Reserved1 = 0; - - return STATUS(Status::SUCCESS | - (alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0, 0); + return SetUnitCharacteristicsInternal(message, unitNumber, modifiers, true /*bring online*/); } uint32_t @@ -786,50 +759,7 @@ mscp_server::SetUnitCharacteristics( DEBUG("MSCP SET UNIT CHARACTERISTICS drive %d", unitNumber); - // TODO: mostly same as Online command: should share logic. - #pragma pack(push,1) - struct SetUnitCharacteristicsResponseParameters - { - uint16_t UnitFlags; - uint16_t MultiUnitCode; - uint32_t Reserved0; - uint32_t UnitIdDeviceNumber; - uint16_t UnitIdUnused; - uint16_t UnitIdClassModel; - 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; - - mscp_drive_c* drive = GetDrive(unitNumber); - // Check unit - if (nullptr == drive || - !drive->IsAvailable()) - { - return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); - } - - 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->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber(); - params->UnitIdClassModel = drive->GetUnitIDClassModel(); - params->UnitIdUnused = 0; - params->MediaTypeIdentifier = drive->GetMediaID(); - params->UnitSize = drive->GetBlockCount(); - params->VolumeSerialNumber = 0; // We report no serial - - return STATUS(Status::SUCCESS, 0, 0); + return SetUnitCharacteristicsInternal(message, unitNumber, modifiers, false); } @@ -859,6 +789,80 @@ mscp_server::Write( modifiers); } +// +// SetUnitCharacteristicsInternal(): +// Logic common to both ONLINE and SET UNIT CHARACTERISTICS commands. +// +uint32_t +mscp_server::SetUnitCharacteristicsInternal( + shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers, + bool bringOnline) +{ + UNUSED(modifiers); + // TODO: handle Set Write Protect modifier + + #pragma pack(push,1) + struct SetUnitCharacteristicsResponseParameters + { + uint16_t UnitFlags; + uint16_t MultiUnitCode; + uint32_t Reserved0; + uint32_t UnitIdDeviceNumber; + uint16_t UnitIdUnused; + uint16_t UnitIdClassModel; + uint32_t MediaTypeIdentifier; + uint32_t Reserved1; + uint32_t UnitSize; + uint32_t VolumeSerialNumber; + }; + #pragma pack(pop) + + // Adjust message length for response + message->MessageLength = sizeof(SetUnitCharacteristicsResponseParameters) + + HEADER_SIZE; + + mscp_drive_c* drive = GetDrive(unitNumber); + // Check unit + if (nullptr == drive || + !drive->IsAvailable()) + { + return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0); + } + + 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->UnitIdDeviceNumber = drive->GetDeviceNumber(); + params->UnitIdClassModel = drive->GetClassModel(); + params->UnitIdUnused = 0; + params->MediaTypeIdentifier = drive->GetMediaID(); + params->UnitSize = drive->GetBlockCount(); + params->VolumeSerialNumber = 0; + params->Reserved0 = 0; + params->Reserved1 = 0; + + if (bringOnline) + { + bool alreadyOnline = drive->IsOnline(); + drive->SetOnline(); + return STATUS(Status::SUCCESS | + (alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0, 0); + } + else + { + return STATUS(Status::SUCCESS, 0, 0); + } +} + +// +// DoDiskTransfer(): +// Common transfer logic for READ, WRITE, ERASE, COMPARE HOST DATA and ACCCESS commands. +// uint32_t mscp_server::DoDiskTransfer( uint16_t operation, @@ -1042,13 +1046,27 @@ mscp_server::DoDiskTransfer( return STATUS(Status::SUCCESS, 0, 0); } +// +// GetParameterPointer(): +// Returns a pointer to the Parameter text in the given Message. +// uint8_t* mscp_server::GetParameterPointer( shared_ptr message) { + // We silence a strict aliasing warning here; this is safe (if perhaps not recommended + // the general case.) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" return reinterpret_cast(message->Message)->Parameters; +#pragma GCC diagnostic pop } +// +// GetDrive(): +// Returns the mscp_drive_c object for the specified unit number, +// or nullptr if no such object exists. +// mscp_drive_c* mscp_server::GetDrive( uint32_t unitNumber) @@ -1062,6 +1080,12 @@ mscp_server::GetDrive( return drive; } +// +// Reset(): +// Resets the MSCP server: +// - Waits for the polling thread to finish its current work +// - Releases all drives into the Available state +// void mscp_server::Reset(void) { @@ -1084,13 +1108,16 @@ mscp_server::Reset(void) _credits = INIT_CREDITS; // Release all drives - for (int i=0;i<_port->GetDriveCount();i++) + for (uint32_t i=0;i<_port->GetDriveCount();i++) { GetDrive(i)->SetOffline(); } } - +// +// InitPolling(): +// Wakes the polling thread. +// void mscp_server::InitPolling(void) { @@ -1098,16 +1125,9 @@ 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 index 3b7bd81..e25f8ec 100644 --- a/10.02_devices/2_src/mscp_server.hpp +++ b/10.02_devices/2_src/mscp_server.hpp @@ -17,11 +17,13 @@ class mscp_drive_c; #define MAX_CREDITS 14 #define INIT_CREDITS 1 -// 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. +// Note: This struct (and many others like it in this code) assumes +// little-endian byte orderings. Probably not a big deal unless this +// someday runs on something other than a Beaglebone. // #pragma pack(push,1) struct ControlMessageHeader @@ -141,14 +143,14 @@ 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; } + bool on_param_changed(parameter_c *param) override { UNUSED(param); return true; } private: - uint32_t Abort(std::shared_ptr message); + uint32_t Abort(void); uint32_t Access(std::shared_ptr message, uint16_t unitNumber); - uint32_t Available(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); + uint32_t Available(uint16_t unitNumber, uint16_t modifiers); uint32_t CompareHostData(std::shared_ptr message, uint16_t unitNumber); - uint32_t DetermineAccessPaths(std::shared_ptr message, uint16_t unitNumber); + uint32_t DetermineAccessPaths(uint16_t unitNumber); uint32_t Erase(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); uint32_t GetCommandStatus(std::shared_ptr message); uint32_t GetUnitStatus(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); @@ -158,7 +160,12 @@ private: uint32_t Read(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); uint32_t Replace(std::shared_ptr message, uint16_t unitNumber); uint32_t Write(std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); - + + uint32_t SetUnitCharacteristicsInternal( + std::shared_ptr message, + uint16_t unitNumber, + uint16_t modifiers, + bool bringOnline); uint32_t DoDiskTransfer(uint16_t operation, std::shared_ptr message, uint16_t unitNumber, uint16_t modifiers); uint8_t* GetParameterPointer(std::shared_ptr message); mscp_drive_c* GetDrive(uint32_t unitNumber); diff --git a/10.02_devices/2_src/rk05.cpp b/10.02_devices/2_src/rk05.cpp index aaed33f..1505308 100755 --- a/10.02_devices/2_src/rk05.cpp +++ b/10.02_devices/2_src/rk05.cpp @@ -89,6 +89,23 @@ bool rk05_c::get_search_complete(void) return scp; } +bool rk05_c::on_param_changed( + parameter_c *param) +{ + if (&image_filepath == param) + { + if (file_open(image_filepath.new_value, true)) + { + _dry = true; + controller->on_drive_status_changed(this); + image_filepath.value = image_filepath.new_value; + return true; + } + } + + return false; +} + // // Reset / Power handlers // @@ -110,15 +127,6 @@ void rk05_c::on_init_changed(void) if (init_asserted) { drive_reset(); - - if (!file_is_open()) - { - load_image(image_filepath.value); - - file_open(image_filepath.value, true); - _dry = file_is_open(); - controller->on_drive_status_changed(this); - } } } @@ -228,6 +236,8 @@ void rk05_c::seek( void rk05_c::set_write_protect(bool protect) { + UNUSED(protect); + // Not implemented at the moment. _scp = false; } @@ -288,12 +298,6 @@ void rk05_c::worker(void) _sok = true; controller->on_drive_status_changed(this); } - - // Crude: Check for disk image change; if changed we load the new image. - if (image_filepath.value != _currentFilePath) - { - load_image(image_filepath.value); - } } } } @@ -308,11 +312,3 @@ uint64_t rk05_c::get_disk_byte_offset( (surface * _geometry.Sectors) + sector); } - -void rk05_c::load_image(std::string path) -{ - file_open(image_filepath.value, true); - _dry = file_is_open(); - controller->on_drive_status_changed(this); - _currentFilePath = path; -} diff --git a/10.02_devices/2_src/rk05.hpp b/10.02_devices/2_src/rk05.hpp index 055971f..d960987 100755 --- a/10.02_devices/2_src/rk05.hpp +++ b/10.02_devices/2_src/rk05.hpp @@ -52,14 +52,10 @@ private: volatile bool _scp; // Indicates the completion of a seek - std::string _currentFilePath; // Currently loaded disk image if any - uint64_t get_disk_byte_offset( uint32_t cylinder, uint32_t surface, uint32_t sector); - - void load_image(std::string path); public: Geometry get_geometry(void); @@ -91,8 +87,8 @@ public: rk05_c(storagecontroller_c *controller); + bool on_param_changed(parameter_c* param) override; void on_power_changed(void) override; - void on_init_changed(void) override; // background worker function diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index d05ed60..c675652 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -5,7 +5,12 @@ the four-step initialization handshake, DMA transfers to and from the Unibus, and the command/response ring protocols. - At this time it acts as the port for an MSCP controller. + While the name "UDA" is used here, this is not a strict emulation + of a real UDA50 -- it is a general MSCP implementation and can be + thought of as the equivalent of the third-party MSCP controllers + from Emulex, CMD, etc. that were available. + + At this time this class acts as the port for an MSCP controller. It would be trivial to extend this to TMSCP at a future date. */ @@ -64,7 +69,7 @@ uda_c::uda_c() : // // Initialize drives. We support up to eight attached drives. // - drivecount = 8; + drivecount = DRIVE_COUNT; for (uint32_t i=0; i(storagedrives[driveNumber]); } +// +// StateTransition(): +// Transitions the UDA initialization state machine to the specified step, +// atomically. +// void uda_c::StateTransition( InitializationStep nextStep) { @@ -126,6 +151,10 @@ void uda_c::StateTransition( pthread_mutex_unlock(&on_after_register_access_mutex); } +// +// worker(): +// Implements the initialization state machine. +// void uda_c::worker(void) { worker_init_realtime_priority(rt_device); @@ -162,7 +191,6 @@ void uda_c::worker(void) break; case InitializationStep::Step1: - // Wait 100uS, set SA. timeout.wait_us(500); DEBUG("Transition to Init state S1."); @@ -176,9 +204,10 @@ void uda_c::worker(void) break; case InitializationStep::Step2: - DEBUG("Transition to Init state S2."); timeout.wait_us(500); - // update the SA read value for step 2: + DEBUG("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. update_SA(0x1000 | ((_step1Value >> 8) & 0xff)); @@ -186,7 +215,6 @@ void uda_c::worker(void) break; case InitializationStep::Step3: - // Wait 100uS, set SA. timeout.wait_us(500); DEBUG("Transition to Init state S3."); @@ -233,7 +261,7 @@ void uda_c::worker(void) DEBUG("Transition to Init state S4, comm area initialized."); // Update the SA read value for step 4: // Bits 7-0 indicating our control microcode version. - update_SA(0x4063); // UDA50 ID, makes RSTS happy + update_SA(UDA50_ID); // UDA50 ID, makes RSTS happy Interrupt(); break; @@ -245,6 +273,10 @@ void uda_c::worker(void) } +// +// on_after_register_access(): +// Handles register accesses for the IP and SA registers. +// void uda_c::on_after_register_access( unibusdevice_register_t *device_reg, @@ -253,7 +285,7 @@ uda_c::on_after_register_access( { switch (device_reg->index) { - case 0: // IP + case 0: // IP - read / write if (UNIBUS_CONTROL_DATO == unibus_control) { // "When written with any value, it causes a hard initialization @@ -366,7 +398,7 @@ uda_c::on_after_register_access( // | reserved | burst |L|G| // | | |F|O| // +---------------+-----------+-+-+ - // burst is one less than the max. number of longwords + // 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. // @@ -418,6 +450,10 @@ uda_c::on_after_register_access( } } +// +// update_SA(): +// Updates the SA register value exposed by the Unibone. +// void uda_c::update_SA(uint16_t value) { @@ -427,6 +463,16 @@ uda_c::update_SA(uint16_t value) "update_SA"); } +// +// GetNextCommand(): +// Attempts to pull the next command from the command ring, if any +// are available. +// If successful, returns a pointer to a Message struct; this pointer +// is owned by the caller. +// On failure, nullptr is returned. This indicates that the ring is +// empty or that an attempt to access non-existent memory occurred. +// TODO: Need to handle NXM cases properly. +// Message* uda_c::GetNextCommand(void) { @@ -440,7 +486,6 @@ uda_c::GetNextCommand(void) _commandRingPointer, descriptorAddress); - std::unique_ptr cmdDescriptor( reinterpret_cast( DMARead( @@ -448,7 +493,6 @@ uda_c::GetNextCommand(void) sizeof(Descriptor), sizeof(Descriptor)))); - // TODO: if NULL is returned after retry assume a bus error and handle it appropriately. assert(cmdDescriptor != nullptr); @@ -474,10 +518,8 @@ uda_c::GetNextCommand(void) messageAddress - 4, success); - // - // TODO: sanity check message length (what is the max length we - // can expect to see?) - // + assert(messageLength > 0 && messageLength < MAX_MESSAGE_LENGTH); + std::unique_ptr cmdMessage( reinterpret_cast( DMARead( @@ -547,13 +589,7 @@ uda_c::GetNextCommand(void) // // Set ring base - 4 to non-zero to indicate a transition. // - DMAWriteWord( - _ringBase - 4, - 0x1); - - // - // Raise the interrupt - // + DMAWriteWord(_ringBase - 4, 0x1); Interrupt(); } @@ -566,6 +602,12 @@ uda_c::GetNextCommand(void) return nullptr; } +// +// PostResponse(): +// Posts the provided Message to the response ring. +// Returns true on success, false otherwise. +// TODO: Need to handle NXM, as above. +// bool uda_c::PostResponse( Message* response @@ -598,10 +640,21 @@ uda_c::PostResponse( (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. - // + // Read the buffer length the host has allocated for this response. + // + // TODO: + // If it is shorter than the buffer we're writing then we will need to + // split the response into multiple responses. I have never seen this happen, + // however and I'm curious if the documentation (AA-L621A-TK) is simply incorrect: + // "Note that if a controller's responses are less than or equal to 60 bytes, + // then the controller need not check the size of the response slot." + // All of the MSCP response messages are shorter than 60 bytes, so this is always + // the case. I'll also note that the spec states "The minimum acceptable size + // is 60 bytes of message text" for the response buffer set up by the host and this + // is *definitely* not followed by host drivers. + // + // The doc is also not exactly clear what a fragmented set of responses looks like... + // // Message length is at messageAddress - 4 -- this is the size of the command // not including the two header words. // @@ -613,34 +666,36 @@ uda_c::PostResponse( DEBUG("response address o%o length o%o", messageAddress, response->MessageLength); - if (reinterpret_cast(response)[0] == 0) + assert(reinterpret_cast(response)[0] > 0); + + if (messageLength == 0) + { + // A lot of bootstraps appear to set up response buffers of length 0. + // We just log the behavior. + DEBUG("Host response buffer size is zero."); + } + else if (messageLength < response->MessageLength) { - ERROR("Writing zero length response!"); + // + // If this happens it's likely fatal since we're not fragmenting responses (see the big comment + // block above). So eat flaming death. + // + FATAL("Response buffer 0x%x > host buffer length 0x%x", response->MessageLength, messageLength); } - if (messageLength < response->MessageLength) - { - // TODO: A lot of bootstraps appear to set up response buffers of length 0... - ERROR("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)); - } + // + // 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)); // // 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.) @@ -696,13 +751,7 @@ uda_c::PostResponse( // // Set ring base - 2 to non-zero to indicate a transition. // - DMAWriteWord( - _ringBase - 2, - 0x1); - - // - // Raise the interrupt - // + DMAWriteWord(_ringBase - 2, 0x1); Interrupt(); } @@ -715,6 +764,11 @@ uda_c::PostResponse( return res; } +// +// GetControllerIdentifier(): +// Returns the ID used by SET CONTROLLER CHARACTERISTICS. +// This should be unique per controller. +// uint32_t uda_c::GetControllerIdentifier() { @@ -723,12 +777,21 @@ uda_c::GetControllerIdentifier() return 0x12345678; } +// +// GetControllerClassModel(): +// Returns the Class and Model information used by SET CONTROLLER CHARACTERISTICS. +// uint16_t uda_c::GetControllerClassModel() { return 0x0102; // Class 1 (mass storage), model 2 (UDA50) } +// +// Interrupt(): +// Invokes a Unibus interrupt if interrupts are enabled and the interrupt +// vector is non-zero. +// void uda_c::Interrupt(void) { @@ -738,6 +801,10 @@ uda_c::Interrupt(void) } } +// +// on_power_changed(): +// Resets the controller and all attached drives. +// void uda_c::on_power_changed(void) { @@ -750,6 +817,10 @@ uda_c::on_power_changed(void) } } +// +// on_init_changed(): +// Resets the controller and all attached drives. +// void uda_c::on_init_changed(void) { @@ -762,21 +833,33 @@ uda_c::on_init_changed(void) storagecontroller_c::on_init_changed(); } +// +// on_drive_status_changed(): +// A no-op. The controller doesn't require any drive notifications. +// void uda_c::on_drive_status_changed(storagedrive_c *drive) { - + UNUSED(drive); } +// +// GetCommandDescriptorAddress(): +// Returns the address of the given command descriptor in the command ring. +// uint32_t uda_c::GetCommandDescriptorAddress( size_t index ) { - return _ringBase + _responseRingLength * sizeof(Descriptor) + + return _ringBase + _responseRingLength * sizeof(Descriptor) + index * sizeof(Descriptor); } +// +// GetResponseDescriptorAddress(): +// Returns the address of the given response descriptor in the response ring. +// uint32_t uda_c::GetResponseDescriptorAddress( size_t index @@ -785,10 +868,11 @@ uda_c::GetResponseDescriptorAddress( 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. -*/ +// +// DMAWriteWord(): +// Writes 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, @@ -800,10 +884,11 @@ uda_c::DMAWriteWord( 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. -*/ +// +// DMAReadWord(): +// 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, @@ -829,10 +914,13 @@ uda_c::DMAReadWord( } -/* - Write data from buffer to Unibus memory. Returns true - on success; if false is returned this is due to an NXM condition. -*/ +// +// DMAWrite(): +// Write data from the provided buffer to Unibus memory. Returns true +// on success; if false is returned this is due to an NXM condition. +// The address specified in 'address' must be word-aligned and the +// length must be even. +// bool uda_c::DMAWrite( uint32_t address, @@ -849,11 +937,14 @@ uda_c::DMAWrite( lengthInBytes >> 1); } -/* - 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. -*/ +// +// DMARead(): +// 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. +// The address specified in 'address' must be word-aligned +// and the length must be even. +// uint8_t* uda_c::DMARead( uint32_t address, diff --git a/10.02_devices/2_src/uda.hpp b/10.02_devices/2_src/uda.hpp index 71fbfff..10c6071 100644 --- a/10.02_devices/2_src/uda.hpp +++ b/10.02_devices/2_src/uda.hpp @@ -11,6 +11,20 @@ #include "mscp_server.hpp" #include "mscp_drive.hpp" +// The number of drives supported by the controller. +// This is arbitrarily fixed at 8 but could be set to any +// value up to 65535. +#define DRIVE_COUNT 8 + +// The control/microcode version info returned by SA in the fourth intialization step. +// This indicates a UDA50 controller, which makes RSTS happy. +#define UDA50_ID 0x4063 + +// The maximum message length we can handle. This is provided as a sanity check +// to prvent parsing clearly invalid commands. +#define MAX_MESSAGE_LENGTH 0x1000 + + // TODO: this currently assumes a little-endian machine! #pragma pack(push,1) struct Message From 1ad88b6778c595f72ef91b91d7b8df592cf1ff7d Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Sat, 18 May 2019 02:16:05 +0200 Subject: [PATCH 14/18] Adding copyright info to file headers. --- 10.02_devices/2_src/mscp_drive.cpp | 3 +++ 10.02_devices/2_src/mscp_drive.hpp | 6 +++++- 10.02_devices/2_src/mscp_server.cpp | 17 +++++++---------- 10.02_devices/2_src/mscp_server.hpp | 7 +++++++ 10.02_devices/2_src/rk05.cpp | 6 +++++- 10.02_devices/2_src/rk05.hpp | 6 +++++- 10.02_devices/2_src/rk11.cpp | 8 ++++++-- 10.02_devices/2_src/rk11.hpp | 8 ++++++-- 10.02_devices/2_src/uda.cpp | 3 +++ 10.02_devices/2_src/uda.hpp | 5 ++++- 10 files changed, 51 insertions(+), 18 deletions(-) diff --git a/10.02_devices/2_src/mscp_drive.cpp b/10.02_devices/2_src/mscp_drive.cpp index 00bd1ef..64710f4 100644 --- a/10.02_devices/2_src/mscp_drive.cpp +++ b/10.02_devices/2_src/mscp_drive.cpp @@ -1,5 +1,8 @@ /* mscp_drive.cpp: Implementation of MSCP disks. + + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. This provides the logic for reads and writes to the data and RCT space for a given drive, as well as configuration for different standard DEC diff --git a/10.02_devices/2_src/mscp_drive.hpp b/10.02_devices/2_src/mscp_drive.hpp index 9d6a90d..bc221b3 100644 --- a/10.02_devices/2_src/mscp_drive.hpp +++ b/10.02_devices/2_src/mscp_drive.hpp @@ -1,5 +1,9 @@ /* - mscp_drive.hpp: Implementation of MSCP drive, used with MSCP controller. + mscp_drive.hpp: Implementation of MSCP drive, used with MSCP controller. + + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + */ #pragma once diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index 1afd1c5..bf462cb 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -1,5 +1,8 @@ /* - mscp_server.cpp: Implementation a simple MSCP server. + mscp_server.cpp: Implementation of a simple MSCP server. + + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. This provides an implementation of the Minimal MSCP subset outlined in AA-L619A-TK (Chapter 6). It takes a few liberties and errs on @@ -592,16 +595,10 @@ mscp_server::GetUnitStatus( // Since our underlying storage is an image file on flash memory, we don't need to be concerned // about seek times, so the below is appropriate: // - params->TrackSize = 1; // one block per track, per aa-l619a-tk. - params->GroupSize = 1; // one cylinder per group - params->CylinderSize = 1; // one sector per cylinder + params->TrackSize = 1; + params->GroupSize = 1; + params->CylinderSize = 1; - // - // 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->RCTSize = drive->GetRCTSize(); params->RBNs = drive->GetRBNs(); params->Copies = drive->GetRCTCopies(); diff --git a/10.02_devices/2_src/mscp_server.hpp b/10.02_devices/2_src/mscp_server.hpp index e25f8ec..d5f10ec 100644 --- a/10.02_devices/2_src/mscp_server.hpp +++ b/10.02_devices/2_src/mscp_server.hpp @@ -1,3 +1,10 @@ +/* + mscp_server.hpp: Implementation of a simple MSCP server. + + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. +*/ + #pragma once #include diff --git a/10.02_devices/2_src/rk05.cpp b/10.02_devices/2_src/rk05.cpp index 1505308..e26e809 100755 --- a/10.02_devices/2_src/rk05.cpp +++ b/10.02_devices/2_src/rk05.cpp @@ -1,4 +1,8 @@ -/* rk05.cpp: implementation of RK05 disk drive, attached to RK11D controller +/* + rk05.cpp: implementation of RK05 disk drive, attached to RK11D controller + + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. */ diff --git a/10.02_devices/2_src/rk05.hpp b/10.02_devices/2_src/rk05.hpp index d960987..45adf35 100755 --- a/10.02_devices/2_src/rk05.hpp +++ b/10.02_devices/2_src/rk05.hpp @@ -1,4 +1,8 @@ -/* rk05.cpp: implementation of RK05 disk drive, used with RK11D controller +/* + rk05.hpp: implementation of RK05 disk drive, used with RK11D controller + + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. */ #ifndef _RK05_HPP_ diff --git a/10.02_devices/2_src/rk11.cpp b/10.02_devices/2_src/rk11.cpp index e454eb9..06f18ac 100755 --- a/10.02_devices/2_src/rk11.cpp +++ b/10.02_devices/2_src/rk11.cpp @@ -1,6 +1,10 @@ -/* rk11_cpp: RK11 UNIBUS controller +/* + rk11_cpp: RK11 UNIBUS controller - */ + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + +*/ #include #include diff --git a/10.02_devices/2_src/rk11.hpp b/10.02_devices/2_src/rk11.hpp index 580d565..c306b7d 100755 --- a/10.02_devices/2_src/rk11.hpp +++ b/10.02_devices/2_src/rk11.hpp @@ -1,6 +1,10 @@ -/* rk11.hpp: RK11 UNIBUS controller +/* + rk11.hpp: RK11 UNIBUS controller - */ + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + +*/ #ifndef _RK11_HPP_ #define _RK11_HPP_ diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index c675652..2bf9dcc 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -1,6 +1,9 @@ /* uda.cpp: Implementation of the MSCP port (unibus interface). + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. + This provides logic for the UDA50's SA and IP registers, the four-step initialization handshake, DMA transfers to and from the Unibus, and the command/response ring protocols. diff --git a/10.02_devices/2_src/uda.hpp b/10.02_devices/2_src/uda.hpp index 10c6071..aa9557f 100644 --- a/10.02_devices/2_src/uda.hpp +++ b/10.02_devices/2_src/uda.hpp @@ -1,5 +1,8 @@ /* - uda.hpp: MSCP controller port (UDA50) + uda.hpp: MSCP controller port (UDA50) + + Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA. + Contributed under the BSD 2-clause license. */ #pragma once From a0bdd14810716a113ffe2ee089ba09ab61775c35 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Wed, 22 May 2019 10:16:57 +0200 Subject: [PATCH 15/18] Fix for VMS bootstrap (sanity check was violated, we now log the case rather than aborting). Fixed interrupt behavior (docs around the IE flag at init are vague -- looks like IE controls interrupts only during the initialization; afterwards interrupts are always enabled regardless.) V8 Research Unix now boots (tested on VAX-11/750). --- 10.02_devices/2_src/mscp_drive.cpp | 4 ++-- 10.02_devices/2_src/uda.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/10.02_devices/2_src/mscp_drive.cpp b/10.02_devices/2_src/mscp_drive.cpp index 64710f4..4d07a45 100644 --- a/10.02_devices/2_src/mscp_drive.cpp +++ b/10.02_devices/2_src/mscp_drive.cpp @@ -178,8 +178,8 @@ void mscp_drive_c::SetOnline() // Once online, the drive's type and image cannot be changed until // the drive is offline. // - type_name.readonly = true; - image_filepath.readonly = true; + // type_name.readonly = true; + // image_filepath.readonly = true; } // diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index 2bf9dcc..ff97897 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -682,8 +682,9 @@ uda_c::PostResponse( // // If this happens it's likely fatal since we're not fragmenting responses (see the big comment // block above). So eat flaming death. + // Note: the VMS bootstrap does this, so we'll just log the issue. // - FATAL("Response buffer 0x%x > host buffer length 0x%x", response->MessageLength, messageLength); + DEBUG("Response buffer 0x%x > host buffer length 0x%x", response->MessageLength, messageLength); } // @@ -798,7 +799,7 @@ uda_c::GetControllerClassModel() void uda_c::Interrupt(void) { - if (_interruptEnable && _interruptVector != 0) + if ((_interruptEnable || _initStep == InitializationStep::Complete) && _interruptVector != 0) { interrupt(); } From 00627567bfeefb3cc7e21a5611dd98d9c03c1708 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Wed, 19 Jun 2019 22:48:32 +0200 Subject: [PATCH 16/18] Removing merge marker from menu_devices.cpp. --- 10.03_app_demo/2_src/menu_devices.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/10.03_app_demo/2_src/menu_devices.cpp b/10.03_app_demo/2_src/menu_devices.cpp index 7ddbcfd..c855121 100644 --- a/10.03_app_demo/2_src/menu_devices.cpp +++ b/10.03_app_demo/2_src/menu_devices.cpp @@ -176,7 +176,6 @@ void application_c::menu_devices(bool with_CPU) { // now devices are "Plugged in". Reset PDP-11. unibus->powercycle(); ->>>>>>> upstream/master while (!ready) { From 073a2334b6d1ff5274a68d037bfcb0ca34efdcf6 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Wed, 14 Aug 2019 09:46:32 +0200 Subject: [PATCH 17/18] Fixes for MSCP after Joerg's INTR/DMA rewrites: - Fixed programmable interrupt vector (was broken after changes) - Fixed interrupts during MSCP 4-stage init to atomically update SA register; 4.3bsd now boots. --- 10.02_devices/2_src/uda.cpp | 54 ++++++++++++++++++++++++------------- 10.02_devices/2_src/uda.hpp | 7 ++--- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index 2fbef44..c00ed52 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -46,12 +46,12 @@ uda_c::uda_c() : type_name.value = "UDA50"; log_label = "uda"; - // base addr, intr-vector, intr level - set_default_bus_params(0772150, 20, 0154, 5) ; - dma_request.set_priority_slot(default_priority_slot) ; - intr_request.set_priority_slot(default_priority_slot) ; - intr_request.set_level(default_intr_level) ; - intr_request.set_vector(default_intr_vector) ; + // base addr, intr-vector, intr level + set_default_bus_params(0772150, 20, 0154, 5) ; + dma_request.set_priority_slot(default_priority_slot) ; + intr_request.set_priority_slot(default_priority_slot) ; + intr_request.set_level(default_intr_level) ; + intr_request.set_vector(default_intr_vector) ; // The UDA50 controller has two registers. register_count = 2; @@ -98,8 +98,8 @@ uda_c::~uda_c() } bool uda_c::on_param_changed(parameter_c *param) { - // no own parameter or "enable" logic - return storagecontroller_c::on_param_changed(param) ; // more actions (for enable) + // no own parameter or "enable" logic + return storagecontroller_c::on_param_changed(param) ; // more actions (for enable) } @@ -169,7 +169,7 @@ void uda_c::StateTransition( // void uda_c::worker(unsigned instance) { - UNUSED(instance) ; // only one + UNUSED(instance) ; // only one worker_init_realtime_priority(rt_device); @@ -224,8 +224,7 @@ void uda_c::worker(unsigned instance) // 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. - update_SA(0x1000 | ((_step1Value >> 8) & 0xff)); - Interrupt(); + Interrupt(0x1000 | ((_step1Value >> 8) & 0xff)); break; case InitializationStep::Step3: @@ -234,8 +233,7 @@ void uda_c::worker(unsigned instance) DEBUG("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. - update_SA(0x2000 | (_step1Value & 0xff)); - Interrupt(); + Interrupt(0x2000 | (_step1Value & 0xff)); break; case InitializationStep::Step4: @@ -275,8 +273,7 @@ void uda_c::worker(unsigned instance) DEBUG("Transition to Init state S4, comm area initialized."); // Update the SA read value for step 4: // Bits 7-0 indicating our control microcode version. - update_SA(UDA50_ID); // UDA50 ID, makes RSTS happy - Interrupt(); + Interrupt(UDA50_ID); // UDA50 ID, makes RSTS happy break; case InitializationStep::Complete: @@ -304,7 +301,8 @@ uda_c::on_after_register_access( { // "When written with any value, it causes a hard initialization // of the port and the device controller." - DEBUG("Reset due to IP read"); + DEBUG("Reset due to IP read"); + update_SA(0x0); StateTransition(InitializationStep::Uninitialized); } else @@ -352,7 +350,9 @@ uda_c::on_after_register_access( // during initialization. _step1Value = value; - intr_vector.value = _interruptVector = ((value & 0x7f) << 2); + _interruptVector = ((value & 0x7f) << 2); + + intr_request.set_vector(_interruptVector); _interruptEnable = !!(value & 0x80); _responseRingLength = (1 << ((value & 0x700) >> 8)); _commandRingLength = (1 << ((value & 0x3800) >> 11)); @@ -802,6 +802,24 @@ uda_c::GetControllerClassModel() return 0x0102; // Class 1 (mass storage), model 2 (UDA50) } +// +// Interrupt(): +// Invokes a Unibus interrupt if interrupts are enabled and the interrupt +// vector is non-zero. Updates SA to the specified value atomically. +// +void +uda_c::Interrupt(uint16_t sa_value) +{ + if ((_interruptEnable || _initStep == InitializationStep::Complete) && _interruptVector != 0) + { + unibusadapter->INTR(intr_request, SA_reg, sa_value); + } + else + { + update_SA(sa_value); + } +} + // // Interrupt(): // Invokes a Unibus interrupt if interrupts are enabled and the interrupt @@ -812,7 +830,7 @@ uda_c::Interrupt(void) { if ((_interruptEnable || _initStep == InitializationStep::Complete) && _interruptVector != 0) { - unibusadapter->INTR(intr_request, NULL, 0); // todo: link to interupt register + unibusadapter->INTR(intr_request, NULL, 0); } } diff --git a/10.02_devices/2_src/uda.hpp b/10.02_devices/2_src/uda.hpp index b7e861c..9c46b6a 100644 --- a/10.02_devices/2_src/uda.hpp +++ b/10.02_devices/2_src/uda.hpp @@ -76,9 +76,9 @@ public: void on_drive_status_changed(storagedrive_c *drive) override; - // As every storage controller UDA has one INTR and DMA - dma_request_c dma_request = dma_request_c(this) ; // operated by unibusadapter - intr_request_c intr_request = intr_request_c(this) ; + // As every storage controller UDA has one INTR and DMA + dma_request_c dma_request = dma_request_c(this) ; // operated by unibusadapter + intr_request_c intr_request = intr_request_c(this) ; public: @@ -103,6 +103,7 @@ public: private: // TODO: consolidate these private/public groups here void Reset(void); + void Interrupt(uint16_t sa_value); void Interrupt(void); uint32_t GetCommandDescriptorAddress(size_t index); From 6f1b4767164c31a6798194e91ef8f51b2cb2b4de Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Fri, 16 Aug 2019 02:23:32 +0200 Subject: [PATCH 18/18] Cleaned up signaling of DMA/INTR completion (using pthread_cond_wait). Tweaked MSYN timeout value from 350ns to 400ns to compensate for timing changes with latest PRU code -- MSCP works reliably on PDP-11/84 again. --- 10.01_base/2_src/arm/priorityrequest.cpp | 2 +- 10.01_base/2_src/arm/priorityrequest.hpp | 2 +- 10.01_base/2_src/arm/unibusadapter.cpp | 41 +++++++++++------------- 10.01_base/2_src/shared/tuning.h | 2 +- 10.02_devices/2_src/mscp_server.cpp | 7 ++-- 10.02_devices/2_src/uda.cpp | 6 ++-- 6 files changed, 27 insertions(+), 33 deletions(-) diff --git a/10.01_base/2_src/arm/priorityrequest.cpp b/10.01_base/2_src/arm/priorityrequest.cpp index 309645f..d239ab8 100644 --- a/10.01_base/2_src/arm/priorityrequest.cpp +++ b/10.01_base/2_src/arm/priorityrequest.cpp @@ -37,7 +37,7 @@ priority_request_c::priority_request_c(unibusdevice_c *device) { this->executing_on_PRU = false; this->slot = 0xff; // uninitialized, asserts() if used complete_mutex = PTHREAD_MUTEX_INITIALIZER; - //complete_cond = PTHREAD_COND_INITIALIZER; // PRU signal notifies request on completeness + complete_cond = PTHREAD_COND_INITIALIZER; // PRU signal notifies request on completeness } priority_request_c::~priority_request_c() { diff --git a/10.01_base/2_src/arm/priorityrequest.hpp b/10.01_base/2_src/arm/priorityrequest.hpp index 41c86e5..c6c61e5 100644 --- a/10.01_base/2_src/arm/priorityrequest.hpp +++ b/10.01_base/2_src/arm/priorityrequest.hpp @@ -82,7 +82,7 @@ public: // PRU -> signal -> worker() -> request -> device. INTR/DMA pthread_mutex_t complete_mutex; - //pthread_cond_t complete_cond; // PRU signal notifies request on completeness + pthread_cond_t complete_cond; // PRU signal notifies request on completeness priority_request_c(unibusdevice_c *device); virtual ~priority_request_c(); // not used, but need dynamic_cast diff --git a/10.01_base/2_src/arm/unibusadapter.cpp b/10.01_base/2_src/arm/unibusadapter.cpp index d83333b..d8b3a9f 100644 --- a/10.01_base/2_src/arm/unibusadapter.cpp +++ b/10.01_base/2_src/arm/unibusadapter.cpp @@ -296,13 +296,14 @@ void unibusadapter_c::requests_cancel_scheduled(void) { if ((req = prl->slot_request[slot])) { dma_request_c *dmareq; req->executing_on_PRU = false; - req->complete = true; if ((dmareq = dynamic_cast(req))) dmareq->success = false; // device gets an DMA error, but will not understand prl->slot_request[slot] = NULL; - // signal to blockin DMA() or INTR() - pthread_mutex_unlock(&req->complete_mutex); - //pthread_cond_signal(&req->complete_cond); + // signal to blocking DMA() or INTR() + pthread_mutex_lock(&req->complete_mutex); + req->complete = true; + pthread_cond_signal(&req->complete_cond); + pthread_mutex_unlock(&req->complete_mutex); } } } @@ -489,12 +490,10 @@ void unibusadapter_c::request_active_complete(unsigned level_index) { prl->active = NULL; // signal to DMA() or INTR() - tmprq->complete = true; // close to signal - pthread_mutex_unlock(&tmprq->complete_mutex); - -// pthread_cond_signal(&tmprq->complete_cond); - -// pthread_cond_broadcast(&tmprq->complete_cond); + pthread_mutex_lock(&tmprq->complete_mutex); + tmprq->complete = true; + pthread_cond_signal(&tmprq->complete_cond); + pthread_mutex_unlock(&tmprq->complete_mutex); } // Request a DMA cycle from Arbitrator. @@ -522,9 +521,9 @@ void unibusadapter_c::DMA(dma_request_c& dma_request, bool blocking, uint8_t uni priority_request_level_c *prl = &request_levels[PRIORITY_LEVEL_INDEX_NPR]; assert(prl->slot_request[dma_request.slot] == NULL); // not scheduled or prev completed - pthread_mutex_lock(&dma_request.complete_mutex); // lock early, else PRU can signal cond before we lock // dma_request.level-index, priority_slot in constructor dma_request.complete = false; + dma_request.success = false; dma_request.executing_on_PRU = false; dma_request.unibus_control = unibus_control; dma_request.unibus_start_addr = unibus_addr; @@ -549,20 +548,13 @@ void unibusadapter_c::DMA(dma_request_c& dma_request, bool blocking, uint8_t uni // DEBUG("device DMA start: %s @ %06o, len=%d", unibus->control2text(unibus_control), unibus_addr, wordcount); if (blocking) { - // acquire locked mutex => wait for worker to release pthread_mutex_lock(&dma_request.complete_mutex); + // DMA() is blocking: Wait for request to finish. + while (!dma_request.complete) { + int res = pthread_cond_wait(&dma_request.complete_cond, &dma_request.complete_mutex); + assert(!res); + } pthread_mutex_unlock(&dma_request.complete_mutex); - - /* - // DMA() is blocking: Wait for request to finish. - // pthread_mutex_lock(&dma_request.mutex); - while (!dma_request.complete) { - // busy waiting OK - int res = pthread_cond_wait(&dma_request.complete_cond, &dma_request.complete_mutex); - assert(!res) ; - dma_request.dbg_complete_sig_received++ ; - } - */ } } @@ -685,6 +677,9 @@ void unibusadapter_c::cancel_INTR(intr_request_c& intr_request) { } // both empty, or both filled assert((prl->slot_request_mask == 0) == (prl->active == NULL)); + + pthread_mutex_lock(&intr_request.complete_mutex); + pthread_cond_signal(&intr_request.complete_cond); pthread_mutex_unlock(&intr_request.complete_mutex); pthread_mutex_unlock(&requests_mutex); // lock schedule table operations diff --git a/10.01_base/2_src/shared/tuning.h b/10.01_base/2_src/shared/tuning.h index d677d7f..6f6de3e 100644 --- a/10.01_base/2_src/shared/tuning.h +++ b/10.01_base/2_src/shared/tuning.h @@ -81,4 +81,4 @@ // Addtional delay on PDP11s with private memory interconnect (PMI) // and UNIBUS/PMI translation? // Experiments with "250" made still occasional errors. -#define UNIBUS_DMA_MASTER_PRE_MSYN_NS 350 +#define UNIBUS_DMA_MASTER_PRE_MSYN_NS 400 diff --git a/10.02_devices/2_src/mscp_server.cpp b/10.02_devices/2_src/mscp_server.cpp index cc4e17f..39582a2 100644 --- a/10.02_devices/2_src/mscp_server.cpp +++ b/10.02_devices/2_src/mscp_server.cpp @@ -200,16 +200,17 @@ mscp_server::Poll(void) // std::queue> messages; + int msgCount = 0; while (!_abort_polling && _pollState != PollingState::InitRestart) { shared_ptr message(_port->GetNextCommand()); - if (nullptr == message) { - DEBUG("End of command ring; %d messages to be executed."); + DEBUG("End of command ring; %d messages to be executed.", msgCount); break; } + msgCount++; messages.push(message); } @@ -221,8 +222,6 @@ mscp_server::Poll(void) shared_ptr message(messages.front()); messages.pop(); - DEBUG("Processing message."); - // // Handle the message. We dispatch on opcodes to the // appropriate methods. These methods modify the message diff --git a/10.02_devices/2_src/uda.cpp b/10.02_devices/2_src/uda.cpp index c00ed52..d7a12d9 100644 --- a/10.02_devices/2_src/uda.cpp +++ b/10.02_devices/2_src/uda.cpp @@ -531,7 +531,7 @@ uda_c::GetNextCommand(void) DMAReadWord( messageAddress - 4, success); - + assert(messageLength > 0 && messageLength < MAX_MESSAGE_LENGTH); std::unique_ptr cmdMessage( @@ -591,7 +591,7 @@ uda_c::GetNextCommand(void) DMAWrite( descriptorAddress, sizeof(Descriptor), - reinterpret_cast(cmdDescriptor.get())); + reinterpret_cast(cmdDescriptor.get())); // // Move to the next descriptor in the ring for next time. @@ -1007,6 +1007,6 @@ uda_c::DMARead( } else { - return nullptr; + return nullptr; } }