mirror of
https://github.com/livingcomputermuseum/UniBone.git
synced 2026-01-28 12:49:08 +00:00
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.
This commit is contained in:
721
10.02_devices/2_src/mscp_server.cpp
Normal file
721
10.02_devices/2_src/mscp_server.cpp
Normal file
@@ -0,0 +1,721 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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<mscp_server*>(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<void*>(_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<void*>(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> 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<ControlMessageHeader*>(message->Message);
|
||||
|
||||
uint16_t *cmdbuf = reinterpret_cast<uint16_t*>(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<uint32_t>(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> 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<GetUnitStatusResponseParameters*>(
|
||||
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> 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<OnlineResponseParameters*>(
|
||||
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> 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<SetControllerCharacteristicsParameters*>(
|
||||
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> 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<SetUnitCharacteristicsResponseParameters*>(
|
||||
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> 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<ReadParameters*>(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> 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<WriteParameters*>(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<uint8_t> 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> message)
|
||||
{
|
||||
return reinterpret_cast<ControlMessageHeader*>(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);
|
||||
}
|
||||
|
||||
172
10.02_devices/2_src/mscp_server.hpp
Normal file
172
10.02_devices/2_src/mscp_server.hpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
|
||||
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> message, uint16_t unitNumber, uint16_t modifiers);
|
||||
uint32_t Online(std::shared_ptr<Message> message, uint16_t unitNumber, uint16_t modifiers);
|
||||
uint32_t SetControllerCharacteristics(std::shared_ptr<Message> message);
|
||||
uint32_t SetUnitCharacteristics(std::shared_ptr<Message> message, uint16_t unitNumber, uint16_t modifiers);
|
||||
uint32_t Read(std::shared_ptr<Message> message, uint16_t unitNumber, uint16_t modifiers);
|
||||
uint32_t Write(std::shared_ptr<Message> message, uint16_t unitNumber, uint16_t modifiers);
|
||||
|
||||
uint8_t* GetParameterPointer(std::shared_ptr<Message> 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<uint8_t> _diskBuffer;
|
||||
uint32_t _diskBufferSize = 237212 * 512; // RA80 size
|
||||
bool _unitOnline;
|
||||
|
||||
// Credits available
|
||||
uint32_t _credits;
|
||||
};
|
||||
|
||||
890
10.02_devices/2_src/uda.cpp
Normal file
890
10.02_devices/2_src/uda.cpp
Normal file
@@ -0,0 +1,890 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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<uint8_t*>(&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<uint8_t*>(&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<Descriptor> cmdDescriptor;
|
||||
for(int retry = 0 ; retry < 10; retry++)
|
||||
{
|
||||
cmdDescriptor.reset(
|
||||
reinterpret_cast<Descriptor*>(
|
||||
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<Message> cmdMessage(
|
||||
// reinterpret_cast<Message*>(
|
||||
uint16_t* data = reinterpret_cast<uint16_t*>(
|
||||
DMARead(
|
||||
messageAddress - 4,
|
||||
messageLength + 4));
|
||||
/*
|
||||
for(int i=0;i<(messageLength + 4) / 2; i++)
|
||||
{
|
||||
INFO("o%o", data[i]);
|
||||
}
|
||||
*/
|
||||
|
||||
std::unique_ptr<Message> cmdMessage(reinterpret_cast<Message*>(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<Descriptor> previousDescriptor(
|
||||
reinterpret_cast<Descriptor*>(
|
||||
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<uint8_t*>(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<Message> response
|
||||
)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
// Grab the next descriptor.
|
||||
uint32_t descriptorAddress = GetResponseDescriptorAddress(_responseRingPointer);
|
||||
std::unique_ptr<Descriptor> cmdDescriptor(
|
||||
reinterpret_cast<Descriptor*>(
|
||||
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<uint8_t*>(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<Descriptor> previousDescriptor(
|
||||
reinterpret_cast<Descriptor*>(
|
||||
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<uint8_t*>(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<uint8_t*>(&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<uint16_t *>(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<uint16_t*>(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<uint8_t*>(buffer);
|
||||
}
|
||||
162
10.02_devices/2_src/uda.hpp
Normal file
162
10.02_devices/2_src/uda.hpp
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
uda.hpp: MSCP controller port (UDA50)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#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<Message> 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<mscp_server> _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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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 $@
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user