1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-03-06 11:13:21 +00:00

Major refactoring for overlapped-seeks. Moved most of the RP drive logic to rp_drive_c,

where I should have put it to begin with.  Working better, still a few glitches to track
down and a number of register bits that need to be hooked up properly.
This commit is contained in:
Josh Dersch
2020-04-01 01:56:11 +02:00
parent 8906fe3485
commit 06b2a6ebc9
6 changed files with 523 additions and 516 deletions

View File

@@ -15,14 +15,6 @@ using namespace std;
#include "massbus_device.hpp"
#include "massbus_rp.hpp"
void* WorkerInit(
void* context)
{
massbus_rp_c* rp = reinterpret_cast<massbus_rp_c*>(context);
rp->Worker();
return nullptr;
}
void* SpinInit(
void* context)
{
@@ -36,40 +28,22 @@ massbus_rp_c::massbus_rp_c(
device_c(),
_controller(controller),
_selectedUnit(0),
_workerState(WorkerState::Idle),
_workerWakeupCond(PTHREAD_COND_INITIALIZER),
_workerMutex(PTHREAD_MUTEX_INITIALIZER),
_err(false),
_ata(false),
_ned(false),
_attnSummary(0),
_rmr(false)
_updateLock(PTHREAD_MUTEX_INITIALIZER)
{
name.value="MASSBUS_RP";
type_name.value = "massbus_rp_c";
log_label = "MASSBUS_RP";
_newCommand = { 0 };
// Start the worker thread
pthread_attr_t attribs;
pthread_attr_init(&attribs);
int status = pthread_create(
&_workerThread,
&attribs,
&WorkerInit,
reinterpret_cast<void*>(this));
pthread_attr_t attribs2;
pthread_attr_init(&attribs2);
status = pthread_create(
uint32_t status = pthread_create(
&_spinThread,
&attribs2,
&SpinInit,
reinterpret_cast<void*>(this));
}
massbus_rp_c::~massbus_rp_c()
@@ -125,9 +99,15 @@ void
massbus_rp_c::SelectUnit(
uint32_t unit)
{
_selectedUnit = unit;
UpdateStatus(_selectedUnit, false, false);
UpdateDriveRegisters();
if (_selectedUnit != unit)
{
_selectedUnit = unit;
// Let the drive know it's been selected, this
// may cause status register updates.
SelectedDrive()->Select();
UpdateDriveRegisters();
}
}
void
@@ -135,28 +115,17 @@ massbus_rp_c::WriteRegister(
uint32_t reg,
uint16_t value)
{
DEBUG("RP reg write: unit %d register 0%o value 0%o", _selectedUnit, reg, value);
INFO ("RP reg write: unit %d register 0%o value 0%o", _selectedUnit, reg, value);
rp_drive_c* drive = SelectedDrive();
if (!drive->IsDriveReady() &&
reg != 0 && // CS1 is allowed as long as GO isn't set (will be checked in DoCommand)
reg != (uint32_t)Registers::AttentionSummary)
{
// Any attempt to modify a drive register other than Attention Summary
// while the drive is busy is invalid.
// For now treat this as fatal (it's not but real code shouldn't be doing it so this
// is a diagnostic at the moment.)
FATAL("Register modification while drive busy.");
_rmr = true;
UpdateStatus(_selectedUnit, false, false);
return;
}
switch(static_cast<Registers>(reg))
{
case Registers::Control:
DoCommand(value);
//
// Select unit as necessary.
//
drive->DoCommand(value);
break;
case Registers::DesiredSectorTrackAddress:
@@ -185,8 +154,7 @@ massbus_rp_c::WriteRegister(
//
// Based on diagnostic (ZRJGE0) behavior, writing ANY value here forces an error.
//
DEBUG("Error 1 Reg write o%o, value is now o%o", value, _error1);
UpdateStatus(_selectedUnit, false, true); // Force composite error.
drive->ForceDiagnosticError();
break;
default:
@@ -195,159 +163,6 @@ massbus_rp_c::WriteRegister(
}
}
void massbus_rp_c::DoCommand(
uint16_t command)
{
FunctionCode function = static_cast<FunctionCode>((command & RP_FUNC) >> 1);
rp_drive_c* drive = SelectedDrive();
// check for GO bit; if unset we have nothing to do here,
if ((command & RP_GO) == 0)
{
return;
}
DEBUG("RP function 0%o, unit %o", function, _selectedUnit);
if (!drive->IsConnected())
{
// Early return for disconnected drives;
// set NED and ERR bits
_err = true;
_ata = true;
_ned = true; // TODO: should be done at RH11 level!
drive->ClearVolumeValid();
UpdateStatus(_selectedUnit, true, false);
return;
}
if (!SelectedDrive()->IsDriveReady())
{
// This should never happen.
FATAL("Command sent while not ready!");
}
_ned = false;
_ata = false;
switch(static_cast<FunctionCode>(function))
{
case FunctionCode::Nop:
// Nothing.
UpdateStatus(_selectedUnit, true, false);
break;
case FunctionCode::Unload:
// Unsure how best to implement this:
// This puts the drive in STANDBY state, meaning the heads are
// retracted and the spindle powers down. The command doesn't actually *finish*
// until the pack is manually spun back up. This could be implemented
// by unloading the disk image and waiting until a new one is loaded.
// Right now I'm just treating it as a no-op, at least until I can find a good
// way to test it using real software.
DEBUG("RP Unload");
_ata = true;
UpdateStatus(_selectedUnit, true, false);
break;
case FunctionCode::DriveClear:
// p. 2-11 of EK-RP056-MM-01:
// "The following registers and conditions within the DCL are cleared
// by this command:
// - Status Register (ATA and ERR status bits)
// - All three Error registers
// - Attention Summary Register
// - ECC Position and Pattern Registers
// - The Diagnostic Mode Bit
DEBUG("RP Drive Clear");
_ata = false;
_attnSummary = 0;
_error1 = 0;
_error2 = 0;
_error3 = 0;
_rmr = false;
_ned = false;
_maint = false;
UpdateStatus(_selectedUnit, false, false);
break;
case FunctionCode::Release:
DEBUG("RP Release");
// This is a no-op, this only applies to dual-ported configurations,
// which we are not.
break;
case FunctionCode::ReadInPreset:
DEBUG("RP Read-In Preset");
//
// "This command sets the VV (volume valid) bit, clears the desired
// sector/track address register, and clears the FMT, HCI, and ECI
// bits in the offset register. It is used to bootstrap the device."
//
drive->SetVolumeValid();
drive->SetDriveReady();
drive->SetDesiredSector(0);
drive->SetDesiredTrack(0);;
drive->SetOffset(0);
UpdateDriveRegisters();
UpdateStatus(_selectedUnit, false, false); /* do not interrupt */
break;
case FunctionCode::PackAcknowledge:
DEBUG("RP Pack Acknowledge");
drive->SetVolumeValid();
UpdateStatus(_selectedUnit, false, false);
break;
case FunctionCode::ReadData:
case FunctionCode::WriteData:
case FunctionCode::WriteHeaderAndData:
case FunctionCode::ReadHeaderAndData:
case FunctionCode::Search:
case FunctionCode::Seek:
case FunctionCode::Recalibrate:
case FunctionCode::Offset:
case FunctionCode::ReturnToCenterline:
DEBUG("RP Read/Write Data or head-motion command");
{
// Clear the unit's DRY bit
drive->ClearDriveReady();
if (function == FunctionCode::Search ||
function == FunctionCode::Seek ||
function == FunctionCode::Recalibrate ||
function == FunctionCode::Offset ||
function == FunctionCode::ReturnToCenterline)
{
drive->SetPositioningInProgress();
}
UpdateStatus(_selectedUnit, false, false);
pthread_mutex_lock(&_workerMutex);
// Save a copy of command data for the worker to consume
_newCommand.unit = _selectedUnit;
_newCommand.function = function;
_newCommand.bus_address = _controller->GetBusAddress();
_newCommand.word_count = _controller->GetWordCount();
_newCommand.ready = true;
// Wake the worker
pthread_cond_signal(&_workerWakeupCond);
pthread_mutex_unlock(&_workerMutex);
}
break;
default:
FATAL("Unimplemented RP function.");
break;
}
}
uint16_t
massbus_rp_c::ReadRegister(
uint32_t reg)
@@ -377,65 +192,49 @@ massbus_rp_c::ReadRegister(
// Register update functions
//
void
massbus_rp_c::UpdateStatus(
massbus_rp_c::DriveStatus(
uint32_t unit,
bool complete,
bool diagForceError)
uint16_t status,
uint16_t error1,
bool ata,
bool complete)
{
pthread_mutex_lock(&_updateLock);
// Most of these status bits (except possibly ATTN)
// are for the currently selected drive.
rp_drive_c* drive = SelectedDrive();
_error1 =
(_rmr ? 04 : 0) |
(drive->GetAddressOverflow() ? 01000 : 0) |
(drive->GetInvalidAddress() ? 02000 : 0) |
(drive->GetWriteLockError() ? 04000 : 0);
if (_error1 != 0)
// Update status registers only for the selected drive.
if (unit == _selectedUnit)
{
_err = true;
_ata = true;
_controller->WriteRegister(static_cast<uint32_t>(Registers::Status), status);
_controller->WriteRegister(static_cast<uint32_t>(Registers::Error1), error1);
}
else if (diagForceError)
// Update the Attention Summary register for the reporting drive:
if (ata)
{
_err = false;
_ata = true;
}
else
{
_err = false;
}
_status =
(drive->GetVolumeValid() ? 0100 : 0) |
(drive->IsDriveReady() ? 0200 : 0) |
(0400) | // Drive preset -- always set for a single-controller disk
(drive->GetReadLastSector() ? 02000 : 0) | // Last sector read
(drive->IsWriteLocked() ? 04000 : 0) | // Write lock
(drive->IsPackLoaded() ? 010000 : 0) | // Medium online
(drive->IsPositioningInProgress() ? 020000 : 0) | // PIP
(_err ? 040000 : 0) | // Composite error
(_ata ? 0100000 : 0);
DEBUG("Unit %d Status: o%o", _selectedUnit, _status);
_controller->WriteRegister(static_cast<uint32_t>(Registers::Status), _status);
_controller->WriteRegister(static_cast<uint32_t>(Registers::Error1), _error1);
// Update the Attention Summary register if the requested disk is online:
// Note that we may be setting ATTN for a drive other than the currently selected one.
if (_ata && GetDrive(unit)->IsConnected())
{
_attnSummary |= (0x1 << unit); // TODO: these only get set, and are latched until
// manually cleared?
DEBUG("Attention Summary is now o%o", _attnSummary);
_attnSummary |= (0x1 << unit);
INFO ("Attention Summary is now o%o", _attnSummary);
}
_controller->WriteRegister(static_cast<uint32_t>(Registers::AttentionSummary), _attnSummary);
// Inform controller of status update.
_controller->BusStatus(complete, drive->IsDriveReady(), _ata, _err, drive->IsConnected(), _ned);
// TODO: ready status is a hack that serializes drive commands;
// a real RH11/RP06 setup allows overlapped seeks, we do not (yet).
bool ready = true;
for(int i=0;i<8;i++)
{
if (GetDrive(i)->IsConnected() && !GetDrive(i)->IsDriveReady())
{
ready = false;// We're ready if there isn't a command in progress right now.
break;
}
}
_controller->BusStatus(complete, ready, ata, (error1 != 0), SelectedDrive()->IsConnected(), _ned);
pthread_mutex_unlock(&_updateLock);
}
void
@@ -460,17 +259,8 @@ void
massbus_rp_c::Reset()
{
// Reset registers to their defaults
_ata = false;
_attnSummary = 0;
_error1 = 0;
_rmr = false;
_selectedUnit = 0;
UpdateStatus(_selectedUnit, false, false);
UpdateDriveRegisters();
_newCommand.ready = false;
SelectUnit(0);
}
rp_drive_c*
@@ -486,162 +276,6 @@ massbus_rp_c::GetDrive(uint16_t unit)
return _controller->GetDrive(unit);
}
// Background worker function
void
massbus_rp_c::Worker()
{
worker_init_realtime_priority(rt_device);
_workerState = WorkerState::Idle;
WorkerCommand command = { 0 };
timeout_c timeout;
DEBUG("massbus worker started.");
while (!workers_terminate)
{
switch(_workerState)
{
case WorkerState::Idle:
// Wait for work.
pthread_mutex_lock(&_workerMutex);
while (!_newCommand.ready)
{
pthread_cond_wait(
&_workerWakeupCond,
&_workerMutex);
}
// Make a local copy of the new command
command = _newCommand;
_newCommand.ready = false;
pthread_mutex_unlock(&_workerMutex);
_workerState = WorkerState::Execute;
break;
case WorkerState::Execute:
{
rp_drive_c* drive = GetDrive(command.unit);
switch(command.function)
{
case FunctionCode::ReadData:
{
uint16_t* buffer = nullptr;
if (drive->Read(
command.word_count,
&buffer))
{
//
// Data read: do DMA transfer to memory.
//
_controller->DiskReadTransfer(
command.bus_address,
command.word_count,
buffer);
// Free buffer
delete buffer;
}
else
{
// Read failed:
DEBUG("Read failed.");
_ata = true;
}
// Return drive to ready state
drive->SetDriveReady();
_workerState = WorkerState::Finish;
}
break;
case FunctionCode::WriteData:
{
//
// Data write: do DMA transfer from memory.
//
uint16_t* buffer = _controller->DiskWriteTransfer(
command.bus_address,
command.word_count);
if (!buffer || !drive->Write(
command.word_count,
buffer))
{
// Write failed:
DEBUG("Write failed.");
_ata = true;
}
delete buffer;
// Return drive to ready state
drive->SetDriveReady();
_workerState = WorkerState::Finish;
}
break;
case FunctionCode::Search:
{
if (!drive->Search())
{
// Search failed
DEBUG("Search failed");
}
// Return to ready state, set attention bit.
drive->SetDriveReady();
_ata = true;
_workerState = WorkerState::Finish;
}
break;
case FunctionCode::Seek:
if (!drive->SeekTo())
{
// Seek failed
DEBUG("Seek failed");
}
drive->SetDriveReady();
_ata = true;
_workerState = WorkerState::Finish;
break;
case FunctionCode::Offset:
case FunctionCode::ReturnToCenterline:
// We don't support adjusting the head position between
// cylinders so these are both effectively no-ops, but
// they're no-ops that need to take a small amount of time
// to complete.
DEBUG("OFFSET/RETURN TO CL");
timeout.wait_ms(10);
drive->SetDriveReady();
_ata = true;
_workerState = WorkerState::Finish;
break;
default:
FATAL("Unimplemented drive function %d", command.function);
break;
}
}
break;
case WorkerState::Finish:
_workerState = WorkerState::Idle;
GetDrive(_newCommand.unit)->SetDriveReady();
UpdateStatus(_newCommand.unit, true, false);
UpdateDriveRegisters();
break;
}
}
}
void
massbus_rp_c::Spin(void)
{

View File

@@ -16,6 +16,8 @@ using namespace std;
#include "rh11.hpp"
#include "massbus_device.hpp"
class rp_drive_c;
// Registers
enum class Registers
{
@@ -37,31 +39,6 @@ enum class Registers
ECCPattern = 017,
};
// Control Function codes
#define RP_GO 01
#define RP_FUNC 076
enum class FunctionCode
{
Nop = 00,
Unload = 01,
Recalibrate = 03,
DriveClear = 04,
Release = 05,
Search = 014,
WriteCheckData = 024,
WriteCheckHeaderAndData = 025,
WriteData = 030,
WriteHeaderAndData = 031,
ReadData = 034,
ReadHeaderAndData = 035,
Seek = 02,
Offset = 06,
ReturnToCenterline = 07,
PackAcknowledge = 011,
ReadInPreset = 010,
};
//
// Register metadata
//
@@ -105,35 +82,17 @@ public:
void WriteRegister(uint32_t register, uint16_t value) override;
uint16_t ReadRegister(uint32_t register) override;
void DriveStatus(uint32_t unit, uint16_t status, uint16_t error1, bool ata, bool complete);
void SelectUnit(uint32_t unit);
// Background worker functions
void Worker();
void Spin();
private:
struct WorkerCommand
{
uint16_t unit;
volatile uint32_t bus_address;
volatile uint32_t word_count;
volatile FunctionCode function;
volatile bool ready;
} _newCommand;
enum WorkerState
{
Idle = 0,
Execute = 1,
Finish = 2,
} _workerState;
void DoCommand(uint16_t command);
void on_power_changed(void) override;
void on_init_changed(void) override;
void UpdateStatus(uint32_t unit, bool completion, bool diagForceError);
void UpdateDriveRegisters();
rp_drive_c* SelectedDrive();
@@ -167,26 +126,17 @@ private:
volatile uint32_t _selectedUnit;
// Register data
volatile uint16_t _status;
volatile uint16_t _error1;
volatile uint16_t _maint;
volatile uint16_t _attnSummary;
volatile uint16_t _error2;
volatile uint16_t _error3;
// Status bits that we track
volatile bool _err;
volatile bool _ata;
volatile bool _rmr;
// RH11 ready signal (ugly: this should be in the rh11 code!)
volatile bool _ned; // ditto
// Worker thread
pthread_t _workerThread;
pthread_cond_t _workerWakeupCond;
pthread_mutex_t _workerMutex;
// Drive status update lock
pthread_mutex_t _updateLock;
// Spin thread
pthread_t _spinThread;
};

View File

@@ -14,9 +14,9 @@
#include "unibusadapter.hpp"
#include "unibusdevice.hpp"
#include "storagecontroller.hpp"
#include "rp_drive.hpp"
#include "rh11.hpp"
#include "massbus_rp.hpp"
#include "rp_drive.hpp"
// Maps the unibus register index to the MASSBUS register number.
// -1 entries are local to the rh11.
@@ -214,7 +214,7 @@ rh11_c::rh11_c() :
drivecount = RH_DRIVE_COUNT;
for (uint32_t i=0; i<drivecount; i++)
{
rp_drive_c *drive = new rp_drive_c(this, i);
rp_drive_c *drive = new rp_drive_c(this, static_cast<massbus_rp_c*>(_massbus.get()), i);
drive->unitno.value = i;
drive->name.value = name.value + std::to_string(i);
drive->log_label = drive->name.value;
@@ -606,7 +606,7 @@ void rh11_c::on_after_register_access(
{
DEBUG("Forced interrupt.");
unibusadapter->INTR(intr_request, nullptr, 0);
}
}
}
DEBUG("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go);

View File

@@ -19,7 +19,8 @@ using namespace std;
#include "unibusdevice.hpp"
#include "storagecontroller.hpp"
#include "massbus_device.hpp"
#include "rp_drive.hpp"
class rp_drive_c;
#define RH_DRIVE_COUNT 8

View File

@@ -13,26 +13,39 @@ using namespace std;
#include "logger.hpp"
#include "utils.hpp"
#include "rh11.hpp"
#include "massbus_rp.hpp"
#include "rp_drive.hpp"
rp_drive_c::rp_drive_c(storagecontroller_c *controller, uint32_t driveNumber) :
rp_drive_c::rp_drive_c(rh11_c *controller, massbus_rp_c* bus, uint32_t driveNumber) :
storagedrive_c(controller),
_driveNumber(driveNumber),
_desiredCylinder(0),
_currentCylinder(0),
_desiredTrack(0),
_offset(0),
_ata(false),
_ned(false),
_rmr(false),
_ready(true),
_lst(false),
_aoe(false),
_iae(false),
_wle(false),
_pip(false),
_vv(false)
_vv(false),
_workerState(WorkerState::Idle),
_workerWakeupCond(PTHREAD_COND_INITIALIZER),
_workerMutex(PTHREAD_MUTEX_INITIALIZER)
{
set_workers_count(0) ; // needs no worker()
set_workers_count(1);
log_label = "RP";
SetDriveType("RP06");
_newCommand = { 0 };
_controller = controller;
_bus = bus;
}
rp_drive_c::~rp_drive_c()
@@ -68,9 +81,8 @@ rp_drive_c::on_param_changed(parameter_c *param)
// New image; this is analogous to a new pack being spun up.
// Set the Volume Valid bit so software knows things have changed.
// TODO: should alert massbus_rp so that it can update status regs immediately.
SetVolumeValid();
_vv = true;
UpdateStatus(false, false);
return true;
}
@@ -81,7 +93,7 @@ rp_drive_c::on_param_changed(parameter_c *param)
}
//
// GetBlockSize():
// GetSectorSize():
// Returns the size, in bytes, of a single sector on this drive.
// This is either 512 or 576 bytes.
//
@@ -105,6 +117,364 @@ rp_drive_c::IsPackLoaded()
return file_is_open();
}
void
rp_drive_c::Select()
{
// Just force an update of status registers for this disk.
UpdateStatus(false, false);
}
void rp_drive_c::DoCommand(
uint16_t command)
{
FunctionCode function = static_cast<FunctionCode>((command & RP_FUNC) >> 1);
// check for GO bit; if unset we have nothing to do here,
if ((command & RP_GO) == 0)
{
return;
}
INFO ("RP function 0%o, unit %o", function, _driveNumber);
if (!_ready)
{
// For now, halt -- This should never happen with valid code.
// After things are stable, this should set error bits.
FATAL("Unit %d - Command sent while not ready!", _driveNumber);
}
_ned = false;
_ata = false;
switch(static_cast<FunctionCode>(function))
{
case FunctionCode::Nop:
// Nothing.
UpdateStatus(true, false);
break;
case FunctionCode::Unload:
// Unsure how best to implement this:
// This puts the drive in STANDBY state, meaning the heads are
// retracted and the spindle powers down. The command doesn't actually *finish*
// until the pack is manually spun back up. This could be implemented
// by unloading the disk image and waiting until a new one is loaded.
// Right now I'm just treating it as a no-op, at least until I can find a good
// way to test it using real software.
DEBUG("RP Unload");
_ata = true;
UpdateStatus(true, false);
break;
case FunctionCode::DriveClear:
// p. 2-11 of EK-RP056-MM-01:
// "The following registers and conditions within the DCL are cleared
// by this command:
// - Status Register (ATA and ERR status bits)
// - All three Error registers
// - Attention Summary Register
// - ECC Position and Pattern Registers
// - The Diagnostic Mode Bit
INFO ("RP Drive Clear");
_ata = false;
_rmr = false;
_ned = false;
UpdateStatus(false, false);
break;
case FunctionCode::Release:
DEBUG("RP Release");
// This is a no-op, this only applies to dual-ported configurations,
// which we are not.
break;
case FunctionCode::ReadInPreset:
INFO ("RP Read-In Preset");
//
// "This command sets the VV (volume valid) bit, clears the desired
// sector/track address register, and clears the FMT, HCI, and ECI
// bits in the offset register. It is used to bootstrap the device."
//
_vv = true;
_ready = true;
_desiredSector = 0;
_desiredTrack = 0;
_offset = 0;
UpdateStatus(false, false);
break;
case FunctionCode::PackAcknowledge:
DEBUG("RP Pack Acknowledge");
_vv = true;
UpdateStatus(false, false);
break;
case FunctionCode::ReadData:
case FunctionCode::WriteData:
case FunctionCode::WriteHeaderAndData:
case FunctionCode::ReadHeaderAndData:
case FunctionCode::Search:
case FunctionCode::Seek:
case FunctionCode::Recalibrate:
case FunctionCode::Offset:
case FunctionCode::ReturnToCenterline:
INFO ("RP Read/Write Data or head-motion command");
{
// If this drive is not connected, head-motion commands cannot be
// executed.
if (!IsConnected())
{
_ata = true;
_ned = true;
UpdateStatus(true, false);
return;
}
// Clear DRY.
_ready = false;
if (function == FunctionCode::Search ||
function == FunctionCode::Seek ||
function == FunctionCode::Recalibrate ||
function == FunctionCode::Offset ||
function == FunctionCode::ReturnToCenterline)
{
// Positioning in progress.
_pip = true;
}
UpdateStatus(false, false);
pthread_mutex_lock(&_workerMutex);
// Sanity check: no other command should be executing on this drive
// right now.
if (_newCommand.ready)
{
FATAL("Command o%o sent while worker for drive %d is busy!",
function, _driveNumber);
}
// Save a copy of command data for the worker to consume
_newCommand.function = function;
_newCommand.bus_address = _controller->GetBusAddress();
_newCommand.word_count = _controller->GetWordCount();
_newCommand.ready = true;
// Wake the worker
pthread_cond_signal(&_workerWakeupCond);
pthread_mutex_unlock(&_workerMutex);
}
break;
default:
FATAL("Unimplemented RP function.");
break;
}
}
// Background worker function
void
rp_drive_c::worker(unsigned instance)
{
UNUSED(instance);
worker_init_realtime_priority(rt_device);
_workerState = WorkerState::Idle;
WorkerCommand command = { 0 };
timeout_c timeout;
INFO ("rp_drive worker started.");
while (!workers_terminate)
{
switch(_workerState)
{
case WorkerState::Idle:
// Wait for work.
pthread_mutex_lock(&_workerMutex);
while (!_newCommand.ready)
{
pthread_cond_wait(
&_workerWakeupCond,
&_workerMutex);
}
// Make a local copy of the new command
command = _newCommand;
pthread_mutex_unlock(&_workerMutex);
_workerState = WorkerState::Execute;
break;
case WorkerState::Execute:
{
switch(command.function)
{
case FunctionCode::ReadData:
{
uint16_t* buffer = nullptr;
if (Read(
command.word_count,
&buffer))
{
//
// Data read: do DMA transfer to memory.
//
_controller->DiskReadTransfer(
command.bus_address,
command.word_count,
buffer);
// Free buffer
delete buffer;
}
else
{
// Read failed:
DEBUG("Read failed.");
_ata = true;
}
_workerState = WorkerState::Finish;
}
break;
case FunctionCode::WriteData:
{
//
// Data write: do DMA transfer from memory.
//
uint16_t* buffer = _controller->DiskWriteTransfer(
command.bus_address,
command.word_count);
if (!buffer || !Write(
command.word_count,
buffer))
{
// Write failed:
DEBUG("Write failed.");
_ata = true;
}
delete buffer;
_workerState = WorkerState::Finish;
}
break;
case FunctionCode::Search:
{
if (!Search())
{
// Search failed
DEBUG("Search failed");
}
// Return to ready state, set attention bit.
_ata = true;
_workerState = WorkerState::Finish;
}
break;
case FunctionCode::Seek:
if (!SeekTo())
{
// Seek failed
DEBUG("Seek failed");
}
_ata = true;
_workerState = WorkerState::Finish;
break;
case FunctionCode::Offset:
case FunctionCode::ReturnToCenterline:
// We don't support adjusting the head position between
// cylinders so these are both effectively no-ops, but
// they're no-ops that need to take a small amount of time
// to complete.
DEBUG("OFFSET/RETURN TO CL");
timeout.wait_ms(10);
_ata = true;
_workerState = WorkerState::Finish;
break;
default:
FATAL("Unimplemented drive function %d", command.function);
break;
}
}
break;
case WorkerState::Finish:
_workerState = WorkerState::Idle;
// Drive is ready again.
_ready = true;
_pip = false;
pthread_mutex_lock(&_workerMutex);
_newCommand.ready = false;
pthread_mutex_unlock(&_workerMutex);
UpdateStatus(true, false);
break;
}
}
}
void
rp_drive_c::ForceDiagnosticError()
{
UpdateStatus(false, true);
}
//
// Register update functions
//
void
rp_drive_c::UpdateStatus(
bool complete,
bool diagForceError)
{
uint16_t error1 =
(_rmr ? 04 : 0) |
(_aoe ? 01000 : 0) |
(_iae ? 02000 : 0) |
(_wle ? 04000 : 0);
bool err = false;
if (error1 != 0)
{
err = true;
_ata = true;
}
else if (diagForceError)
{
_ata = true;
}
uint16_t status =
(_vv ? 0100 : 0) |
(_ready ? 0200 : 0) |
(0400) | // Drive preset -- always set for a single-controller disk
(_lst ? 02000 : 0) | // Last sector read
(IsWriteLocked() ? 04000 : 0) | // Write lock
(IsPackLoaded() ? 010000 : 0) | // Medium online
(_pip ? 020000 : 0) | // PIP
(err ? 040000 : 0) | // Composite error
(_ata ? 0100000 : 0);
INFO ("Unit %d error1: o%o", _driveNumber, error1);
INFO ("Unit %d Status: o%o", _driveNumber, status);
// Inform controller of status update.
_bus->DriveStatus(_driveNumber, status, error1, _ata, complete);
}
bool
rp_drive_c::SeekTo()
{
@@ -157,7 +527,7 @@ rp_drive_c::Write(
uint32_t offset = GetSectorForCHS(_currentCylinder, _desiredTrack, _desiredSector);
file_write(reinterpret_cast<uint8_t*>(buffer), offset * GetSectorSize(), countInWords * 2);
timeout_c timeout;
timeout.wait_us(500);
timeout.wait_us(2500);
return true;
}
@@ -195,7 +565,7 @@ rp_drive_c::Read(
DEBUG("Read from sector offset o%o", offset);
file_read(reinterpret_cast<uint8_t*>(*buffer), offset * GetSectorSize(), countInWords * 2);
timeout_c timeout;
timeout.wait_us(500);
timeout.wait_us(2500);
return true;
}
@@ -218,7 +588,6 @@ rp_drive_c::Search(void)
DEBUG("Search commencing.");
timeout.wait_ms(20);
_pip = false;
DEBUG("Search completed.");
_currentCylinder = _desiredCylinder;

View File

@@ -13,6 +13,34 @@
#include <memory> // unique_ptr
#include "parameter.hpp"
#include "storagedrive.hpp"
#include "massbus_rp.hpp"
#include "rh11.hpp"
// Control Function codes
#define RP_GO 01
#define RP_FUNC 076
enum class FunctionCode
{
Nop = 00,
Unload = 01,
Recalibrate = 03,
DriveClear = 04,
Release = 05,
Search = 014,
WriteCheckData = 024,
WriteCheckHeaderAndData = 025,
WriteData = 030,
WriteHeaderAndData = 031,
ReadData = 034,
ReadHeaderAndData = 035,
Seek = 02,
Offset = 06,
ReturnToCenterline = 07,
PackAcknowledge = 011,
ReadInPreset = 010,
};
//
// Implements the backing store for RP0X disk images
@@ -20,16 +48,16 @@
class rp_drive_c: public storagedrive_c
{
public:
rp_drive_c(storagecontroller_c *controller, uint32_t driveNumber);
rp_drive_c(rh11_c *controller, massbus_rp_c* bus, uint32_t driveNumber);
~rp_drive_c(void);
void Reset();
bool on_param_changed(parameter_c *param) override;
uint32_t GetSectorSize(void);
uint32_t GetType(void);
void Select();
void DoCommand(uint16_t command);
void ForceDiagnosticError();
// Register data reported via RH11
void SetDesiredCylinder(uint32_t cylinder) { _desiredCylinder = cylinder; }
void SetDesiredTrack(uint32_t track) { _desiredTrack = track; }
void SetDesiredSector(uint32_t sector) { _desiredSector = sector; }
@@ -39,41 +67,53 @@ public:
uint32_t GetDesiredSector(void) { return _desiredSector; }
uint16_t GetOffset(void) { return _offset; }
uint32_t GetCurrentCylinder(void) { return _currentCylinder; }
bool IsConnected(void) { return true; /* todo: make config. parameter */ }
bool IsPackLoaded(void);
bool IsDriveReady(void) { return _ready; }
bool IsWriteLocked(void) { return false; /* for now */ }
bool IsPositioningInProgress(void) { return _pip; }
void SetPositioningInProgress(void) { _pip = true; } // TODO: THIS IS A HACK
bool GetReadLastSector(void) { return _lst; }
bool GetAddressOverflow(void) { return _aoe; }
bool GetInvalidAddress(void) { return _iae; }
bool GetWriteLockError(void) { return _wle; }
void SetDriveReady(void) { _ready = true; }
void ClearDriveReady(void) { _ready = false; }
void SetVolumeValid(void) { _vv = true; }
void ClearVolumeValid(void) { _vv = false; }
bool GetVolumeValid(void) { return _vv; }
uint16_t GetDriveType(void) { return _driveInfo.TypeNumber; }
uint16_t GetSerialNumber(void) { return 012345; } // TODO: Make configurable parameter
uint32_t GetSectorSize(void);
bool SeekTo();
bool Write(uint32_t countInWords, uint16_t* buffer);
bool Read(uint32_t countInWords, uint16_t** outBuffer);
bool Search();
// Useful public status bits
bool IsConnected(void) { return true; /* todo: make config. parameter */ }
bool IsDriveReady(void) { return _ready; }
bool IsWriteLocked(void) { return false; /* also make config. parameter */ }
bool IsPackLoaded(void);
public:
// storagedrive_c methods
bool on_param_changed(parameter_c *param) override;
void on_power_changed(void) override;
void on_init_changed(void) override;
// background worker function
void worker(unsigned instance) override;
private:
struct WorkerCommand
{
volatile uint32_t bus_address;
volatile uint32_t word_count;
volatile FunctionCode function;
volatile bool ready;
} _newCommand;
enum WorkerState
{
Idle = 0,
Execute = 1,
Finish = 2,
} _workerState;
uint32_t _desiredCylinder;
uint32_t _desiredTrack;
uint32_t _desiredSector;
uint16_t _offset;
uint32_t _currentCylinder;
uint32_t _driveNumber;
bool _ata;
bool _ned;
bool _rmr;
bool _ready;
bool _lst;
bool _aoe;
@@ -82,14 +122,27 @@ private:
bool _pip;
bool _vv;
rh11_c* _controller;
massbus_rp_c* _bus;
// Worker mutex
pthread_cond_t _workerWakeupCond;
pthread_mutex_t _workerMutex;
bool SeekTo();
bool Write(uint32_t countInWords, uint16_t* buffer);
bool Read(uint32_t countInWords, uint16_t** outBuffer);
bool Search();
bool ValidateCHS(uint32_t cylinder, uint32_t track, uint32_t sector);
uint32_t GetSectorForCHS(uint32_t cylinder, uint32_t track, uint32_t sector);
uint32_t GetBlockSize();
bool SetDriveType(const char* typeName);
void UpdateCapacity();
void UpdateStatus(bool complete, bool diagForceError);
struct DriveInfo
{
char TypeName[16];