1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-01-11 23:52:51 +00:00
Josh Dersch e037b0d36d Added dato_mask to allow discerning DATOB operations to emulated registers.
This allows the RH11's RHCS1 register to function properly.  2.11BSD now boots!
2020-03-25 07:06:54 +01:00

1079 lines
37 KiB
C++
Executable File

/*
rk11_cpp: RK11 UNIBUS controller
Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA.
Contributed under the BSD 2-clause license.
*/
#include <string.h>
#include <assert.h>
#include <pthread.h>
#include "unibus.h"
#include "unibusadapter.hpp"
#include "unibusdevice.hpp"
#include "storagecontroller.hpp"
#include "rk11.hpp"
#include "rk05.hpp"
rk11_c::rk11_c() :
storagecontroller_c(),
_new_command_ready(false)
{
// static config
name.value = "rk";
type_name.value = "RK11";
log_label = "rk";
// base addr, intr-vector, intr level
set_default_bus_params(0777400, 10, 0220, 5) ;
// The RK11 controller has seven registers,
// We allocate 8 because one address in the address space is unused.
register_count = 8;
// Drive Status register (read-only)
RKDS_reg = &(this->registers[0]); // @ base addr
strcpy(RKDS_reg->name, "RKDS");
RKDS_reg->active_on_dati = false; // no controller state change
RKDS_reg->active_on_dato = false;
RKDS_reg->reset_value = 0;
RKDS_reg->writable_bits = 0x0000; // read only
// Error Register (read-only)
RKER_reg = &(this->registers[1]); // @ base addr + 2
strcpy(RKER_reg->name, "RKER");
RKER_reg->active_on_dati = false; // no controller state change
RKER_reg->active_on_dato = false;
RKER_reg->reset_value = 0;
RKER_reg->writable_bits = 0x0000; // read only
// Control Status Register (read/write)
RKCS_reg = &(this->registers[2]); // @ base addr + 4
strcpy(RKCS_reg->name, "RKCS");
RKCS_reg->active_on_dati = false;
RKCS_reg->active_on_dato = true;
RKCS_reg->reset_value = 0x0080; // RDY high after INIT
RKCS_reg->writable_bits = 0x0f7f;
// Word Count Register (read/write)
RKWC_reg = &(this->registers[3]); // @ base addr + 6
strcpy(RKWC_reg->name, "RKWC");
RKWC_reg->active_on_dati = false;
RKWC_reg->active_on_dato = false;
RKWC_reg->reset_value = 0;
RKWC_reg->writable_bits = 0xffff;
// Current Bus Address Register (read/write)
RKBA_reg = &(this->registers[4]); // @ base addr + 10
strcpy(RKBA_reg->name, "RKBA");
RKBA_reg->active_on_dati = false;
RKBA_reg->active_on_dato = false;
RKBA_reg->reset_value = 0;
RKBA_reg->writable_bits = 0xffff;
// Disk Address Register (write only?)
RKDA_reg = &(this->registers[5]); // @ base addr + 12
strcpy(RKDA_reg->name, "RKDA");
RKDA_reg->active_on_dati = false;
RKDA_reg->active_on_dato = true; // To allow writes only when controller is in READY state.
RKDA_reg->reset_value = 0;
RKDA_reg->writable_bits = 0xffff;
// Unused entry (@ Base addr + 14)
// Data Buffer Register (read only)
RKDB_reg = &(this->registers[7]); // @ base addr + 16
strcpy(RKDB_reg->name, "RKDB");
RKDB_reg->active_on_dati = false;
RKDB_reg->active_on_dato = false;
RKDB_reg->reset_value = 0;
RKDB_reg->writable_bits = 0x0000; // read only
_rkda_drive = 0;
//
// Drive configuration: up to eight drives.
//
drivecount = 8;
for (uint32_t i=0; i<drivecount; i++)
{
rk05_c *drive = new rk05_c(this);
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);
}
}
rk11_c::~rk11_c()
{
for (uint32_t i=0; i<drivecount; i++)
{
delete storagedrives[i];
}
}
// return false, if illegal parameter value.
// verify "new_value", must output error messages
bool rk11_c::on_param_changed(parameter_c *param) {
// no own parameter or "enable" logic
if (param == &priority_slot) {
dma_request.set_priority_slot(priority_slot.new_value);
intr_request.set_priority_slot(priority_slot.new_value);
} else if (param == &intr_level) {
intr_request.set_level(intr_level.new_value);
} else if (param == &intr_vector) {
intr_request.set_vector(intr_vector.new_value);
}
return storagecontroller_c::on_param_changed(param) ; // more actions (for enable)
}
void rk11_c::dma_transfer(DMARequest &request)
{
timeout_c timeout;
if (request.iba)
{
//
// If IBA is set for this transfer ("inhibit incrementing Bus Address") then what
// happens on the real machine is each word being transferred goes to the same
// address. We can simplify these operations without having to do weird things
// with DMA.
//
if (request.write)
{
// Write FROM buffer TO unibus memory, IBA on:
// We only need to write the last word in the buffer to memory.
unibusadapter->DMA(dma_request, true,
UNIBUS_CONTROL_DATO,
request.address,
request.buffer + request.count - 1,
1);
request.timeout = !dma_request.success ;
}
else
{
// Read FROM unibus memory TO buffer, IBA on:
// We read a single word from the unibus and fill the
// entire buffer with this value.
unibusadapter->DMA(dma_request, true,
UNIBUS_CONTROL_DATI,
request.address,
request.buffer,
1);
request.timeout = !dma_request.success ;
}
}
else
{
// Just a normal DMA transfer.
if (request.write)
{
// Write FROM buffer TO unibus memory
unibusadapter->DMA(dma_request, true,
UNIBUS_CONTROL_DATO,
request.address,
request.buffer,
request.count);
request.timeout = !dma_request.success ;
}
else
{
// Read FROM unibus memory TO buffer
unibusadapter->DMA(dma_request, true,
UNIBUS_CONTROL_DATI,
request.address,
request.buffer,
request.count);
request.timeout = !dma_request.success ;
}
}
// If an IBA DMA read from memory, we need to fill the request buffer
// with the single word returned from memory by the DMA operation.
if (request.iba && !request.write)
{
for(uint32_t i = 1; i < request.count; i++)
{
request.buffer[i] = request.buffer[0];
}
}
}
// Background worker.
// Handle device operations.
void rk11_c::worker(unsigned instance)
{
UNUSED(instance) ; // only one
worker_init_realtime_priority(rt_device);
_worker_state = Worker_Idle;
WorkerCommand command = { 0 };
bool do_interrupt = true;
timeout_c timeout;
while (!workers_terminate)
{
switch (_worker_state)
{
case Worker_Idle:
{
pthread_mutex_lock(&on_after_register_access_mutex);
while (!_new_command_ready)
{
pthread_cond_wait(
&on_after_register_access_cond,
&on_after_register_access_mutex);
}
//
// Make a local copy of the new command to execute.
//
command = _new_command;
_new_command_ready = false;
pthread_mutex_unlock(&on_after_register_access_mutex);
//
// Clear GO now that we've accepted the command.
//
_go = false;
update_RKCS();
//
// We will interrupt after completion of a command except
// in certain error cases.
//
do_interrupt = true;
//
// Move to the execution state to start executing the command.
//
_worker_state = Worker_Execute;
}
break;
case Worker_Execute:
switch(command.function)
{
case Write:
case Read:
case Write_Check:
{
bool write = command.function == Write;
bool read_format = command.function == Read && command.format;
bool read = command.function == Read && !command.format;
bool write_check = command.function == Write_Check;
// We loop over the requested address range
// and submit DMA requests one at a time, waiting for
// their completion.
uint16_t sectorBuffer[256];
uint16_t checkBuffer[256];
uint32_t current_address = command.address;
int16_t current_count = -(int16_t)(get_register_dato_value(RKWC_reg));
bool abort = false;
while(current_count > 0 && !abort)
{
// If a new command has been written in the CS register, abandon
// this one ASAP.
if (_new_command_ready)
{
DEBUG("Command canceled.");
abort = true;
continue;
}
// Validate that the current disk address is valid.
if (!validate_seek())
{
// The above check set error bits accordingly.
// Just abort the transfer now.
// Set OVR if we've gone off the end of the disk.
if (_rkda_cyl > 202)
{
_ovr = true;
// update_RKER();
}
abort = true;
continue;
}
//
// Clear the buffer. This is only necessary because short writes
// and reads expect the rest of the sector to be filled with zeroes.
//
memset(sectorBuffer, 0, sizeof(sectorBuffer));
if (read)
{
// Doing a normal read from disk: Grab the sector data and then
// DMA it into memory.
selected_drive()->read_sector(
_rkda_cyl,
_rkda_surface,
_rkda_sector,
sectorBuffer);
}
else if (read_format)
{
// Doing a Read Format: Read only the header word from the disk
// and DMA that single word into memory.
// We don't actually read the header word from disk since we don't
// store header data in the disk image; we just fake it up --
// since we always seek correctly this is all that is required.
//
// The header is just the cylinder address, as in RKDA 05-12 (p. 3-9)
sectorBuffer[0] = (_rkda_cyl << 5);
}
else if (write_check)
{
// Doing a Write Check: Grab the sector data from the disk into
// the check buffer.
selected_drive()->read_sector(
_rkda_cyl,
_rkda_surface,
_rkda_sector,
checkBuffer);
}
//
// Normal Read, or Write/Write Format: DMA the data to/from memory,
// etc.
//
// build the DMA transfer request:
DMARequest request = { 0 };
request.address = current_address;
request.count = (!read_format) ?
min(static_cast<int16_t>(256) , current_count) :
1;
request.write = !(write || write_check); // Inverted sense from disk action
request.timeout = false;
request.buffer = sectorBuffer;
request.iba = command.iba;
// And actually do the transfer.
dma_transfer(request);
// Check completion status -- if there was an error,
// we'll abort and set the appropriate flags.
if (request.timeout)
{
_nxm = true;
_he = true;
_err = true;
// update_RKCS();
// update_RKER();
abort = true;
}
else
{
if (write)
{
// Doing a write to disk: Write the buffer DMA'd from
// memory to the disk.
selected_drive()->write_sector(
_rkda_cyl,
_rkda_surface,
_rkda_sector,
sectorBuffer);
}
else if (write_check)
{
// Compare check buffer with sector buffer, if there are
// any discrepancies, set WCE and interrupt as necessary.
for (int i = 0; i < request.count; i++)
{
if (sectorBuffer[i] != checkBuffer[i])
{
_wce = true;
_err = true;
// update_RKER();
// update_RKCS();
if (_sse)
{
// Finish this transfer and abort.
abort = true;
break;
}
}
}
if (_wce && _sse)
{
// Control action stops on error after this transfer
// if SSE (Stop on Soft Error) is set.
abort = true;
}
}
else // Read
{
// After read complete, set RKDB to the last word
// read (this satisfies ZRKK):
set_register_dati_value(
RKDB_reg,
sectorBuffer[request.count - 1],
"RK11 READ");
}
}
// Transfer completed. Move to next and update registers.
current_count -= request.count;
set_register_dati_value(
RKWC_reg,
(uint16_t)(-current_count),
__func__);
if (!command.iba)
{
current_address += (request.count * 2); // Byte address
set_register_dati_value(
RKBA_reg,
(uint16_t)current_address,
__func__);
_mex = ((current_address) >> 16) & 0x3;
// update_RKCS();
}
// Move to next disk address
increment_RKDA();
// And go around, do it again.
}
// timeout.wait_us(100);
DEBUG("R/W: Complete.");
_worker_state = Worker_Finish;
}
break;
case Read_Check:
//
// "...is identical to a normal Read function, except that no
// NPRs occur. Only the checksum is calculated and compared
// with the checksum read from the disk drive... it must be
// performed on a whole-sector basis only."
// Since all data is error free in our emulation, the only
// thing we do here is increment RKDA the requisite number
// of times and clear RKWC.
//
{
uint32_t current_address = command.address;
int16_t current_count = -(int16_t)(get_register_dato_value(RKWC_reg));
// TODO: could do all this without going through the motions.
while (current_count > 0)
{
if (!validate_seek())
{
// TODO: set RKER, etc.
break;
}
current_count -= 256; // TODO: remove hardcoding.
if (!command.iba)
{
current_address += (256 * 2); // Byte address
set_register_dati_value(
RKWC_reg,
(uint16_t)(-current_count),
__func__);
_mex = ((current_address) >> 16) & 0x3;
// update_RKCS();
}
// Move to next disk address.
increment_RKDA();
// And go around, do it again.
}
_worker_state = Worker_Finish;
}
break;
case Seek:
//
// Per ZRKK, if IDE is set, an interrupt is raised at the beginning
// of the seek, and another is raised when the seek is complete.
//
if (validate_seek())
{
// Per ZRKK, ID bits in RKDS get cleared at the beginning of a seek.
_id = 0;
update_RKDS();
// Start the seek; this will complete asynchronously.
selected_drive()->seek(
_rkda_cyl);
}
else
{
do_interrupt = false;
}
_worker_state = Worker_Finish;
break;
case Drive_Reset:
if (check_drive_present())
{
selected_drive()->drive_reset();
}
else
{
do_interrupt = false;
}
_worker_state = Worker_Finish;
break;
default:
INFO("Unhandled function %d.", command.function);
// For now we just move to the Finish state so that future
// commands can execute.
_worker_state = Worker_Finish;
break;
}
break;
case Worker_Finish:
// Set RDY, interrupt (if enabled) and go back to the Idle state.
// We do this so that the update of ER, CS regs and the interrupt
// are atomic w.r.t. RKCS access (diagnostic code polls CS and will
// start a new operation immediately, lowering RDY before we invoke
// the interrupt, causing behavior diagnostics do not expect.)
pthread_mutex_lock(&on_after_register_access_mutex);
_rdy = true;
update_RKER();
update_RKCS();
// Interrupt unless specified not to.
if (do_interrupt)
{
assert(_rdy);
invoke_interrupt();
}
pthread_mutex_unlock(&on_after_register_access_mutex);
_worker_state = Worker_Idle;
break;
}
}
}
bool rk11_c::validate_seek(void)
{
bool error = false;
if (_rkda_sector > 11)
{
_nxs = true;
error = true;
}
if (_rkda_cyl > 202)
{
_nxc = true;
error = true;
}
if (!check_drive_present())
{
_nxd = true;
error = true;
}
if (error)
{
// Update the RKER register
// update_RKER();
// Set the Control/Status register HE and ERR registers.
_he = true;
_err = true;
// update_RKCS();
// Do not interrupt here, caller will do so based on
// our return value.
// invoke_interrupt();
}
return !error;
}
void rk11_c::increment_RKDA()
{
_rkda_sector++;
if (_rkda_sector > 11)
{
_rkda_sector = 0;
_rkda_surface++;
if (_rkda_surface > 1)
{
_rkda_surface = 0;
_rkda_cyl++;
}
}
update_RKDA();
}
//
// process DATI/DATO access to the RK11's "active" registers.
// !! called asynchronuously by PRU, with SSYN asserted and blocking UNIBUS.
// The time between PRU event and program flow into this callback
// is determined by ARM Linux context switch
//
// UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc:
// do not read back dati_flipflops.
void rk11_c::on_after_register_access(
unibusdevice_register_t *device_reg,
uint8_t unibus_control,
uint16_t dato_mask)
{
UNUSED(unibus_control);
UNUSED(dato_mask);
// The RK11 has only one "active" register, RKCS.
// When "GO" bit is set, kick off an operation and clear the
// RDY bit.
switch(device_reg->index)
{
case 2: // RKCS
_go = !!(RKCS_reg->active_dato_flipflops & 0x1);
_function = (RKCS_reg->active_dato_flipflops & 0xe) >> 1;
_mex = (RKCS_reg->active_dato_flipflops & 0x30) >> 4;
_ide = !!(RKCS_reg->active_dato_flipflops & 0x40);
_sse = !!(RKCS_reg->active_dato_flipflops & 0x100);
_exb = !!(RKCS_reg->active_dato_flipflops & 0x200);
_fmt = !!(RKCS_reg->active_dato_flipflops & 0x400);
_iba = !!(RKCS_reg->active_dato_flipflops & 0x800);
//
// GO bit is set:
// "Causes the control to carry out the function contained in bits 01 through 03 of the
// "RKCS (Function). Remains set until the control actually begins to respond to GO, which
// may take from 1us to 3.3ms, depending on the current operation of the selected disk drive..."
// TODO: what happens if RDY is low and GO is high?
if (_go)
{
bool error = false;
//
// Check for PGE (Programming Error): use of FMT with a function other than Read or Write.
//
if (_fmt && (_function != Read && _function != Write && _function != Control_Reset))
{
_pge = true;
_he = true;
_err = true;
// GO gets cleared
_go = false;
// update_RKER();
// update_RKCS();
// error = true;
}
else
{
switch(_function)
{
case Control_Reset:
//
// "The Control Reset function initializes all internal registers and flip-flops
// and clears all the bits of the seven programmable registers except RKCS 07 (RDY)
// which it sets, and RKDS 01 through 11, which are not affected.
//
reset_controller();
break;
case Write_Lock:
//
// "Write-protects a selected disk drive until the condition is overridden by the
// operation of the corresponding WT PROT switch on the disk drive..."
//
selected_drive()->set_write_protect(true);
_scp = false;
// update_RKCS();
break;
default:
// All other commands take significant time and happen on the worker thread.
// First check:
// If this is not a Drive Reset command and the drive is not ready or has taken
// a fault, we will abort, set the appropriate error bits and interrupt.
//
if (_function != Drive_Reset && !check_drive_ready())
{
_dre = true;
// update_RKER();
_he = true;
_err = true;
_scp = false;
_go = false;
// update_RKCS();
// INFO("setting DRE, fn %o dr rdy %d rws rdy %d", _function,
// selected_drive()->get_drive_ready(),
// selected_drive()->get_rws_ready());
// invoke_interrupt();
error = true;
break;
}
// If this is an attempt to address a nonexistent (not present or not
// loaded) drive, this triggers an NXD error.
if (!check_drive_present())
{
_nxd = true;
_he = true;
_err = true;
_scp = false;
_go = false;
// update_RKCS();
// update_RKER();
// invoke_interrupt();
error = true;
break;
}
// Clear RDY, SCP bits:
pthread_mutex_lock(&on_after_register_access_mutex);
_rdy = false;
_scp = false;
// update_RKCS();
//
// Stow command data for this operation so that if RKCS gets changed
// mid-op weird things won't happen.
//
_new_command.address = get_register_dato_value(RKBA_reg) | (_mex << 16);
_new_command.drive = selected_drive();
_new_command.function = _function;
_new_command.interrupt = _ide;
_new_command.stop_on_soft_error = _sse;
_new_command.format = _fmt;
_new_command.iba = _iba;
_new_command_ready = true;
//
// Wake the worker.
//
pthread_cond_signal(&on_after_register_access_cond);
pthread_mutex_unlock(&on_after_register_access_mutex);
break;
}
}
update_RKER();
update_RKCS();
if (error)
{
assert(_rdy);
invoke_interrupt();
}
}
else
{
// Stow the value in the register.
update_RKCS();
// If IDE is set (interrupt on done) and RDY is set, we will interrupt.
if (_rdy)
{
invoke_interrupt();
}
}
break;
case 5: // RKDA
//
// Writes are only accepted when RDY is high.
//
if (_rdy)
{
uint32_t oldDrive = _rkda_drive;
_rkda_sector = (RKDA_reg->active_dato_flipflops & 0xf);
_rkda_surface = (RKDA_reg->active_dato_flipflops & 0x10) >> 4;
_rkda_cyl = (RKDA_reg->active_dato_flipflops & 0x1fe0) >> 5;
_rkda_drive = (RKDA_reg->active_dato_flipflops & 0xe000) >> 13;
update_RKDA();
// Pick up new drive's status if drive ID changed.
if (_rkda_drive != oldDrive)
{
update_RKDS();
}
}
break;
default:
// This should never happen.
assert(false);
break;
}
}
bool rk11_c::check_drive_ready(void)
{
if (!selected_drive()->get_drive_ready() ||
selected_drive()->get_seek_incomplete() ||
selected_drive()->get_drive_unsafe() ||
selected_drive()->get_drive_power_low())
{
return false;
}
else
{
return true;
}
}
bool rk11_c::check_drive_present(void)
{
return (selected_drive()->get_drive_ready());
}
void rk11_c::on_drive_status_changed(storagedrive_c *drive)
{
// only update if drive reporting a change
// is the same as currently selected drive in RKDA.
if (drive->unitno.value == selected_drive()->unitno.value)
{
update_RKDS();
}
// If interrupts are enabled and the drive has completed a seek,
// interrupt now. Note that the call to get_search_complete() has
// the side effect (eww) of resetting the drive's SCP bit, so we do it
// first (so it always gets cleared even if we're not interrupting.)
if (dynamic_cast<rk05_c*>(drive)->get_search_complete() &&
_ide)
{
// Set SCP to indicate that this interrupt was due to a previous
// Seek or Drive Reset function.
_scp = true;
_id = drive->unitno.value;
update_RKDS();
update_RKCS();
invoke_interrupt();
}
}
void rk11_c::update_RKER(void)
{
unsigned short new_RKER =
(_wce ? 0x0001 : 0x0000) |
(_cse ? 0x0002 : 0x0000) |
(_nxs ? 0x0020 : 0x0000) |
(_nxc ? 0x0040 : 0x0000) |
(_nxd ? 0x0080 : 0x0000) |
(_te ? 0x0100 : 0x0000) |
(_dlt ? 0x0200 : 0x0000) |
(_nxm ? 0x0400 : 0x0000) |
(_pge ? 0x0800 : 0x0000) |
(_ske ? 0x1000 : 0x0000) |
(_wlo ? 0x2000 : 0x0000) |
(_ovr ? 0x4000 : 0x0000) |
(_dre ? 0x8000 : 0x0000);
set_register_dati_value(
RKER_reg,
new_RKER,
"update_RKER");
}
void rk11_c::update_RKCS(void)
{
unsigned short new_RKCS =
(_go ? 0x0001 : 0x0000) |
(_function << 1) |
(_mex << 4) |
(_ide ? 0x0040 : 0x0000) |
(_rdy ? 0x0080 : 0x0000) |
(_sse ? 0x0100 : 0x0000) |
(_exb ? 0x0200 : 0x0000) |
(_fmt ? 0x0400 : 0x0000) |
(_iba ? 0x0800 : 0x0000) |
(_scp ? 0x2000 : 0x0000) |
(_he ? 0x4000 : 0x0000) |
(_err ? 0x8000 : 0x0000);
set_register_dati_value(
RKCS_reg,
new_RKCS,
"update_RKCS");
}
void rk11_c::update_RKDS(void)
{
rk05_c* drive = selected_drive();
uint32_t sectorCounter = drive->get_sector_counter();
bool sceqsa = sectorCounter == _rkda_sector;
bool wps = drive->get_write_protect();
bool rwsrdy = drive->get_rws_ready();
bool dry = drive->get_drive_ready();
bool sok = drive->get_sector_counter_ok();
bool sin = drive->get_seek_incomplete();
bool dru = drive->get_drive_unsafe();
bool rk05 = drive->get_rk05_disk_online();
bool dpl = drive->get_drive_power_low();
unsigned short new_RKDS =
sectorCounter |
(sceqsa ? 0x0010 : 0x0000) |
(wps ? 0x0020 : 0x0000) |
(rwsrdy ? 0x0040 : 0x0000) |
(dry ? 0x0080 : 0x0000) |
(sok ? 0x0100 : 0x0000) |
(sin ? 0x0200 : 0x0000) |
(dru ? 0x0400 : 0x0000) |
(rk05 ? 0x0800 : 0x0000) |
(dpl ? 0x1000 : 0x0000) |
(_id << 13);
set_register_dati_value(
RKDS_reg,
new_RKDS,
"update_RKDS");
}
void rk11_c::update_RKDA(void)
{
unsigned short new_RKDA =
_rkda_sector |
(_rkda_surface << 4) |
(_rkda_cyl << 5) |
(_rkda_drive << 13);
set_register_dati_value(
RKDA_reg,
new_RKDA,
"update_RKDA");
}
void rk11_c::invoke_interrupt(void)
{
//
// Invoke an interrupt if the IDE bit of RKCS is set.
//
if (_ide)
{
unibusadapter->INTR(intr_request, NULL, 0); // todo: link to interupt register
}
}
void rk11_c::reset_controller(void)
{
// This will reset the DATI values to their defaults.
// We then need to reset our copy of the values to correspond.
reset_unibus_registers();
//
// Clear all RKER bits.
//
_wce = false;
_cse = false;
_nxs = false;
_nxc = false;
_nxd = false;
_te = false;
_dlt = false;
_nxm = false;
_pge = false;
_ske = false;
_wlo = false;
_ovr = false;
_dre = false;
update_RKER();
//
// Clear all RKCS bits except RDY.
//
_go = false;
_function = 0;
_mex = 0;
_ide = false;
_rdy = true;
_sse = false;
_exb = false;
_fmt = false;
_iba = false;
_scp = false;
_he = false;
_err = false;
update_RKCS();
//
// Clear all RKDA bits.
//
_rkda_sector = 0;
_rkda_surface = 0;
_rkda_cyl = 0;
_rkda_drive = 0;
update_RKDA();
//
// Update RKDS bits to match the newly selected drive (drive 0)
//
_id = 0;
update_RKDS();
//
// Clear RKWC
//
set_register_dati_value(
RKWC_reg,
0,
"reset_controller");
//
// Clear RKBA
//
set_register_dati_value(
RKBA_reg,
0,
"reset_controller");
}
void rk11_c::on_power_changed(void)
{
storagecontroller_c::on_power_changed();
if (power_down)
{
// power-on defaults
reset_controller();
}
}
// UNIBUS INIT: clear all registers
void rk11_c::on_init_changed(void)
{
// write all registers to "reset-values"
if (init_asserted)
{
reset_controller();
}
storagecontroller_c::on_init_changed();
}
rk05_c* rk11_c::selected_drive(void)
{
return dynamic_cast<rk05_c*>(storagedrives[_rkda_drive]);
}