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