mirror of
https://github.com/livingcomputermuseum/UniBone.git
synced 2026-04-19 01:08:17 +00:00
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.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<void*>(_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> message(_port->GetNextCommand());
|
||||
shared_ptr<Message> 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<ControlMessageHeader*>(message->Message);
|
||||
|
||||
uint16_t *cmdbuf = reinterpret_cast<uint16_t*>(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<uint32_t>(MAX_CREDITS));
|
||||
uint8_t grantedCredits = min(_credits, static_cast<uint8_t>(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<ReadParameters*>(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<uint8_t> 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<WriteParameters*>(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<uint8_t> buffer(_port->DMARead(
|
||||
unique_ptr<uint8_t> 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<ControlMessageHeader*>(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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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> message, uint16_t unitNumber, uint16_t modifiers);
|
||||
|
||||
uint8_t* GetParameterPointer(std::shared_ptr<Message> 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<uint8_t> _diskBuffer;
|
||||
uint32_t _diskBufferSize = 237212 * 512; // RA80 size
|
||||
bool _unitOnline;
|
||||
|
||||
// Credits available
|
||||
uint32_t _credits;
|
||||
uint8_t _credits;
|
||||
};
|
||||
|
||||
|
||||
@@ -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; i<drivecount; i++)
|
||||
{
|
||||
mscp_drive_c *drive = new mscp_drive_c(this, i);
|
||||
drive->unitno.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; i<drivecount; i++)
|
||||
{
|
||||
delete storagedrives[i];
|
||||
}
|
||||
|
||||
storagedrives.clear();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
_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<mscp_drive_c*>(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<Descriptor> cmdDescriptor;
|
||||
for(int retry = 0 ; retry < 10; retry++)
|
||||
{
|
||||
cmdDescriptor.reset(
|
||||
std::unique_ptr<Descriptor> cmdDescriptor(
|
||||
reinterpret_cast<Descriptor*>(
|
||||
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<Message> cmdMessage(
|
||||
// reinterpret_cast<Message*>(
|
||||
uint16_t* data = reinterpret_cast<uint16_t*>(
|
||||
std::unique_ptr<Message> cmdMessage(
|
||||
reinterpret_cast<Message*>(
|
||||
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));
|
||||
|
||||
messageLength + 4,
|
||||
sizeof(Message))));
|
||||
|
||||
//
|
||||
// Handle Ring Transitions (from full to not-full) and associated
|
||||
@@ -501,6 +524,7 @@ uda_c::GetNextCommand(void)
|
||||
reinterpret_cast<Descriptor*>(
|
||||
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<Message> response
|
||||
Message* response
|
||||
)
|
||||
{
|
||||
bool res = false;
|
||||
@@ -567,6 +590,7 @@ uda_c::PostResponse(
|
||||
reinterpret_cast<Descriptor*>(
|
||||
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<uint16_t*>(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<uint8_t*>(response.get()));
|
||||
reinterpret_cast<uint8_t*>(response));
|
||||
}
|
||||
|
||||
//
|
||||
@@ -646,6 +677,7 @@ uda_c::PostResponse(
|
||||
reinterpret_cast<Descriptor*>(
|
||||
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<uint16_t*>(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<uint16_t*>(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<uint8_t*>(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<uint8_t*>(buffer);
|
||||
// timer.wait_us(250);
|
||||
return reinterpret_cast<uint8_t*>(buffer);
|
||||
}
|
||||
|
||||
@@ -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<Message> 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<mscp_server> _server;
|
||||
std::shared_ptr<mscp_server> _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)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user