mirror of
https://github.com/livingcomputermuseum/UniBone.git
synced 2026-01-13 07:19:30 +00:00
Further RH11/RP0x implementation. Boots 2.11BSD then falls over; code is currently a tangly mess.
Started cleanup of old code that got mangled by someone running "indent" over it. Ugh.
This commit is contained in:
parent
a2c8d6f2bc
commit
311b5f48a7
@ -17,6 +17,9 @@ class massbus_device_c
|
||||
{
|
||||
public:
|
||||
|
||||
// Resets the bus
|
||||
virtual void Reset() = 0;
|
||||
|
||||
//
|
||||
// MASSBUS Register Metadata:
|
||||
//
|
||||
|
||||
@ -10,19 +10,51 @@
|
||||
using namespace std;
|
||||
|
||||
#include "storagedrive.hpp"
|
||||
#include "rp_drive.hpp"
|
||||
#include "rh11.hpp"
|
||||
#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;
|
||||
}
|
||||
|
||||
massbus_rp_c::massbus_rp_c(
|
||||
rh11_c* controller) :
|
||||
device_c(),
|
||||
_controller(controller)
|
||||
_controller(controller),
|
||||
_selectedUnit(0),
|
||||
_desiredSector(0),
|
||||
_desiredTrack(0),
|
||||
_desiredCylinder(0),
|
||||
_workerState(WorkerState::Idle),
|
||||
_workerWakeupCond(PTHREAD_COND_INITIALIZER),
|
||||
_workerMutex(PTHREAD_MUTEX_INITIALIZER),
|
||||
_vv(false),
|
||||
_err(false),
|
||||
_ata(false),
|
||||
_ready(true),
|
||||
_ned(false)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
massbus_rp_c::~massbus_rp_c()
|
||||
@ -32,16 +64,16 @@ massbus_rp_c::~massbus_rp_c()
|
||||
|
||||
bool
|
||||
massbus_rp_c::ImplementsRegister(
|
||||
uint32_t register)
|
||||
uint32_t reg)
|
||||
{
|
||||
return false;
|
||||
return (reg > 0) && (reg < 16);
|
||||
}
|
||||
|
||||
std::string
|
||||
massbus_rp_c::RegisterName(
|
||||
uint32_t register)
|
||||
uint32_t reg)
|
||||
{
|
||||
std::string name("foo");
|
||||
std::string name(_registerMetadata[reg].Name);
|
||||
|
||||
return name;
|
||||
}
|
||||
@ -82,12 +114,29 @@ massbus_rp_c::WriteRegister(
|
||||
{
|
||||
INFO("RP reg write: unit %d register 0%o value 0%o", unit, reg, value);
|
||||
|
||||
switch(reg)
|
||||
switch(static_cast<Registers>(reg))
|
||||
{
|
||||
case 0:
|
||||
case Registers::Control:
|
||||
DoCommand(unit, value);
|
||||
break;
|
||||
|
||||
case Registers::DesiredSectorTrackAddress:
|
||||
_desiredTrack = (value & 0x1f00) >> 8;
|
||||
_desiredSector = (value & 0x1f);
|
||||
UpdateDesiredSectorTrack();
|
||||
|
||||
INFO("Desired Sector Track Address: track %d, sector %d",
|
||||
_desiredTrack,
|
||||
_desiredSector);
|
||||
break;
|
||||
|
||||
case Registers::DesiredCylinderAddress:
|
||||
_desiredCylinder = value & 0x3ff;
|
||||
UpdateDesiredCylinder();
|
||||
|
||||
INFO("Desired Cylinder Address o%o (o%o)", _desiredCylinder, value);
|
||||
break;
|
||||
|
||||
default:
|
||||
FATAL("Unimplemented RP register write.");
|
||||
break;
|
||||
@ -104,11 +153,25 @@ void massbus_rp_c::DoCommand(
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t function = (command & RP_FUNC) >> 1;
|
||||
FunctionCode function = static_cast<FunctionCode>((command & RP_FUNC) >> 1);
|
||||
_selectedUnit = unit;
|
||||
|
||||
INFO("RP function 0%o, unit %o", function, _selectedUnit);
|
||||
|
||||
INFO("RP function 0%o", function);
|
||||
if (!SelectedDrive()->IsAvailable())
|
||||
{
|
||||
// Early return for disconnected drives;
|
||||
// set NED and ERR bits
|
||||
_err = true;
|
||||
_ned = true; // TODO: should be done at RH11 level!
|
||||
_vv = false;
|
||||
UpdateStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
switch(function)
|
||||
_ned = false;
|
||||
|
||||
switch(static_cast<FunctionCode>(function))
|
||||
{
|
||||
case FunctionCode::Nop:
|
||||
// Nothing.
|
||||
@ -121,11 +184,60 @@ void massbus_rp_c::DoCommand(
|
||||
// sector/track address register, and clears the FMT, HCI, and ECI
|
||||
// bits in the offset register. It is used to bootstrap the device."
|
||||
//
|
||||
set_bit
|
||||
_vv = true;
|
||||
_desiredSector = 0;
|
||||
_desiredTrack = 0;
|
||||
_offset = 0;
|
||||
UpdateStatus();
|
||||
UpdateDesiredSectorTrack();
|
||||
UpdateOffset();
|
||||
break;
|
||||
|
||||
case FunctionCode::ReadData:
|
||||
INFO("RP Read Data");
|
||||
case FunctionCode::WriteData:
|
||||
case FunctionCode::WriteHeaderAndData:
|
||||
case FunctionCode::ReadHeaderAndData:
|
||||
case FunctionCode::Search:
|
||||
INFO("RP Read/Write Data or Search");
|
||||
|
||||
// Ensure the drive is ready, error if not
|
||||
if (!SelectedDrive()->IsDriveReady())
|
||||
{
|
||||
_vv = false;
|
||||
_err = true;
|
||||
_ata = true;
|
||||
_ready = true;
|
||||
UpdateStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear the unit's DRY bit
|
||||
SelectedDrive()->ClearDriveReady();
|
||||
|
||||
if (function == FunctionCode::Search)
|
||||
{
|
||||
SelectedDrive()->SetPositioningInProgress();
|
||||
}
|
||||
|
||||
// Clear ATA and READY
|
||||
_ata = false;
|
||||
_ready = false;
|
||||
UpdateStatus();
|
||||
|
||||
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:
|
||||
@ -147,12 +259,255 @@ massbus_rp_c::ReadRegister(
|
||||
return 0;
|
||||
}
|
||||
|
||||
// background worker function
|
||||
void
|
||||
massbus_rp_c::worker(
|
||||
unsigned instance)
|
||||
//
|
||||
// Register update functions
|
||||
//
|
||||
void
|
||||
massbus_rp_c::UpdateStatus()
|
||||
{
|
||||
|
||||
_error1 =
|
||||
(SelectedDrive()->GetAddressOverflow() ? 01000 : 0) |
|
||||
(SelectedDrive()->GetInvalidAddress() ? 02000 : 0) |
|
||||
(SelectedDrive()->GetWriteLockError() ? 04000 : 0);
|
||||
|
||||
if (_error1 != 0)
|
||||
{
|
||||
_err = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_err = false;
|
||||
}
|
||||
|
||||
_status =
|
||||
(_vv ? 0100 : 0) |
|
||||
(SelectedDrive()->IsDriveReady() ? 0200 : 0) |
|
||||
(0400) | // Drive preset -- always set for a single-controller disk
|
||||
(SelectedDrive()->GetReadLastSector() ? 02000 : 0) | // Last sector read
|
||||
(SelectedDrive()->IsWriteLocked() ? 04000 : 0) | // Write lock
|
||||
(SelectedDrive()->IsAvailable() ? 010000 : 0) | // Medium online
|
||||
(SelectedDrive()->IsPositioningInProgress() ? 020000 : 0) | // PIP
|
||||
(_err ? 040000 : 0) | // Composite error
|
||||
(_ata ? 0100000 : 0);
|
||||
|
||||
_controller->WriteRegister(static_cast<uint32_t>(Registers::Status), _status);
|
||||
_controller->WriteRegister(static_cast<uint32_t>(Registers::Error1), _error1);
|
||||
|
||||
// Inform controller of status update.
|
||||
_controller->BusStatus(_ready, _ata, _err, _ned);
|
||||
}
|
||||
|
||||
void
|
||||
massbus_rp_c::UpdateDesiredSectorTrack()
|
||||
{
|
||||
uint16_t desiredSectorTrack = (_desiredSector | (_desiredTrack << 8));
|
||||
_controller->WriteRegister(static_cast<uint32_t>(Registers::DesiredSectorTrackAddress), desiredSectorTrack);
|
||||
}
|
||||
|
||||
void
|
||||
massbus_rp_c::UpdateDesiredCylinder()
|
||||
{
|
||||
_controller->WriteRegister(static_cast<uint32_t>(Registers::DesiredCylinderAddress), _desiredCylinder);
|
||||
}
|
||||
|
||||
void
|
||||
massbus_rp_c::UpdateOffset()
|
||||
{
|
||||
_controller->WriteRegister(static_cast<uint32_t>(Registers::Offset), _offset);
|
||||
}
|
||||
|
||||
void
|
||||
massbus_rp_c::Reset()
|
||||
{
|
||||
// TODO: reset all drives
|
||||
|
||||
// Reset registers to their defaults
|
||||
_ata = false;
|
||||
_ready = true;
|
||||
UpdateStatus();
|
||||
|
||||
_desiredSector = 0;
|
||||
_desiredTrack = 0;
|
||||
UpdateDesiredSectorTrack();
|
||||
|
||||
_desiredCylinder = 0;
|
||||
UpdateDesiredCylinder();
|
||||
|
||||
_offset = 0;
|
||||
UpdateOffset();
|
||||
|
||||
_selectedUnit = 0;
|
||||
_newCommand.ready = false;
|
||||
|
||||
}
|
||||
|
||||
rp_drive_c*
|
||||
massbus_rp_c::SelectedDrive()
|
||||
{
|
||||
return _controller->GetDrive(_selectedUnit);
|
||||
}
|
||||
|
||||
// Background worker function
|
||||
void
|
||||
massbus_rp_c::Worker()
|
||||
{
|
||||
worker_init_realtime_priority(rt_device);
|
||||
|
||||
_workerState = WorkerState::Idle;
|
||||
WorkerCommand command = { 0 };
|
||||
|
||||
timeout_c timeout;
|
||||
|
||||
INFO("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;
|
||||
INFO("worker awoken.");
|
||||
break;
|
||||
|
||||
case WorkerState::Execute:
|
||||
switch(command.function)
|
||||
{
|
||||
case FunctionCode::ReadData:
|
||||
{
|
||||
INFO("READ CHS %d/%d/%d, %d words to address o%o",
|
||||
_desiredCylinder,
|
||||
_desiredTrack,
|
||||
_desiredSector,
|
||||
_newCommand.word_count,
|
||||
_newCommand.bus_address);
|
||||
|
||||
uint16_t* buffer = nullptr;
|
||||
if (SelectedDrive()->Read(
|
||||
_desiredCylinder,
|
||||
_desiredTrack,
|
||||
_desiredSector,
|
||||
_newCommand.word_count,
|
||||
&buffer))
|
||||
{
|
||||
//
|
||||
// Data read: do DMA transfer to memory.
|
||||
//
|
||||
_controller->DMAWrite(
|
||||
_newCommand.bus_address,
|
||||
_newCommand.word_count,
|
||||
buffer);
|
||||
|
||||
// Free buffer
|
||||
delete buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read failed:
|
||||
INFO("Read failed.");
|
||||
}
|
||||
|
||||
// Return drive to ready state
|
||||
SelectedDrive()->SetDriveReady();
|
||||
|
||||
// Signal attention and update status.
|
||||
_ata = true;
|
||||
_ready = true;
|
||||
UpdateStatus();
|
||||
_workerState = WorkerState::Finish;
|
||||
}
|
||||
break;
|
||||
|
||||
case FunctionCode::WriteData:
|
||||
{
|
||||
INFO("WRITE CHS %d/%d/%d, %d words from address o%o",
|
||||
_desiredCylinder,
|
||||
_desiredTrack,
|
||||
_desiredSector,
|
||||
_newCommand.word_count,
|
||||
_newCommand.bus_address);
|
||||
|
||||
|
||||
uint16_t* buffer = new uint16_t[_newCommand.word_count];
|
||||
assert(buffer);
|
||||
|
||||
if (!SelectedDrive()->Write(
|
||||
_desiredCylinder,
|
||||
_desiredTrack,
|
||||
_desiredSector,
|
||||
_newCommand.word_count,
|
||||
buffer))
|
||||
{
|
||||
// Write failed:
|
||||
INFO("Write failed.");
|
||||
}
|
||||
|
||||
delete buffer;
|
||||
|
||||
// Return drive to ready state
|
||||
SelectedDrive()->SetDriveReady();
|
||||
|
||||
// Signal attention and update status.
|
||||
_ata = true;
|
||||
_ready = true;
|
||||
UpdateStatus();
|
||||
_workerState = WorkerState::Finish;
|
||||
}
|
||||
break;
|
||||
|
||||
case FunctionCode::Search:
|
||||
{
|
||||
INFO("SEARCH CHS %d/%d/%d",
|
||||
_desiredCylinder,
|
||||
_desiredTrack,
|
||||
_desiredSector);
|
||||
|
||||
if (!SelectedDrive()->Search(
|
||||
_desiredCylinder,
|
||||
_desiredTrack,
|
||||
_desiredSector))
|
||||
{
|
||||
// Search failed
|
||||
INFO("Search failed");
|
||||
}
|
||||
|
||||
// Return to ready state
|
||||
SelectedDrive()->SetDriveReady();
|
||||
_ata = true;
|
||||
_ready = true;
|
||||
UpdateStatus();
|
||||
_workerState = WorkerState::Finish;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
FATAL("Unimplemented drive function %d", command.function);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WorkerState::Finish:
|
||||
_workerState = WorkerState::Idle;
|
||||
SelectedDrive()->SetDriveReady();
|
||||
UpdateStatus();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -164,5 +519,5 @@ massbus_rp_c::on_power_changed(void)
|
||||
void
|
||||
massbus_rp_c::on_init_changed(void)
|
||||
{
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ enum class Registers
|
||||
Error1 = 02,
|
||||
Maintenance = 03,
|
||||
AttentionSummary = 04,
|
||||
DesiredAddress = 05,
|
||||
DesiredSectorTrackAddress = 05,
|
||||
LookAhead = 07,
|
||||
DriveType = 06,
|
||||
SerialNo = 014,
|
||||
@ -67,6 +67,7 @@ enum class FunctionCode
|
||||
//
|
||||
struct rp_register_data
|
||||
{
|
||||
char Name[16];
|
||||
bool ActiveOnDATI;
|
||||
bool ActiveOnDATO;
|
||||
uint16_t ResetValue;
|
||||
@ -84,6 +85,8 @@ public:
|
||||
virtual ~massbus_rp_c();
|
||||
|
||||
public:
|
||||
void Reset();
|
||||
|
||||
bool ImplementsRegister(uint32_t register) override;
|
||||
std::string RegisterName(uint32_t register) override;
|
||||
|
||||
@ -96,37 +99,90 @@ public:
|
||||
void WriteRegister(uint32_t unit, uint32_t register, uint16_t value) override;
|
||||
uint16_t ReadRegister(uint32_t unit, uint32_t register) override;
|
||||
|
||||
// Background worker function
|
||||
void Worker();
|
||||
private:
|
||||
void DoCommand(uint32_t unit, uint16_t command);
|
||||
struct WorkerCommand
|
||||
{
|
||||
volatile uint32_t unit;
|
||||
volatile uint32_t bus_address;
|
||||
volatile uint32_t word_count;
|
||||
volatile FunctionCode function;
|
||||
volatile bool ready;
|
||||
} _newCommand;
|
||||
|
||||
// background worker function
|
||||
void worker(unsigned instance) override;
|
||||
enum WorkerState
|
||||
{
|
||||
Idle = 0,
|
||||
Execute = 1,
|
||||
Finish = 2,
|
||||
} _workerState;
|
||||
|
||||
void DoCommand(uint32_t unit, uint16_t command);
|
||||
|
||||
void on_power_changed(void) override;
|
||||
void on_init_changed(void) override;
|
||||
|
||||
void UpdateStatus();
|
||||
void UpdateDesiredSectorTrack();
|
||||
void UpdateDesiredCylinder();
|
||||
void UpdateOffset();
|
||||
|
||||
rp_drive_c* SelectedDrive();
|
||||
|
||||
private:
|
||||
rh11_c* _controller;
|
||||
|
||||
rp_register_data _registerMetadata[16] =
|
||||
{
|
||||
{ false, false, 0, 0 }, // 0, not used
|
||||
{ false, false, 0, 0177700 }, // Status
|
||||
{ false, true , 0, 0177777 }, // Error #1 - writable by diagnostics
|
||||
{ false, true , 0, 0177777 }, // Maintenance
|
||||
{ false, false, 0, 0 }, // Attention summary
|
||||
{ false, false, 0, 0017437 }, // Desired Sector/Track
|
||||
{ false, false, 0, 0 }, // Look Ahead
|
||||
{ false, false, 0, 0 }, // Drive Type
|
||||
{ false, false, 0, 0 }, // Serial Number
|
||||
{ false, false, 0, 0177777 }, // Offset
|
||||
{ false, false, 0, 0001777 }, // Desired Cylinder
|
||||
{ false, false, 0, 0 }, // Current Cylinder
|
||||
{ false, false, 0, 0 }, // Error #2
|
||||
{ false, false, 0, 0 }, // Error #3
|
||||
{ false, false, 0, 0 }, // ECC Position
|
||||
{ false, false, 0, 0 }, // ECC Pattern
|
||||
// Name DATI DATO
|
||||
{ "INV", false, false, 0, 0 }, // 0, not used
|
||||
{ "CS1", false, 0, 0177700 }, // Status
|
||||
{ "ER1", false, true , 0, 0177777 }, // Error #1 - writable by diagnostics
|
||||
{ "MR" , false, true , 0, 0177777 }, // Maintenance
|
||||
{ "ATN", false, false, 0, 0 }, // Attention summary
|
||||
{ "DA" , false, true, 0, 0017437 }, // Desired Sector/Track
|
||||
{ "LA" , false, false, 0, 0 }, // Look Ahead
|
||||
{ "DT" , false, false, 0, 0 }, // Drive Type
|
||||
{ "SN" , false, false, 0, 0 }, // Serial Number
|
||||
{ "OFF", false, false, 0, 0177777 }, // Offset
|
||||
{ "DCY", false, true, 0, 0001777 }, // Desired Cylinder
|
||||
{ "CCY", false, false, 0, 0 }, // Current Cylinder
|
||||
{ "ER2", false, false, 0, 0 }, // Error #2
|
||||
{ "ER3", false, false, 0, 0 }, // Error #3
|
||||
{ "EPO", false, false, 0, 0 }, // ECC Position
|
||||
{ "EPA", false, false, 0, 0 }, // ECC Pattern
|
||||
};
|
||||
|
||||
// Unit selected by last command register write
|
||||
uint32_t _selectedUnit;
|
||||
|
||||
// Register data
|
||||
uint16_t _status;
|
||||
uint16_t _error1;
|
||||
uint16_t _maint;
|
||||
uint16_t _attn;
|
||||
uint16_t _desiredSector;
|
||||
uint16_t _desiredTrack;
|
||||
uint16_t _offset;
|
||||
uint16_t _desiredCylinder;
|
||||
uint16_t _currentCylinder;
|
||||
uint16_t _error2;
|
||||
uint16_t _error3;
|
||||
|
||||
// Status bits that we track
|
||||
bool _vv;
|
||||
bool _err;
|
||||
bool _ata;
|
||||
|
||||
// RH11 ready signal (ugly: this should be in the rh11 code!)
|
||||
bool _ready;
|
||||
bool _ned; // ditto
|
||||
|
||||
// Worker thread
|
||||
pthread_t _workerThread;
|
||||
pthread_cond_t _workerWakeupCond;
|
||||
pthread_mutex_t _workerMutex;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <memory> // unique_ptr
|
||||
#include <memory>
|
||||
#include "parameter.hpp"
|
||||
#include "storagedrive.hpp"
|
||||
|
||||
@ -20,90 +20,96 @@
|
||||
class mscp_drive_c: public storagedrive_c
|
||||
{
|
||||
public:
|
||||
mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber);
|
||||
~mscp_drive_c(void);
|
||||
mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber);
|
||||
~mscp_drive_c(void);
|
||||
|
||||
bool on_param_changed(parameter_c *param) override;
|
||||
bool on_param_changed(parameter_c *param) override;
|
||||
|
||||
uint32_t GetBlockSize(void);
|
||||
uint32_t GetBlockCount(void);
|
||||
uint32_t GetRCTBlockCount(void);
|
||||
uint32_t GetMediaID(void);
|
||||
uint32_t GetDeviceNumber(void);
|
||||
uint16_t GetClassModel(void);
|
||||
uint16_t GetRCTSize(void);
|
||||
uint8_t GetRBNs(void);
|
||||
uint8_t GetRCTCopies(void);
|
||||
uint32_t GetBlockSize(void);
|
||||
uint32_t GetBlockCount(void);
|
||||
uint32_t GetRCTBlockCount(void);
|
||||
uint32_t GetMediaID(void);
|
||||
uint32_t GetDeviceNumber(void);
|
||||
uint16_t GetClassModel(void);
|
||||
uint16_t GetRCTSize(void);
|
||||
uint8_t GetRBNs(void);
|
||||
uint8_t GetRCTCopies(void);
|
||||
|
||||
void SetOnline(void);
|
||||
void SetOffline(void);bool IsOnline(void);bool IsAvailable(void);
|
||||
void SetOnline(void);
|
||||
void SetOffline(void);bool IsOnline(void);bool IsAvailable(void);
|
||||
|
||||
void Write(uint32_t blockNumber, size_t lengthInBytes, uint8_t* buffer);
|
||||
void Write(uint32_t blockNumber, size_t lengthInBytes, uint8_t* buffer);
|
||||
|
||||
uint8_t* Read(uint32_t blockNumber, size_t lengthInBytes);
|
||||
uint8_t* Read(uint32_t blockNumber, size_t lengthInBytes);
|
||||
|
||||
void WriteRCTBlock(uint32_t rctBlockNumber, uint8_t* buffer);
|
||||
void WriteRCTBlock(uint32_t rctBlockNumber, uint8_t* buffer);
|
||||
|
||||
uint8_t* ReadRCTBlock(uint32_t rctBlockNumber);
|
||||
uint8_t* ReadRCTBlock(uint32_t rctBlockNumber);
|
||||
|
||||
public:
|
||||
void on_power_changed(void) override;
|
||||
void on_init_changed(void) override;
|
||||
void on_power_changed(void) override;
|
||||
void on_init_changed(void) override;
|
||||
|
||||
public:
|
||||
parameter_bool_c use_image_size = parameter_bool_c(this, "useimagesize", "uis", false,
|
||||
"Determine unit size from image file instead of drive type");
|
||||
parameter_bool_c use_image_size = parameter_bool_c(this, "useimagesize", "uis", false,
|
||||
"Determine unit size from image file instead of drive type");
|
||||
|
||||
private:
|
||||
|
||||
struct DriveInfo
|
||||
struct DriveInfo
|
||||
{
|
||||
char TypeName[16];
|
||||
size_t BlockCount;
|
||||
uint32_t MediaID;
|
||||
uint8_t Model;
|
||||
uint16_t RCTSize;bool Removable;bool ReadOnly;
|
||||
char TypeName[16];
|
||||
size_t BlockCount;
|
||||
uint32_t MediaID;
|
||||
uint8_t Model;
|
||||
uint16_t RCTSize;bool Removable;bool ReadOnly;
|
||||
};
|
||||
|
||||
//
|
||||
// TODO: add a lot more drive types.
|
||||
// Does it make sense to support drive types not native to Unibus machines (SCSI types, for example?)
|
||||
// Need to add a ClassID table entry in that eventuality...
|
||||
// Also TODO: RCTSize parameters taken from SIMH rq source; how valid are these?
|
||||
DriveInfo g_driveTable[21] {
|
||||
// Name Blocks MediaID Model RCTSize Removable ReadOnly
|
||||
{ "RX50", 800, 0x25658032, 7, 0, true, false }, { "RX33", 2400, 0x25658021, 10, 0,
|
||||
true, false }, { "RD51", 21600, 0x25644033, 6, 36, false, false }, { "RD31",
|
||||
41560, 0x2564401f, 12, 3, false, false }, { "RC25", 50902, 0x20643019, 2, 0,
|
||||
true, false }, { "RC25F", 50902, 0x20643319, 3, 0, true, false }, { "RD52",
|
||||
60480, 0x25644034, 8, 4, false, false }, { "RD32", 83236, 0x25641047, 15, 4,
|
||||
false, false }, { "RD53", 138672, 0x25644035, 9, 5, false, false }, {
|
||||
"RA80", 237212, 0x20643019, 1, 0, false, false }, { "RD54", 311200,
|
||||
0x25644036, 13, 7, false, false }, { "RA60", 400176, 0x22a4103c, 4, 1008,
|
||||
true, false }, { "RA70", 547041, 0x20643019, 18, 198, false, false }, {
|
||||
"RA81", 891072, 0x25641051, 5, 2856, false, false }, { "RA82", 1216665,
|
||||
0x25641052, 11, 3420, false, false }, { "RA71", 1367310, 0x25641047, 40,
|
||||
1428, false, false },
|
||||
{ "RA72", 1953300, 0x25641048, 37, 2040, false, false }, { "RA90", 2376153,
|
||||
0x2564105a, 19, 1794, false, false }, { "RA92", 2940951, 0x2564105c, 29,
|
||||
949, false, false }, { "RA73", 3920490, 0x25641049, 47, 198, false, false },
|
||||
{ "", 0, 0, 0, 0, false, false } };
|
||||
//
|
||||
// TODO: add a lot more drive types.
|
||||
// Does it make sense to support drive types not native to Unibus machines (SCSI types, for example?)
|
||||
// Need to add a ClassID table entry in that eventuality...
|
||||
// Also TODO: RCTSize parameters taken from SIMH rq source; how valid are these?
|
||||
DriveInfo g_driveTable[21] {
|
||||
// Name Blocks MediaID Model RCTSize Removable ReadOnly
|
||||
{ "RX50", 800, 0x25658032, 7, 0, true, false },
|
||||
{ "RX33", 2400, 0x25658021, 10, 0, true, false },
|
||||
{ "RD51", 21600, 0x25644033, 6, 36, false, false },
|
||||
{ "RD31", 41560, 0x2564401f, 12, 3, false, false },
|
||||
{ "RC25", 50902, 0x20643019, 2, 0, true, false },
|
||||
{ "RC25F", 50902, 0x20643319, 3, 0, true, false },
|
||||
{ "RD52", 60480, 0x25644034, 8, 4, false, false },
|
||||
{ "RD32", 83236, 0x25641047, 15, 4, false, false },
|
||||
{ "RD53", 138672, 0x25644035, 9, 5, false, false },
|
||||
{ "RA80", 237212, 0x20643019, 1, 0, false, false },
|
||||
{ "RD54", 311200, 0x25644036, 13, 7, false, false },
|
||||
{ "RA60", 400176, 0x22a4103c, 4, 1008, true, false },
|
||||
{ "RA70", 547041, 0x20643019, 18, 198, false, false },
|
||||
{ "RA81", 891072, 0x25641051, 5, 2856, false, false },
|
||||
{ "RA82", 1216665, 0x25641052, 11, 3420, false, false },
|
||||
{ "RA71", 1367310, 0x25641047, 40, 1428, false, false },
|
||||
{ "RA72", 1953300, 0x25641048, 37, 2040, false, false },
|
||||
{ "RA90", 2376153, 0x2564105a, 19, 1794, false, false },
|
||||
{ "RA92", 2940951, 0x2564105c, 29, 949, false, false },
|
||||
{ "RA73", 3920490, 0x25641049, 47, 198, false, false },
|
||||
{ "", 0, 0, 0, 0, false, false }
|
||||
};
|
||||
|
||||
bool SetDriveType(const char* typeName);
|
||||
void UpdateCapacity(void);
|
||||
void UpdateMetadata(void);
|
||||
DriveInfo _driveInfo;bool _online;
|
||||
uint32_t _unitDeviceNumber;
|
||||
uint16_t _unitClassModel;
|
||||
bool _useImageSize;
|
||||
bool SetDriveType(const char* typeName);
|
||||
void UpdateCapacity(void);
|
||||
void UpdateMetadata(void);
|
||||
DriveInfo _driveInfo;bool _online;
|
||||
uint32_t _unitDeviceNumber;
|
||||
uint16_t _unitClassModel;
|
||||
bool _useImageSize;
|
||||
|
||||
//
|
||||
// RCT ("Replacement and Caching Table") data:
|
||||
// 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;
|
||||
//
|
||||
// RCT ("Replacement and Caching Table") data:
|
||||
// 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;
|
||||
};
|
||||
|
||||
@ -14,9 +14,84 @@
|
||||
#include "unibusadapter.hpp"
|
||||
#include "unibusdevice.hpp"
|
||||
#include "storagecontroller.hpp"
|
||||
#include "rp_drive.hpp"
|
||||
#include "rh11.hpp"
|
||||
#include "massbus_rp.hpp"
|
||||
|
||||
// Maps the unibus register index to the MASSBUS register number.
|
||||
// -1 entries are local to the rh11.
|
||||
int32_t _unibusToMassbusRegisterMap[] =
|
||||
{
|
||||
00, // 776700
|
||||
-1, // 776702
|
||||
-1, // 776704
|
||||
05, // 776706
|
||||
-1, // 776710
|
||||
01, // 776712
|
||||
02, // 776714
|
||||
04, // 776716
|
||||
07, // 776720
|
||||
-1, // 776722
|
||||
03, // 776724
|
||||
06, // 776726
|
||||
010, // 776730
|
||||
011, // 776732
|
||||
012, // 776734
|
||||
013, // 776736
|
||||
014, // 776740
|
||||
015, // 776742
|
||||
016, // etc.
|
||||
017,
|
||||
020,
|
||||
021,
|
||||
022,
|
||||
023,
|
||||
024,
|
||||
025,
|
||||
026,
|
||||
027,
|
||||
030,
|
||||
031,
|
||||
032,
|
||||
033
|
||||
};
|
||||
|
||||
int32_t _massbusToUnibusRegisterMap[] =
|
||||
{
|
||||
00, // 0
|
||||
05,
|
||||
06,
|
||||
012,
|
||||
07,
|
||||
03,
|
||||
013,
|
||||
010,
|
||||
014, // 10
|
||||
015,
|
||||
016,
|
||||
017,
|
||||
020,
|
||||
021,
|
||||
022,
|
||||
023,
|
||||
024, // 20
|
||||
025,
|
||||
026,
|
||||
027,
|
||||
030,
|
||||
031,
|
||||
032,
|
||||
033,
|
||||
034, // 30
|
||||
035,
|
||||
036,
|
||||
037,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
};
|
||||
|
||||
rh11_c::rh11_c() :
|
||||
storagecontroller_c(),
|
||||
_massbus(nullptr),
|
||||
@ -34,7 +109,6 @@ rh11_c::rh11_c() :
|
||||
// right now it is hardcoded for moving-head disks.
|
||||
set_default_bus_params(0776700, 11, 0254, 5);
|
||||
|
||||
// TODO: allow configuration for different device types
|
||||
_massbus.reset(new massbus_rp_c(this));
|
||||
|
||||
// The RH11 controller exposes up to 32 registers, not all are used
|
||||
@ -53,7 +127,7 @@ rh11_c::rh11_c() :
|
||||
strcpy(RH_reg[i]->name, "RHCS1");
|
||||
RH_reg[i]->active_on_dati = false;
|
||||
RH_reg[i]->active_on_dato = true;
|
||||
RH_reg[i]->reset_value = 0x0008;
|
||||
RH_reg[i]->reset_value = 0x0080;
|
||||
RH_reg[i]->writable_bits = 0x037f;
|
||||
break;
|
||||
|
||||
@ -61,7 +135,7 @@ rh11_c::rh11_c() :
|
||||
// Word count
|
||||
strcpy(RH_reg[i]->name, "RHWC");
|
||||
RH_reg[i]->active_on_dati = false;
|
||||
RH_reg[i]->active_on_dato = false;
|
||||
RH_reg[i]->active_on_dato = true;
|
||||
RH_reg[i]->reset_value = 0;
|
||||
RH_reg[i]->writable_bits = 0xffff;
|
||||
break;
|
||||
@ -70,7 +144,7 @@ rh11_c::rh11_c() :
|
||||
// Bus address
|
||||
strcpy(RH_reg[i]->name, "RHBA");
|
||||
RH_reg[i]->active_on_dati = false;
|
||||
RH_reg[i]->active_on_dato = false;
|
||||
RH_reg[i]->active_on_dato = true;
|
||||
RH_reg[i]->reset_value = 0;
|
||||
RH_reg[i]->writable_bits = 0xffff;
|
||||
break;
|
||||
@ -81,14 +155,14 @@ rh11_c::rh11_c() :
|
||||
RH_reg[i]->active_on_dati = false;
|
||||
RH_reg[i]->active_on_dato = true;
|
||||
RH_reg[i]->reset_value = 0;
|
||||
RH_reg[i]->writable_bits = 0x003f;
|
||||
RH_reg[i]->writable_bits = 0x223f;
|
||||
break;
|
||||
|
||||
case RHDB:
|
||||
// Data Buffer (maintenance only)
|
||||
strcpy(RH_reg[i]->name, "RHDB");
|
||||
RH_reg[i]->active_on_dati = true;
|
||||
RH_reg[i]->active_on_dato = true;
|
||||
RH_reg[i]->active_on_dati = false;
|
||||
RH_reg[i]->active_on_dato = false;
|
||||
RH_reg[i]->reset_value = 0;
|
||||
RH_reg[i]->writable_bits = 0xffff;
|
||||
break;
|
||||
@ -98,13 +172,14 @@ rh11_c::rh11_c() :
|
||||
// This is a "REMOTE" register implemented by the device(s)
|
||||
// attached to the massbus.
|
||||
//
|
||||
if (_massbus->ImplementsRegister(i))
|
||||
int32_t ma = _unibusToMassbusRegisterMap[i];
|
||||
if (ma >= 0 && _massbus->ImplementsRegister(ma))
|
||||
{
|
||||
strcpy(RH_reg[i]->name, _massbus->RegisterName(i).c_str());
|
||||
RH_reg[i]->active_on_dati = _massbus->RegisterActiveOnDATI(i);
|
||||
RH_reg[i]->active_on_dato = _massbus->RegisterActiveOnDATO(i);
|
||||
RH_reg[i]->reset_value = _massbus->RegisterResetValue(i);
|
||||
RH_reg[i]->writable_bits = _massbus->RegisterWritableBits(i);
|
||||
strcpy(RH_reg[i]->name, _massbus->RegisterName(ma).c_str());
|
||||
RH_reg[i]->active_on_dati = _massbus->RegisterActiveOnDATI(ma);
|
||||
RH_reg[i]->active_on_dato = _massbus->RegisterActiveOnDATO(ma);
|
||||
RH_reg[i]->reset_value = _massbus->RegisterResetValue(ma);
|
||||
RH_reg[i]->writable_bits = _massbus->RegisterWritableBits(ma);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -115,6 +190,22 @@ rh11_c::rh11_c() :
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// We can have up to eight attached drives.
|
||||
// Really drives should "belong" to massbus_c...
|
||||
// but that complicates configuration, etc.
|
||||
//
|
||||
drivecount = RH_DRIVE_COUNT;
|
||||
for (uint32_t i=0; i<drivecount; i++)
|
||||
{
|
||||
rp_drive_c *drive = new rp_drive_c(this, i);
|
||||
drive->unitno.value = i;
|
||||
drive->name.value = name.value + std::to_string(i);
|
||||
drive->log_label = drive->name.value;
|
||||
drive->parent = this;
|
||||
storagedrives.push_back(drive);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
rh11_c::~rh11_c()
|
||||
@ -122,7 +213,55 @@ rh11_c::~rh11_c()
|
||||
}
|
||||
|
||||
void
|
||||
rh11_c::write_register(
|
||||
rh11_c::BusStatus(
|
||||
bool ready,
|
||||
bool attention,
|
||||
bool error,
|
||||
bool ned)
|
||||
{
|
||||
INFO("Massbus status update attn %d, error %d, ned %d", attention, error, ned);
|
||||
|
||||
uint16_t currentStatus = get_register_dato_value(
|
||||
RH_reg[RHCS1]);
|
||||
|
||||
INFO("RHCS1 is currently o%o", currentStatus);
|
||||
|
||||
currentStatus &= ~(0140200); // move to constant
|
||||
currentStatus |=
|
||||
(attention ? 0100000 : 0) | // check when this actually gets set?
|
||||
(error ? 0040000 : 0) |
|
||||
(ready ? 0000200 : 0); // controller ready bit (should clear when busy)
|
||||
|
||||
set_register_dati_value(
|
||||
RH_reg[RHCS1],
|
||||
currentStatus,
|
||||
"BusStatus");
|
||||
|
||||
INFO("RHCS1 is now o%o", currentStatus);
|
||||
|
||||
currentStatus = get_register_dato_value(RH_reg[RHCS2]);
|
||||
|
||||
INFO("RHCS2 is currently o%o", currentStatus);
|
||||
|
||||
currentStatus &= ~(010000);
|
||||
currentStatus |=
|
||||
(ned ? 010000 : 0); // non-existent drive
|
||||
|
||||
set_register_dati_value(
|
||||
RH_reg[RHCS2],
|
||||
currentStatus,
|
||||
"BusStatus");
|
||||
|
||||
INFO("RHCS2 is now o%o", currentStatus);
|
||||
|
||||
if (attention)
|
||||
{
|
||||
Interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
rh11_c::WriteRegister(
|
||||
uint32_t reg,
|
||||
uint16_t value)
|
||||
{
|
||||
@ -133,13 +272,94 @@ rh11_c::write_register(
|
||||
}
|
||||
|
||||
uint16_t
|
||||
rh11_c::read_register(
|
||||
rh11_c::ReadRegister(
|
||||
uint32_t reg)
|
||||
{
|
||||
return get_register_dato_value(
|
||||
RH_reg[_massbusToUnibusRegisterMap[reg]]);
|
||||
}
|
||||
|
||||
rp_drive_c*
|
||||
rh11_c::GetDrive(
|
||||
uint32_t driveNumber)
|
||||
{
|
||||
assert (driveNumber < RH_DRIVE_COUNT);
|
||||
|
||||
return dynamic_cast<rp_drive_c*>(storagedrives[driveNumber]);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
rh11_c::GetBusAddress()
|
||||
{
|
||||
return _busAddress;
|
||||
}
|
||||
|
||||
uint16_t
|
||||
rh11_c::GetWordCount()
|
||||
{
|
||||
return -get_register_dato_value(RH_reg[RHWC]);
|
||||
}
|
||||
|
||||
bool
|
||||
rh11_c::DMAWrite(
|
||||
uint32_t address,
|
||||
size_t lengthInWords,
|
||||
uint16_t* buffer)
|
||||
{
|
||||
assert (address < 0x40000);
|
||||
|
||||
for(size_t i=0;i<lengthInWords;i++)
|
||||
{
|
||||
printf("o%o ", buffer[i]);
|
||||
}
|
||||
|
||||
INFO("DMA Write o%o, length %d", address, lengthInWords);
|
||||
unibusadapter->DMA(
|
||||
dma_request,
|
||||
true,
|
||||
UNIBUS_CONTROL_DATO,
|
||||
address,
|
||||
buffer,
|
||||
lengthInWords);
|
||||
|
||||
INFO("Success: %d", dma_request.success);
|
||||
|
||||
return dma_request.success;
|
||||
}
|
||||
|
||||
uint16_t*
|
||||
rh11_c::DMARead(
|
||||
uint32_t address,
|
||||
size_t lengthInWords)
|
||||
{
|
||||
assert (address < 0x40000);
|
||||
|
||||
INFO("DMA Read o%o, length %d", address, lengthInWords);
|
||||
|
||||
uint16_t* buffer = new uint16_t[lengthInWords];
|
||||
assert (buffer);
|
||||
|
||||
memset(reinterpret_cast<uint8_t*>(buffer), 0xc3, lengthInWords * 2);
|
||||
|
||||
unibusadapter->DMA(
|
||||
dma_request,
|
||||
true,
|
||||
UNIBUS_CONTROL_DATI,
|
||||
address,
|
||||
buffer,
|
||||
lengthInWords);
|
||||
|
||||
if (dma_request.success)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// return false, if illegal parameter value.
|
||||
// verify "new_value", must output error messages
|
||||
bool rh11_c::on_param_changed(parameter_c *param)
|
||||
@ -224,12 +444,41 @@ void rh11_c::on_after_register_access(
|
||||
_busAddressIncrementProhibit = !!(value & 0x8);
|
||||
_parityTest = !!(value & 0x10);
|
||||
|
||||
// TODO: handle System Register Clear (bit 5)
|
||||
// TODO: handle System Register Clear (bit 5)
|
||||
if (value & 040) // controller clear
|
||||
{
|
||||
// clear error bits
|
||||
value &= ~(010040);
|
||||
set_register_dati_value(
|
||||
RH_reg[RHCS2],
|
||||
value,
|
||||
"Reset");
|
||||
}
|
||||
INFO("RHCS2: unit %d", _unit);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RHWC: // Word count
|
||||
{
|
||||
if (UNIBUS_CONTROL_DATO == unibus_control)
|
||||
{
|
||||
INFO("RHWC: o%o", value);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case RHBA: // Bus address
|
||||
{
|
||||
if (UNIBUS_CONTROL_DATO == unibus_control)
|
||||
{
|
||||
_busAddress = (_busAddress & 0x30000) | value;
|
||||
INFO("RHBA: o%o", _busAddress);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// See if a massbus device wishes to answer
|
||||
if (RH_reg[device_reg->index] != nullptr)
|
||||
@ -246,13 +495,22 @@ void rh11_c::on_after_register_access(
|
||||
"on_after_register_access");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO("Unhandled register write o%o", device_reg->index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void rh11_c::invoke_interrupt(void)
|
||||
void rh11_c::Interrupt(void)
|
||||
{
|
||||
if(_interruptEnable)
|
||||
{
|
||||
INFO("Interrupt!");
|
||||
unibusadapter->INTR(intr_request, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void rh11_c::reset_controller(void)
|
||||
|
||||
@ -19,80 +19,9 @@ using namespace std;
|
||||
#include "unibusdevice.hpp"
|
||||
#include "storagecontroller.hpp"
|
||||
#include "massbus_device.hpp"
|
||||
#include "rp_drive.hpp"
|
||||
|
||||
// Maps the unibus register index to the MASSBUS register number.
|
||||
// -1 entries are local to the rh11.
|
||||
int32_t _unibusToMassbusRegisterMap[] =
|
||||
{
|
||||
00, // 776700
|
||||
-1, // 776702
|
||||
-1, // 776704
|
||||
05, // 776706
|
||||
-1, // 776710
|
||||
01, // 776712
|
||||
02, // 776714
|
||||
04, // 776716
|
||||
07, // 776720
|
||||
-1, // 776722
|
||||
03, // 776724
|
||||
06, // 776726
|
||||
010, // 776730
|
||||
011, // 776732
|
||||
012, // 776734
|
||||
013, // 776736
|
||||
014, // 776740
|
||||
015, // 776742
|
||||
016, // etc.
|
||||
017,
|
||||
020,
|
||||
021,
|
||||
022,
|
||||
023,
|
||||
024,
|
||||
025,
|
||||
026,
|
||||
027,
|
||||
030,
|
||||
031,
|
||||
032,
|
||||
033
|
||||
};
|
||||
|
||||
int32_t _massbusToUnibusRegisterMap[] =
|
||||
{
|
||||
00, // 0
|
||||
05,
|
||||
06,
|
||||
012,
|
||||
07,
|
||||
03,
|
||||
013,
|
||||
010,
|
||||
014, // 10
|
||||
015,
|
||||
016,
|
||||
017,
|
||||
020,
|
||||
021,
|
||||
022,
|
||||
023,
|
||||
024, // 20
|
||||
025,
|
||||
026,
|
||||
027,
|
||||
030,
|
||||
031,
|
||||
032,
|
||||
033,
|
||||
034, // 30
|
||||
035,
|
||||
036,
|
||||
037,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
};
|
||||
#define RH_DRIVE_COUNT 8
|
||||
|
||||
class rh11_c: public storagecontroller_c
|
||||
{
|
||||
@ -113,7 +42,7 @@ private:
|
||||
dma_request_c dma_request = dma_request_c(this); // operated by unibusadapter
|
||||
intr_request_c intr_request = intr_request_c(this);
|
||||
|
||||
void invoke_interrupt(void);
|
||||
void Interrupt(void);
|
||||
void reset_controller(void);
|
||||
|
||||
private:
|
||||
@ -134,9 +63,18 @@ public:
|
||||
rh11_c();
|
||||
virtual ~rh11_c();
|
||||
|
||||
void BusStatus(bool ready, bool attention, bool error, bool ned);
|
||||
|
||||
// Unibus register access (for devices on massbus)
|
||||
void write_register(uint32_t reg, uint16_t value);
|
||||
uint16_t read_register(uint32_t reg);
|
||||
void WriteRegister(uint32_t reg, uint16_t value);
|
||||
uint16_t ReadRegister(uint32_t reg);
|
||||
|
||||
rp_drive_c* GetDrive(uint32_t driveNumber);
|
||||
uint32_t GetBusAddress();
|
||||
uint16_t GetWordCount();
|
||||
|
||||
bool DMAWrite(uint32_t address, size_t lengthInWords, uint16_t* buffer);
|
||||
uint16_t* DMARead(uint32_t address, size_t lengthInWords);
|
||||
|
||||
public:
|
||||
|
||||
|
||||
305
10.02_devices/2_src/rp_drive.cpp
Normal file
305
10.02_devices/2_src/rp_drive.cpp
Normal file
@ -0,0 +1,305 @@
|
||||
/*
|
||||
rp_drive.cpp: Implementation of RP0X disks.
|
||||
|
||||
Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA.
|
||||
Contributed under the BSD 2-clause license.
|
||||
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#include "logger.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "rp_drive.hpp"
|
||||
|
||||
rp_drive_c::rp_drive_c(storagecontroller_c *controller, uint32_t driveNumber) :
|
||||
storagedrive_c(controller),
|
||||
_driveNumber(driveNumber),
|
||||
_ready(true),
|
||||
_lst(false),
|
||||
_aoe(false),
|
||||
_iae(false),
|
||||
_wle(false),
|
||||
_pip(false)
|
||||
{
|
||||
set_workers_count(0) ; // needs no worker()
|
||||
log_label = "RP";
|
||||
SetDriveType("RP06");
|
||||
}
|
||||
|
||||
rp_drive_c::~rp_drive_c()
|
||||
{
|
||||
if (file_is_open())
|
||||
{
|
||||
file_close();
|
||||
}
|
||||
}
|
||||
|
||||
// on_param_changed():
|
||||
// Handles configuration parameter changes.
|
||||
bool
|
||||
rp_drive_c::on_param_changed(parameter_c *param)
|
||||
{
|
||||
// no own "enable" logic
|
||||
if (&type_name == param)
|
||||
{
|
||||
return SetDriveType(type_name.new_value.c_str());
|
||||
}
|
||||
else if (&image_filepath == param)
|
||||
{
|
||||
// Try to open the image file.
|
||||
if (file_open(image_filepath.new_value, true))
|
||||
{
|
||||
image_filepath.value = image_filepath.new_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return device_c::on_param_changed(param); // more actions (for enable)false;
|
||||
}
|
||||
|
||||
//
|
||||
// GetBlockSize():
|
||||
// Returns the size, in bytes, of a single sector on this drive.
|
||||
// This is either 512 or 576 bytes.
|
||||
//
|
||||
uint32_t
|
||||
rp_drive_c::GetSectorSize()
|
||||
{
|
||||
//
|
||||
// For the time being this is always 512 bytes.
|
||||
//
|
||||
return 512;
|
||||
}
|
||||
|
||||
//
|
||||
// IsAvailable():
|
||||
// Indicates whether this drive is available (i.e. has an image
|
||||
// assigned to it and can thus be used by the controller.)
|
||||
//
|
||||
bool
|
||||
rp_drive_c::IsAvailable()
|
||||
{
|
||||
return file_is_open();
|
||||
}
|
||||
|
||||
bool
|
||||
rp_drive_c::SeekTo(
|
||||
uint32_t destinationCylinder)
|
||||
{
|
||||
// TODO: delay by appropriate amount
|
||||
|
||||
_iae = !(destinationCylinder < _driveInfo.Cylinders);
|
||||
|
||||
if (IsAvailable() && !_iae)
|
||||
{
|
||||
_currentCylinder = destinationCylinder;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false; // no good
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
rp_drive_c::GetCurrentCylinder()
|
||||
{
|
||||
return _currentCylinder;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// TODO: on all reads/writes, an implied seek takes place if the
|
||||
// specified cylinder is not the current cylinder.
|
||||
//
|
||||
|
||||
//
|
||||
// Writes the specified number of words from the provided buffer,
|
||||
// starting at the specified C/H/S address.
|
||||
//
|
||||
bool
|
||||
rp_drive_c::Write(
|
||||
uint32_t cylinder,
|
||||
uint32_t track,
|
||||
uint32_t sector,
|
||||
size_t countInWords,
|
||||
uint16_t* buffer)
|
||||
{
|
||||
_iae = !ValidateCHS(cylinder, track, sector);
|
||||
_wle = IsWriteLocked();
|
||||
|
||||
// TODO: handle address overflow
|
||||
|
||||
if (!IsAvailable() || _iae || _wle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t offset = GetSectorForCHS(cylinder, track, sector);
|
||||
file_write(reinterpret_cast<uint8_t*>(buffer), offset * GetSectorSize(), countInWords * 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Reads the specifed number of words starting at the specified logical
|
||||
// block. Returns a pointer to a buffer containing the data read.
|
||||
// Caller is responsible for freeing this buffer.
|
||||
//
|
||||
bool
|
||||
rp_drive_c::Read(
|
||||
uint32_t cylinder,
|
||||
uint32_t track,
|
||||
uint32_t sector,
|
||||
size_t countInWords,
|
||||
uint16_t** buffer)
|
||||
{
|
||||
|
||||
_iae = !ValidateCHS(cylinder, track, sector);
|
||||
_wle = false;
|
||||
|
||||
if (!IsAvailable() || _iae)
|
||||
{
|
||||
*buffer = nullptr;
|
||||
INFO("Failure: avail %d valid %d", IsAvailable(), ValidateCHS(cylinder, track, sector));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
*buffer = new uint16_t[countInWords];
|
||||
|
||||
assert(nullptr != *buffer);
|
||||
|
||||
uint32_t offset = GetSectorForCHS(cylinder, track, sector);
|
||||
INFO("Read from sector offset o%o", offset);
|
||||
file_read(reinterpret_cast<uint8_t*>(*buffer), offset * GetSectorSize(), countInWords * 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
rp_drive_c::Search(
|
||||
uint32_t cylinder,
|
||||
uint32_t track,
|
||||
uint32_t sector)
|
||||
{
|
||||
_iae = !ValidateCHS(cylinder, track, sector);
|
||||
|
||||
if (!IsAvailable() || _iae)
|
||||
{
|
||||
INFO("Failure: avail %d valid %d", IsAvailable(), ValidateCHS(cylinder, track, sector));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is just a no-op, as we don't emulate read errors. We just delay a tiny bit.
|
||||
timeout_c timeout;
|
||||
|
||||
INFO("Search commencing.");
|
||||
_pip = true;
|
||||
|
||||
timeout.wait_ms(50);
|
||||
_pip = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uint32_t
|
||||
rp_drive_c::GetSectorForCHS(
|
||||
uint32_t cylinder,
|
||||
uint32_t track,
|
||||
uint32_t sector)
|
||||
{
|
||||
return (cylinder * _driveInfo.Tracks * _driveInfo.Sectors) +
|
||||
(track * _driveInfo.Sectors) +
|
||||
sector;
|
||||
}
|
||||
|
||||
//
|
||||
// Updates the capacity parameter of the drive based on the block count and block size.
|
||||
//
|
||||
void
|
||||
rp_drive_c::UpdateCapacity()
|
||||
{
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
bool
|
||||
rp_drive_c::ValidateCHS(
|
||||
uint32_t cylinder,
|
||||
uint32_t track,
|
||||
uint32_t sector)
|
||||
{
|
||||
if (cylinder >= _driveInfo.Cylinders ||
|
||||
track >= _driveInfo.Tracks ||
|
||||
sector >= _driveInfo.Sectors)
|
||||
{
|
||||
return false; // Out of range
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// SetDriveType():
|
||||
// Updates this drive's type to the specified type (i.e.
|
||||
// RP05 or RM80).
|
||||
// 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
|
||||
rp_drive_c::SetDriveType(
|
||||
const char* typeName)
|
||||
{
|
||||
//
|
||||
// Search through drive data table for name,
|
||||
// and if valid, set the type appropriately.
|
||||
//
|
||||
int index = 0;
|
||||
while (g_driveTable[index].Cylinders != 0)
|
||||
{
|
||||
if (!strcasecmp(typeName, g_driveTable[index].TypeName))
|
||||
{
|
||||
_driveInfo = g_driveTable[index];
|
||||
type_name.value = _driveInfo.TypeName;
|
||||
UpdateCapacity();
|
||||
return true;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// Not found
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// on_power_changed():
|
||||
// Handle power change notifications.
|
||||
//
|
||||
void
|
||||
rp_drive_c::on_power_changed(void)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// on_init_changed():
|
||||
// Handle INIT signal.
|
||||
void
|
||||
rp_drive_c::on_init_changed(void)
|
||||
{
|
||||
}
|
||||
|
||||
91
10.02_devices/2_src/rp_drive.hpp
Normal file
91
10.02_devices/2_src/rp_drive.hpp
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
rp_drive.hpp: Implementation of RP0X drives, used with RH11 controller.
|
||||
|
||||
Copyright Vulcan Inc. 2020 via Living Computers: Museum + Labs, Seattle, WA.
|
||||
Contributed under the BSD 2-clause license.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <memory> // unique_ptr
|
||||
#include "parameter.hpp"
|
||||
#include "storagedrive.hpp"
|
||||
|
||||
//
|
||||
// Implements the backing store for RP0X disk images
|
||||
//
|
||||
class rp_drive_c: public storagedrive_c
|
||||
{
|
||||
public:
|
||||
rp_drive_c(storagecontroller_c *controller, uint32_t driveNumber);
|
||||
~rp_drive_c(void);
|
||||
|
||||
bool on_param_changed(parameter_c *param) override;
|
||||
|
||||
uint32_t GetSectorSize(void);
|
||||
uint32_t GetType(void);
|
||||
|
||||
bool IsAvailable(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; }
|
||||
|
||||
bool SeekTo(uint32_t cylinder);
|
||||
uint32_t GetCurrentCylinder();
|
||||
|
||||
bool Write(uint32_t cylinder, uint32_t track, uint32_t sector, uint32_t countInWords, uint16_t* buffer);
|
||||
bool Read(uint32_t cylinder, uint32_t track, uint32_t sector, uint32_t countInWords, uint16_t** outBuffer);
|
||||
bool Search(uint32_t cylinder, uint32_t track, uint32_t sector);
|
||||
|
||||
public:
|
||||
void on_power_changed(void) override;
|
||||
void on_init_changed(void) override;
|
||||
|
||||
private:
|
||||
uint32_t _currentCylinder;
|
||||
uint32_t _driveNumber;
|
||||
bool _ready;
|
||||
bool _lst;
|
||||
bool _aoe;
|
||||
bool _iae;
|
||||
bool _wle;
|
||||
bool _pip;
|
||||
|
||||
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();
|
||||
|
||||
struct DriveInfo
|
||||
{
|
||||
char TypeName[16];
|
||||
uint16_t TypeNumber;
|
||||
uint16_t Cylinders;
|
||||
uint16_t Tracks;
|
||||
uint16_t Sectors;
|
||||
} _driveInfo;
|
||||
|
||||
|
||||
DriveInfo g_driveTable[5]
|
||||
{
|
||||
// Name Type Cylinders Tracks Sectors
|
||||
{ "RP04", 020, 411, 19, 22 },
|
||||
{ "RP05", 021, 411, 19, 22 },
|
||||
{ "RP06", 022, 815, 19, 22 },
|
||||
{ "RP07", 023, 630, 32, 50 }, // TODO: verify
|
||||
{ "", 0, 0, 0, 0 },
|
||||
};
|
||||
};
|
||||
@ -106,6 +106,7 @@ OBJECTS = $(OBJDIR)/application.o \
|
||||
$(OBJDIR)/mscp_drive.o \
|
||||
$(OBJDIR)/rh11.o \
|
||||
$(OBJDIR)/massbus_rp.o \
|
||||
$(OBJDIR)/rp_drive.o \
|
||||
$(OBJDIR)/rs232.o \
|
||||
$(OBJDIR)/rs232adapter.o \
|
||||
$(OBJDIR)/dl11w.o \
|
||||
@ -250,6 +251,9 @@ $(OBJDIR)/massbus_device.o : $(DEVICE_SRC_DIR)/massbus_device.cpp $(DEVICE_SRC
|
||||
$(OBJDIR)/massbus_rp.o : $(DEVICE_SRC_DIR)/massbus_rp.cpp $(DEVICE_SRC_DIR)/massbus_rp.hpp
|
||||
$(CC) $(CCFLAGS) $< -o $@
|
||||
|
||||
$(OBJDIR)/rp_drive.o : $(DEVICE_SRC_DIR)/rp_drive.cpp $(DEVICE_SRC_DIR)/rp_drive.hpp
|
||||
$(CC) $(CCFLAGS) $< -o $@
|
||||
|
||||
$(OBJDIR)/rs232.o : $(DEVICE_SRC_DIR)/rs232.cpp $(DEVICE_SRC_DIR)/rs232.hpp
|
||||
$(CC) $(CCFLAGS) $< -o $@
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user