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:
parent
6bc7703e63
commit
e037b0d36d
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
// 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)
|
||||
{
|
||||
_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);
|
||||
|
||||
// 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."
|
||||
//
|
||||
UpdateCS1();
|
||||
// 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.
|
||||
|
||||
DEBUG("RHCS1: DATO o%o MASK o%o", value, dato_mask);
|
||||
|
||||
if ((dato_mask & 0xff00) == 0xff00)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ((dato_mask & 0x00ff) == 0x00ff)
|
||||
{
|
||||
bool ready = !!(value & 0200);
|
||||
_interruptEnable = !!(value & 0100);
|
||||
_function = (value & 076) >> 1;
|
||||
_go = (value & 01);
|
||||
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
984
10.02_devices/2_src/uda.cpp.old
Normal file
984
10.02_devices/2_src/uda.cpp.old
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user