1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-04-02 20:17:07 +00:00

Further cleanup, implementation of missing commands.

This commit is contained in:
Josh Dersch
2020-04-02 02:55:33 +02:00
parent 06b2a6ebc9
commit 01755df8c2
6 changed files with 192 additions and 94 deletions

View File

@@ -107,6 +107,7 @@ massbus_rp_c::SelectUnit(
// may cause status register updates.
SelectedDrive()->Select();
UpdateDriveRegisters();
UpdateDriveInfo();
}
}
@@ -122,9 +123,6 @@ massbus_rp_c::WriteRegister(
switch(static_cast<Registers>(reg))
{
case Registers::Control:
//
// Select unit as necessary.
//
drive->DoCommand(value);
break;
@@ -142,9 +140,16 @@ massbus_rp_c::WriteRegister(
case Registers::AttentionSummary:
// Clear bits in the Attention Summary register specified in the
// written value:
_attnSummary &= ~(value & 0xff);
_controller->WriteRegister(reg, _attnSummary);
DEBUG("Attention Summary write o%o, value is now o%o", value, _attnSummary);
for(int i=0;i<8;i++)
{
if (value & (0x1 << i))
{
GetDrive(i)->ClearAttention();
}
}
UpdateAttentionSummary();
INFO ("Attention Summary write o%o, value is now o%o", value, _attnSummary);
break;
case Registers::Error1:
@@ -167,19 +172,10 @@ uint16_t
massbus_rp_c::ReadRegister(
uint32_t reg)
{
DEBUG("RP reg read: unit %d register 0%o", _selectedUnit, reg);
INFO ("RP reg read: unit %d register 0%o", _selectedUnit, reg);
switch(static_cast<Registers>(reg))
{
case Registers::DriveType:
return SelectedDrive()->GetDriveType() | 020000; // Moving head (MOVE TO CONSTANT)
break;
case Registers::SerialNo:
return SelectedDrive()->GetSerialNumber();
break;
default:
FATAL("Unimplemented register read %o", reg);
break;
@@ -206,37 +202,54 @@ massbus_rp_c::DriveStatus(
{
_controller->WriteRegister(static_cast<uint32_t>(Registers::Status), status);
_controller->WriteRegister(static_cast<uint32_t>(Registers::Error1), error1);
UpdateDriveRegisters();
}
// Update the Attention Summary register for the reporting drive:
if (ata)
// if (ata) // TODO: be nice to avoid doing this every time.
{
_attnSummary |= (0x1 << unit);
UpdateAttentionSummary();
INFO ("Attention Summary is now o%o", _attnSummary);
}
_controller->WriteRegister(static_cast<uint32_t>(Registers::AttentionSummary), _attnSummary);
// Inform controller of status update.
// 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);
_controller->BusStatus(complete, ata, (error1 != 0), SelectedDrive()->IsConnected(), _ned);
pthread_mutex_unlock(&_updateLock);
}
void
massbus_rp_c::UpdateAttentionSummary()
{
//
// Collect summary bits from all drives
//
_attnSummary = 0;
for(int i=0;i<8;i++)
{
bool attn = GetDrive(i)->GetAttention();
if (attn)
{
_attnSummary |= (0x1 << i);
}
}
_controller->WriteRegister(static_cast<uint32_t>(Registers::AttentionSummary), _attnSummary);
}
void
massbus_rp_c::UpdateDriveInfo()
{
rp_drive_c* drive = SelectedDrive();
_controller->WriteRegister(static_cast<uint32_t>(Registers::DriveType),
drive->GetDriveType() | 020000);
_controller->WriteRegister(static_cast<uint32_t>(Registers::SerialNo),
drive->GetSerialNumber());
}
void
massbus_rp_c::UpdateDriveRegisters()
{

View File

@@ -92,9 +92,11 @@ public:
private:
void on_power_changed(void) override;
void on_init_changed(void) override;
void UpdateAttentionSummary();
void UpdateDriveInfo();
void UpdateDriveRegisters();
rp_drive_c* SelectedDrive();
rp_drive_c* GetDrive(uint16_t unit);
@@ -110,13 +112,13 @@ private:
{ "MR" , false, true, 0, 0177777 }, // 3, Maintenance
{ "ATN", false, true, 0, 0377 }, // 4, Attention summary
{ "DA" , false, true, 0, 0017437 }, // 5, Desired Sector/Track
{ "DT" , true, true, 0, 0 }, // 6, Drive Type
{ "DT" , false, false, 020022, 0 }, // 6, Drive Type
{ "LA" , false, false, 0, 0 }, // 7, Look Ahead
{ "ER2", false, false, 0, 0 }, // 10, Error #2
{ "OFF", false, false, 0, 0177777 }, // 11, Offset
{ "DCY", false, true, 0, 0001777 }, // 12, Desired Cylinder
{ "CCY", false, false, 0, 0 }, // 13, Current Cylinder
{ "SN" , true, true, 0, 0 }, // 14, Serial Number
{ "SN" , false, false, 012345, 0 }, // 14, Serial Number
{ "ER3", false, false, 0, 0 }, // 15, Error #3
{ "EPO", false, false, 0, 0 }, // 16, ECC Position
{ "EPA", false, false, 0, 0 }, // 17, ECC Pattern

View File

@@ -221,34 +221,48 @@ rh11_c::rh11_c() :
drive->parent = this;
storagedrives.push_back(drive);
}
_massbus->SelectUnit(0);
}
rh11_c::~rh11_c()
{
}
void
rh11_c::StartDataTransfer()
{
_ready = false;
UpdateCS1(false);
}
// TODO: RENAME! This is invoked when the drive is finished with the given command.
void
rh11_c::BusStatus(
bool completion,
bool ready,
bool attention,
bool error,
bool avail,
bool ned)
{
DEBUG("Massbus status update attn %d, error %d, ned %d", attention, error, ned);
INFO ("Massbus status update attn %d, error %d, ned %d", attention, error, ned);
_attention = attention;
_error = error;
_avail = avail;
_ready = ready;
bool ready = false;
if (completion)
{
// clear the GO bit from the CS word if the drive is finished.
_go = false;
_go = false;
// If the controller was busy due to a data transfer,
// on completion, we are ready again and may need to interrupt.
if (!_ready & completion)
{
_ready = ready = true;
}
}
UpdateCS1(false /* no interrupt, yet */);
@@ -280,7 +294,7 @@ rh11_c::UpdateCS1(bool interrupt)
(_function << 1) |
(_go ? 0000001 : 0);
DEBUG("RHCS1 is now o%o", newStatus);
INFO ("RHCS1 is now o%o", newStatus);
if (interrupt)
{
@@ -308,7 +322,7 @@ void rh11_c::UpdateCS2()
(_busAddressIncrementProhibit ? 0000010 : 0) |
(_unit);
DEBUG("RHCS2 is now o%o", newStatus);
INFO ("RHCS2 is now o%o", newStatus);
set_register_dati_value(
RH_reg[RHCS2],
@@ -566,7 +580,7 @@ void rh11_c::on_after_register_access(
// We thus update only the affected bits, and only send the function
// code to the Massbus when the LSB is written.
DEBUG("RHCS1: DATO o%o MASK o%o", value, dato_mask);
INFO ("RHCS1: DATO o%o MASK o%o", value, dato_mask);
if ((dato_mask & 0xff00) == 0xff00)
{
@@ -604,12 +618,12 @@ void rh11_c::on_after_register_access(
//
if (ready && _interruptEnable)
{
DEBUG("Forced interrupt.");
INFO ("Forced interrupt.");
unibusadapter->INTR(intr_request, nullptr, 0);
}
}
DEBUG("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go);
INFO ("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go);
UpdateCS1(false /* no interrupt */);
if ((dato_mask & 0x00ff) == 0x00ff)
@@ -631,8 +645,8 @@ void rh11_c::on_after_register_access(
_controllerClear = !!(value & 040);
_parityError = !!(value & 0020000);
DEBUG("RHCS2 write: o%o", value);
DEBUG("RHCS2: perror %d, unit %d inc %d ptest %d clear %d",
INFO ("RHCS2 write: o%o", value);
INFO ("RHCS2: perror %d, unit %d inc %d ptest %d clear %d",
_parityError, _unit, _busAddressIncrementProhibit, _parityTest, _controllerClear);
// On unit change, select new drive
@@ -645,7 +659,7 @@ void rh11_c::on_after_register_access(
// TODO: handle System Register Clear (bit 5)
if (_controllerClear)
{
DEBUG("Controller Clear");
INFO ("Controller Clear");
_interruptEnable = false;
for (uint32_t i=0; i<drivecount; i++)
@@ -659,7 +673,7 @@ void rh11_c::on_after_register_access(
_ned = false;
_nxm = false;
DEBUG("RHCS2: is now o%o", value);
INFO ("RHCS2: is now o%o", value);
_controllerClear = false;
}
@@ -672,7 +686,7 @@ void rh11_c::on_after_register_access(
{
if (UNIBUS_CONTROL_DATO == unibus_control)
{
DEBUG("RHWC: o%o", value);
INFO ("RHWC: o%o", value);
set_register_dati_value(
RH_reg[RHWC],
@@ -687,7 +701,7 @@ void rh11_c::on_after_register_access(
if (UNIBUS_CONTROL_DATO == unibus_control)
{
_busAddress = (_busAddress & 0x30000) | value;
DEBUG("RHBA: o%o", _busAddress);
INFO ("RHBA: o%o", _busAddress);
set_register_dati_value(
RH_reg[RHWC],

View File

@@ -87,7 +87,9 @@ public:
rh11_c();
virtual ~rh11_c();
void BusStatus(bool completion, bool ready, bool attention, bool error, bool avail, bool ned);
void StartDataTransfer();
void StopDataTransfer();
void BusStatus(bool completion, bool attention, bool error, bool avail, bool ned);
// Unibus register access (for devices on massbus)
void WriteRegister(uint32_t reg, uint16_t value);

View File

@@ -59,7 +59,16 @@ rp_drive_c::~rp_drive_c()
void
rp_drive_c::Reset()
{
INFO ("Drive %d reset", _driveNumber);
// TODO: how to deal with the worker thread.
// In the case of a Controller Clear, for example,
// it needs to terminate its work immediately, which may
// not be possible if a DMA transfer is in progress.
// If we can make the above happen, it's reasonable to block
// here until the worker has returned to idle.
// (Maybe?)
}
// on_param_changed():
@@ -137,12 +146,15 @@ void rp_drive_c::DoCommand(
INFO ("RP function 0%o, unit %o", function, _driveNumber);
if (!_ready)
if (!_ready && FunctionCode::DriveClear != function)
{
// 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);
}
// TODO: when Error Summary bit is set in ER1, the drive will accept no commands
// apart from a DRIVE CLEAR command.
_ned = false;
_ata = false;
@@ -162,7 +174,7 @@ void rp_drive_c::DoCommand(
// 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");
INFO ("RP Unload");
_ata = true;
UpdateStatus(true, false);
break;
@@ -185,9 +197,11 @@ void rp_drive_c::DoCommand(
break;
case FunctionCode::Release:
DEBUG("RP Release");
INFO ("RP Release");
// This is a no-op, this only applies to dual-ported configurations,
// which we are not.
_ata = false;
UpdateStatus(false, false);
break;
case FunctionCode::ReadInPreset:
@@ -206,7 +220,7 @@ void rp_drive_c::DoCommand(
break;
case FunctionCode::PackAcknowledge:
DEBUG("RP Pack Acknowledge");
INFO ("RP Pack Acknowledge");
_vv = true;
UpdateStatus(false, false);
break;
@@ -244,6 +258,13 @@ void rp_drive_c::DoCommand(
// Positioning in progress.
_pip = true;
}
else
{
// This is a data transfer command,
// let the controller know so it can clear its READY bit
// for the duration.
_controller->StartDataTransfer();
}
UpdateStatus(false, false);
@@ -266,6 +287,7 @@ void rp_drive_c::DoCommand(
// Wake the worker
pthread_cond_signal(&_workerWakeupCond);
pthread_mutex_unlock(&_workerMutex);
INFO ("Command queued for worker.");
}
break;
@@ -281,14 +303,14 @@ rp_drive_c::worker(unsigned instance)
{
UNUSED(instance);
worker_init_realtime_priority(rt_device);
// worker_init_realtime_priority(rt_device);
_workerState = WorkerState::Idle;
WorkerCommand command = { 0 };
timeout_c timeout;
INFO ("rp_drive worker started.");
INFO ("rp_drive %d worker started.", _driveNumber);
while (!workers_terminate)
{
switch(_workerState)
@@ -312,31 +334,69 @@ rp_drive_c::worker(unsigned instance)
case WorkerState::Execute:
{
INFO ("Worker executing function o%o", command.function);
switch(command.function)
{
case FunctionCode::ReadHeaderAndData:
case FunctionCode::ReadData:
{
uint16_t* buffer = nullptr;
if (Read(
command.word_count,
&buffer))
INFO ("Read wc %d", command.word_count);
if (FunctionCode::ReadHeaderAndData == command.function)
{
//
// Data read: do DMA transfer to memory.
//
// Per EK-RP056-MM-01 a READ HEADER AND DATA command
// is functionally identical to a READ DATA command
// except four extra words are sent prior to the sector data.
// These are:
// 1. Cylinder address and format bit:
// TODO: where is this format bit?
// 2. Sector/Track address:
// bits 0-4 specify sector, bits 8-12 indicate track.
// (i.e identical to the DA register format)
// 3, 4. Key Field - programmer defined, at format time.
// We return a header that matches the expected disk address.
uint16_t* header = new uint16_t[4];
assert(header);
header[0] = _desiredCylinder;
header[1] = _desiredSector | (_desiredTrack << 8);
header[2] = 0;
header[3] = 0;
_controller->DiskReadTransfer(
command.bus_address,
command.word_count,
buffer);
// Free buffer
delete buffer;
std::min(4, command.word_count),
header);
command.bus_address += 8;
command.word_count -= 4;
delete[] header;
}
else
// Any words left?
if (command.word_count > 0)
{
// Read failed:
DEBUG("Read failed.");
_ata = true;
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:
INFO ("Read failed.");
_ata = true;
}
}
_workerState = WorkerState::Finish;
@@ -357,11 +417,11 @@ rp_drive_c::worker(unsigned instance)
buffer))
{
// Write failed:
DEBUG("Write failed.");
INFO ("Write failed.");
_ata = true;
}
delete buffer;
delete[] buffer;
_workerState = WorkerState::Finish;
}
@@ -372,7 +432,7 @@ rp_drive_c::worker(unsigned instance)
if (!Search())
{
// Search failed
DEBUG("Search failed");
INFO ("Search failed");
}
// Return to ready state, set attention bit.
@@ -381,11 +441,16 @@ rp_drive_c::worker(unsigned instance)
}
break;
case FunctionCode::Recalibrate:
INFO ("RECALIBRATE");
// Treat a Recal as a seek to zero.
_desiredCylinder = 0;
// Fall through to seek.
case FunctionCode::Seek:
if (!SeekTo())
{
// Seek failed
DEBUG("Seek failed");
INFO ("Seek failed");
}
_ata = true;
_workerState = WorkerState::Finish;
@@ -397,7 +462,7 @@ rp_drive_c::worker(unsigned instance)
// 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");
INFO ("OFFSET/RETURN TO CL");
timeout.wait_ms(10);
_ata = true;
_workerState = WorkerState::Finish;
@@ -418,7 +483,7 @@ rp_drive_c::worker(unsigned instance)
pthread_mutex_lock(&_workerMutex);
_newCommand.ready = false;
pthread_mutex_unlock(&_workerMutex);
UpdateStatus(true, false);
UpdateStatus(true, false);
break;
}
@@ -550,7 +615,7 @@ rp_drive_c::Read(
if (!IsConnected() || !IsPackLoaded() || _iae)
{
*buffer = nullptr;
DEBUG("Failure: connected %d loaded %d valid %d", IsConnected(), IsPackLoaded(), _iae);
INFO ("Failure: connected %d loaded %d valid %d", IsConnected(), IsPackLoaded(), _iae);
return false;
}
else
@@ -562,7 +627,7 @@ rp_drive_c::Read(
assert(nullptr != *buffer);
uint32_t offset = GetSectorForCHS(_currentCylinder, _desiredTrack, _desiredSector);
DEBUG("Read from sector offset o%o", offset);
INFO ("Read from sector offset o%o", offset);
file_read(reinterpret_cast<uint8_t*>(*buffer), offset * GetSectorSize(), countInWords * 2);
timeout_c timeout;
timeout.wait_us(2500);
@@ -578,7 +643,7 @@ rp_drive_c::Search(void)
if (!IsConnected() || !IsPackLoaded() || _iae)
{
DEBUG("Failure: connected &d loaded %d valid %d", IsConnected(), IsPackLoaded(), _iae);
INFO ("Failure: connected &d loaded %d valid %d", IsConnected(), IsPackLoaded(), _iae);
return false;
}
else
@@ -586,9 +651,9 @@ rp_drive_c::Search(void)
// This is just a no-op, as we don't emulate read errors. We just delay a tiny bit.
timeout_c timeout;
DEBUG("Search commencing.");
INFO ("Search commencing.");
timeout.wait_ms(20);
DEBUG("Search completed.");
INFO ("Search completed.");
_currentCylinder = _desiredCylinder;
return true;

View File

@@ -62,13 +62,15 @@ public:
void SetDesiredTrack(uint32_t track) { _desiredTrack = track; }
void SetDesiredSector(uint32_t sector) { _desiredSector = sector; }
void SetOffset(uint16_t offset) { _offset = offset; }
void ClearAttention() { _ata = false; }
uint32_t GetDesiredCylinder(void) { return _desiredCylinder; }
uint32_t GetDesiredTrack(void) { return _desiredTrack; }
uint32_t GetDesiredSector(void) { return _desiredSector; }
uint16_t GetOffset(void) { return _offset; }
uint32_t GetCurrentCylinder(void) { return _currentCylinder; }
uint16_t GetDriveType(void) { return _driveInfo.TypeNumber; }
bool GetAttention() { return _ata; }
uint16_t GetDriveType(void) { return _driveInfo.TypeNumber; }
uint16_t GetSerialNumber(void) { return 012345; } // TODO: Make configurable parameter
uint32_t GetSectorSize(void);
@@ -91,9 +93,9 @@ private:
struct WorkerCommand
{
volatile uint32_t bus_address;
volatile uint32_t word_count;
volatile FunctionCode function;
uint32_t bus_address;
int32_t word_count;
FunctionCode function;
volatile bool ready;
} _newCommand;