1
0
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:
Josh Dersch 2020-01-25 03:09:49 +01:00
parent a2c8d6f2bc
commit 311b5f48a7
9 changed files with 1214 additions and 198 deletions

View File

@ -17,6 +17,9 @@ class massbus_device_c
{
public:
// Resets the bus
virtual void Reset() = 0;
//
// MASSBUS Register Metadata:
//

View File

@ -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();
}

View File

@ -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

View File

@ -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;
};

View File

@ -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)

View File

@ -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:

View 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)
{
}

View 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 },
};
};

View File

@ -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 $@