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:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user