1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-04-17 16:43:30 +00:00

General cleanup: Refactored ONLINE / SET UNIT CHARACTERISTICS code, added comment headers to most functions.

Fixed RK05 to properly use on_param_changed instead of ugly hacks for the image path.
This commit is contained in:
Josh Dersch
2019-05-18 00:57:07 +02:00
parent 398c54ee3f
commit 2265a2067c
8 changed files with 479 additions and 256 deletions

View File

@@ -1,5 +1,15 @@
/*
mscp_drive.cpp: Implementation of MSCP disks.
This provides the logic for reads and writes to the data and RCT space
for a given drive, as well as configuration for different standard DEC
drive types.
Disk data is backed by an image file on disk. RCT data exists only in
memory and is not saved -- it is provided to satisfy software that
expects the RCT area to exist. Since no bad sectors will ever actually
exist, the RCT area has no real purpose, so it is ephemeral in this
implementation.
*/
#include <assert.h>
@@ -23,7 +33,7 @@ mscp_drive_c::mscp_drive_c(
SetOffline();
// Calculate the unit's ID:
_unitIDDeviceNumber = driveNumber + 1;
_unitDeviceNumber = driveNumber + 1;
}
mscp_drive_c::~mscp_drive_c()
@@ -34,6 +44,11 @@ mscp_drive_c::~mscp_drive_c()
}
}
//
// GetBlockSize():
// Returns the size, in bytes, of a single block on this drive.
// This is either 512 or 576 bytes.
//
uint32_t mscp_drive_c::GetBlockSize()
{
//
@@ -42,6 +57,11 @@ uint32_t mscp_drive_c::GetBlockSize()
return 512;
}
//
// GetBlockCount():
// Get the size of the data space (not including RCT area) of this
// drive, in blocks.
//
uint32_t mscp_drive_c::GetBlockCount()
{
if (_useImageSize)
@@ -58,51 +78,95 @@ uint32_t mscp_drive_c::GetBlockCount()
}
}
//
// GetRCTBlockCount():
// Returns the total size of the RCT area in blocks.
//
uint32_t mscp_drive_c::GetRCTBlockCount()
{
return _driveInfo.RCTSize * GetRCTCopies();
}
//
// GetMediaID():
// Returns the media ID specific to this drive's type.
//
uint32_t mscp_drive_c::GetMediaID()
{
return _driveInfo.MediaID;
}
uint32_t mscp_drive_c::GetUnitIDDeviceNumber()
//
// GetDeviceNumber():
// Returns the unique device number for this drive.
//
uint32_t mscp_drive_c::GetDeviceNumber()
{
return _unitIDDeviceNumber;
return _unitDeviceNumber;
}
uint16_t mscp_drive_c::GetUnitIDClassModel()
//
// GetClassModel():
// Returns the class and model information for this drive.
//
uint16_t mscp_drive_c::GetClassModel()
{
return _unitIDClassModel;
return _unitClassModel;
}
//
// GetRCTSize():
// Returns the size of one copy of the RCT.
//
uint16_t mscp_drive_c::GetRCTSize()
{
return _driveInfo.RCTSize;
}
//
// GetRBNs():
// Returns the number of replacement blocks per track for
// this drive.
//
uint8_t mscp_drive_c::GetRBNs()
{
return 0;
}
//
// GetRCTCopies():
// Returns the number of copies of the RCT present in the RCT
// area.
//
uint8_t mscp_drive_c::GetRCTCopies()
{
return 1;
}
//
// IsAvailable():
// Indicates whether this drive is available (i.e. has an image
// assigned to it and can thus be used by the controller.)
//
bool mscp_drive_c::IsAvailable()
{
return file_is_open();
}
//
// IsOnline():
// Indicates whether this drive has been placed into an Online
// state (for example by the ONLINE command).
//
bool mscp_drive_c::IsOnline()
{
return _online;
}
//
// SetOnline():
// Brings the drive online.
//
void mscp_drive_c::SetOnline()
{
_online = true;
@@ -115,6 +179,10 @@ void mscp_drive_c::SetOnline()
image_filepath.readonly = true;
}
//
// SetOffline():
// Takes the drive offline.
//
void mscp_drive_c::SetOffline()
{
_online = false;
@@ -196,15 +264,23 @@ uint8_t* mscp_drive_c::ReadRCTBlock(
return buffer;
}
//
// UpdateCapacity():
// Updates the capacity parameter of the drive based on the block count and block size.
//
void mscp_drive_c::UpdateCapacity()
{
capacity.value =
GetBlockCount() * GetBlockSize();
}
//
// UpdateMetadata():
// Updates the Unit Class / Model info and RCT area based on the selected drive type.
//
void mscp_drive_c::UpdateMetadata()
{
_unitIDClassModel = 0x0200 | _driveInfo.Model;
_unitClassModel = 0x0200 | _driveInfo.Model;
// Initialize the RCT area
size_t rctSize = _driveInfo.RCTSize * GetBlockSize();
@@ -213,6 +289,10 @@ void mscp_drive_c::UpdateMetadata()
memset(reinterpret_cast<void *>(_rctData.get()), 0, rctSize);
}
//
// on_param_changed():
// Handles configuration parameter changes.
//
bool mscp_drive_c::on_param_changed(
parameter_c *param)
{
@@ -246,6 +326,14 @@ bool mscp_drive_c::on_param_changed(
return false;
}
//
// SetDriveType():
// Updates this drive's type to the specified type (i.e.
// RA90 or RD54).
// If the specified type is not found in our list of known
// drive types, the drive's type is not changed and false
// is returned.
//
bool mscp_drive_c::SetDriveType(const char* typeName)
{
//
@@ -271,17 +359,28 @@ bool mscp_drive_c::SetDriveType(const char* typeName)
return false;
}
//
// worker():
// worker method for this drive. No work is necessary.
//
void mscp_drive_c::worker(void)
{
// Nothing to do here at the moment.
}
//
// on_power_changed():
// Handle power change notifications.
//
void mscp_drive_c::on_power_changed(void)
{
// Take the drive offline due to power change
SetOffline();
}
//
// on_init_changed():
// Handle INIT signal.
void mscp_drive_c::on_init_changed(void)
{
// Take the drive offline due to reset

View File

@@ -22,8 +22,8 @@ public:
uint32_t GetBlockCount(void);
uint32_t GetRCTBlockCount(void);
uint32_t GetMediaID(void);
uint32_t GetUnitIDDeviceNumber(void);
uint16_t GetUnitIDClassModel(void);
uint32_t GetDeviceNumber(void);
uint16_t GetClassModel(void);
uint16_t GetRCTSize(void);
uint8_t GetRBNs(void);
uint8_t GetRCTCopies(void);
@@ -109,16 +109,16 @@ private:
void UpdateMetadata(void);
DriveInfo _driveInfo;
bool _online;
uint32_t _unitIDDeviceNumber;
uint16_t _unitIDClassModel;
uint32_t _unitDeviceNumber;
uint16_t _unitClassModel;
bool _useImageSize;
//
// RCT ("Replacement and Caching Table") data:
// At this time we provide the minimum required by the MSCP spec,
// a single block used to provide the volume write-protect flags.
// We do not require additional RCT space because the underlying media
// (an image file) doesn't have bad sectors.
// The size of this area varies depending on the drive. This is
// provided only to appease software that expects the RCT to exist --
// since there will never be any bad sectors in our disk images
// there is no other purpose.
// This data is not persisted to disk as it is unnecessary.
//
unique_ptr<uint8_t> _rctData;

View File

@@ -1,7 +1,6 @@
/*
mscp_server.cpp: Implementation a simple MSCP server.
This provides an implementation of the Minimal MSCP subset outlined
in AA-L619A-TK (Chapter 6). It takes a few liberties and errs on
the side of implementation simplicity.
@@ -18,7 +17,7 @@
there's no good reason to make it more complex: real MSCP
controllers (like the original UDA50) would resequence commands
to allow optimal throughput across multiple units, etc. On the
unibone, the underlying storage and the execution speed of the
Unibone, the underlying storage and the execution speed of the
processor is orders of magnitude faster, so even a brute-force
braindead implementation like this can saturate the Unibus.
@@ -44,17 +43,18 @@ using namespace std;
#include "mscp_server.hpp"
#include "uda.hpp"
//
// polling_worker():
// Runs the main MSCP polling thread.
//
void* polling_worker(
void *context)
{
mscp_server* server = reinterpret_cast<mscp_server*>(context);
server->Poll();
return nullptr;
}
mscp_server::mscp_server(
uda_c *port) :
device_c(),
@@ -66,7 +66,7 @@ mscp_server::mscp_server(
polling_mutex(PTHREAD_MUTEX_INITIALIZER),
_credits(INIT_CREDITS)
{
// Alias the port pointer. We do not own the port, merely reference it.
// Alias the port pointer. We do not own the port, we merely reference it.
_port = port;
StartPollingThread();
@@ -78,6 +78,10 @@ mscp_server::~mscp_server()
AbortPollingThread();
}
//
// StartPollingThread():
// Initializes the MSCP polling thread and starts it running.
//
void
mscp_server::StartPollingThread(void)
{
@@ -105,6 +109,10 @@ mscp_server::StartPollingThread(void)
DEBUG("Polling thread created.");
}
//
// AbortPollingThread():
// Stops the MSCP polling thread.
//
void
mscp_server::AbortPollingThread(void)
{
@@ -126,6 +134,14 @@ mscp_server::AbortPollingThread(void)
DEBUG("Polling thread aborted.");
}
//
// Poll():
// The MSCP polling thread.
// This thread waits to be awoken, then pulls messages from the MSCP command
// ring and executes them. When no work is left to be done, it goes back to
// sleep.
// This is awoken by a write to the UDA IP register.
//
void
mscp_server::Poll(void)
{
@@ -176,8 +192,7 @@ mscp_server::Poll(void)
}
//
// Pull commands from the queue until it is empty, at which
// point we sleep until awoken again.
// Pull commands from the queue until it is empty or we're told to quit.
//
while(!messages.empty() && !_abort_polling && _pollState != PollingState::InitRestart)
{
@@ -210,7 +225,7 @@ mscp_server::Poll(void)
switch (header->Word3.Command.Opcode)
{
case Opcodes::ABORT:
cmdStatus = Abort(message);
cmdStatus = Abort();
break;
case Opcodes::ACCESS:
@@ -218,7 +233,7 @@ mscp_server::Poll(void)
break;
case Opcodes::AVAILABLE:
cmdStatus = Available(message, header->UnitNumber, modifiers);
cmdStatus = Available(header->UnitNumber, modifiers);
break;
case Opcodes::COMPARE_HOST_DATA:
@@ -226,7 +241,7 @@ mscp_server::Poll(void)
break;
case Opcodes::DETERMINE_ACCESS_PATHS:
cmdStatus = DetermineAccessPaths(message, header->UnitNumber);
cmdStatus = DetermineAccessPaths(header->UnitNumber);
break;
case Opcodes::ERASE:
@@ -295,13 +310,10 @@ mscp_server::Poll(void)
header->Word3.End.Endcode & Endcodes::END)
{
//
// We steal the hack from simh:
// We steal the credits hack from simh:
// The controller gives all of its credits to the host,
// thereafter it supplies one credit for every response
// packet sent.
//
// Max 14 credits, also C++ is flaming garbage, thanks for replacing "min"
// with something so incredibly annoying to use.
//
uint8_t grantedCredits = min(_credits, static_cast<uint8_t>(MAX_CREDITS));
_credits -= grantedCredits;
@@ -315,6 +327,7 @@ mscp_server::Poll(void)
//
// Post the response to the port's response ring.
// If everything is working properly, there should always be room.
//
if(!_port->PostResponse(message.get()))
{
@@ -326,6 +339,11 @@ mscp_server::Poll(void)
//
}
//
// Go back to sleep. If a UDA reset is pending, we need to signal
// the Reset() call so it knows we've completed our poll and are
// returning to sleep (i.e. the polling thread is now reset.)
//
pthread_mutex_lock(&polling_mutex);
if (_pollState == PollingState::InitRestart)
{
@@ -349,9 +367,12 @@ mscp_server::Poll(void)
DEBUG("MSCP Polling thread exiting.");
}
//
// The following are all implementations of the MSCP commands we support.
//
uint32_t
mscp_server::Abort(
shared_ptr<Message> message)
mscp_server::Abort()
{
INFO("MSCP ABORT");
@@ -367,18 +388,16 @@ mscp_server::Abort(
return STATUS(Status::SUCCESS, 0, 0);
}
uint32_t
mscp_server::Available(
shared_ptr<Message> message,
uint16_t unitNumber,
uint16_t modifiers)
{
UNUSED(modifiers);
// Message has no message-specific data.
// We don't do much with this now...
// Just set the specified drive as Available if appropriate.
// We do nothing with the spin-down modifier.
DEBUG("MSCP AVAILABLE");
mscp_drive_c* drive = GetDrive(unitNumber);
@@ -423,7 +442,6 @@ mscp_server::CompareHostData(
uint32_t
mscp_server::DetermineAccessPaths(
shared_ptr<Message> message,
uint16_t unitNumber)
{
DEBUG("MSCP DETERMINE ACCESS PATHS drive %d", unitNumber);
@@ -432,7 +450,16 @@ mscp_server::DetermineAccessPaths(
// if the unit is incapable of being connected to more than one
// controller." That's us!
return STATUS(Status::SUCCESS, 0, 0);
mscp_drive_c* drive = GetDrive(unitNumber);
if (nullptr == drive ||
!drive->IsAvailable())
{
return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0);
}
else
{
return STATUS(Status::SUCCESS, 0, 0);
}
}
uint32_t
@@ -465,7 +492,6 @@ mscp_server::GetCommandStatus(
message->MessageLength = sizeof(GetCommandStatusResponseParameters)
+ HEADER_SIZE;
GetCommandStatusResponseParameters* params =
reinterpret_cast<GetCommandStatusResponseParameters*>(
GetParameterPointer(message));
@@ -513,7 +539,6 @@ mscp_server::GetUnitStatus(
message->MessageLength = sizeof(GetUnitStatusResponseParameters) +
HEADER_SIZE;
ControlMessageHeader* header =
reinterpret_cast<ControlMessageHeader*>(message->Message);
@@ -551,8 +576,8 @@ mscp_server::GetUnitStatus(
params->Reserved2 = 0;
params->UnitFlags = 0; // TODO: 0 for now, which is sane.
params->MultiUnitCode = 0; // Controller dependent, we don't support multi-unit drives.
params->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber();
params->UnitIdClassModel = drive->GetUnitIDClassModel();
params->UnitIdDeviceNumber = drive->GetDeviceNumber();
params->UnitIdClassModel = drive->GetClassModel();
params->UnitIdUnused = 0;
params->MediaTypeIdentifier = drive->GetMediaID();
params->ShadowUnit = unitNumber; // Always equal to unit number
@@ -618,61 +643,9 @@ mscp_server::Online(
// host-settable flags we can't support.
//
// TODO: "The ONLINE command performs a SET UNIT CHARACTERISTICS
// "The ONLINE command performs a SET UNIT CHARACTERISTICS
// operation after bringing a unit 'Unit-Online'"
// This code could be refactored w/th S_U_C handler.
//
#pragma pack(push,1)
struct OnlineResponseParameters
{
uint16_t UnitFlags;
uint16_t MultiUnitCode;
uint32_t Reserved0;
uint32_t UnitIdDeviceNumber;
uint16_t UnitIdUnused;
uint16_t UnitIdClassModel;
uint32_t MediaTypeIdentifier;
uint32_t Reserved1;
uint32_t UnitSize;
uint32_t VolumeSerialNumber;
};
#pragma pack(pop)
DEBUG("MSCP ONLINE drive %d", unitNumber);
// Adjust message length for response
message->MessageLength = sizeof(OnlineResponseParameters) +
HEADER_SIZE;
mscp_drive_c* drive = GetDrive(unitNumber);
if (nullptr == drive ||
!drive->IsAvailable())
{
return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0);
}
bool alreadyOnline = drive->IsOnline();
drive->SetOnline();
OnlineResponseParameters* params =
reinterpret_cast<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->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber();
params->UnitIdClassModel = drive->GetUnitIDClassModel();
params->UnitIdUnused = 0;
params->MediaTypeIdentifier = drive->GetMediaID();
params->UnitSize = drive->GetBlockCount();
params->VolumeSerialNumber = 1; // We report no serial
params->Reserved0 = 0;
params->Reserved1 = 0;
return STATUS(Status::SUCCESS |
(alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0, 0);
return SetUnitCharacteristicsInternal(message, unitNumber, modifiers, true /*bring online*/);
}
uint32_t
@@ -786,50 +759,7 @@ mscp_server::SetUnitCharacteristics(
DEBUG("MSCP SET UNIT CHARACTERISTICS drive %d", unitNumber);
// TODO: mostly same as Online command: should share logic.
#pragma pack(push,1)
struct SetUnitCharacteristicsResponseParameters
{
uint16_t UnitFlags;
uint16_t MultiUnitCode;
uint32_t Reserved0;
uint32_t UnitIdDeviceNumber;
uint16_t UnitIdUnused;
uint16_t UnitIdClassModel;
uint32_t MediaTypeIdentifier;
uint32_t Reserved1;
uint16_t ShadowUnit;
uint32_t UnitSize;
uint32_t VolumeSerialNumber;
};
#pragma pack(pop)
// Adjust message length for response
message->MessageLength = sizeof(SetUnitCharacteristicsResponseParameters) +
HEADER_SIZE;
mscp_drive_c* drive = GetDrive(unitNumber);
// Check unit
if (nullptr == drive ||
!drive->IsAvailable())
{
return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0);
}
SetUnitCharacteristicsResponseParameters* params =
reinterpret_cast<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->UnitIdDeviceNumber = drive->GetUnitIDDeviceNumber();
params->UnitIdClassModel = drive->GetUnitIDClassModel();
params->UnitIdUnused = 0;
params->MediaTypeIdentifier = drive->GetMediaID();
params->UnitSize = drive->GetBlockCount();
params->VolumeSerialNumber = 0; // We report no serial
return STATUS(Status::SUCCESS, 0, 0);
return SetUnitCharacteristicsInternal(message, unitNumber, modifiers, false);
}
@@ -859,6 +789,80 @@ mscp_server::Write(
modifiers);
}
//
// SetUnitCharacteristicsInternal():
// Logic common to both ONLINE and SET UNIT CHARACTERISTICS commands.
//
uint32_t
mscp_server::SetUnitCharacteristicsInternal(
shared_ptr<Message> message,
uint16_t unitNumber,
uint16_t modifiers,
bool bringOnline)
{
UNUSED(modifiers);
// TODO: handle Set Write Protect modifier
#pragma pack(push,1)
struct SetUnitCharacteristicsResponseParameters
{
uint16_t UnitFlags;
uint16_t MultiUnitCode;
uint32_t Reserved0;
uint32_t UnitIdDeviceNumber;
uint16_t UnitIdUnused;
uint16_t UnitIdClassModel;
uint32_t MediaTypeIdentifier;
uint32_t Reserved1;
uint32_t UnitSize;
uint32_t VolumeSerialNumber;
};
#pragma pack(pop)
// Adjust message length for response
message->MessageLength = sizeof(SetUnitCharacteristicsResponseParameters) +
HEADER_SIZE;
mscp_drive_c* drive = GetDrive(unitNumber);
// Check unit
if (nullptr == drive ||
!drive->IsAvailable())
{
return STATUS(Status::UNIT_OFFLINE, UnitOfflineSubcodes::UNIT_UNKNOWN, 0);
}
SetUnitCharacteristicsResponseParameters* params =
reinterpret_cast<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->UnitIdDeviceNumber = drive->GetDeviceNumber();
params->UnitIdClassModel = drive->GetClassModel();
params->UnitIdUnused = 0;
params->MediaTypeIdentifier = drive->GetMediaID();
params->UnitSize = drive->GetBlockCount();
params->VolumeSerialNumber = 0;
params->Reserved0 = 0;
params->Reserved1 = 0;
if (bringOnline)
{
bool alreadyOnline = drive->IsOnline();
drive->SetOnline();
return STATUS(Status::SUCCESS |
(alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0, 0);
}
else
{
return STATUS(Status::SUCCESS, 0, 0);
}
}
//
// DoDiskTransfer():
// Common transfer logic for READ, WRITE, ERASE, COMPARE HOST DATA and ACCCESS commands.
//
uint32_t
mscp_server::DoDiskTransfer(
uint16_t operation,
@@ -1042,13 +1046,27 @@ mscp_server::DoDiskTransfer(
return STATUS(Status::SUCCESS, 0, 0);
}
//
// GetParameterPointer():
// Returns a pointer to the Parameter text in the given Message.
//
uint8_t*
mscp_server::GetParameterPointer(
shared_ptr<Message> message)
{
// We silence a strict aliasing warning here; this is safe (if perhaps not recommended
// the general case.)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
return reinterpret_cast<ControlMessageHeader*>(message->Message)->Parameters;
#pragma GCC diagnostic pop
}
//
// GetDrive():
// Returns the mscp_drive_c object for the specified unit number,
// or nullptr if no such object exists.
//
mscp_drive_c*
mscp_server::GetDrive(
uint32_t unitNumber)
@@ -1062,6 +1080,12 @@ mscp_server::GetDrive(
return drive;
}
//
// Reset():
// Resets the MSCP server:
// - Waits for the polling thread to finish its current work
// - Releases all drives into the Available state
//
void
mscp_server::Reset(void)
{
@@ -1084,13 +1108,16 @@ mscp_server::Reset(void)
_credits = INIT_CREDITS;
// Release all drives
for (int i=0;i<_port->GetDriveCount();i++)
for (uint32_t i=0;i<_port->GetDriveCount();i++)
{
GetDrive(i)->SetOffline();
}
}
//
// InitPolling():
// Wakes the polling thread.
//
void
mscp_server::InitPolling(void)
{
@@ -1098,16 +1125,9 @@ mscp_server::InitPolling(void)
// Wake the polling thread if not already awoken.
//
pthread_mutex_lock(&polling_mutex);
if (true) //!_continue_polling)
{
DEBUG("Waking polling thread.");
_pollState = PollingState::InitRun;
pthread_cond_signal(&polling_cond);
}
else
{
DEBUG("Polling already active.");
}
pthread_mutex_unlock(&polling_mutex);
}

View File

@@ -17,11 +17,13 @@ class mscp_drive_c;
#define MAX_CREDITS 14
#define INIT_CREDITS 1
// TODO: Dependent on little-endian hardware
//
// ControlMessageHeader encapsulates the standard MSCP control
// message header: a 12-byte header followed by up to 36 bytes of
// parameters.
// Note: This struct (and many others like it in this code) assumes
// little-endian byte orderings. Probably not a big deal unless this
// someday runs on something other than a Beaglebone.
//
#pragma pack(push,1)
struct ControlMessageHeader
@@ -141,14 +143,14 @@ public:
void on_power_changed(void) override {}
void on_init_changed(void) override {}
void worker(void) override {}
bool on_param_changed(parameter_c *param) override { return true; }
bool on_param_changed(parameter_c *param) override { UNUSED(param); return true; }
private:
uint32_t Abort(std::shared_ptr<Message> message);
uint32_t Abort(void);
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 Available(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 DetermineAccessPaths(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);
@@ -158,7 +160,12 @@ private:
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 SetUnitCharacteristicsInternal(
std::shared_ptr<Message> message,
uint16_t unitNumber,
uint16_t modifiers,
bool bringOnline);
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

@@ -89,6 +89,23 @@ bool rk05_c::get_search_complete(void)
return scp;
}
bool rk05_c::on_param_changed(
parameter_c *param)
{
if (&image_filepath == param)
{
if (file_open(image_filepath.new_value, true))
{
_dry = true;
controller->on_drive_status_changed(this);
image_filepath.value = image_filepath.new_value;
return true;
}
}
return false;
}
//
// Reset / Power handlers
//
@@ -110,15 +127,6 @@ void rk05_c::on_init_changed(void)
if (init_asserted)
{
drive_reset();
if (!file_is_open())
{
load_image(image_filepath.value);
file_open(image_filepath.value, true);
_dry = file_is_open();
controller->on_drive_status_changed(this);
}
}
}
@@ -228,6 +236,8 @@ void rk05_c::seek(
void rk05_c::set_write_protect(bool protect)
{
UNUSED(protect);
// Not implemented at the moment.
_scp = false;
}
@@ -288,12 +298,6 @@ void rk05_c::worker(void)
_sok = true;
controller->on_drive_status_changed(this);
}
// Crude: Check for disk image change; if changed we load the new image.
if (image_filepath.value != _currentFilePath)
{
load_image(image_filepath.value);
}
}
}
}
@@ -308,11 +312,3 @@ uint64_t rk05_c::get_disk_byte_offset(
(surface * _geometry.Sectors) +
sector);
}
void rk05_c::load_image(std::string path)
{
file_open(image_filepath.value, true);
_dry = file_is_open();
controller->on_drive_status_changed(this);
_currentFilePath = path;
}

View File

@@ -52,14 +52,10 @@ private:
volatile bool _scp; // Indicates the completion of a seek
std::string _currentFilePath; // Currently loaded disk image if any
uint64_t get_disk_byte_offset(
uint32_t cylinder,
uint32_t surface,
uint32_t sector);
void load_image(std::string path);
public:
Geometry get_geometry(void);
@@ -91,8 +87,8 @@ public:
rk05_c(storagecontroller_c *controller);
bool on_param_changed(parameter_c* param) override;
void on_power_changed(void) override;
void on_init_changed(void) override;
// background worker function

View File

@@ -5,7 +5,12 @@
the four-step initialization handshake, DMA transfers to and
from the Unibus, and the command/response ring protocols.
At this time it acts as the port for an MSCP controller.
While the name "UDA" is used here, this is not a strict emulation
of a real UDA50 -- it is a general MSCP implementation and can be
thought of as the equivalent of the third-party MSCP controllers
from Emulex, CMD, etc. that were available.
At this time this class acts as the port for an MSCP controller.
It would be trivial to extend this to TMSCP at a future date.
*/
@@ -64,7 +69,7 @@ uda_c::uda_c() :
//
// Initialize drives. We support up to eight attached drives.
//
drivecount = 8;
drivecount = DRIVE_COUNT;
for (uint32_t i=0; i<drivecount; i++)
{
mscp_drive_c *drive = new mscp_drive_c(this, i);
@@ -86,6 +91,12 @@ uda_c::~uda_c()
storagedrives.clear();
}
//
// Reset():
// Resets the UDA controller state.
// Resets the attached MSCP server, which may take
// significant time.
//
void uda_c::Reset(void)
{
DEBUG("UDA reset");
@@ -103,11 +114,20 @@ void uda_c::Reset(void)
_purgeInterruptEnable = false;
}
//
// GetDriveCount():
// Returns the number of drives that can be attached to this controller.
//
uint32_t uda_c::GetDriveCount(void)
{
return drivecount;
}
//
// GetDrive():
// Returns a pointer to an mscp_drive_c object for the specified drive number.
// This pointer is owned by the UDA class.
//
mscp_drive_c* uda_c::GetDrive(
uint32_t driveNumber)
{
@@ -116,6 +136,11 @@ mscp_drive_c* uda_c::GetDrive(
return dynamic_cast<mscp_drive_c*>(storagedrives[driveNumber]);
}
//
// StateTransition():
// Transitions the UDA initialization state machine to the specified step,
// atomically.
//
void uda_c::StateTransition(
InitializationStep nextStep)
{
@@ -126,6 +151,10 @@ void uda_c::StateTransition(
pthread_mutex_unlock(&on_after_register_access_mutex);
}
//
// worker():
// Implements the initialization state machine.
//
void uda_c::worker(void)
{
worker_init_realtime_priority(rt_device);
@@ -162,7 +191,6 @@ void uda_c::worker(void)
break;
case InitializationStep::Step1:
// Wait 100uS, set SA.
timeout.wait_us(500);
DEBUG("Transition to Init state S1.");
@@ -176,9 +204,10 @@ void uda_c::worker(void)
break;
case InitializationStep::Step2:
DEBUG("Transition to Init state S2.");
timeout.wait_us(500);
// update the SA read value for step 2:
DEBUG("Transition to Init state S2.");
// Update the SA read value for step 2:
// S2 is set, unibus port type (0), SA bits 15-8 written
// by the host in step 1.
update_SA(0x1000 | ((_step1Value >> 8) & 0xff));
@@ -186,7 +215,6 @@ void uda_c::worker(void)
break;
case InitializationStep::Step3:
// Wait 100uS, set SA.
timeout.wait_us(500);
DEBUG("Transition to Init state S3.");
@@ -233,7 +261,7 @@ void uda_c::worker(void)
DEBUG("Transition to Init state S4, comm area initialized.");
// Update the SA read value for step 4:
// Bits 7-0 indicating our control microcode version.
update_SA(0x4063); // UDA50 ID, makes RSTS happy
update_SA(UDA50_ID); // UDA50 ID, makes RSTS happy
Interrupt();
break;
@@ -245,6 +273,10 @@ void uda_c::worker(void)
}
//
// on_after_register_access():
// Handles register accesses for the IP and SA registers.
//
void
uda_c::on_after_register_access(
unibusdevice_register_t *device_reg,
@@ -253,7 +285,7 @@ uda_c::on_after_register_access(
{
switch (device_reg->index)
{
case 0: // IP
case 0: // IP - read / write
if (UNIBUS_CONTROL_DATO == unibus_control)
{
// "When written with any value, it causes a hard initialization
@@ -366,7 +398,7 @@ uda_c::on_after_register_access(
// | reserved | burst |L|G|
// | | |F|O|
// +---------------+-----------+-+-+
// burst is one less than the max. number of longwords
// Burst is one less than the max. number of longwords
// the host is willing to allow per DMA transfer.
// If zero, the port uses its default burst count.
//
@@ -418,6 +450,10 @@ uda_c::on_after_register_access(
}
}
//
// update_SA():
// Updates the SA register value exposed by the Unibone.
//
void
uda_c::update_SA(uint16_t value)
{
@@ -427,6 +463,16 @@ uda_c::update_SA(uint16_t value)
"update_SA");
}
//
// GetNextCommand():
// Attempts to pull the next command from the command ring, if any
// are available.
// If successful, returns a pointer to a Message struct; this pointer
// is owned by the caller.
// On failure, nullptr is returned. This indicates that the ring is
// empty or that an attempt to access non-existent memory occurred.
// TODO: Need to handle NXM cases properly.
//
Message*
uda_c::GetNextCommand(void)
{
@@ -440,7 +486,6 @@ uda_c::GetNextCommand(void)
_commandRingPointer,
descriptorAddress);
std::unique_ptr<Descriptor> cmdDescriptor(
reinterpret_cast<Descriptor*>(
DMARead(
@@ -448,7 +493,6 @@ uda_c::GetNextCommand(void)
sizeof(Descriptor),
sizeof(Descriptor))));
// TODO: if NULL is returned after retry assume a bus error and handle it appropriately.
assert(cmdDescriptor != nullptr);
@@ -474,10 +518,8 @@ uda_c::GetNextCommand(void)
messageAddress - 4,
success);
//
// TODO: sanity check message length (what is the max length we
// can expect to see?)
//
assert(messageLength > 0 && messageLength < MAX_MESSAGE_LENGTH);
std::unique_ptr<Message> cmdMessage(
reinterpret_cast<Message*>(
DMARead(
@@ -547,13 +589,7 @@ uda_c::GetNextCommand(void)
//
// Set ring base - 4 to non-zero to indicate a transition.
//
DMAWriteWord(
_ringBase - 4,
0x1);
//
// Raise the interrupt
//
DMAWriteWord(_ringBase - 4, 0x1);
Interrupt();
}
@@ -566,6 +602,12 @@ uda_c::GetNextCommand(void)
return nullptr;
}
//
// PostResponse():
// Posts the provided Message to the response ring.
// Returns true on success, false otherwise.
// TODO: Need to handle NXM, as above.
//
bool
uda_c::PostResponse(
Message* response
@@ -598,10 +640,21 @@ uda_c::PostResponse(
(cmdDescriptor->Word1.Fields.EnvelopeHigh << 16);
//
// Read the buffer length the host has allocated for this response;
// if it is shorter than the buffer we're writing then we will need to
// split the response into multiple responses.
//
// Read the buffer length the host has allocated for this response.
//
// TODO:
// If it is shorter than the buffer we're writing then we will need to
// split the response into multiple responses. I have never seen this happen,
// however and I'm curious if the documentation (AA-L621A-TK) is simply incorrect:
// "Note that if a controller's responses are less than or equal to 60 bytes,
// then the controller need not check the size of the response slot."
// All of the MSCP response messages are shorter than 60 bytes, so this is always
// the case. I'll also note that the spec states "The minimum acceptable size
// is 60 bytes of message text" for the response buffer set up by the host and this
// is *definitely* not followed by host drivers.
//
// The doc is also not exactly clear what a fragmented set of responses looks like...
//
// Message length is at messageAddress - 4 -- this is the size of the command
// not including the two header words.
//
@@ -613,34 +666,36 @@ uda_c::PostResponse(
DEBUG("response address o%o length o%o", messageAddress, response->MessageLength);
if (reinterpret_cast<uint16_t*>(response)[0] == 0)
assert(reinterpret_cast<uint16_t*>(response)[0] > 0);
if (messageLength == 0)
{
// A lot of bootstraps appear to set up response buffers of length 0.
// We just log the behavior.
DEBUG("Host response buffer size is zero.");
}
else if (messageLength < response->MessageLength)
{
ERROR("Writing zero length response!");
//
// If this happens it's likely fatal since we're not fragmenting responses (see the big comment
// block above). So eat flaming death.
//
FATAL("Response buffer 0x%x > host buffer length 0x%x", response->MessageLength, messageLength);
}
if (messageLength < response->MessageLength)
{
// TODO: A lot of bootstraps appear to set up response buffers of length 0...
ERROR("Response buffer %x > message length %x", response->MessageLength, messageLength);
}
// else
{
//
// This will fit; simply copy the response message over the top
// of the buffer allocated on the host -- this updates the header fields
// as necessary and provides the actual response data to the host.
//
DMAWrite(
messageAddress - 4,
response->MessageLength + 4,
reinterpret_cast<uint8_t*>(response));
}
//
// This will fit; simply copy the response message over the top
// of the buffer allocated on the host -- this updates the header fields
// as necessary and provides the actual response data to the host.
//
DMAWrite(
messageAddress - 4,
response->MessageLength + 4,
reinterpret_cast<uint8_t*>(response));
//
// Check if a transition from empty to non-empty occurred, interrupt if requested.
//
// TODO: factor this code out as it's basically identical to the code in GetNextCommand.
//
// If the previous entry in the ring is owned by the Port then that indicates
// that the ring was previously empty (i.e. the descriptor we're now returning
// is the first entry returned to the ring by the Port.)
@@ -696,13 +751,7 @@ uda_c::PostResponse(
//
// Set ring base - 2 to non-zero to indicate a transition.
//
DMAWriteWord(
_ringBase - 2,
0x1);
//
// Raise the interrupt
//
DMAWriteWord(_ringBase - 2, 0x1);
Interrupt();
}
@@ -715,6 +764,11 @@ uda_c::PostResponse(
return res;
}
//
// GetControllerIdentifier():
// Returns the ID used by SET CONTROLLER CHARACTERISTICS.
// This should be unique per controller.
//
uint32_t
uda_c::GetControllerIdentifier()
{
@@ -723,12 +777,21 @@ uda_c::GetControllerIdentifier()
return 0x12345678;
}
//
// GetControllerClassModel():
// Returns the Class and Model information used by SET CONTROLLER CHARACTERISTICS.
//
uint16_t
uda_c::GetControllerClassModel()
{
return 0x0102; // Class 1 (mass storage), model 2 (UDA50)
}
//
// Interrupt():
// Invokes a Unibus interrupt if interrupts are enabled and the interrupt
// vector is non-zero.
//
void
uda_c::Interrupt(void)
{
@@ -738,6 +801,10 @@ uda_c::Interrupt(void)
}
}
//
// on_power_changed():
// Resets the controller and all attached drives.
//
void
uda_c::on_power_changed(void)
{
@@ -750,6 +817,10 @@ uda_c::on_power_changed(void)
}
}
//
// on_init_changed():
// Resets the controller and all attached drives.
//
void
uda_c::on_init_changed(void)
{
@@ -762,21 +833,33 @@ uda_c::on_init_changed(void)
storagecontroller_c::on_init_changed();
}
//
// on_drive_status_changed():
// A no-op. The controller doesn't require any drive notifications.
//
void
uda_c::on_drive_status_changed(storagedrive_c *drive)
{
UNUSED(drive);
}
//
// GetCommandDescriptorAddress():
// Returns the address of the given command descriptor in the command ring.
//
uint32_t
uda_c::GetCommandDescriptorAddress(
size_t index
)
{
return _ringBase + _responseRingLength * sizeof(Descriptor) +
return _ringBase + _responseRingLength * sizeof(Descriptor) +
index * sizeof(Descriptor);
}
//
// GetResponseDescriptorAddress():
// Returns the address of the given response descriptor in the response ring.
//
uint32_t
uda_c::GetResponseDescriptorAddress(
size_t index
@@ -785,10 +868,11 @@ uda_c::GetResponseDescriptorAddress(
return _ringBase + index * sizeof(Descriptor);
}
/*
Write a single word to Unibus memory. Returns true
on success; if false is returned this is due to an NXM condition.
*/
//
// DMAWriteWord():
// Writes a single word to Unibus memory. Returns true
// on success; if false is returned this is due to an NXM condition.
//
bool
uda_c::DMAWriteWord(
uint32_t address,
@@ -800,10 +884,11 @@ uda_c::DMAWriteWord(
reinterpret_cast<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.
*/
//
// DMAReadWord():
// Read a single word from Unibus memory. Returns the word read on success.
// the success field indicates the success or failure of the read.
//
uint16_t
uda_c::DMAReadWord(
uint32_t address,
@@ -829,10 +914,13 @@ uda_c::DMAReadWord(
}
/*
Write data from buffer to Unibus memory. Returns true
on success; if false is returned this is due to an NXM condition.
*/
//
// DMAWrite():
// Write data from the provided buffer to Unibus memory. Returns true
// on success; if false is returned this is due to an NXM condition.
// The address specified in 'address' must be word-aligned and the
// length must be even.
//
bool
uda_c::DMAWrite(
uint32_t address,
@@ -849,11 +937,14 @@ uda_c::DMAWrite(
lengthInBytes >> 1);
}
/*
Read data from Unibus memory into the returned buffer.
Buffer returned is nullptr if memory could not be read.
Caller is responsible for freeing the buffer when done.
*/
//
// DMARead():
// Read data from Unibus memory into the returned buffer.
// Buffer returned is nullptr if memory could not be read.
// Caller is responsible for freeing the buffer when done.
// The address specified in 'address' must be word-aligned
// and the length must be even.
//
uint8_t*
uda_c::DMARead(
uint32_t address,

View File

@@ -11,6 +11,20 @@
#include "mscp_server.hpp"
#include "mscp_drive.hpp"
// The number of drives supported by the controller.
// This is arbitrarily fixed at 8 but could be set to any
// value up to 65535.
#define DRIVE_COUNT 8
// The control/microcode version info returned by SA in the fourth intialization step.
// This indicates a UDA50 controller, which makes RSTS happy.
#define UDA50_ID 0x4063
// The maximum message length we can handle. This is provided as a sanity check
// to prvent parsing clearly invalid commands.
#define MAX_MESSAGE_LENGTH 0x1000
// TODO: this currently assumes a little-endian machine!
#pragma pack(push,1)
struct Message