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