1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-01-11 23:52:51 +00:00

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!
This commit is contained in:
Josh Dersch 2020-03-25 07:06:54 +01:00
parent 6bc7703e63
commit e037b0d36d
22 changed files with 1185 additions and 126 deletions

View File

@ -841,6 +841,7 @@ void unibusadapter_c::worker_deviceregister_event() {
uint16_t evt_data = mailbox->events.deviceregister.data;
unibusdevice_register_t *device_reg = &(device->registers[evt_idx]);
uint8_t unibus_control = mailbox->events.deviceregister.unibus_control;
uint16_t dato_mask = 0xffff;
/* call device event callback
@ -860,7 +861,7 @@ void unibusadapter_c::worker_deviceregister_event() {
// signal: changed by UNIBUS
device->log_register_event("DATI", device_reg);
device->on_after_register_access(device_reg, unibus_control);
device->on_after_register_access(device_reg, unibus_control, 0);
} else if (device_reg->active_on_dato && UNIBUS_CONTROL_IS_DATO(unibus_control)) {
// uint16_t reg_value_written = device_reg->shared_register->value;
// restore value accessible by DATI
@ -882,18 +883,24 @@ void unibusadapter_c::worker_deviceregister_event() {
evt_data &= device_reg->writable_bits; // clear unused bits
// save written value
if (evt_addr & 1) // odd address: bits 15:8 written
{
device_reg->active_dato_flipflops = (device_reg->active_dato_flipflops & 0x00ff)
| (evt_data & 0xff00);
dato_mask = 0xff00;
}
else
{
// even address : bits 7:0 written
device_reg->active_dato_flipflops = (device_reg->active_dato_flipflops & 0xff00)
| (evt_data & 0x00ff);
dato_mask = 0x00ff;
}
unibus_control = UNIBUS_CONTROL_DATO; // simulate 16 bit access
// signal: changed by UNIBUS
device->log_register_event("DATOB", device_reg);
break;
}
device->on_after_register_access(device_reg, unibus_control);
device->on_after_register_access(device_reg, unibus_control, dato_mask);
/*
DEBUG(LL_DEBUG, LC_UNIBUS, "dev.reg=%d.%d, %s, addr %06o, data %06o->%06o",
device_handle, evt_idx,

View File

@ -156,7 +156,7 @@ public:
// INTR/DMA as task to separate INTR/DMA scheduler
// -> orders INTR/DMA of different devices, wait for UNIBUS idle.
virtual void on_after_register_access(unibusdevice_register_t *device_reg,
uint8_t unibus_control) = 0;
uint8_t unibus_control, uint16_t dato_mask) = 0;
// communication between on_after_register_access() and device_c::worker()
// see pthread_cond_wait()* examples
pthread_cond_t on_after_register_access_cond = PTHREAD_COND_INITIALIZER;

View File

@ -260,7 +260,7 @@ void cpu_c::worker(unsigned instance) {
// UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc:
// do not read back dati_flipflops.
void cpu_c::on_after_register_access(unibusdevice_register_t *device_reg,
uint8_t unibus_control) {
uint8_t unibus_control, uint16_t dato_mask) {
// nothing todo
UNUSED(device_reg);
UNUSED(unibus_control);

View File

@ -67,7 +67,7 @@ public:
void worker(unsigned instance) override;
// called by unibusadapter on emulated register access
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask)
override;
void on_interrupt(uint16_t vector);

View File

@ -222,7 +222,7 @@ void demo_io_c::worker(unsigned instance) {
// UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc:
// do not read back dati_flipflops.
void demo_io_c::on_after_register_access(unibusdevice_register_t *device_reg,
uint8_t unibus_control) {
uint8_t unibus_control, uint16_t dato_mask) {
// nothing todo
UNUSED(device_reg);
UNUSED(unibus_control);

View File

@ -61,7 +61,7 @@ public:
void worker(unsigned instance) override;
// called by unibusadapter on emulated register access
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask)
override;
void on_power_changed(void) override;

View File

@ -264,8 +264,9 @@ void slu_c::eval_xbuf_dato_value(void) {
// UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc:
// do not read back dati_flipflops.
void slu_c::on_after_register_access(unibusdevice_register_t *device_reg,
uint8_t unibus_control) {
uint8_t unibus_control, uint16_t dato_mask) {
UNUSED(dato_mask);
// if (unibus_control == UNIBUS_CONTROL_DATO) // bus write
// set_register_dati_value(device_reg, device_reg->active_dato_flipflops, __func__);
@ -510,7 +511,7 @@ void ltc_c::set_lks_dati_value_and_INTR(bool do_intr) {
// process DATI/DATO access to one of my "active" registers
void ltc_c::on_after_register_access(unibusdevice_register_t *device_reg,
uint8_t unibus_control) {
uint8_t unibus_control, uint16_t dato_mask) {
pthread_mutex_lock(&on_after_register_access_mutex);
// not necessary, not harmful?
if (unibus_control == UNIBUS_CONTROL_DATO) // bus write

View File

@ -174,7 +174,7 @@ public:
void worker_xmt(void);
// called by unibusadapter on emulated register access
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask)
override;
bool on_param_changed(parameter_c *param) override; // must implement
@ -213,7 +213,7 @@ public:
void worker(unsigned instance) override;
// called by unibusadapter on emulated register access
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask)
override;
bool on_param_changed(parameter_c *param) override; // must implement

View File

@ -23,6 +23,14 @@ void* WorkerInit(
return nullptr;
}
void* SpinInit(
void* context)
{
massbus_rp_c* rp = reinterpret_cast<massbus_rp_c*>(context);
rp->Spin();
return nullptr;
}
massbus_rp_c::massbus_rp_c(
rh11_c* controller) :
device_c(),
@ -55,6 +63,16 @@ massbus_rp_c::massbus_rp_c(
&attribs,
&WorkerInit,
reinterpret_cast<void*>(this));
pthread_attr_t attribs2;
pthread_attr_init(&attribs2);
status = pthread_create(
&_spinThread,
&attribs2,
&SpinInit,
reinterpret_cast<void*>(this));
}
massbus_rp_c::~massbus_rp_c()
@ -112,7 +130,7 @@ massbus_rp_c::WriteRegister(
uint32_t reg,
uint16_t value)
{
INFO("RP reg write: unit %d register 0%o value 0%o", unit, reg, value);
DEBUG("RP reg write: unit %d register 0%o value 0%o", unit, reg, value);
if (!SelectedDrive()->IsDriveReady() &&
reg != 0 && // CS1 is allowed as long as GO isn't set (will be checked in DoCommand)
@ -120,14 +138,12 @@ massbus_rp_c::WriteRegister(
{
// Any attempt to modify a drive register other than Attention Summary
// while the drive is busy is invalid.
INFO("Register modification while drive busy.");
DEBUG("Register modification while drive busy.");
_rmr = true;
UpdateStatus(false, false);
return;
}
static uint32_t frup = 0;
switch(static_cast<Registers>(reg))
{
case Registers::Control:
@ -139,7 +155,7 @@ massbus_rp_c::WriteRegister(
_desiredSector = (value & 0x1f);
UpdateDesiredSectorTrack();
INFO("Desired Sector Track Address: track %d, sector %d",
DEBUG("Desired Sector Track Address: track %d, sector %d",
_desiredTrack,
_desiredSector);
break;
@ -148,7 +164,7 @@ massbus_rp_c::WriteRegister(
_desiredCylinder = value & 0x3ff;
UpdateDesiredCylinder();
INFO("Desired Cylinder Address o%o (o%o)", _desiredCylinder, value);
DEBUG("Desired Cylinder Address o%o (o%o)", _desiredCylinder, value);
break;
case Registers::AttentionSummary:
@ -156,7 +172,7 @@ massbus_rp_c::WriteRegister(
// written value:
_attnSummary &= ~(value & 0xff);
_controller->WriteRegister(reg, _attnSummary);
INFO("Attention Summary write o%o, value is now o%o", value, _attnSummary);
DEBUG("Attention Summary write o%o, value is now o%o", value, _attnSummary);
break;
case Registers::Error1:
@ -166,7 +182,7 @@ massbus_rp_c::WriteRegister(
//
// Based on diagnostic (ZRJGE0) behavior, writing ANY value here forces an error.
//
INFO("Error 1 Reg write o%o, value is now o%o", value, _error1);
DEBUG("Error 1 Reg write o%o, value is now o%o", value, _error1);
UpdateStatus(false, true); // Force composite error.
break;
@ -189,7 +205,7 @@ void massbus_rp_c::DoCommand(
FunctionCode function = static_cast<FunctionCode>((command & RP_FUNC) >> 1);
_selectedUnit = unit;
INFO("RP function 0%o, unit %o", function, _selectedUnit);
DEBUG("RP function 0%o, unit %o", function, _selectedUnit);
if (!SelectedDrive()->IsConnected())
{
@ -220,7 +236,7 @@ void massbus_rp_c::DoCommand(
break;
case FunctionCode::ReadInPreset:
INFO("RP Read-In Preset");
DEBUG("RP Read-In Preset");
//
// "This command sets the VV (volume valid) bit, clears the desired
// sector/track address register, and clears the FMT, HCI, and ECI
@ -241,7 +257,7 @@ void massbus_rp_c::DoCommand(
case FunctionCode::WriteHeaderAndData:
case FunctionCode::ReadHeaderAndData:
case FunctionCode::Search:
INFO("RP Read/Write Data or Search");
DEBUG("RP Read/Write Data or Search");
{
// Clear the unit's DRY bit
SelectedDrive()->ClearDriveReady();
@ -284,7 +300,7 @@ massbus_rp_c::ReadRegister(
uint32_t unit,
uint32_t reg)
{
INFO("*** RP reg read: unit %d register 0%o", unit, reg);
DEBUG("RP reg read: unit %d register 0%o", unit, reg);
switch(static_cast<Registers>(reg))
{
@ -297,11 +313,6 @@ massbus_rp_c::ReadRegister(
return SelectedDrive()->GetSerialNumber();
break;
case Registers::AttentionSummary:
INFO("attn: o%o", _attnSummary);
return _attnSummary;
break;
default:
FATAL("Unimplemented register read %o", reg);
break;
@ -351,7 +362,7 @@ massbus_rp_c::UpdateStatus(
(_err ? 040000 : 0) | // Composite error
(_ata ? 0100000 : 0);
INFO("Unit %d Status: o%o", _selectedUnit, _status);
DEBUG("Unit %d Status: o%o", _selectedUnit, _status);
_controller->WriteRegister(static_cast<uint32_t>(Registers::Status), _status);
_controller->WriteRegister(static_cast<uint32_t>(Registers::Error1), _error1);
@ -361,7 +372,7 @@ massbus_rp_c::UpdateStatus(
{
_attnSummary |= (0x1 << _selectedUnit); // TODO: these only get set, and are latched until
// manually cleared?
INFO("Attention Summary is now o%o", _attnSummary);
DEBUG("Attention Summary is now o%o", _attnSummary);
}
_controller->WriteRegister(static_cast<uint32_t>(Registers::AttentionSummary), _attnSummary);
@ -375,10 +386,6 @@ massbus_rp_c::UpdateDesiredSectorTrack()
{
uint16_t desiredSectorTrack = (_desiredSector | (_desiredTrack << 8));
_controller->WriteRegister(static_cast<uint32_t>(Registers::DesiredSectorTrackAddress), desiredSectorTrack);
// Fudge: We update the look-ahead sector value to be the last-requested sector - 1
uint16_t lookAhead = (((_desiredSector - 1) % 22) << 6);
_controller->WriteRegister(static_cast<uint32_t>(Registers::LookAhead), lookAhead);
}
void
@ -446,7 +453,7 @@ massbus_rp_c::Worker()
timeout_c timeout;
INFO("massbus worker started.");
DEBUG("massbus worker started.");
while (!workers_terminate)
{
switch(_workerState)
@ -474,7 +481,7 @@ massbus_rp_c::Worker()
{
case FunctionCode::ReadData:
{
INFO("READ CHS %d/%d/%d, %d words to address o%o",
DEBUG("READ CHS %d/%d/%d, %d words to address o%o",
_newCommand.cylinder,
_newCommand.track,
_newCommand.sector,
@ -503,7 +510,7 @@ massbus_rp_c::Worker()
else
{
// Read failed:
INFO("Read failed.");
DEBUG("Read failed.");
_ata = true;
}
@ -516,7 +523,7 @@ massbus_rp_c::Worker()
case FunctionCode::WriteData:
{
INFO("WRITE CHS %d/%d/%d, %d words from address o%o",
DEBUG("WRITE CHS %d/%d/%d, %d words from address o%o",
_newCommand.cylinder,
_newCommand.track,
_newCommand.sector,
@ -538,7 +545,7 @@ massbus_rp_c::Worker()
buffer))
{
// Write failed:
INFO("Write failed.");
DEBUG("Write failed.");
_ata = true;
}
@ -553,7 +560,7 @@ massbus_rp_c::Worker()
case FunctionCode::Search:
{
INFO("SEARCH CHS %d/%d/%d",
DEBUG("SEARCH CHS %d/%d/%d",
_newCommand.cylinder,
_newCommand.track,
_newCommand.sector);
@ -564,7 +571,7 @@ massbus_rp_c::Worker()
_newCommand.sector))
{
// Search failed
INFO("Search failed");
DEBUG("Search failed");
}
@ -593,6 +600,31 @@ massbus_rp_c::Worker()
}
}
void
massbus_rp_c::Spin(void)
{
//
// All this worker does is simulate the spinning of the disk by
// updating the LookAhead register periodically. In reality there'd be a
// different value for every drive but also in reality there'd be a gigantic
// washing-machine-sized drive spinning aluminum disks plated with rust, so...
//
uint16_t lookAhead = 0;
timeout_c timer;
timer.wait_ms(2500);
while(true)
{
timer.wait_ms(10);
// We update only the sector count portion of the register.
lookAhead = (lookAhead + 1) % 22;
_controller->WriteRegister(static_cast<uint32_t>(Registers::LookAhead), lookAhead << 6);
}
}
void
massbus_rp_c::on_power_changed(void)
{

View File

@ -105,8 +105,10 @@ 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
// Background worker functions
void Worker();
void Spin();
private:
struct WorkerCommand
{
@ -147,10 +149,10 @@ private:
{
// Name DATI DATO
{ "INV", false, false, 0, 0 }, // 0, not used
{ "CS1", false, true, 0, 0041577 }, // 1, Status
{ "CS1", false, true, 0, 0041777 }, // 1, Status
{ "ER1", false, true, 0, 0177777 }, // 2, Error #1 - writable by diagnostics
{ "MR" , false, true, 0, 0177777 }, // 3, Maintenance
{ "ATN", true, true, 0, 0377 }, // 4, Attention summary
{ "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
{ "LA" , false, false, 0, 0 }, // 7, Look Ahead
@ -192,6 +194,9 @@ private:
pthread_t _workerThread;
pthread_cond_t _workerWakeupCond;
pthread_mutex_t _workerMutex;
// Spin thread
pthread_t _spinThread;
};
#endif

View File

@ -144,7 +144,7 @@ rh11_c::rh11_c() :
RH_reg[i]->active_on_dati = false;
RH_reg[i]->active_on_dato = true;
RH_reg[i]->reset_value = 0000200;
RH_reg[i]->writable_bits = 041577;
RH_reg[i]->writable_bits = 041777;
break;
case RHWC:
@ -238,7 +238,7 @@ rh11_c::BusStatus(
bool avail,
bool ned)
{
INFO("Massbus status update attn %d, error %d, ned %d", attention, error, ned);
DEBUG("Massbus status update attn %d, error %d, ned %d", attention, error, ned);
_attention = attention;
_error = error;
@ -279,7 +279,7 @@ rh11_c::UpdateCS1()
(_function << 1) |
(_go ? 0000001 : 0);
INFO("RHCS1 is now o%o", newStatus);
DEBUG("RHCS1 is now o%o", newStatus);
set_register_dati_value(
RH_reg[RHCS1],
@ -300,7 +300,7 @@ void rh11_c::UpdateCS2()
(_busAddressIncrementProhibit ? 0000010 : 0) |
(_unit);
INFO("RHCS2 is now o%o", newStatus);
DEBUG("RHCS2 is now o%o", newStatus);
set_register_dati_value(
RH_reg[RHCS2],
@ -402,23 +402,14 @@ rh11_c::IncrementBusAddress(uint32_t delta)
{
_busAddress += delta;
uint16_t currentStatus = get_register_dato_value(RH_reg[RHCS1]);
// Clear extended address bits
currentStatus &= ~(0x300);
currentStatus |= ((_busAddress & 0x30000) >> 8);
set_register_dati_value(
RH_reg[RHCS1],
currentStatus,
"SetBusAddress");
UpdateCS1();
set_register_dati_value(
RH_reg[RHBA],
(_busAddress & 0xffff),
"IncrementBusAddress");
INFO("BA Reg incr: o%o", _busAddress);
DEBUG("BA Reg incr: o%o", _busAddress);
}
uint16_t
@ -440,7 +431,7 @@ rh11_c::DecrementWordCount(uint16_t delta)
"DecrementWordCount");
INFO("WC Reg decr: o%o", currentWordCount);
DEBUG("WC Reg decr: o%o", currentWordCount);
}
bool
@ -451,12 +442,7 @@ rh11_c::DMAWrite(
{
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);
DEBUG("DMA Write o%o, length %d", address, lengthInWords);
unibusadapter->DMA(
dma_request,
true,
@ -465,7 +451,7 @@ rh11_c::DMAWrite(
buffer,
lengthInWords);
INFO("Success: %d", dma_request.success);
DEBUG("Success: %d", dma_request.success);
assert(dma_request.success);
return dma_request.success;
@ -478,7 +464,7 @@ rh11_c::DMARead(
{
assert (address < 0x40000);
INFO("DMA Read o%o, length %d", address, lengthInWords);
DEBUG("DMA Read o%o, length %d", address, lengthInWords);
uint16_t* buffer = new uint16_t[lengthInWords];
assert (buffer);
@ -553,7 +539,8 @@ void rh11_c::worker(unsigned instance)
// do not read back dati_flipflops.
void rh11_c::on_after_register_access(
unibusdevice_register_t *device_reg,
uint8_t unibus_control)
uint8_t unibus_control,
uint16_t dato_mask)
{
uint16_t value = device_reg->active_dato_flipflops;
@ -563,35 +550,65 @@ void rh11_c::on_after_register_access(
{
if (UNIBUS_CONTROL_DATO == unibus_control)
{
INFO("RHCS1: DATO o%o", value);
_error = !!(value & 0040000);
_busAddress = (_busAddress & 0177777) | ((value & 01400) << 8);
_interruptEnable = !!(value & 0100);
_function = (value & 076) >> 1;
_go = (value & 01);
//
// Byte-oriented writes are allowed to RH11 registers; thus far
// CS1 is the only one where distinguishing DATO from DATOB actually
// makes any difference: if the MSB is written separately from the
// LSB and the GO bit is in play, unintended operations may ensue.
// We thus update only the affected bits, and only send the function
// code to the Massbus when the LSB is written.
// Bit 14 (Transfer Error) is writeable; the function of this is not documented
// In the handbook or engineering manuals. The schematics are eluding me as well.
// 2.11bsd writes this bit, as do diagnostics (and they expect it to be read back..)
// SIMH suggests this is used to clear error bits, so we'll do that here.
if (_error)
DEBUG("RHCS1: DATO o%o MASK o%o", value, dato_mask);
if ((dato_mask & 0xff00) == 0xff00)
{
_error = false;
_parityError = false;
_mcbParityError = false;
_ned = false;
_nxm = false;
// MSB
_error = !!(value & 0040000);
_busAddress = (_busAddress & 0177777) | ((value & 01400) << 8);
// Bit 14 (Transfer Error) is writeable; the function of this is not documented
// In the handbook or engineering manuals. The schematics are eluding me as well.
// 2.11bsd writes this bit.
// SIMH suggests this is used to clear error bits, so we'll do that here.
if (_error)
{
_error = false;
_parityError = false;
_mcbParityError = false;
_ned = false;
_nxm = false;
}
}
INFO("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go);
// Let the massbus device take a crack at the shared bits
_massbus->WriteRegister(_unit, RHCS1, value & 077);
if ((dato_mask & 0x00ff) == 0x00ff)
{
bool ready = !!(value & 0200);
_interruptEnable = !!(value & 0100);
_function = (value & 076) >> 1;
_go = (value & 01);
// TODO: Per RH11-AB_OptionDescr.pdf (p 4-17):
// "The program can force an interrupt by loading bits D06 H (IE) and D07 H (RDY) in the CS1
// register which direct sets the INTR flip-flop."
//
//
// Per RH11-AB_OptionDescr.pdf (p 4-17):
// "The program can force an interrupt by loading bits D06 H (IE) and D07 H (RDY) in the CS1
// register which direct sets the INTR flip-flop."
// 2.11bsd's autoconfig routines do this.
//
if (ready && _interruptEnable)
{
DEBUG("Forced interrupt.");
unibusadapter->INTR(intr_request, nullptr, 0);
}
}
DEBUG("RHCS1: IE %d BA o%o func o%o go %d", _interruptEnable, _busAddress, _function, _go);
UpdateCS1();
if ((dato_mask & 0x00ff) == 0x00ff)
{
// Let the massbus device take a crack at the shared bits.
_massbus->WriteRegister(_unit, RHCS1, value & 077);
}
}
}
break;
@ -606,14 +623,14 @@ void rh11_c::on_after_register_access(
_controllerClear = !!(value & 040);
_parityError = !!(value & 0020000);
INFO("RHCS2 write: o%o", value);
INFO("RHCS2: perror %d, unit %d inc %d ptest %d clear %d",
DEBUG("RHCS2 write: o%o", value);
DEBUG("RHCS2: perror %d, unit %d inc %d ptest %d clear %d",
_parityError, _unit, _busAddressIncrementProhibit, _parityTest, _controllerClear);
// TODO: handle System Register Clear (bit 5)
if (_controllerClear)
{
INFO("Controller Clear");
DEBUG("Controller Clear");
_interruptEnable = false;
_massbus->Reset();
@ -622,7 +639,7 @@ void rh11_c::on_after_register_access(
_ned = false;
_nxm = false;
INFO("RHCS2: is now o%o", value);
DEBUG("RHCS2: is now o%o", value);
_controllerClear = false;
}
@ -635,7 +652,7 @@ void rh11_c::on_after_register_access(
{
if (UNIBUS_CONTROL_DATO == unibus_control)
{
INFO("RHWC: o%o", value);
DEBUG("RHWC: o%o", value);
set_register_dati_value(
RH_reg[RHWC],
@ -650,7 +667,7 @@ void rh11_c::on_after_register_access(
if (UNIBUS_CONTROL_DATO == unibus_control)
{
_busAddress = (_busAddress & 0x30000) | value;
INFO("RHBA: o%o", _busAddress);
DEBUG("RHBA: o%o", _busAddress);
set_register_dati_value(
RH_reg[RHWC],
@ -671,7 +688,7 @@ void rh11_c::on_after_register_access(
}
else
{
INFO("massbus reg read %o", device_reg->index);
DEBUG("massbus reg read %o", device_reg->index);
set_register_dati_value(
device_reg,
_massbus->ReadRegister(_unit, _unibusToMassbusRegisterMap[device_reg->index]),
@ -680,7 +697,7 @@ void rh11_c::on_after_register_access(
}
else
{
INFO("Unhandled register write o%o", device_reg->index);
DEBUG("Unhandled register write o%o", device_reg->index);
}
break;
}
@ -691,13 +708,13 @@ void rh11_c::Interrupt(void)
{
if(_interruptEnable)
{
INFO("Interrupt!");
unibusadapter->INTR(intr_request, nullptr, 0);
DEBUG("Interrupt!");
// IE is cleared after the interrupt is raised
// Actual bit is cleared in BusStatus, this should be fixed.
// IE is cleared after the interrupt is raised (probably should be at the same time.)
_interruptEnable = false;
UpdateCS1();
unibusadapter->INTR(intr_request, nullptr, 0);
}
}

View File

@ -108,7 +108,8 @@ public:
// called by unibusadapter on emulated register access
void on_after_register_access(
unibusdevice_register_t *device_reg,
uint8_t unibus_control) override;
uint8_t unibus_control,
uint16_t dato_mask) override;
bool on_param_changed(parameter_c *param) override;
void on_power_changed(void) override;

View File

@ -636,9 +636,11 @@ void rk11_c::increment_RKDA()
// do not read back dati_flipflops.
void rk11_c::on_after_register_access(
unibusdevice_register_t *device_reg,
uint8_t unibus_control)
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

View File

@ -167,7 +167,8 @@ public:
// called by unibusadapter on emulated register access
void on_after_register_access(
unibusdevice_register_t *device_reg,
uint8_t unibus_control) override;
uint8_t unibus_control,
uint16_t dato_mask) override;
bool on_param_changed(parameter_c *param) override;

View File

@ -345,7 +345,9 @@ void RL11_c::set_MP_dati_silo(const char *debug_info) {
// UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc:
// do not read back dati_flipflops.
void RL11_c::on_after_register_access(unibusdevice_register_t *device_reg,
uint8_t unibus_control) {
uint8_t unibus_control, uint16_t dato_mask) {
UNUSED(dato_mask);
// on drive select:
// move status of new drive to controller status register
// on command: signal worker thread

View File

@ -114,7 +114,7 @@ public:
// called by unibusadapter after DATI/DATO access to active emulated register
// Runs at 100% RT priority, UNIBUS is stopped by SSYN while this is running.
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask)
override;
void on_power_changed(void) override;

View File

@ -95,10 +95,14 @@ rp_drive_c::SeekTo(
{
// TODO: delay by appropriate amount
timeout_c timeout;
_iae = !(destinationCylinder < _driveInfo.Cylinders);
if (IsConnected() && IsPackLoaded() && !_iae)
{
timeout.wait_ms(50);
_currentCylinder = destinationCylinder;
return true;
}
@ -170,7 +174,7 @@ rp_drive_c::Read(
if (!IsConnected() || !IsPackLoaded() || _iae)
{
*buffer = nullptr;
INFO("Failure: connected %d loaded %d valid %d", IsConnected(), IsPackLoaded(), ValidateCHS(cylinder, track, sector));
DEBUG("Failure: connected %d loaded %d valid %d", IsConnected(), IsPackLoaded(), ValidateCHS(cylinder, track, sector));
return false;
}
else
@ -182,9 +186,8 @@ rp_drive_c::Read(
assert(nullptr != *buffer);
uint32_t offset = GetSectorForCHS(cylinder, track, sector);
INFO("Read from sector offset o%o", offset);
DEBUG("Read from sector offset o%o", offset);
file_read(reinterpret_cast<uint8_t*>(*buffer), offset * GetSectorSize(), countInWords * 2);
return true;
}
}
@ -199,7 +202,7 @@ rp_drive_c::Search(
if (!IsConnected() || !IsPackLoaded() || _iae)
{
INFO("Failure: connected &d loaded %d valid %d", IsConnected(), IsPackLoaded(), ValidateCHS(cylinder, track, sector));
DEBUG("Failure: connected &d loaded %d valid %d", IsConnected(), IsPackLoaded(), ValidateCHS(cylinder, track, sector));
return false;
}
else
@ -207,10 +210,10 @@ rp_drive_c::Search(
// 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.");
timeout.wait_ms(250);
DEBUG("Search commencing.");
timeout.wait_ms(20);
_pip = false;
INFO("Search completed.");
DEBUG("Search completed.");
_currentCylinder = cylinder;
return true;

View File

@ -160,8 +160,9 @@ bool testcontroller_c::on_param_changed(parameter_c *param) {
// UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc:
// do not read back dati_flipflops.
void testcontroller_c::on_after_register_access(unibusdevice_register_t *device_reg,
uint8_t unibus_control) {
uint8_t unibus_control, uint16_t dato_mask) {
UNUSED(dato_mask);
// emulate a plain memory cell: written values can be read unchanged
if (unibus_control == UNIBUS_CONTROL_DATI) {
}

View File

@ -61,7 +61,7 @@ public:
void worker(unsigned instance) override;
// called by unibusadapter on emulated register access
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control, uint16_t dato_mask)
override;
void on_power_changed(void) override;

View File

@ -296,9 +296,11 @@ void uda_c::worker(unsigned instance)
void
uda_c::on_after_register_access(
unibusdevice_register_t *device_reg,
uint8_t unibus_control
)
uint8_t unibus_control,
uint16_t dato_mask)
{
UNUSED(dato_mask);
switch (device_reg->index)
{
case 0: // IP - read / write

View File

@ -0,0 +1,984 @@
/*
uda.cpp: Implementation of the MSCP port (unibus interface).
Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA.
Contributed under the BSD 2-clause license.
This provides logic for the UDA50's SA and IP registers,
the four-step initialization handshake, DMA transfers to and
from the Unibus, and the command/response ring protocols.
While the name "UDA" is used here, this is not a strict emulation
of a real UDA50 -- it is a general MSCP implementation and can be
thought of as the equivalent of the third-party MSCP controllers
from Emulex, CMD, etc. that were available.
At this time this class acts as the port for an MSCP controller.
It would be trivial to extend this to TMSCP at a future date.
*/
#include <string.h>
#include <assert.h>
#include "unibus.h"
#include "unibusadapter.hpp"
#include "unibusdevice.hpp"
#include "storagecontroller.hpp"
#include "mscp_drive.hpp"
#include "uda.hpp"
uda_c::uda_c() :
storagecontroller_c(),
_server(nullptr),
_ringBase(0),
_commandRingLength(0),
_responseRingLength(0),
_commandRingPointer(0),
_responseRingPointer(0),
_interruptVector(0),
_interruptEnable(false),
_purgeInterruptEnable(false),
_step1Value(0),
_initStep(InitializationStep::Uninitialized),
_next_step(false)
{
name.value = "uda";
type_name.value = "UDA50";
log_label = "uda";
default_base_addr = 0772150;
default_intr_vector = 0154;
default_intr_level = 5;
// The UDA50 controller has two registers.
register_count = 2;
IP_reg = &(this->registers[0]); // @ base addr
strcpy(IP_reg->name, "IP");
IP_reg->active_on_dati = true;
IP_reg->active_on_dato = true;
IP_reg->reset_value = 0;
IP_reg->writable_bits = 0xffff;
SA_reg = &(this->registers[1]); // @ base addr + 2
strcpy(SA_reg->name, "SA");
SA_reg->active_on_dati = false;
SA_reg->active_on_dato = true;
SA_reg->reset_value = 0;
SA_reg->writable_bits = 0xffff;
_server.reset(new mscp_server(this));
//
// Initialize drives. We support up to eight attached drives.
//
drivecount = DRIVE_COUNT;
for (uint32_t i=0; i<drivecount; i++)
{
mscp_drive_c *drive = new mscp_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);
}
}
uda_c::~uda_c()
{
for(uint32_t i=0; i<drivecount; i++)
{
delete storagedrives[i];
}
storagedrives.clear();
}
//
// Reset():
// Resets the UDA controller state.
// Resets the attached MSCP server, which may take
// significant time.
//
void uda_c::Reset(void)
{
DEBUG("UDA reset");
_server->Reset();
_ringBase = 0;
_commandRingLength = 0;
_responseRingLength = 0;
_commandRingPointer = 0;
_responseRingPointer = 0;
_interruptVector = 0;
intr_vector.value = 0;
_interruptEnable = false;
_purgeInterruptEnable = false;
}
//
// GetDriveCount():
// Returns the number of drives that can be attached to this controller.
//
uint32_t uda_c::GetDriveCount(void)
{
return drivecount;
}
//
// GetDrive():
// Returns a pointer to an mscp_drive_c object for the specified drive number.
// This pointer is owned by the UDA class.
//
mscp_drive_c* uda_c::GetDrive(
uint32_t driveNumber)
{
assert(driveNumber < drivecount);
return dynamic_cast<mscp_drive_c*>(storagedrives[driveNumber]);
}
//
// StateTransition():
// Transitions the UDA initialization state machine to the specified step,
// atomically.
//
void uda_c::StateTransition(
InitializationStep nextStep)
{
pthread_mutex_lock(&on_after_register_access_mutex);
_initStep = nextStep;
_next_step = true;
pthread_cond_signal(&on_after_register_access_cond);
pthread_mutex_unlock(&on_after_register_access_mutex);
}
//
// worker():
// Implements the initialization state machine.
//
void uda_c::worker(void)
{
worker_init_realtime_priority(rt_device);
timeout_c timeout;
while (!worker_terminate)
{
//
// Wait to be awoken.
//
pthread_mutex_lock(&on_after_register_access_mutex);
while (!_next_step)
{
pthread_cond_wait(
&on_after_register_access_cond,
&on_after_register_access_mutex);
}
_next_step = false;
pthread_mutex_unlock(&on_after_register_access_mutex);
switch (_initStep)
{
case InitializationStep::Uninitialized:
INFO ("Transition to Init state Uninitialized.");
// SA should already be zero but we'll be extra sure here.
update_SA(0x0);
// Reset the controller: This may take some time as we must
// wait for the MSCP server to wrap up its current workitem.
Reset();
StateTransition(InitializationStep::Step1);
break;
case InitializationStep::Step1:
timeout.wait_ms(500);
INFO ("Transition to Init state S1.");
//
// S1 is set, all other bits zero. This indicates that we
// support a host-settable interrupt vector, that we do not
// implement enhanced diagnostics, and that no errors have
// occurred.
//
update_SA(0x0800);
break;
case InitializationStep::Step2:
timeout.wait_ms(1500);
INFO ("Transition to Init state S2.");
// Update the SA read value for step 2:
// S2 is set, unibus port type (0), SA bits 15-8 written
// by the host in step 1.
Interrupt();
update_SA(0x1000 | ((_step1Value >> 8) & 0xff));
//Interrupt();
break;
case InitializationStep::Step3:
timeout.wait_ms(500);
INFO ("Transition to Init state S3.");
// Update the SA read value for step 3:
// S3 set, plus SA bits 7-0 written by the host in step 1.
update_SA(0x2000 | (_step1Value & 0xff));
Interrupt();
break;
case InitializationStep::Step4:
timeout.wait_us(100);
// Clear communications area, set SA
INFO ("Clearing comm area at 0x%x. Purge header: %d", _ringBase, _purgeInterruptEnable);
INFO ("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength);
{
int headerSize = _purgeInterruptEnable ? 8 : 4;
for(uint32_t i = 0;
i < (_responseRingLength + _commandRingLength) * sizeof(Descriptor) + headerSize;
i += 2)
{
DMAWriteWord(_ringBase + i - headerSize, 0x0);
}
}
//
// Set the ownership bit on all descriptors in the response ring
// to indicate that the port owns them.
//
Descriptor blankDescriptor;
blankDescriptor.Word0.Word0 = 0;
blankDescriptor.Word1.Word1 = 0;
blankDescriptor.Word1.Fields.Ownership = 1;
for(uint32_t i = 0; i < _responseRingLength; i++)
{
DMAWrite(
GetResponseDescriptorAddress(i),
sizeof(Descriptor),
reinterpret_cast<uint8_t*>(&blankDescriptor));
}
INFO ("Transition to Init state S4, comm area initialized.");
// Update the SA read value for step 4:
// Bits 7-0 indicating our control microcode version.
update_SA(UDA50_ID); // UDA50 ID, makes RSTS happy
Interrupt();
break;
case InitializationStep::Complete:
INFO ("Initialization complete.");
break;
}
}
}
//
// on_after_register_access():
// Handles register accesses for the IP and SA registers.
//
void
uda_c::on_after_register_access(
unibusdevice_register_t *device_reg,
uint8_t unibus_control
)
{
switch (device_reg->index)
{
case 0: // IP - read / write
if (UNIBUS_CONTROL_DATO == unibus_control)
{
// "When written with any value, it causes a hard initialization
// of the port and the device controller."
DEBUG("Reset due to IP read");
StateTransition(InitializationStep::Uninitialized);
}
else
{
// "When read while the port is operating, it causes the controller
// to initiate polling..."
if (_initStep == InitializationStep::Complete)
{
DEBUG("Request to start polling.");
_server->InitPolling();
}
}
break;
case 1: // SA - write only
uint16_t value = SA_reg->active_dato_flipflops;
switch (_initStep)
{
case InitializationStep::Uninitialized:
// Should not occur, we treat it like step1 here.
DEBUG("Write to SA in Uninitialized state.");
case InitializationStep::Step1:
// Host writes the following:
// 15 13 11 10 8 7 6 0
// +-+-+-----+-----+-+-------------+
// |1|W|c rng|r rng|I| int vector |
// | |R| lng | lng |E|(address / 4)|
// +-+-+-----+-----+-+-------------+
// WR = 1 tells the port to enter diagnostic wrap
// mode (which we ignore).
//
// c rng lng is the number of slots (32 bits each)
// in the command ring, expressed as a power of two.
//
// r rng lng is as above, but for the response ring.
//
// IE=1 means the host is requesting an interrupt
// at the end of the completion of init steps 1-3.
//
// int vector determines if interrupts will be generated
// by the port. If this field is non-zero, interupts will
// be generated during normal operation and, if IE=1,
// during initialization.
_step1Value = value;
intr_vector.value = _interruptVector = ((value & 0x7f) << 2);
_interruptEnable = !!(value & 0x80);
_responseRingLength = (1 << ((value & 0x700) >> 8));
_commandRingLength = (1 << ((value & 0x3800) >> 11));
INFO ("Step1: 0x%x", value);
INFO ("resp ring 0x%x", _responseRingLength);
INFO ("cmd ring 0x%x", _commandRingLength);
INFO ("vector 0x%x", _interruptVector);
INFO ("ie %d", _interruptEnable);
// Move to step 2.
StateTransition(InitializationStep::Step2);
break;
case InitializationStep::Step2:
// Host writes the following:
// 15 1 0
// +-----------------------------+-+
// | ringbase low |P|
// | (address) |I|
// +-----------------------------+-+
// ringbase low is the low-order portion of word
// [ringbase+0] of the communications area. This is a
// 16-bit byte address whose low-order bit is zero implicitly.
//
_ringBase = value & 0xfffe;
_purgeInterruptEnable = !!(value & 0x1);
INFO ("Step2: rb 0x%x pi %d", _ringBase, _purgeInterruptEnable);
// Move to step 3 and interrupt as necessary.
StateTransition(InitializationStep::Step3);
break;
case InitializationStep::Step3:
// Host writes the following:
// 15 0
// +-+-----------------------------+
// |P| ringbase hi |
// |P| (address) |
// +-+-----------------------------+
// PP = 1 means the host is requesting execution of
// purge and poll tests, which we ignore because we can.
//
// ringbase hi is the high-order portion of the address
// [ringbase+0].
_ringBase |= ((value & 0x7fff) << 16);
INFO ("Step3: ringbase 0x%x", _ringBase);
// Move to step 4 and interrupt as necessary.
StateTransition(InitializationStep::Step4);
break;
case InitializationStep::Step4:
// Host writes the following:
// 15 8 7 1 0
// +---------------+-----------+-+-+
// | reserved | burst |L|G|
// | | |F|O|
// +---------------+-----------+-+-+
// Burst is one less than the max. number of longwords
// the host is willing to allow per DMA transfer.
// If zero, the port uses its default burst count.
//
// LF=1 means that the host wants a "last fail" response
// packet when initialization is complete.
//
// GO=1 means that the controller should enter its functional
// microcode as soon as initialization completes.
//
// Note that if GO=0 when initialization completes, the port
// will continue to read SA until the host forces SA bit 0 to
// make the transition 0->1.
//
// There is no explicit interrupt at the end of Step 4.
//
// TODO: For now we ignore burst settings.
// We also ignore Last Fail report requests since we aren't
// supporting onboard diagnostics and there's nothing to
// report.
//
INFO ("Step4: 0x%x", value);
if (value & 0x1)
{
//
// GO is set, move to the Complete state. The worker will
// start the controller running.
//
StateTransition(InitializationStep::Complete);
// The VMS bootstrap expects SA to be zero IMMEDIATELY
// after completion.
update_SA(0x0);
}
else
{
// GO unset, wait until it is.
}
break;
case InitializationStep::Complete:
// "When zeroed by the host during both initialization and normal
// operation, it signals the port that the host has successfully
// completed a bus adapter purge in response to a port-initiated
// purge request.
// We don't deal with bus adapter purges, yet.
break;
}
break;
}
}
//
// update_SA():
// Updates the SA register value exposed by the Unibone.
//
void
uda_c::update_SA(uint16_t value)
{
set_register_dati_value(
SA_reg,
value,
"update_SA");
}
//
// GetNextCommand():
// Attempts to pull the next command from the command ring, if any
// are available.
// If successful, returns a pointer to a Message struct; this pointer
// is owned by the caller.
// On failure, nullptr is returned. This indicates that the ring is
// empty or that an attempt to access non-existent memory occurred.
// TODO: Need to handle NXM cases properly.
//
Message*
uda_c::GetNextCommand(void)
{
timeout_c timer;
// Grab the next descriptor being pointed to
uint32_t descriptorAddress =
GetCommandDescriptorAddress(_commandRingPointer);
DEBUG("Next descriptor (ring ptr 0x%x) address is o%o",
_commandRingPointer,
descriptorAddress);
std::unique_ptr<Descriptor> cmdDescriptor(
reinterpret_cast<Descriptor*>(
DMARead(
descriptorAddress,
sizeof(Descriptor),
sizeof(Descriptor))));
// TODO: if NULL is returned after retry assume a bus error and handle it appropriately.
assert(cmdDescriptor != nullptr);
// Check owner bit: if set, ownership has been passed to us, in which case
// we can attempt to pull the actual message from memory.
if (cmdDescriptor->Word1.Fields.Ownership)
{
bool doInterrupt = false;
uint32_t messageAddress =
cmdDescriptor->Word0.EnvelopeLow |
(cmdDescriptor->Word1.Fields.EnvelopeHigh << 16);
DEBUG("Next message address is o%o, flag %d",
messageAddress, cmdDescriptor->Word1.Fields.Flag);
//
// Grab the message length; this is at messageAddress - 4
//
bool success = false;
uint16_t messageLength =
DMAReadWord(
messageAddress - 4,
success);
assert(messageLength > 0 && messageLength < MAX_MESSAGE_LENGTH);
std::unique_ptr<Message> cmdMessage(
reinterpret_cast<Message*>(
DMARead(
messageAddress - 4,
messageLength + 4,
sizeof(Message))));
//
// Handle Ring Transitions (from full to not-full) and associated
// interrupts.
// If the previous entry in the ring is owned by the Port then that indicates
// that the ring was previously full (i.e. the descriptor we're now returning
// is the first free entry.)
//
if (cmdDescriptor->Word1.Fields.Flag)
{
//
// Flag is set, host is requesting a transition interrupt.
// Check the previous entry in the ring.
//
if (_commandRingLength == 1)
{
// Degenerate case: If the ring is of size 1 we always interrupt.
doInterrupt = true;
}
else
{
uint32_t previousDescriptorAddress =
GetCommandDescriptorAddress(
(_commandRingPointer - 1) % _commandRingLength);
std::unique_ptr<Descriptor> previousDescriptor(
reinterpret_cast<Descriptor*>(
DMARead(
previousDescriptorAddress,
sizeof(Descriptor),
sizeof(Descriptor))));
if (previousDescriptor->Word1.Fields.Ownership)
{
// We own the previous descriptor, so the ring was previously
// full.
doInterrupt = true;
}
}
}
//
// Message retrieved; reset the Owner bit of the command descriptor,
// set the Flag bit (to indicate that we've processed it)
// and return a pointer to the message.
//
cmdDescriptor->Word1.Fields.Ownership = 0;
cmdDescriptor->Word1.Fields.Flag = 1;
DMAWrite(
descriptorAddress,
sizeof(Descriptor),
reinterpret_cast<uint8_t*>(cmdDescriptor.get()));
//
// Move to the next descriptor in the ring for next time.
_commandRingPointer = (_commandRingPointer + 1) % _commandRingLength;
// Post an interrupt as necessary.
if (doInterrupt)
{
//
// Set ring base - 4 to non-zero to indicate a transition.
//
DMAWriteWord(_ringBase - 4, 0x1);
Interrupt();
}
return cmdMessage.release();
}
DEBUG("No descriptor found. 0x%x 0x%x", cmdDescriptor->Word0.Word0, cmdDescriptor->Word1.Word1);
// No descriptor available.
return nullptr;
}
//
// PostResponse():
// Posts the provided Message to the response ring.
// Returns true on success, false otherwise.
// TODO: Need to handle NXM, as above.
//
bool
uda_c::PostResponse(
Message* response
)
{
bool res = false;
// Grab the next descriptor.
uint32_t descriptorAddress = GetResponseDescriptorAddress(_responseRingPointer);
std::unique_ptr<Descriptor> cmdDescriptor(
reinterpret_cast<Descriptor*>(
DMARead(
descriptorAddress,
sizeof(Descriptor),
sizeof(Descriptor))));
// TODO: if NULL is returned assume a bus error and handle it appropriately.
//
// Check owner bit: if set, ownership has been passed to us, in which case
// we can use this descriptor and fill in the response buffer it points to.
// If not, we return false to indicate to the caller the need to try again later.
//
if (cmdDescriptor->Word1.Fields.Ownership)
{
bool doInterrupt = false;
uint32_t messageAddress =
cmdDescriptor->Word0.EnvelopeLow |
(cmdDescriptor->Word1.Fields.EnvelopeHigh << 16);
//
// Read the buffer length the host has allocated for this response.
//
// TODO:
// If it is shorter than the buffer we're writing then we will need to
// split the response into multiple responses. I have never seen this happen,
// however and I'm curious if the documentation (AA-L621A-TK) is simply incorrect:
// "Note that if a controller's responses are less than or equal to 60 bytes,
// then the controller need not check the size of the response slot."
// All of the MSCP response messages are shorter than 60 bytes, so this is always
// the case. I'll also note that the spec states "The minimum acceptable size
// is 60 bytes of message text" for the response buffer set up by the host and this
// is *definitely* not followed by host drivers.
//
// The doc is also not exactly clear what a fragmented set of responses looks like...
//
// Message length is at messageAddress - 4 -- this is the size of the command
// not including the two header words.
//
bool success = false;
uint16_t messageLength =
DMAReadWord(
messageAddress - 4,
success);
DEBUG("response address o%o length o%o", messageAddress, response->MessageLength);
assert(reinterpret_cast<uint16_t*>(response)[0] > 0);
if (messageLength == 0)
{
// A lot of bootstraps appear to set up response buffers of length 0.
// We just log the behavior.
DEBUG("Host response buffer size is zero.");
}
else if (messageLength < response->MessageLength)
{
//
// If this happens it's likely fatal since we're not fragmenting responses (see the big comment
// block above). So eat flaming death.
// Note: the VMS bootstrap does this, so we'll just log the issue.
//
DEBUG("Response buffer 0x%x > host buffer length 0x%x", response->MessageLength, messageLength);
}
//
// This will fit; simply copy the response message over the top
// of the buffer allocated on the host -- this updates the header fields
// as necessary and provides the actual response data to the host.
//
DMAWrite(
messageAddress - 4,
response->MessageLength + 4,
reinterpret_cast<uint8_t*>(response));
//
// Check if a transition from empty to non-empty occurred, interrupt if requested.
//
// If the previous entry in the ring is owned by the Port then that indicates
// that the ring was previously empty (i.e. the descriptor we're now returning
// is the first entry returned to the ring by the Port.)
//
if (cmdDescriptor->Word1.Fields.Flag)
{
//
// Flag is set, host is requesting a transition interrupt.
// Check the previous entry in the ring.
//
if (_responseRingLength == 1)
{
// Degenerate case: If the ring is of size 1 we always interrupt.
doInterrupt = true;
}
else
{
uint32_t previousDescriptorAddress =
GetResponseDescriptorAddress(
(_responseRingPointer - 1) % _responseRingLength);
std::unique_ptr<Descriptor> previousDescriptor(
reinterpret_cast<Descriptor*>(
DMARead(
previousDescriptorAddress,
sizeof(Descriptor),
sizeof(Descriptor))));
if (previousDescriptor->Word1.Fields.Ownership)
{
// We own the previous descriptor, so the ring was previously
// full.
doInterrupt = true;
}
}
}
//
// Message posted; reset the Owner bit of the response descriptor,
// and set the Flag bit (to indicate that we've processed it).
//
cmdDescriptor->Word1.Fields.Ownership = 0;
cmdDescriptor->Word1.Fields.Flag = 1;
DMAWrite(
descriptorAddress,
sizeof(Descriptor),
reinterpret_cast<uint8_t*>(cmdDescriptor.get()));
// Post an interrupt as necessary.
if (doInterrupt)
{
DEBUG("Response ring no longer empty, interrupting.");
//
// Set ring base - 2 to non-zero to indicate a transition.
//
DMAWriteWord(_ringBase - 2, 0x1);
Interrupt();
}
res = true;
// Move to the next descriptor in the ring for next time.
_responseRingPointer = (_responseRingPointer + 1) % _responseRingLength;
}
return res;
}
//
// GetControllerIdentifier():
// Returns the ID used by SET CONTROLLER CHARACTERISTICS.
// This should be unique per controller.
//
uint32_t
uda_c::GetControllerIdentifier()
{
// TODO: make this not hardcoded
// ID 0x12345678
return 0x12345678;
}
//
// GetControllerClassModel():
// Returns the Class and Model information used by SET CONTROLLER CHARACTERISTICS.
//
uint16_t
uda_c::GetControllerClassModel()
{
return 0x0102; // Class 1 (mass storage), model 2 (UDA50)
}
//
// Interrupt():
// Invokes a Unibus interrupt if interrupts are enabled and the interrupt
// vector is non-zero.
//
void
uda_c::Interrupt(void)
{
if ((_interruptEnable || _initStep == InitializationStep::Complete) && _interruptVector != 0)
{
interrupt();
}
}
//
// on_power_changed():
// Resets the controller and all attached drives.
//
void
uda_c::on_power_changed(void)
{
storagecontroller_c::on_power_changed();
if (power_down)
{
DEBUG("Reset due to power change");
StateTransition(InitializationStep::Uninitialized);
}
}
//
// on_init_changed():
// Resets the controller and all attached drives.
//
void
uda_c::on_init_changed(void)
{
if (init_asserted)
{
DEBUG("Reset due to INIT");
StateTransition(InitializationStep::Uninitialized);
}
storagecontroller_c::on_init_changed();
}
//
// on_drive_status_changed():
// A no-op. The controller doesn't require any drive notifications.
//
void
uda_c::on_drive_status_changed(storagedrive_c *drive)
{
UNUSED(drive);
}
//
// GetCommandDescriptorAddress():
// Returns the address of the given command descriptor in the command ring.
//
uint32_t
uda_c::GetCommandDescriptorAddress(
size_t index
)
{
return _ringBase + _responseRingLength * sizeof(Descriptor) +
index * sizeof(Descriptor);
}
//
// GetResponseDescriptorAddress():
// Returns the address of the given response descriptor in the response ring.
//
uint32_t
uda_c::GetResponseDescriptorAddress(
size_t index
)
{
return _ringBase + index * sizeof(Descriptor);
}
//
// DMAWriteWord():
// Writes a single word to Unibus memory. Returns true
// on success; if false is returned this is due to an NXM condition.
//
bool
uda_c::DMAWriteWord(
uint32_t address,
uint16_t word)
{
return DMAWrite(
address,
sizeof(uint16_t),
reinterpret_cast<uint8_t*>(&word));
}
//
// DMAReadWord():
// Read a single word from Unibus memory. Returns the word read on success.
// the success field indicates the success or failure of the read.
//
uint16_t
uda_c::DMAReadWord(
uint32_t address,
bool& success)
{
uint8_t* buffer = DMARead(
address,
sizeof(uint16_t),
sizeof(uint16_t));
if (buffer)
{
success = true;
uint16_t retval = *reinterpret_cast<uint16_t *>(buffer);
delete[] buffer;
return retval;
}
else
{
success = false;
return 0;
}
}
//
// DMAWrite():
// Write data from the provided buffer to Unibus memory. Returns true
// on success; if false is returned this is due to an NXM condition.
// The address specified in 'address' must be word-aligned and the
// length must be even.
//
bool
uda_c::DMAWrite(
uint32_t address,
size_t lengthInBytes,
uint8_t* buffer)
{
assert ((lengthInBytes % 2) == 0);
assert (address < 0x40000);
return unibusadapter->request_DMA(
UNIBUS_CONTROL_DATO,
address,
reinterpret_cast<uint16_t*>(buffer),
lengthInBytes >> 1);
}
//
// DMARead():
// Read data from Unibus memory into the returned buffer.
// Buffer returned is nullptr if memory could not be read.
// Caller is responsible for freeing the buffer when done.
// The address specified in 'address' must be word-aligned
// and the length must be even.
//
uint8_t*
uda_c::DMARead(
uint32_t address,
size_t lengthInBytes,
size_t bufferSize)
{
assert (bufferSize >= lengthInBytes);
assert((lengthInBytes % 2) == 0);
assert (address < 0x40000);
uint16_t* buffer = new uint16_t[bufferSize >> 1];
assert(buffer);
memset(reinterpret_cast<uint8_t*>(buffer), 0xc3, bufferSize);
bool success = unibusadapter->request_DMA(
UNIBUS_CONTROL_DATI,
address,
buffer,
lengthInBytes >> 1);
if (success)
{
return reinterpret_cast<uint8_t*>(buffer);
}
else
{
return nullptr;
}
}

View File

@ -68,7 +68,8 @@ public:
void on_after_register_access(
unibusdevice_register_t *device_reg,
uint8_t unibus_control) override;
uint8_t unibus_control,
uint16_t dato_mask) override;
void on_power_changed(void) override;
void on_init_changed(void) override;