1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-02-26 08:44:18 +00:00

Implemented the last few unimplemented MSCP commands; as yet untested with real PDP-11/VAX code (have yet to find a case that uses them.)

General code cleanup/refactoring.  Added header comments.

Added "use image size" parameter for MSCP disks -- block count derived from image file size rather than DEC drive geometry; allows for arbitrarily large disks (up to 2TB, theoretically.)
This commit is contained in:
Josh Dersch
2019-05-08 05:34:40 +02:00
parent a00f0592dc
commit c6958e1660
7 changed files with 430 additions and 154 deletions

View File

@@ -145,6 +145,12 @@ void storagedrive_c::file_write(uint8_t *buffer, uint64_t position, unsigned len
f.flush();
}
uint64_t storagedrive_c::file_size(void)
{
f.seekp(0, ios::end);
return f.tellp();
}
void storagedrive_c::file_close(void) {
assert(file_is_open());
f.close();

View File

@@ -67,6 +67,7 @@ public:
bool file_is_open(void);
void file_read(uint8_t *buffer, uint64_t position, unsigned len);
void file_write(uint8_t *buffer, uint64_t position, unsigned len);
uint64_t file_size(void);
void file_close(void);
storagedrive_c(storagecontroller_c *controller);

View File

@@ -3,6 +3,7 @@
*/
#include <assert.h>
#include <memory>
using namespace std;
@@ -14,7 +15,8 @@ using namespace std;
mscp_drive_c::mscp_drive_c(
storagecontroller_c *controller,
uint32_t driveNumber) :
storagedrive_c(controller)
storagedrive_c(controller),
_useImageSize(false)
{
log_label = "MSCPD";
SetDriveType("RA81");
@@ -22,7 +24,11 @@ mscp_drive_c::mscp_drive_c(
// Calculate the unit's ID:
// drive number in upper 32 bits, class/model in lower.
_unitID = (static_cast<uint64_t>(0xffffffff) << 32) | 0x02020000;
_unitID = (static_cast<uint64_t>(driveNumber) << 32) | 0x02020000;
// Initialize the RCT area
_rctData.reset(new uint8_t[GetBlockSize()]);
memset(reinterpret_cast<void *>(_rctData.get()), 0, GetBlockSize());
}
mscp_drive_c::~mscp_drive_c()
@@ -43,9 +49,27 @@ uint32_t mscp_drive_c::GetBlockSize()
uint32_t mscp_drive_c::GetBlockCount()
{
// TODO: need to be able to handle drives of arbitrary size, not just
// DEC-branded units.
return _driveInfo.BlockCount;
if (_useImageSize)
{
// Return the image size / Block size (rounding down).
return file_size() / GetBlockSize();
}
else
{
//
// Use the size defined by the drive type.
//
return _driveInfo.BlockCount;
}
}
uint32_t mscp_drive_c::GetRCTBlockCount()
{
//
// We provide only a single RCT block, required by the MSCP spec for the volume
// write-protect flags.
//
return 1;
}
uint32_t mscp_drive_c::GetMediaID()
@@ -123,6 +147,50 @@ uint8_t* mscp_drive_c::Read(
return buffer;
}
//
// Writes a single block's worth of data from the provided buffer into the
// RCT area at the specified RCT block. Buffer must be at least as large
// as the disk's block size.
//
void mscp_drive_c::WriteRCTBlock(
uint32_t rctBlockNumber,
uint8_t* buffer)
{
assert (rctBlockNumber < GetRCTBlockCount());
memcpy(
reinterpret_cast<void *>(_rctData.get() + rctBlockNumber * GetBlockSize()),
reinterpret_cast<void *>(buffer),
GetBlockSize());
}
//
// Reads a single block's worth of data from the RCT area (at the specified
// block offset). Returns a pointer to a buffer containing the data read.
// Caller is responsible for freeing this buffer.
//
uint8_t* mscp_drive_c::ReadRCTBlock(
uint32_t rctBlockNumber)
{
assert (rctBlockNumber < GetRCTBlockCount());
uint8_t* buffer = new uint8_t[GetBlockSize()];
assert (nullptr != buffer);
memcpy(
reinterpret_cast<void *>(buffer),
reinterpret_cast<void *>(_rctData.get() + rctBlockNumber * GetBlockSize()),
GetBlockSize());
return buffer;
}
void mscp_drive_c::UpdateCapacity()
{
capacity.value =
GetBlockCount() * GetBlockSize();
}
bool mscp_drive_c::on_param_changed(
parameter_c *param)
{
@@ -138,12 +206,20 @@ bool mscp_drive_c::on_param_changed(
if (file_open(image_filepath.new_value, true))
{
image_filepath.value = image_filepath.new_value;
UpdateCapacity();
return true;
}
//
// TODO: if file is a nonstandard size?
}
else if (&use_image_size == param)
{
_useImageSize = use_image_size.new_value;
use_image_size.value = use_image_size.new_value;
UpdateCapacity();
return true;
}
return false;
}
@@ -161,7 +237,7 @@ bool mscp_drive_c::SetDriveType(const char* typeName)
{
_driveInfo = g_driveTable[index];
type_name.value = _driveInfo.TypeName;
capacity.value = GetBlockCount() * GetBlockSize();
UpdateCapacity();
return true;
}

View File

@@ -20,6 +20,7 @@ public:
uint32_t GetBlockSize(void);
uint32_t GetBlockCount(void);
uint32_t GetRCTBlockCount(void);
uint32_t GetMediaID(void);
uint64_t GetUnitID(void);
@@ -35,7 +36,14 @@ public:
uint8_t* Read(
uint32_t blockNumber,
size_t lengthInBytes);
size_t lengthInBytes);
void WriteRCTBlock(
uint32_t rctBlockNumber,
uint8_t* buffer);
uint8_t* ReadRCTBlock(
uint32_t rctBlockNumber);
public:
bool on_param_changed(parameter_c *param) override;
@@ -44,6 +52,10 @@ public:
void worker(void) override;
public:
parameter_bool_c use_image_size = parameter_bool_c(
this, "useimagesize", "uis", false, "Determine unit size from image file instead of drive type");
private:
struct DriveInfo
@@ -55,7 +67,7 @@ private:
bool ReadOnly;
};
DriveInfo g_driveTable[23]
DriveInfo g_driveTable[22]
{
{ "RX50", 800, 0x25658032, true, false },
{ "RX33", 2400, 0x25658021, true, false },
@@ -78,12 +90,23 @@ private:
{ "RA90", 2376153, 0x2564105a, false, false },
{ "RA92", 2940951, 0x2564105c, false, false },
{ "RA73", 3920490, 0x25641049, false, false },
{ "JD90", 2376153, 0x2564105d, false, false },
{ "", 0, 0, false, false }
};
bool SetDriveType(const char* typeName);
void UpdateCapacity(void);
DriveInfo _driveInfo;
bool _online;
uint64_t _unitID;
bool _useImageSize;
//
// RCT ("Replacement and Caching Table") data:
// At this time we provide the minimum required by the MSCP spec,
// a single block used to provide the volume write-protect flags.
// We do not require additional RCT space because the underlying media
// (an image file) doesn't have bad sectors.
// This data is not persisted to disk as it is unnecessary.
//
unique_ptr<uint8_t> _rctData;
};

View File

@@ -1,7 +1,38 @@
/*
mscp_server.cpp: Implementation a simple MSCP server.
This provides an implementation of the Minimal MSCP subset outlined
in AA-L619A-TK (Chapter 6). It takes a few liberties and errs on
the side of implementation simplicity.
In particular:
All commands are executed sequentially, as they appear in the
command ring. This includes any commands in the "Immediate"
category. Technically this is incorrect: Immediate commands
should execute as soon as possible, before any other commands.
In practice I have yet to find code that cares.
This simplifies the implementation significantly, and apart
from maintaining fealty to the MSCP spec for Immediate commands,
there's no good reason to make it more complex: real MSCP
controllers (like the original UDA50) would resequence commands
to allow optimal throughput across multiple units, etc. On the
unibone, the underlying storage and the execution speed of the
processor is orders of magnitude faster, so even a brute-force
braindead implementation like this can saturate the Unibus.
TODO:
Some commands aren't checked as thoroughly for errors as they could be,
and at this time NXM (attempts to address non-existent memory) are
almost completely unhandled.
*/
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <memory>
using namespace std;
@@ -163,11 +194,24 @@ mscp_server::Poll(void)
header->ReferenceNumber);
uint32_t cmdStatus = 0;
uint16_t modifiers = header->Word3.Command.Modifiers;
switch (header->Word3.Command.Opcode)
{
case Opcodes::ABORT:
cmdStatus = Abort(message);
break;
case Opcodes::ACCESS:
cmdStatus = Access(message, header->UnitNumber);
break;
case Opcodes::AVAILABLE:
cmdStatus = Available(message, header->UnitNumber, header->Word3.Command.Modifiers);
cmdStatus = Available(message, header->UnitNumber, modifiers);
break;
case Opcodes::COMPARE_HOST_DATA:
cmdStatus = CompareHostData(message, header->UnitNumber);
break;
case Opcodes::DETERMINE_ACCESS_PATHS:
@@ -175,15 +219,27 @@ mscp_server::Poll(void)
break;
case Opcodes::ERASE:
cmdStatus = Erase(message, header->UnitNumber, header->Word3.Command.Modifiers);
cmdStatus = Erase(message, header->UnitNumber, modifiers);
break;
case Opcodes::GET_COMMAND_STATUS:
cmdStatus = GetCommandStatus(message);
break;
case Opcodes::GET_UNIT_STATUS:
cmdStatus = GetUnitStatus(message, header->UnitNumber, header->Word3.Command.Modifiers);
cmdStatus = GetUnitStatus(message, header->UnitNumber, modifiers);
break;
case Opcodes::ONLINE:
cmdStatus = Online(message, header->UnitNumber, header->Word3.Command.Modifiers);
cmdStatus = Online(message, header->UnitNumber, modifiers);
break;
case Opcodes::READ:
cmdStatus = Read(message, header->UnitNumber, modifiers);
break;
case Opcodes::REPLACE:
cmdStatus = Replace(message, header->UnitNumber);
break;
case Opcodes::SET_CONTROLLER_CHARACTERISTICS:
@@ -191,15 +247,11 @@ mscp_server::Poll(void)
break;
case Opcodes::SET_UNIT_CHARACTERISTICS:
cmdStatus = SetUnitCharacteristics(message, header->UnitNumber, header->Word3.Command.Modifiers);
break;
case Opcodes::READ:
cmdStatus = Read(message, header->UnitNumber, header->Word3.Command.Modifiers);
cmdStatus = SetUnitCharacteristics(message, header->UnitNumber, modifiers);
break;
case Opcodes::WRITE:
cmdStatus = Write(message, header->UnitNumber, header->Word3.Command.Modifiers);
cmdStatus = Write(message, header->UnitNumber, modifiers);
break;
default:
@@ -260,13 +312,14 @@ mscp_server::Poll(void)
}
//
// This delay works around an issue in the VMS bootstrap -- unsure of the
// This delay works around an issue in various bootstraps -- unsure of the
// exact cause, it appears to be a race condition exacerbated by the speed
// at which we're able to process commands in the ring buffer (we are much
// faster than any real MSCP device ever was.)
// faster than any real MSCP device ever was) and the liberties that bootstrap
// code takes with the MSCP spec to save code space.
// This is likely a hack, we may be papering over a bug somewhere.
//
timer.wait_us(100);
timer.wait_us(2000);
//
// Go around and pick up the next one.
@@ -296,6 +349,25 @@ mscp_server::Poll(void)
DEBUG("MSCP Polling thread exiting.");
}
uint32_t
mscp_server::Abort(
shared_ptr<Message> message)
{
INFO("MSCP ABORT");
//
// Since we do not reorder messages and in fact pick up and execute
// them one at a time, sequentially as they appear in the ring buffer,
// by the time we've gotten this command, the command it's referring
// to is long gone.
// This is legal behavior and it's legal for us to ignore ABORT in this
// case.
//
// We just return SUCCESS here.
return STATUS(Status::SUCCESS, 0, 0);
}
uint32_t
mscp_server::Available(
shared_ptr<Message> message,
@@ -322,6 +394,33 @@ mscp_server::Available(
return STATUS(Status::SUCCESS, 0x40, 0); // still connected
}
uint32_t
mscp_server::Access(
shared_ptr<Message> message,
uint16_t unitNumber)
{
INFO("MSCP ACCESS");
return DoDiskTransfer(
Opcodes::ACCESS,
message,
unitNumber,
0);
}
uint32_t
mscp_server::CompareHostData(
shared_ptr<Message> message,
uint16_t unitNumber)
{
INFO("MSCP COMPARE HOST DATA");
return DoDiskTransfer(
Opcodes::COMPARE_HOST_DATA,
message,
unitNumber,
0);
}
uint32_t
mscp_server::DetermineAccessPaths(
shared_ptr<Message> message,
@@ -342,66 +441,40 @@ mscp_server::Erase(
uint16_t unitNumber,
uint16_t modifiers)
{
return DoDiskTransfer(
Opcodes::ERASE,
message,
unitNumber,
modifiers);
}
uint32_t
mscp_server::GetCommandStatus(
shared_ptr<Message> message)
{
INFO("MSCP GET COMMAND STATUS");
#pragma pack(push,1)
struct EraseParameters
struct GetCommandStatusResponseParameters
{
uint32_t ByteCount;
uint32_t Unused0;
uint32_t Unused1;
uint32_t Unused2;
uint32_t LBN;
uint32_t OutstandingReferenceNumber;
uint32_t CommandStatus;
};
#pragma pack(pop)
// TODO: Factor this code out (shared w/Read and Write)
EraseParameters* params =
reinterpret_cast<EraseParameters*>(GetParameterPointer(message));
message->MessageLength = sizeof(GetCommandStatusResponseParameters)
+ HEADER_SIZE;
DEBUG("MSCP ERASE unit %d chan count %d lbn %d",
unitNumber,
params->ByteCount,
params->LBN);
// Adjust message length for response
message->MessageLength = sizeof(EraseParameters) +
HEADER_SIZE;
mscp_drive_c* drive = GetDrive(unitNumber);
// Check unit
if (nullptr == drive ||
!drive->IsAvailable())
{
return STATUS(Status::UNIT_OFFLINE, 0x3, 0);
}
// Check LBN
if (params->LBN > drive->GetBlockCount() + 1) // + 1 for RCT
{
return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code
}
// Check byte count
if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize())
{
return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0, 0); // TODO: as above
}
GetCommandStatusResponseParameters* params =
reinterpret_cast<GetCommandStatusResponseParameters*>(
GetParameterPointer(message));
//
// OK: do the transfer from a zero'd out buffer to disk
// This will always return zero; as with the ABORT command, at this
// point the command being referenced has already been executed.
//
unique_ptr<uint8_t> memBuffer(new uint8_t[params->ByteCount]);
memset(reinterpret_cast<void*>(memBuffer.get()), 0, params->ByteCount);
drive->Write(params->LBN,
params->ByteCount,
memBuffer.get());
// Set parameters for response.
// We leave ByteCount as is (for now anyway)
// And set First Bad Block to 0. (This is unnecessary since we're
// not reporting a bad block, but we're doing it for completeness.)
params->LBN = 0;
params->CommandStatus = 0;
return STATUS(Status::SUCCESS, 0, 0);
}
@@ -592,6 +665,31 @@ mscp_server::Online(
(alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0, 0);
}
uint32_t
mscp_server::Replace(
shared_ptr<Message> message,
uint16_t unitNumber)
{
INFO("MSCP REPLACE");
//
// We treat this as a success for valid units as we do no block replacement at all.
// Best just to smile and nod. We could be more vigilant and check LBNs, etc...
//
message->MessageLength = HEADER_SIZE;
mscp_drive_c* drive = GetDrive(unitNumber);
if (nullptr == drive ||
!drive->IsAvailable())
{
return STATUS(Status::UNIT_OFFLINE, 0x3, 0); // unknown -- todo move to enum
}
else
{
return STATUS(Status::SUCCESS, 0, 0);
}
}
uint32_t
mscp_server::SetControllerCharacteristics(
shared_ptr<Message> message)
@@ -715,71 +813,11 @@ mscp_server::Read(
uint16_t unitNumber,
uint16_t modifiers)
{
#pragma pack(push,1)
struct ReadParameters
{
uint32_t ByteCount;
uint32_t BufferPhysicalAddress; // upper 8 bits are channel address for VAXen
uint32_t Unused0;
uint32_t Unused1;
uint32_t LBN;
};
#pragma pack(pop)
ReadParameters* params =
reinterpret_cast<ReadParameters*>(GetParameterPointer(message));
DEBUG("MSCP READ unit %d chan o%o pa o%o count %d lbn %d",
return DoDiskTransfer(
Opcodes::READ,
message,
unitNumber,
params->BufferPhysicalAddress >> 24,
params->BufferPhysicalAddress & 0x00ffffff,
params->ByteCount,
params->LBN);
// Adjust message length for response
message->MessageLength = sizeof(ReadParameters) +
HEADER_SIZE;
mscp_drive_c* drive = GetDrive(unitNumber);
// Check unit
if (nullptr == drive ||
!drive->IsAvailable())
{
return STATUS(Status::UNIT_OFFLINE, 0x3, 0);
}
// TODO: Need to rectify reads/writes to RCT area more cleanly
// and enforce block size of 512 for RCT area.
// Check LBN and byte count
if (params->LBN >= drive->GetBlockCount() + 1) // + 1 for RCT write protect flag
{
return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code
}
if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize())
{
return STATUS(Status::INVALID_COMMAND + (0xc << 8), 0, 0); // TODO: as above
}
//
// OK: do the transfer to memory
//
unique_ptr<uint8_t> diskBuffer(drive->Read(params->LBN, params->ByteCount));
_port->DMAWrite(
params->BufferPhysicalAddress & 0x00ffffff,
params->ByteCount,
diskBuffer.get());
// Set parameters for response.
// We leave ByteCount as is (for now anyway)
// And set First Bad Block to 0. (This is unnecessary since we're
// not reporting a bad block, but we're doing it for completeness.)
params->LBN = 0;
return STATUS(Status::SUCCESS, 0, 0);
modifiers);
}
uint32_t
@@ -787,9 +825,23 @@ mscp_server::Write(
shared_ptr<Message> message,
uint16_t unitNumber,
uint16_t modifiers)
{
return DoDiskTransfer(
Opcodes::WRITE,
message,
unitNumber,
modifiers);
}
uint32_t
mscp_server::DoDiskTransfer(
uint16_t operation,
shared_ptr<Message> message,
uint16_t unitNumber,
uint16_t modifiers)
{
#pragma pack(push,1)
struct WriteParameters
struct ReadWriteEraseParameters
{
uint32_t ByteCount;
uint32_t BufferPhysicalAddress; // upper 8 bits are channel address for VAXen
@@ -799,11 +851,11 @@ mscp_server::Write(
};
#pragma pack(pop)
// TODO: Factor this code out (shared w/Read)
WriteParameters* params =
reinterpret_cast<WriteParameters*>(GetParameterPointer(message));
ReadWriteEraseParameters* params =
reinterpret_cast<ReadWriteEraseParameters*>(GetParameterPointer(message));
DEBUG("MSCP WRITE unit %d chan o%o pa o%o count %d lbn %d",
DEBUG("MSCP RWE 0x%x unit %d chan o%o pa o%o count %d lbn %d",
operation,
unitNumber,
params->BufferPhysicalAddress >> 24,
params->BufferPhysicalAddress & 0x00ffffff,
@@ -811,7 +863,7 @@ mscp_server::Write(
params->LBN);
// Adjust message length for response
message->MessageLength = sizeof(WriteParameters) +
message->MessageLength = sizeof(ReadWriteEraseParameters) +
HEADER_SIZE;
mscp_drive_c* drive = GetDrive(unitNumber);
@@ -823,29 +875,130 @@ mscp_server::Write(
return STATUS(Status::UNIT_OFFLINE, 0x3, 0);
}
// Check LBN
if (params->LBN > drive->GetBlockCount() + 1) // + 1 for RCT
// Are we accessing the RCT area?
bool rctAccess = params->LBN >= drive->GetBlockCount();
uint32_t rctBlockNumber = params->LBN - drive->GetBlockCount();
// Check that the LBN is valid
if (params->LBN >= drive->GetBlockCount() + drive->GetRCTBlockCount())
{
return STATUS(Status::INVALID_COMMAND + (0x1c << 8), 0, 0); // TODO: set sub-code
}
// Check byte count
if (params->ByteCount > ((drive->GetBlockCount() + 1) - params->LBN) * drive->GetBlockSize())
// Check byte count:
if (params->ByteCount > ((drive->GetBlockCount() + drive->GetRCTBlockCount()) - params->LBN) * drive->GetBlockSize())
{
return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0, 0); // TODO: as above
}
// If this is an RCT access, byte count must equal the block size.
if (rctAccess && params->ByteCount != drive->GetBlockSize())
{
return STATUS(Status::INVALID_COMMAND + (0x0c << 8), 0, 0); // TODO: again
}
//
// OK: do the transfer from the PDP-11 to a buffer
//
unique_ptr<uint8_t> memBuffer(_port->DMARead(
params->BufferPhysicalAddress & 0x00ffffff,
params->ByteCount,
params->ByteCount));
switch (operation)
{
case Opcodes::ACCESS:
// We don't need to actually do any sort of transfer; ACCESS merely checks
// That the data can be read -- we checked the LBN, etc. above and we
// will never encounter a read error, so there's nothing left to do.
break;
drive->Write(params->LBN,
params->ByteCount,
memBuffer.get());
case Opcodes::COMPARE_HOST_DATA:
{
// Read the data in from disk, read the data in from memory, and compare.
unique_ptr<uint8_t> diskBuffer;
if (rctAccess)
{
diskBuffer.reset(drive->ReadRCTBlock(rctBlockNumber));
}
else
{
diskBuffer.reset(drive->Read(params->LBN, params->ByteCount));
}
unique_ptr<uint8_t> memBuffer(_port->DMARead(
params->BufferPhysicalAddress & 0x00ffffff,
params->ByteCount,
params->ByteCount));
if (!memcmp(diskBuffer.get(), memBuffer.get(), params->ByteCount))
{
// TODO: maybe not do an early return, make code not as ugly? Hm.
return STATUS(Status::COMPARE_ERROR, 0, 0);
}
}
case Opcodes::ERASE:
{
unique_ptr<uint8_t> memBuffer(new uint8_t[params->ByteCount]);
memset(reinterpret_cast<void*>(memBuffer.get()), 0, params->ByteCount);
if (rctAccess)
{
drive->WriteRCTBlock(rctBlockNumber,
memBuffer.get());
}
else
{
drive->Write(params->LBN,
params->ByteCount,
memBuffer.get());
}
}
break;
case Opcodes::READ:
{
unique_ptr<uint8_t> diskBuffer;
if (rctAccess)
{
diskBuffer.reset(drive->ReadRCTBlock(rctBlockNumber));
}
else
{
diskBuffer.reset(drive->Read(params->LBN, params->ByteCount));
}
_port->DMAWrite(
params->BufferPhysicalAddress & 0x00ffffff,
params->ByteCount,
diskBuffer.get());
}
break;
case Opcodes::WRITE:
{
unique_ptr<uint8_t> memBuffer(_port->DMARead(
params->BufferPhysicalAddress & 0x00ffffff,
params->ByteCount,
params->ByteCount));
if (rctAccess)
{
drive->WriteRCTBlock(rctBlockNumber,
memBuffer.get());
}
else
{
drive->Write(params->LBN,
params->ByteCount,
memBuffer.get());
}
}
break;
default:
// Should never happen.
assert(false);
break;
}
// Set parameters for response.
// We leave ByteCount as is (for now anyway)

View File

@@ -134,16 +134,22 @@ public:
bool on_param_changed(parameter_c *param) override { return true; }
private:
uint32_t Abort(std::shared_ptr<Message> message);
uint32_t Access(std::shared_ptr<Message> message, uint16_t unitNumber);
uint32_t Available(std::shared_ptr<Message> message, uint16_t unitNumber, uint16_t modifiers);
uint32_t CompareHostData(std::shared_ptr<Message> message, uint16_t unitNumber);
uint32_t DetermineAccessPaths(std::shared_ptr<Message> message, uint16_t unitNumber);
uint32_t Erase(std::shared_ptr<Message> message, uint16_t unitNumber, uint16_t modifiers);
uint32_t GetCommandStatus(std::shared_ptr<Message> message);
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 Replace(std::shared_ptr<Message> message, uint16_t unitNumber);
uint32_t Write(std::shared_ptr<Message> message, uint16_t unitNumber, uint16_t modifiers);
uint32_t DoDiskTransfer(uint16_t operation, 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);

View File

@@ -1,3 +1,14 @@
/*
uda.cpp: Implementation of the MSCP port (unibus interface).
This provides logic for the UDA50's SA and IP registers,
the four-step initialization handshake, DMA transfers to and
from the Unibus, and the command/response ring protocols.
At this time it acts as the port for an MSCP controller.
It would be trivial to extend this to TMSCP at a future date.
*/
#include <string.h>
#include <assert.h>