mirror of
https://github.com/livingcomputermuseum/UniBone.git
synced 2026-05-01 22:07:16 +00:00
Rewrote lower-level DMA and IRQ handling: DMA and IRQ requests are now queued and will run to completion on their own
without help from the device code (just call request_DMA and when it returns the DMA transfer is complete.) Fixed request_DMA to chunk DMA transfers larger than 1024 bytes to avoid overrunning the mailbox's shared memory. Fixed concurrency issues with DMA requests -- a race condition could cause DMA request data to get clobbered. RT-11 now boots, MSCP behavior is now very reliable.
This commit is contained in:
@@ -50,6 +50,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
//#include <unistd.h> // sleep()
|
//#include <unistd.h> // sleep()
|
||||||
@@ -66,11 +67,58 @@ using namespace std;
|
|||||||
#include "iopageregister.h"
|
#include "iopageregister.h"
|
||||||
#include "unibusadapter.hpp"
|
#include "unibusadapter.hpp"
|
||||||
|
|
||||||
|
dma_request_c::dma_request_c(
|
||||||
|
uint8_t unibus_control,
|
||||||
|
uint32_t unibus_addr,
|
||||||
|
uint16_t* buffer,
|
||||||
|
uint32_t wordcount) :
|
||||||
|
_unibus_control(unibus_control),
|
||||||
|
_unibus_addr(unibus_addr),
|
||||||
|
_unibus_end_addr(0),
|
||||||
|
_buffer(buffer),
|
||||||
|
_wordcount(wordcount),
|
||||||
|
_isComplete(false),
|
||||||
|
_success(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dma_request_c::~dma_request_c()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
irq_request_c::irq_request_c(
|
||||||
|
unsigned level,
|
||||||
|
unsigned vector) :
|
||||||
|
_level(level),
|
||||||
|
_vector(vector)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
irq_request_c::~irq_request_c()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void* bus_worker(
|
||||||
|
void *context)
|
||||||
|
{
|
||||||
|
unibusadapter_c* bus = reinterpret_cast<unibusadapter_c*>(context);
|
||||||
|
bus->dma_worker();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
unibusadapter_c *unibusadapter; // another Singleton
|
unibusadapter_c *unibusadapter; // another Singleton
|
||||||
// is registered in device_c.list<devices> ... order of static constructor calls ???
|
// is registered in device_c.list<devices> ... order of static constructor calls ???
|
||||||
|
|
||||||
unibusadapter_c::unibusadapter_c() :
|
unibusadapter_c::unibusadapter_c() :
|
||||||
device_c() {
|
device_c(),
|
||||||
|
_busWakeup_cond(PTHREAD_COND_INITIALIZER),
|
||||||
|
_requestFinished_cond(PTHREAD_COND_INITIALIZER),
|
||||||
|
_busWorker_mutex(PTHREAD_MUTEX_INITIALIZER)
|
||||||
|
{
|
||||||
unsigned i;
|
unsigned i;
|
||||||
log_label = "UNAPT";
|
log_label = "UNAPT";
|
||||||
|
|
||||||
@@ -82,14 +130,33 @@ unibusadapter_c::unibusadapter_c() :
|
|||||||
line_INIT = false;
|
line_INIT = false;
|
||||||
line_DCLO = false;
|
line_DCLO = false;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Start bus worker thread
|
||||||
|
//
|
||||||
|
pthread_attr_t attribs;
|
||||||
|
pthread_attr_init(&attribs);
|
||||||
|
|
||||||
|
int status = pthread_create(
|
||||||
|
&_busWorker_pthread,
|
||||||
|
&attribs,
|
||||||
|
&bus_worker,
|
||||||
|
reinterpret_cast<void*>(this));
|
||||||
|
|
||||||
|
if (status != 0)
|
||||||
|
{
|
||||||
|
FATAL("Failed to start unibus worker thread. Status 0x%x", status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool unibusadapter_c::on_param_changed(parameter_c *param) {
|
bool unibusadapter_c::on_param_changed(parameter_c *param) {
|
||||||
UNUSED(param);
|
UNUSED(param);
|
||||||
return true ;
|
return true ;
|
||||||
}
|
}
|
||||||
|
|
||||||
void unibusadapter_c::on_power_changed(void) {
|
void unibusadapter_c::on_power_changed(void)
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void unibusadapter_c::on_init_changed(void) {
|
void unibusadapter_c::on_init_changed(void) {
|
||||||
@@ -106,6 +173,14 @@ void unibusadapter_c::worker_init_event() {
|
|||||||
device->init_asserted = line_INIT;
|
device->init_asserted = line_INIT;
|
||||||
device->on_init_changed();
|
device->on_init_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear bus request queues
|
||||||
|
/*
|
||||||
|
pthread_mutex_lock(&_busWorker_mutex);
|
||||||
|
_dmaRequests.clear();
|
||||||
|
_irqRequests.clear();
|
||||||
|
pthread_mutex_unlock(&_busWorker_mutex);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void unibusadapter_c::worker_power_event() {
|
void unibusadapter_c::worker_power_event() {
|
||||||
@@ -118,6 +193,14 @@ void unibusadapter_c::worker_power_event() {
|
|||||||
device->power_down = line_DCLO;
|
device->power_down = line_DCLO;
|
||||||
device->on_power_changed();
|
device->on_power_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear bus request queues
|
||||||
|
/*
|
||||||
|
pthread_mutex_lock(&_busWorker_mutex);
|
||||||
|
_dmaRequests.clear();
|
||||||
|
_irqRequests.clear();
|
||||||
|
pthread_mutex_unlock(&_busWorker_mutex);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// process DATI/DATO access to active device registers
|
// process DATI/DATO access to active device registers
|
||||||
@@ -463,111 +546,213 @@ bool unibusadapter_c::request_INTR_active(const char *error_info) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// request a DMA cycle.
|
// Invoke a DMA transfer.
|
||||||
// unibus_control = UNIBUS_CONTROL_DATI or _DATO
|
// unibus_control = UNIBUS_CONTROL_DATI or _DATO
|
||||||
bool unibusadapter_c::request_DMA(unibusdevice_c *device, uint8_t unibus_control,
|
bool unibusadapter_c::request_DMA(
|
||||||
uint32_t unibus_addr, uint16_t *buffer, unsigned wordcount) {
|
uint8_t unibus_control,
|
||||||
// TODO: if another DMA or INTR is active: put request in queue
|
uint32_t unibus_addr,
|
||||||
UNUSED(device);
|
uint16_t *buffer,
|
||||||
if (request_DMA_active(__func__) || request_INTR_active(__func__))
|
uint32_t wordcount) {
|
||||||
return false;
|
|
||||||
|
|
||||||
mailbox->dma.startaddr = unibus_addr;
|
//
|
||||||
mailbox->dma.control = unibus_control;
|
// Acquire bus mutex; append new request to queue.
|
||||||
mailbox->dma.wordcount = wordcount;
|
// bus worker will wake and service the request in due time.
|
||||||
|
//
|
||||||
|
dma_request_c request(
|
||||||
|
unibus_control,
|
||||||
|
unibus_addr,
|
||||||
|
buffer,
|
||||||
|
wordcount);
|
||||||
|
|
||||||
// save params of current transaction
|
pthread_mutex_lock(&_busWorker_mutex);
|
||||||
cur_DMA_unibus_control = unibus_control;
|
_dmaRequests.push(&request);
|
||||||
cur_DMA_buffer = buffer;
|
pthread_cond_signal(&_busWakeup_cond);
|
||||||
cur_DMA_wordcount = wordcount;
|
pthread_mutex_unlock(&_busWorker_mutex);
|
||||||
|
|
||||||
if (unibus_control == UNIBUS_CONTROL_DATO) {
|
|
||||||
// copy data into mailbox->DMA buffer
|
|
||||||
memcpy((void*) mailbox->dma.words, buffer, 2 * wordcount);
|
|
||||||
}
|
|
||||||
DEBUG("DMA start: %s @ %06o, len=%d", unibus->control2text(unibus_control), unibus_addr,
|
DEBUG("DMA start: %s @ %06o, len=%d", unibus->control2text(unibus_control), unibus_addr,
|
||||||
wordcount);
|
wordcount);
|
||||||
|
|
||||||
// start!
|
//
|
||||||
mailbox->arm2pru_req = ARM2PRU_DMA;
|
// Wait for request to finish.
|
||||||
// PRU now changes state
|
//
|
||||||
return true;
|
pthread_mutex_lock(&_busWorker_mutex);
|
||||||
}
|
while (!request.IsComplete())
|
||||||
|
{
|
||||||
void unibusadapter_c::request_INTR(unibusdevice_c *device, unsigned level, unsigned vector) {
|
pthread_cond_wait(&_requestFinished_cond, &_busWorker_mutex);
|
||||||
// TODO: if another DMA or INTR is active: put request in queue
|
|
||||||
UNUSED(device);
|
|
||||||
|
|
||||||
// it is not an error if the INTR (at same level) is still pending
|
|
||||||
// a device may re-raise its interrupt, an interrupt may remain pending for years.
|
|
||||||
if (request_DMA_active(__func__))
|
|
||||||
return;
|
|
||||||
if (request_INTR_active(NULL))
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (level) {
|
|
||||||
case 4:
|
|
||||||
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B4;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B5;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B6;
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B7;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ERROR("Request_INTR(): Illegal priority %u, aborting", level);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
mailbox->intr.vector = vector;
|
pthread_mutex_unlock(&_busWorker_mutex);
|
||||||
|
|
||||||
// start!
|
|
||||||
mailbox->arm2pru_req = ARM2PRU_INTR;
|
|
||||||
// PRU now changes state
|
|
||||||
|
|
||||||
|
return request.GetSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
// device wants to know state of its requests
|
void unibusadapter_c::dma_worker()
|
||||||
// also checks for completion if the single current DMA or INTR.
|
{
|
||||||
// to be called by device.worker()
|
|
||||||
// result: false = not yet finished, true = complete,
|
|
||||||
// error: return NXM status
|
|
||||||
bool unibusadapter_c::complete_DMA(unibusdevice_c *device, uint32_t *unibus_end_addr,
|
|
||||||
bool *error) {
|
|
||||||
// TODO: access correct request in queue
|
|
||||||
UNUSED(device);
|
|
||||||
|
|
||||||
// rely on RL11 to check for completion and sorting DMA/INTR requests.
|
while(true)
|
||||||
if (request_DMA_active(NULL))
|
{
|
||||||
return false;
|
dma_request_c* dmaReq = nullptr;
|
||||||
|
irq_request_c irqReq(0,0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Wait for the next request.
|
||||||
|
//
|
||||||
|
pthread_mutex_lock(&_busWorker_mutex);
|
||||||
|
while(_dmaRequests.empty() && _irqRequests.empty())
|
||||||
|
{
|
||||||
|
pthread_cond_wait(
|
||||||
|
&_busWakeup_cond,
|
||||||
|
&_busWorker_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
if (cur_DMA_unibus_control == UNIBUS_CONTROL_DATI) {
|
//
|
||||||
// data were read
|
// We have a request: prioritize IRQ over DMA, dequeue from the requisite
|
||||||
// copy result cur_DMA_wordcount from mailbox->DMA bufuffer to cur_DMA_buffer
|
// queue and get to work.
|
||||||
memcpy(cur_DMA_buffer, (void *) mailbox->dma.words, 2 * cur_DMA_wordcount);
|
//
|
||||||
|
if (!_irqRequests.empty())
|
||||||
|
{
|
||||||
|
irq_request_c const& req = _irqRequests.front();
|
||||||
|
irqReq = req;
|
||||||
|
_irqRequests.pop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dmaReq = _dmaRequests.front();
|
||||||
|
_dmaRequests.pop();
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&_busWorker_mutex);
|
||||||
|
|
||||||
|
|
||||||
|
// Sanity check: Should be no active DMA requests on the PRU.
|
||||||
|
assert (!request_DMA_active(nullptr));
|
||||||
|
|
||||||
|
// If there's an IRQ still active, wait for it to finish.
|
||||||
|
// TODO: find a way to avoid having to do this.
|
||||||
|
timeout_c timer;
|
||||||
|
while (request_INTR_active(nullptr))
|
||||||
|
{
|
||||||
|
timer.wait_us(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmaReq)
|
||||||
|
{
|
||||||
|
// We do the DMA transfer in chunks so we can handle arbitrary buffer sizes.
|
||||||
|
// (the PRU mailbox has limited space available.)
|
||||||
|
// Configure the DMA transfer.
|
||||||
|
|
||||||
|
uint32_t maxTransferSize = 512;
|
||||||
|
|
||||||
|
uint32_t wordCount = dmaReq->GetWordCount();
|
||||||
|
uint32_t unibusAddr = dmaReq->GetUnibusAddr();
|
||||||
|
uint32_t bufferOffset = 0;
|
||||||
|
|
||||||
|
while (wordCount > 0)
|
||||||
|
{
|
||||||
|
uint32_t chunkSize = std::min(maxTransferSize, wordCount);
|
||||||
|
|
||||||
|
mailbox->dma.startaddr = unibusAddr + bufferOffset * 2;
|
||||||
|
mailbox->dma.control = dmaReq->GetUnibusControl();
|
||||||
|
mailbox->dma.wordcount = chunkSize;
|
||||||
|
|
||||||
|
// Copy outgoing data into maibox DMA buffer
|
||||||
|
if (dmaReq->GetUnibusControl() == UNIBUS_CONTROL_DATO)
|
||||||
|
{
|
||||||
|
memcpy(
|
||||||
|
(void*)mailbox->dma.words,
|
||||||
|
dmaReq->GetBuffer() + bufferOffset,
|
||||||
|
2 * chunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Start the PRU:
|
||||||
|
mailbox->arm2pru_req = ARM2PRU_DMA;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Wait for the transfer to complete.
|
||||||
|
// TODO: we're polling the mailbox; is there a more efficient way to do this?
|
||||||
|
timeout_c timeout;
|
||||||
|
while (request_DMA_active(NULL))
|
||||||
|
{
|
||||||
|
timeout.wait_us(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmaReq->GetUnibusControl() == UNIBUS_CONTROL_DATI)
|
||||||
|
{
|
||||||
|
// Copy data read from mailbox to user's buffer.
|
||||||
|
memcpy(
|
||||||
|
dmaReq->GetBuffer() + bufferOffset,
|
||||||
|
(void *)mailbox->dma.words,
|
||||||
|
2 * chunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
wordCount -= chunkSize;
|
||||||
|
bufferOffset += chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
dmaReq->SetUnibusEndAddr(mailbox->dma.cur_addr);
|
||||||
|
dmaReq->SetSuccess(mailbox->dma.cur_status == DMA_STATE_READY);
|
||||||
|
|
||||||
|
assert(dmaReq->GetUnibusAddr() + dmaReq->GetWordCount() * 2 == mailbox->dma.cur_addr + 2);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Signal that the request is complete.
|
||||||
|
//
|
||||||
|
pthread_mutex_lock(&_busWorker_mutex);
|
||||||
|
dmaReq->SetComplete();
|
||||||
|
pthread_cond_signal(&_requestFinished_cond);
|
||||||
|
pthread_mutex_unlock(&_busWorker_mutex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Handle interrupt request
|
||||||
|
switch(irqReq.GetInterruptLevel())
|
||||||
|
{
|
||||||
|
case 4:
|
||||||
|
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B5;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B6;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B7;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ERROR("Request_INTR(): Illegal priority %u, aborting", irqReq.GetInterruptLevel());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mailbox->intr.vector = irqReq.GetVector();
|
||||||
|
|
||||||
|
// start!
|
||||||
|
mailbox->arm2pru_req = ARM2PRU_INTR;
|
||||||
|
// PRU now changes state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*unibus_end_addr = mailbox->dma.cur_addr;
|
|
||||||
|
|
||||||
*error = mailbox->dma.cur_status != DMA_STATE_READY;
|
|
||||||
DEBUG("DMA ready: %s @ %06o..%06o, wordcount %d, data=%06o, %06o, ... %s",
|
|
||||||
unibus->control2text(mailbox->dma.control), mailbox->dma.startaddr,
|
|
||||||
mailbox->dma.cur_addr, mailbox->dma.wordcount, mailbox->dma.words[0],
|
|
||||||
mailbox->dma.words[1], *error ? "TIMEOUT" : "OK");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// result: false = not yet finished, true = complete,
|
void unibusadapter_c::request_INTR(uint32_t level, uint32_t vector) {
|
||||||
bool unibusadapter_c::complete_INTR(unibusdevice_c *device) {
|
//
|
||||||
// TODO: access correct request in queue
|
// Acquire bus mutex; append new request to queue.
|
||||||
UNUSED(device);
|
// bus worker will wake and service the request in due time.
|
||||||
|
//
|
||||||
|
irq_request_c request(
|
||||||
|
level,
|
||||||
|
vector);
|
||||||
|
|
||||||
// rely on RL11 to check for completion and sorting DMA/INTR requests.
|
pthread_mutex_lock(&_busWorker_mutex);
|
||||||
return request_INTR_active(NULL);
|
_irqRequests.push(request);
|
||||||
|
pthread_cond_signal(&_busWakeup_cond);
|
||||||
|
pthread_mutex_unlock(&_busWorker_mutex);
|
||||||
|
|
||||||
|
//
|
||||||
|
// And we're done.
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
// debugging: print PRU sharead regsster map
|
// debugging: print PRU sharead regsster map
|
||||||
|
|||||||
@@ -28,17 +28,67 @@
|
|||||||
#define _UNIBUSADAPTER_HPP_
|
#define _UNIBUSADAPTER_HPP_
|
||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
#include "iopageregister.h"
|
#include "iopageregister.h"
|
||||||
#include "unibusdevice.hpp"
|
#include "unibusdevice.hpp"
|
||||||
|
|
||||||
|
class dma_request_c
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
dma_request_c(
|
||||||
|
uint8_t unibus_control,
|
||||||
|
uint32_t unibus_addr,
|
||||||
|
uint16_t *buffer,
|
||||||
|
uint32_t wordcount);
|
||||||
|
|
||||||
|
~dma_request_c();
|
||||||
|
|
||||||
|
uint8_t GetUnibusControl() { return _unibus_control; }
|
||||||
|
uint32_t GetUnibusAddr() { return _unibus_addr; }
|
||||||
|
uint16_t* GetBuffer() { return _buffer; }
|
||||||
|
uint32_t GetWordCount() { return _wordcount; }
|
||||||
|
uint32_t GetUnibusEndAddr() { return _unibus_end_addr; }
|
||||||
|
void SetUnibusEndAddr(uint32_t end) { _unibus_end_addr = end; }
|
||||||
|
|
||||||
|
bool IsComplete() { return _isComplete; }
|
||||||
|
bool GetSuccess() { return _success; }
|
||||||
|
|
||||||
|
void SetComplete() { _isComplete = true; }
|
||||||
|
void SetSuccess(bool success) { _success = success; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint8_t _unibus_control;
|
||||||
|
uint32_t _unibus_addr;
|
||||||
|
uint32_t _unibus_end_addr;
|
||||||
|
uint16_t* _buffer;
|
||||||
|
uint32_t _wordcount;
|
||||||
|
|
||||||
|
bool _isComplete;
|
||||||
|
bool _success;
|
||||||
|
};
|
||||||
|
|
||||||
|
class irq_request_c
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
irq_request_c(
|
||||||
|
uint32_t level,
|
||||||
|
uint32_t vector);
|
||||||
|
|
||||||
|
~irq_request_c();
|
||||||
|
|
||||||
|
uint32_t GetInterruptLevel() { return _level; }
|
||||||
|
uint32_t GetVector() { return _vector; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t _level;
|
||||||
|
uint32_t _vector;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// is a device_c. need a thread (but no params)
|
// is a device_c. need a thread (but no params)
|
||||||
class unibusadapter_c: public device_c {
|
class unibusadapter_c: public device_c {
|
||||||
private:
|
|
||||||
// save params of current DMA transaction
|
|
||||||
volatile uint8_t cur_DMA_unibus_control; // DATI? DATO?
|
|
||||||
uint16_t *cur_DMA_buffer;
|
|
||||||
volatile unsigned cur_DMA_wordcount;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
unibusadapter_c();
|
unibusadapter_c();
|
||||||
@@ -59,21 +109,28 @@ public:
|
|||||||
void worker_power_event(void) ;
|
void worker_power_event(void) ;
|
||||||
void worker_deviceregister_event(void) ;
|
void worker_deviceregister_event(void) ;
|
||||||
void worker(void) override; // background worker function
|
void worker(void) override; // background worker function
|
||||||
|
void dma_worker(void); // background DMA worker
|
||||||
|
|
||||||
bool register_device(unibusdevice_c& device);
|
bool register_device(unibusdevice_c& device);
|
||||||
void unregister_device(unibusdevice_c& device);
|
void unregister_device(unibusdevice_c& device);
|
||||||
|
|
||||||
bool request_DMA_active(const char *error_info) ;
|
bool request_DMA_active(const char *error_info) ;
|
||||||
bool request_INTR_active(const char *error_info) ;
|
bool request_INTR_active(const char *error_info) ;
|
||||||
|
|
||||||
bool request_DMA(unibusdevice_c *device, uint8_t unibus_control, uint32_t unibus_addr,
|
bool request_DMA(uint8_t unibus_control, uint32_t unibus_addr,
|
||||||
uint16_t *buffer, unsigned wordcount);
|
uint16_t *buffer, uint32_t wordcount);
|
||||||
void request_INTR(unibusdevice_c *device, unsigned level, unsigned vector);
|
void request_INTR(uint32_t level, uint32_t vector);
|
||||||
bool complete_DMA(unibusdevice_c *device, uint32_t *unibus_end_addr, bool *error);
|
|
||||||
bool complete_INTR(unibusdevice_c *device);
|
|
||||||
|
|
||||||
void print_shared_register_map(void);
|
void print_shared_register_map(void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::queue<dma_request_c*> _dmaRequests;
|
||||||
|
std::queue<irq_request_c> _irqRequests;
|
||||||
|
pthread_t _busWorker_pthread;
|
||||||
|
pthread_cond_t _busWakeup_cond;
|
||||||
|
pthread_cond_t _requestFinished_cond;
|
||||||
|
pthread_mutex_t _busWorker_mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern unibusadapter_c *unibusadapter; // another Singleton
|
extern unibusadapter_c *unibusadapter; // another Singleton
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ void unibusdevice_c::reset_unibus_registers() {
|
|||||||
// set an UNIBUS interrupt condition with intr_vector and intr_level
|
// set an UNIBUS interrupt condition with intr_vector and intr_level
|
||||||
void unibusdevice_c::interrupt(void) {
|
void unibusdevice_c::interrupt(void) {
|
||||||
// delegate to unibusadapter_c
|
// delegate to unibusadapter_c
|
||||||
unibusadapter->request_INTR(this, intr_level.value, intr_vector.value);
|
unibusadapter->request_INTR(intr_level.value, intr_vector.value);
|
||||||
// WARNING("unibusdevice_c::interrupt() TODO: generated interrupt!");
|
// WARNING("unibusdevice_c::interrupt() TODO: generated interrupt!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ mscp_server::Poll(void)
|
|||||||
|
|
||||||
pthread_mutex_unlock(&polling_mutex);
|
pthread_mutex_unlock(&polling_mutex);
|
||||||
|
|
||||||
//timer.wait_us(100);
|
// timer.wait_us(100);
|
||||||
|
|
||||||
|
|
||||||
if (_abort_polling)
|
if (_abort_polling)
|
||||||
@@ -156,7 +156,8 @@ mscp_server::Poll(void)
|
|||||||
ControlMessageHeader* header =
|
ControlMessageHeader* header =
|
||||||
reinterpret_cast<ControlMessageHeader*>(message->Message);
|
reinterpret_cast<ControlMessageHeader*>(message->Message);
|
||||||
|
|
||||||
DEBUG("Message opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x",
|
DEBUG("Message size 0x%x opcode 0x%x rsvd 0x%x mod 0x%x unit %d, ursvd 0x%x, ref 0x%x",
|
||||||
|
message->MessageLength,
|
||||||
header->Word3.Command.Opcode,
|
header->Word3.Command.Opcode,
|
||||||
header->Word3.Command.Reserved,
|
header->Word3.Command.Reserved,
|
||||||
header->Word3.Command.Modifiers,
|
header->Word3.Command.Modifiers,
|
||||||
@@ -240,7 +241,7 @@ mscp_server::Poll(void)
|
|||||||
message->Word1.Info.Credits = 0;
|
message->Word1.Info.Credits = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.wait_us(250);
|
//timer.wait_us(250);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Post the response to the port's response ring.
|
// Post the response to the port's response ring.
|
||||||
@@ -251,7 +252,7 @@ mscp_server::Poll(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hack: give interrupts time to settle before doing another transfer.
|
// Hack: give interrupts time to settle before doing another transfer.
|
||||||
timer.wait_us(2500);
|
//timer.wait_us(2500);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Go around and pick up the next one.
|
// Go around and pick up the next one.
|
||||||
@@ -295,28 +296,32 @@ mscp_server::GetUnitStatus(
|
|||||||
uint32_t Reserved0;
|
uint32_t Reserved0;
|
||||||
uint64_t UnitIdentifier;
|
uint64_t UnitIdentifier;
|
||||||
uint32_t MediaTypeIdentifier;
|
uint32_t MediaTypeIdentifier;
|
||||||
uint16_t Reserved1;
|
|
||||||
uint16_t ShadowUnit;
|
uint16_t ShadowUnit;
|
||||||
uint16_t GroupSize;
|
uint16_t Reserved1;
|
||||||
uint16_t TrackSize;
|
uint16_t TrackSize;
|
||||||
uint16_t Reserved2;
|
uint16_t GroupSize;
|
||||||
uint16_t CylinderSize;
|
uint16_t CylinderSize;
|
||||||
|
uint16_t Reserved2;
|
||||||
uint32_t RCTStuff;
|
uint32_t RCTStuff;
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
INFO("MSCP GET UNIT STATUS drive %d", unitNumber);
|
||||||
|
|
||||||
|
// Adjust message length for response
|
||||||
|
message->MessageLength = sizeof(GetUnitStatusResponseParameters) +
|
||||||
|
HEADER_SIZE;
|
||||||
|
|
||||||
|
|
||||||
mscp_drive_c* drive = GetDrive(unitNumber);
|
mscp_drive_c* drive = GetDrive(unitNumber);
|
||||||
|
|
||||||
if (nullptr == drive ||
|
if (nullptr == drive ||
|
||||||
!drive->IsAvailable())
|
!drive->IsAvailable())
|
||||||
{
|
{
|
||||||
|
INFO("Returning UNIT OFFLINE");
|
||||||
return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum
|
return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust message length for response
|
|
||||||
message->MessageLength = sizeof(GetUnitStatusResponseParameters) +
|
|
||||||
HEADER_SIZE;
|
|
||||||
|
|
||||||
GetUnitStatusResponseParameters* params =
|
GetUnitStatusResponseParameters* params =
|
||||||
reinterpret_cast<GetUnitStatusResponseParameters*>(
|
reinterpret_cast<GetUnitStatusResponseParameters*>(
|
||||||
GetParameterPointer(message));
|
GetParameterPointer(message));
|
||||||
@@ -333,8 +338,8 @@ mscp_server::GetUnitStatus(
|
|||||||
// or cylinders to speak of (no seek times, etc.)
|
// or cylinders to speak of (no seek times, etc.)
|
||||||
//
|
//
|
||||||
params->TrackSize = 1; // one block per track, per aa-l619a-tk.
|
params->TrackSize = 1; // one block per track, per aa-l619a-tk.
|
||||||
params->GroupSize = 0;
|
params->GroupSize = 1;
|
||||||
params->CylinderSize = 0;
|
params->CylinderSize = 1;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Since we do no bad block replacement (no bad blocks possible in a disk image file)
|
// Since we do no bad block replacement (no bad blocks possible in a disk image file)
|
||||||
@@ -343,7 +348,7 @@ mscp_server::GetUnitStatus(
|
|||||||
// the RCT are present.
|
// the RCT are present.
|
||||||
//
|
//
|
||||||
params->RCTStuff = 0x01000001;
|
params->RCTStuff = 0x01000001;
|
||||||
|
|
||||||
if (drive->IsOnline())
|
if (drive->IsOnline())
|
||||||
{
|
{
|
||||||
return STATUS(Status::SUCCESS, 0);
|
return STATUS(Status::SUCCESS, 0);
|
||||||
@@ -399,20 +404,25 @@ mscp_server::Online(
|
|||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
INFO("MSCP ONLINE drive %d", unitNumber);
|
||||||
|
|
||||||
|
// Adjust message length for response
|
||||||
|
message->MessageLength = sizeof(OnlineResponseParameters) +
|
||||||
|
HEADER_SIZE;
|
||||||
|
|
||||||
mscp_drive_c* drive = GetDrive(unitNumber);
|
mscp_drive_c* drive = GetDrive(unitNumber);
|
||||||
|
|
||||||
if (nullptr == drive ||
|
if (nullptr == drive ||
|
||||||
!drive->IsAvailable())
|
!drive->IsAvailable())
|
||||||
{
|
{
|
||||||
|
INFO("Returning UNIT OFFLINE");
|
||||||
return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum
|
return STATUS(Status::UNIT_OFFLINE, 3); // unknown -- todo move to enum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool alreadyOnline = drive->IsOnline();
|
||||||
|
|
||||||
drive->SetOnline();
|
drive->SetOnline();
|
||||||
|
|
||||||
// Adjust message length for response
|
|
||||||
message->MessageLength = sizeof(OnlineResponseParameters) +
|
|
||||||
HEADER_SIZE;
|
|
||||||
|
|
||||||
OnlineResponseParameters* params =
|
OnlineResponseParameters* params =
|
||||||
reinterpret_cast<OnlineResponseParameters*>(
|
reinterpret_cast<OnlineResponseParameters*>(
|
||||||
GetParameterPointer(message));
|
GetParameterPointer(message));
|
||||||
@@ -422,9 +432,12 @@ mscp_server::Online(
|
|||||||
params->UnitIdentifier = drive->GetUnitID();
|
params->UnitIdentifier = drive->GetUnitID();
|
||||||
params->MediaTypeIdentifier = drive->GetMediaID();
|
params->MediaTypeIdentifier = drive->GetMediaID();
|
||||||
params->UnitSize = drive->GetBlockCount();
|
params->UnitSize = drive->GetBlockCount();
|
||||||
params->VolumeSerialNumber = 0; // We report no serial
|
params->VolumeSerialNumber = 1; // We report no serial
|
||||||
|
params->Reserved0 = 0;
|
||||||
return STATUS(Status::SUCCESS, 0); // TODO: subcode "Already Online"
|
params->Reserved1 = 0;
|
||||||
|
|
||||||
|
return STATUS(Status::SUCCESS |
|
||||||
|
(alreadyOnline ? SuccessSubcodes::ALREADY_ONLINE : SuccessSubcodes::NORMAL), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t
|
uint32_t
|
||||||
@@ -446,6 +459,11 @@ mscp_server::SetControllerCharacteristics(
|
|||||||
reinterpret_cast<SetControllerCharacteristicsParameters*>(
|
reinterpret_cast<SetControllerCharacteristicsParameters*>(
|
||||||
GetParameterPointer(message));
|
GetParameterPointer(message));
|
||||||
|
|
||||||
|
INFO("MSCP SET CONTROLLER CHARACTERISTICS");
|
||||||
|
|
||||||
|
// Adjust message length for response
|
||||||
|
message->MessageLength = sizeof(SetControllerCharacteristicsParameters) +
|
||||||
|
HEADER_SIZE;
|
||||||
//
|
//
|
||||||
// Check the version, if non-zero we must return an Invalid Command
|
// Check the version, if non-zero we must return an Invalid Command
|
||||||
// end message.
|
// end message.
|
||||||
@@ -494,14 +512,7 @@ mscp_server::SetUnitCharacteristics(
|
|||||||
|
|
||||||
// TODO: handle Set Write Protect modifier
|
// TODO: handle Set Write Protect modifier
|
||||||
|
|
||||||
mscp_drive_c* drive = GetDrive(unitNumber);
|
INFO("MSCP SET UNIT CHARACTERISTICS drive %d", unitNumber);
|
||||||
|
|
||||||
// Check unit
|
|
||||||
if (nullptr == drive ||
|
|
||||||
!drive->IsAvailable())
|
|
||||||
{
|
|
||||||
return STATUS(Status::UNIT_OFFLINE, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: mostly same as Online command: should share logic.
|
// TODO: mostly same as Online command: should share logic.
|
||||||
#pragma pack(push,1)
|
#pragma pack(push,1)
|
||||||
@@ -523,6 +534,15 @@ mscp_server::SetUnitCharacteristics(
|
|||||||
message->MessageLength = sizeof(SetUnitCharacteristicsResponseParameters) +
|
message->MessageLength = sizeof(SetUnitCharacteristicsResponseParameters) +
|
||||||
HEADER_SIZE;
|
HEADER_SIZE;
|
||||||
|
|
||||||
|
mscp_drive_c* drive = GetDrive(unitNumber);
|
||||||
|
// Check unit
|
||||||
|
if (nullptr == drive ||
|
||||||
|
!drive->IsAvailable())
|
||||||
|
{
|
||||||
|
INFO("Returning UNIT OFFLINE");
|
||||||
|
return STATUS(Status::UNIT_OFFLINE, 3);
|
||||||
|
}
|
||||||
|
|
||||||
SetUnitCharacteristicsResponseParameters* params =
|
SetUnitCharacteristicsResponseParameters* params =
|
||||||
reinterpret_cast<SetUnitCharacteristicsResponseParameters*>(
|
reinterpret_cast<SetUnitCharacteristicsResponseParameters*>(
|
||||||
GetParameterPointer(message));
|
GetParameterPointer(message));
|
||||||
@@ -558,12 +578,17 @@ mscp_server::Read(
|
|||||||
ReadParameters* params =
|
ReadParameters* params =
|
||||||
reinterpret_cast<ReadParameters*>(GetParameterPointer(message));
|
reinterpret_cast<ReadParameters*>(GetParameterPointer(message));
|
||||||
|
|
||||||
DEBUG("MSCP READ unit %d pa o%o count %d lbn %d",
|
DEBUG("MSCP READ unit %d chan o%o pa o%o count %d lbn %d",
|
||||||
unitNumber,
|
unitNumber,
|
||||||
|
params->BufferPhysicalAddress >> 24,
|
||||||
params->BufferPhysicalAddress & 0x00ffffff,
|
params->BufferPhysicalAddress & 0x00ffffff,
|
||||||
params->ByteCount,
|
params->ByteCount,
|
||||||
params->LBN);
|
params->LBN);
|
||||||
|
|
||||||
|
// Adjust message length for response
|
||||||
|
message->MessageLength = sizeof(ReadParameters) +
|
||||||
|
HEADER_SIZE;
|
||||||
|
|
||||||
mscp_drive_c* drive = GetDrive(unitNumber);
|
mscp_drive_c* drive = GetDrive(unitNumber);
|
||||||
|
|
||||||
// Check unit
|
// Check unit
|
||||||
@@ -597,17 +622,12 @@ mscp_server::Read(
|
|||||||
params->ByteCount,
|
params->ByteCount,
|
||||||
diskBuffer.get());
|
diskBuffer.get());
|
||||||
|
|
||||||
|
|
||||||
// Adjust message length for response
|
|
||||||
message->MessageLength = sizeof(ReadParameters) +
|
|
||||||
HEADER_SIZE;
|
|
||||||
|
|
||||||
// Set parameters for response.
|
// Set parameters for response.
|
||||||
// We leave ByteCount as is (for now anyway)
|
// We leave ByteCount as is (for now anyway)
|
||||||
// And set First Bad Block to 0. (This is unnecessary since we're
|
// And set First Bad Block to 0. (This is unnecessary since we're
|
||||||
// not reporting a bad block, but we're doing it for completeness.)
|
// not reporting a bad block, but we're doing it for completeness.)
|
||||||
params->LBN = 0;
|
params->LBN = 0;
|
||||||
|
|
||||||
return STATUS(Status::SUCCESS,0);
|
return STATUS(Status::SUCCESS,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,12 +652,17 @@ mscp_server::Write(
|
|||||||
WriteParameters* params =
|
WriteParameters* params =
|
||||||
reinterpret_cast<WriteParameters*>(GetParameterPointer(message));
|
reinterpret_cast<WriteParameters*>(GetParameterPointer(message));
|
||||||
|
|
||||||
DEBUG("MSCP WRITE unit %d pa o%o count %d lbn %d",
|
DEBUG("MSCP WRITE unit %d chan o%o pa o%o count %d lbn %d",
|
||||||
unitNumber,
|
unitNumber,
|
||||||
|
params->BufferPhysicalAddress >> 24,
|
||||||
params->BufferPhysicalAddress & 0x00ffffff,
|
params->BufferPhysicalAddress & 0x00ffffff,
|
||||||
params->ByteCount,
|
params->ByteCount,
|
||||||
params->LBN);
|
params->LBN);
|
||||||
|
|
||||||
|
// Adjust message length for response
|
||||||
|
message->MessageLength = sizeof(WriteParameters) +
|
||||||
|
HEADER_SIZE;
|
||||||
|
|
||||||
mscp_drive_c* drive = GetDrive(unitNumber);
|
mscp_drive_c* drive = GetDrive(unitNumber);
|
||||||
|
|
||||||
// Check unit
|
// Check unit
|
||||||
@@ -671,10 +696,6 @@ mscp_server::Write(
|
|||||||
params->ByteCount,
|
params->ByteCount,
|
||||||
memBuffer.get());
|
memBuffer.get());
|
||||||
|
|
||||||
// Adjust message length for response
|
|
||||||
message->MessageLength = sizeof(WriteParameters) +
|
|
||||||
HEADER_SIZE;
|
|
||||||
|
|
||||||
// Set parameters for response.
|
// Set parameters for response.
|
||||||
// We leave ByteCount as is (for now anyway)
|
// We leave ByteCount as is (for now anyway)
|
||||||
// And set First Bad Block to 0. (This is unnecessary since we're
|
// And set First Bad Block to 0. (This is unnecessary since we're
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class mscp_drive_c;
|
|||||||
#define GET_FLAGS(status) (((status) >> 8) & 0xff)
|
#define GET_FLAGS(status) (((status) >> 8) & 0xff)
|
||||||
|
|
||||||
#define MAX_CREDITS 14
|
#define MAX_CREDITS 14
|
||||||
#define INIT_CREDITS 32
|
#define INIT_CREDITS 1
|
||||||
|
|
||||||
// TODO: Dependent on little-endian hardware
|
// TODO: Dependent on little-endian hardware
|
||||||
//
|
//
|
||||||
@@ -95,6 +95,16 @@ enum Status
|
|||||||
DIAGNOSTIC_MESSAGE = 0x1f
|
DIAGNOSTIC_MESSAGE = 0x1f
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum SuccessSubcodes
|
||||||
|
{
|
||||||
|
NORMAL = 0x0,
|
||||||
|
SPIN_DOWN_IGNORED = 0x20,
|
||||||
|
STILL_CONNECTED = 0x40,
|
||||||
|
DUPLICATE_UNIT_NUMBER = 0x80,
|
||||||
|
ALREADY_ONLINE = 0x100,
|
||||||
|
STILL_ONLINE = 0x200,
|
||||||
|
};
|
||||||
|
|
||||||
enum MessageTypes
|
enum MessageTypes
|
||||||
{
|
{
|
||||||
Sequential = 0,
|
Sequential = 0,
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ rk11_c::rk11_c() :
|
|||||||
RKDB_reg->reset_value = 0;
|
RKDB_reg->reset_value = 0;
|
||||||
RKDB_reg->writable_bits = 0x0000; // read only
|
RKDB_reg->writable_bits = 0x0000; // read only
|
||||||
|
|
||||||
|
_rkda_drive = 0;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Drive configuration: up to eight drives.
|
// Drive configuration: up to eight drives.
|
||||||
//
|
//
|
||||||
@@ -127,8 +129,7 @@ void rk11_c::dma_transfer(DMARequest &request)
|
|||||||
{
|
{
|
||||||
// Write FROM buffer TO unibus memory, IBA on:
|
// Write FROM buffer TO unibus memory, IBA on:
|
||||||
// We only need to write the last word in the buffer to memory.
|
// We only need to write the last word in the buffer to memory.
|
||||||
unibusadapter->request_DMA(
|
request.timeout = !unibusadapter->request_DMA(
|
||||||
this,
|
|
||||||
UNIBUS_CONTROL_DATO,
|
UNIBUS_CONTROL_DATO,
|
||||||
request.address,
|
request.address,
|
||||||
request.buffer + request.count - 1,
|
request.buffer + request.count - 1,
|
||||||
@@ -139,8 +140,7 @@ void rk11_c::dma_transfer(DMARequest &request)
|
|||||||
// Read FROM unibus memory TO buffer, IBA on:
|
// Read FROM unibus memory TO buffer, IBA on:
|
||||||
// We read a single word from the unibus and fill the
|
// We read a single word from the unibus and fill the
|
||||||
// entire buffer with this value.
|
// entire buffer with this value.
|
||||||
unibusadapter->request_DMA(
|
request.timeout = !unibusadapter->request_DMA(
|
||||||
this,
|
|
||||||
UNIBUS_CONTROL_DATI,
|
UNIBUS_CONTROL_DATI,
|
||||||
request.address,
|
request.address,
|
||||||
request.buffer,
|
request.buffer,
|
||||||
@@ -153,8 +153,7 @@ void rk11_c::dma_transfer(DMARequest &request)
|
|||||||
if (request.write)
|
if (request.write)
|
||||||
{
|
{
|
||||||
// Write FROM buffer TO unibus memory
|
// Write FROM buffer TO unibus memory
|
||||||
unibusadapter->request_DMA(
|
request.timeout = !unibusadapter->request_DMA(
|
||||||
this,
|
|
||||||
UNIBUS_CONTROL_DATO,
|
UNIBUS_CONTROL_DATO,
|
||||||
request.address,
|
request.address,
|
||||||
request.buffer,
|
request.buffer,
|
||||||
@@ -163,8 +162,7 @@ void rk11_c::dma_transfer(DMARequest &request)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Read FROM unibus memory TO buffer
|
// Read FROM unibus memory TO buffer
|
||||||
unibusadapter->request_DMA(
|
request.timeout = !unibusadapter->request_DMA(
|
||||||
this,
|
|
||||||
UNIBUS_CONTROL_DATI,
|
UNIBUS_CONTROL_DATI,
|
||||||
request.address,
|
request.address,
|
||||||
request.buffer,
|
request.buffer,
|
||||||
@@ -172,20 +170,6 @@ void rk11_c::dma_transfer(DMARequest &request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// And wait for completion.
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
timeout.wait_us(50); // Stolen from RL11
|
|
||||||
uint32_t last_address = 0;
|
|
||||||
if (unibusadapter->complete_DMA(
|
|
||||||
this,
|
|
||||||
&last_address,
|
|
||||||
&request.timeout))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If an IBA DMA read from memory, we need to fill the request buffer
|
// If an IBA DMA read from memory, we need to fill the request buffer
|
||||||
// with the single word returned from memory by the DMA operation.
|
// with the single word returned from memory by the DMA operation.
|
||||||
if (request.iba && !request.write)
|
if (request.iba && !request.write)
|
||||||
|
|||||||
@@ -718,7 +718,7 @@ void RL11_c::state_readwrite() {
|
|||||||
//logger.debug_hexdump(LC_RL, "Read data between disk access and DMA",
|
//logger.debug_hexdump(LC_RL, "Read data between disk access and DMA",
|
||||||
// (uint8_t *) silo, sizeof(silo), NULL);
|
// (uint8_t *) silo, sizeof(silo), NULL);
|
||||||
// start DMA transmission of SILO into memory
|
// start DMA transmission of SILO into memory
|
||||||
unibusadapter->request_DMA(this, UNIBUS_CONTROL_DATO, unibus_address, silo,
|
error_dma_timeout = !unibusadapter->request_DMA(UNIBUS_CONTROL_DATO, unibus_address, silo,
|
||||||
dma_wordcount);
|
dma_wordcount);
|
||||||
} else if (function_code == CMD_WRITE_CHECK) {
|
} else if (function_code == CMD_WRITE_CHECK) {
|
||||||
// read sector data to compare with sector data
|
// read sector data to compare with sector data
|
||||||
@@ -726,26 +726,20 @@ void RL11_c::state_readwrite() {
|
|||||||
// logger.debug_hexdump(LC_RL, "Read data between disk access and DMA",
|
// logger.debug_hexdump(LC_RL, "Read data between disk access and DMA",
|
||||||
// (uint8_t *) silo, sizeof(silo), NULL);
|
// (uint8_t *) silo, sizeof(silo), NULL);
|
||||||
// start DMA transmission of memory to compare with SILO
|
// start DMA transmission of memory to compare with SILO
|
||||||
unibusadapter->request_DMA(this, UNIBUS_CONTROL_DATI, unibus_address, silo_compare,
|
error_dma_timeout = !unibusadapter->request_DMA(UNIBUS_CONTROL_DATI, unibus_address, silo_compare,
|
||||||
dma_wordcount);
|
dma_wordcount);
|
||||||
} else if (function_code == CMD_WRITE_DATA) {
|
} else if (function_code == CMD_WRITE_DATA) {
|
||||||
// start DMA transmission of memory into SILO
|
// start DMA transmission of memory into SILO
|
||||||
unibusadapter->request_DMA(this, UNIBUS_CONTROL_DATI, unibus_address, silo,
|
error_dma_timeout = !unibusadapter->request_DMA(UNIBUS_CONTROL_DATI, unibus_address, silo,
|
||||||
dma_wordcount);
|
dma_wordcount);
|
||||||
}
|
}
|
||||||
|
|
||||||
change_state(RL11_STATE_RW_WAIT_DMA);
|
change_state(RL11_STATE_RW_WAIT_DMA);
|
||||||
break;
|
break;
|
||||||
case RL11_STATE_RW_WAIT_DMA:
|
case RL11_STATE_RW_WAIT_DMA:
|
||||||
// wait for DMA to complete, start read of next sector
|
// Complete DMA, move to next sector
|
||||||
|
|
||||||
// data late DLT does never happen
|
unibus_address += dma_wordcount * 2;
|
||||||
if (!unibusadapter->complete_DMA(this, &unibus_address, (bool *) &error_dma_timeout)) {
|
|
||||||
timeout.wait_us(50); // 50us, but is more because of granularity
|
|
||||||
break; // DMA still in progress
|
|
||||||
}
|
|
||||||
|
|
||||||
unibus_address += 2; // was last address, is now next to fill
|
|
||||||
// if timeout: addr AFTER illegal address (verified)
|
// if timeout: addr AFTER illegal address (verified)
|
||||||
update_unibus_address(unibus_address); // set addr msb to cs
|
update_unibus_address(unibus_address); // set addr msb to cs
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ void uda_c::worker(void)
|
|||||||
|
|
||||||
case InitializationStep::Step1:
|
case InitializationStep::Step1:
|
||||||
// Wait 100uS, set SA.
|
// Wait 100uS, set SA.
|
||||||
timeout.wait_us(10);
|
timeout.wait_us(100);
|
||||||
|
|
||||||
INFO("Transition to Init state S1.");
|
INFO("Transition to Init state S1.");
|
||||||
//
|
//
|
||||||
@@ -163,44 +163,42 @@ void uda_c::worker(void)
|
|||||||
|
|
||||||
case InitializationStep::Step2:
|
case InitializationStep::Step2:
|
||||||
// Wait 100uS, set SA.
|
// Wait 100uS, set SA.
|
||||||
timeout.wait_us(100);
|
timeout.wait_ms(100);
|
||||||
|
|
||||||
INFO("Transition to Init state S2.");
|
INFO("Transition to Init state S2.");
|
||||||
// update the SA read value for step 2:
|
// update the SA read value for step 2:
|
||||||
// S2 is set, unibus port type (0), SA bits 15-8 written
|
// S2 is set, unibus port type (0), SA bits 15-8 written
|
||||||
// by the host in step 1.
|
// by the host in step 1.
|
||||||
_sa = 0x1000 | (_step1Value >> 8);
|
_sa = 0x1000 | ((_step1Value >> 8) & 0xff);
|
||||||
update_SA();
|
update_SA();
|
||||||
|
|
||||||
Interrupt();
|
Interrupt();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case InitializationStep::Step3:
|
case InitializationStep::Step3:
|
||||||
// Wait 100uS, set SA.
|
// Wait 100uS, set SA.
|
||||||
timeout.wait_us(100);
|
timeout.wait_ms(100);
|
||||||
|
|
||||||
INFO("Transition to Init state S3.");
|
INFO("Transition to Init state S3.");
|
||||||
// Update the SA read value for step 3:
|
// Update the SA read value for step 3:
|
||||||
// S3 set, plus SA bits 7-0 written by the host in step 1.
|
// S3 set, plus SA bits 7-0 written by the host in step 1.
|
||||||
_sa = 0x2000 | (_step1Value & 0xff);
|
_sa = 0x2000 | (_step1Value & 0xff);
|
||||||
update_SA();
|
update_SA();
|
||||||
|
|
||||||
Interrupt();
|
Interrupt();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case InitializationStep::Step4:
|
case InitializationStep::Step4:
|
||||||
|
|
||||||
// Clear communications area, set SA
|
// Clear communications area, set SA
|
||||||
INFO("Clearing comm area at 0x%x.", _ringBase);
|
INFO("Clearing comm area at 0x%x.", _ringBase);
|
||||||
INFO("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength);
|
INFO("resp 0x%x comm 0x%x", _responseRingLength, _commandRingLength);
|
||||||
|
|
||||||
// TODO: -6 and -8 are described; do these always get cleared or only
|
// TODO: -6 and -8 are described; do these always get cleared or only
|
||||||
// on VAXen? ZUDJ diag only expects -2 and -4 to be cleared...
|
// on VAXen? ZUDJ diag only expects -2 and -4 to be cleared...
|
||||||
|
|
||||||
for(uint32_t i = 0;
|
for(uint32_t i = 0;
|
||||||
i < (_responseRingLength + _commandRingLength) * sizeof(Descriptor) + 8;
|
i < (_responseRingLength + _commandRingLength) * sizeof(Descriptor) + 4;
|
||||||
i += 2)
|
i += 2)
|
||||||
{
|
{
|
||||||
DMAWriteWord(_ringBase - 4 + i, 0x0);
|
DMAWriteWord(_ringBase + i - 4, 0x0);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -208,7 +206,6 @@ void uda_c::worker(void)
|
|||||||
// to indicate that the port owns them.
|
// to indicate that the port owns them.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
Descriptor blankDescriptor;
|
Descriptor blankDescriptor;
|
||||||
blankDescriptor.Word0.Word0 = 0;
|
blankDescriptor.Word0.Word0 = 0;
|
||||||
blankDescriptor.Word1.Word1 = 0;
|
blankDescriptor.Word1.Word1 = 0;
|
||||||
@@ -220,7 +217,7 @@ void uda_c::worker(void)
|
|||||||
GetResponseDescriptorAddress(i),
|
GetResponseDescriptorAddress(i),
|
||||||
sizeof(Descriptor),
|
sizeof(Descriptor),
|
||||||
reinterpret_cast<uint8_t*>(&blankDescriptor));
|
reinterpret_cast<uint8_t*>(&blankDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
INFO("Transition to Init state S4.");
|
INFO("Transition to Init state S4.");
|
||||||
// Update the SA read value for step 4:
|
// Update the SA read value for step 4:
|
||||||
@@ -233,9 +230,9 @@ void uda_c::worker(void)
|
|||||||
|
|
||||||
case InitializationStep::Complete:
|
case InitializationStep::Complete:
|
||||||
INFO("Transition to Init state Complete. Initializing response ring.");
|
INFO("Transition to Init state Complete. Initializing response ring.");
|
||||||
_sa = 0x0;
|
//_sa = 0x0;
|
||||||
update_SA();
|
//update_SA();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Set the ownership bit on all descriptors in the response ring
|
// Set the ownership bit on all descriptors in the response ring
|
||||||
// to indicate that the port owns them.
|
// to indicate that the port owns them.
|
||||||
@@ -282,7 +279,7 @@ uda_c::on_after_register_access(
|
|||||||
// to initiate polling..."
|
// to initiate polling..."
|
||||||
if (_initStep == InitializationStep::Complete)
|
if (_initStep == InitializationStep::Complete)
|
||||||
{
|
{
|
||||||
//INFO("Request to start polling.");
|
INFO("Request to start polling.");
|
||||||
_server->InitPolling();
|
_server->InitPolling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,9 +322,9 @@ uda_c::on_after_register_access(
|
|||||||
_responseRingLength = (1 << ((value & 0x700) >> 8));
|
_responseRingLength = (1 << ((value & 0x700) >> 8));
|
||||||
_commandRingLength = (1 << ((value & 0x3800) >> 11));
|
_commandRingLength = (1 << ((value & 0x3800) >> 11));
|
||||||
|
|
||||||
DEBUG("Step1: 0x%x", value);
|
INFO("Step1: 0x%x", value);
|
||||||
DEBUG("resp ring 0x%x", _responseRingLength);
|
INFO("resp ring 0x%x", _responseRingLength);
|
||||||
DEBUG("cmd ring 0x%x", _commandRingLength);
|
INFO("cmd ring 0x%x", _commandRingLength);
|
||||||
|
|
||||||
// Move to step 2.
|
// Move to step 2.
|
||||||
StateTransition(InitializationStep::Step2);
|
StateTransition(InitializationStep::Step2);
|
||||||
@@ -347,7 +344,7 @@ uda_c::on_after_register_access(
|
|||||||
_ringBase = value & 0xfffe;
|
_ringBase = value & 0xfffe;
|
||||||
_purgeInterruptEnable = !!(value & 0x1);
|
_purgeInterruptEnable = !!(value & 0x1);
|
||||||
|
|
||||||
DEBUG("Step2: 0x%x", value);
|
INFO("Step2: 0x%x", value);
|
||||||
// Move to step 3 and interrupt as necessary.
|
// Move to step 3 and interrupt as necessary.
|
||||||
StateTransition(InitializationStep::Step3);
|
StateTransition(InitializationStep::Step3);
|
||||||
break;
|
break;
|
||||||
@@ -366,7 +363,7 @@ uda_c::on_after_register_access(
|
|||||||
// [ringbase+0].
|
// [ringbase+0].
|
||||||
_ringBase |= ((value & 0x7fff) << 16);
|
_ringBase |= ((value & 0x7fff) << 16);
|
||||||
|
|
||||||
DEBUG("Step3: 0x%x", value);
|
INFO("Step3: 0x%x", value);
|
||||||
// Move to step 4 and interrupt as necessary.
|
// Move to step 4 and interrupt as necessary.
|
||||||
StateTransition(InitializationStep::Step4);
|
StateTransition(InitializationStep::Step4);
|
||||||
break;
|
break;
|
||||||
@@ -407,6 +404,10 @@ uda_c::on_after_register_access(
|
|||||||
// start the controller running.
|
// start the controller running.
|
||||||
//
|
//
|
||||||
StateTransition(InitializationStep::Complete);
|
StateTransition(InitializationStep::Complete);
|
||||||
|
// The VMS bootstrap expects SA to be zero IMMEDIATELY
|
||||||
|
// after completion.
|
||||||
|
_sa = 0;
|
||||||
|
update_SA();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -560,7 +561,7 @@ uda_c::GetNextCommand(void)
|
|||||||
//
|
//
|
||||||
DMAWriteWord(
|
DMAWriteWord(
|
||||||
_ringBase - 4,
|
_ringBase - 4,
|
||||||
0xff);
|
0xffff);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Raise the interrupt
|
// Raise the interrupt
|
||||||
@@ -709,7 +710,7 @@ uda_c::PostResponse(
|
|||||||
//
|
//
|
||||||
DMAWriteWord(
|
DMAWriteWord(
|
||||||
_ringBase - 2,
|
_ringBase - 2,
|
||||||
0xff);
|
0xffff);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Raise the interrupt
|
// Raise the interrupt
|
||||||
@@ -844,51 +845,13 @@ uda_c::DMAWrite(
|
|||||||
size_t lengthInBytes,
|
size_t lengthInBytes,
|
||||||
uint8_t* buffer)
|
uint8_t* buffer)
|
||||||
{
|
{
|
||||||
bool timeout = false;
|
|
||||||
timeout_c timer;
|
|
||||||
|
|
||||||
assert ((lengthInBytes % 2) == 0);
|
assert ((lengthInBytes % 2) == 0);
|
||||||
|
|
||||||
// Retry the transfer to work around lower-level DMA issues
|
return unibusadapter->request_DMA(
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
if(!unibusadapter->request_DMA_active("uda r") &&
|
|
||||||
!unibusadapter->request_INTR_active("uda w"))
|
|
||||||
{
|
|
||||||
unibusadapter->request_DMA(
|
|
||||||
this,
|
|
||||||
UNIBUS_CONTROL_DATO,
|
UNIBUS_CONTROL_DATO,
|
||||||
address,
|
address,
|
||||||
reinterpret_cast<uint16_t*>(buffer),
|
reinterpret_cast<uint16_t*>(buffer),
|
||||||
lengthInBytes >> 1);
|
lengthInBytes >> 1);
|
||||||
|
|
||||||
// Wait for completion
|
|
||||||
uint32_t last_address = 0;
|
|
||||||
while(!unibusadapter->complete_DMA(
|
|
||||||
this,
|
|
||||||
&last_address,
|
|
||||||
&timeout))
|
|
||||||
{
|
|
||||||
timer.wait_us(50);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!timeout)
|
|
||||||
{
|
|
||||||
// Success!
|
|
||||||
// timer.wait_us(250); // also a hack
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
INFO(" DMA WRITE FAILED, RETRYING.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try again
|
|
||||||
timer.wait_us(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -911,50 +874,18 @@ uda_c::DMARead(
|
|||||||
|
|
||||||
memset(reinterpret_cast<uint8_t*>(buffer), 0xc3, bufferSize);
|
memset(reinterpret_cast<uint8_t*>(buffer), 0xc3, bufferSize);
|
||||||
|
|
||||||
bool timeout = false;
|
bool success = unibusadapter->request_DMA(
|
||||||
timeout_c timer;
|
UNIBUS_CONTROL_DATI,
|
||||||
|
address,
|
||||||
|
buffer,
|
||||||
|
lengthInBytes >> 1);
|
||||||
|
|
||||||
// We retry the transfer to work around lower-level DMA issues
|
if (success)
|
||||||
while(true)
|
{
|
||||||
{
|
return reinterpret_cast<uint8_t*>(buffer);
|
||||||
timeout = false;
|
}
|
||||||
|
else
|
||||||
if(!unibusadapter->request_DMA_active("uda r") &&
|
{
|
||||||
!unibusadapter->request_INTR_active("uda w"))
|
return nullptr;
|
||||||
{
|
|
||||||
unibusadapter->request_DMA(
|
|
||||||
this,
|
|
||||||
UNIBUS_CONTROL_DATI,
|
|
||||||
address,
|
|
||||||
buffer,
|
|
||||||
lengthInBytes >> 1);
|
|
||||||
|
|
||||||
uint32_t last_address = 0;
|
|
||||||
// Wait for completion
|
|
||||||
while (!unibusadapter->complete_DMA(
|
|
||||||
this,
|
|
||||||
&last_address,
|
|
||||||
&timeout))
|
|
||||||
{
|
|
||||||
timer.wait_us(50);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!timeout)
|
|
||||||
{
|
|
||||||
// success!
|
|
||||||
// timer.wait_us(250);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
INFO("DMA READ FAILED (addr o%o length o%o, lastaddr o%o RETRYING", address, lengthInBytes, last_address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try again
|
|
||||||
timer.wait_us(100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// timer.wait_us(250);
|
|
||||||
return reinterpret_cast<uint8_t*>(buffer);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ OBJECTS = $(OBJDIR)/application.o \
|
|||||||
$(OBJDIR)/rk05.o \
|
$(OBJDIR)/rk05.o \
|
||||||
$(OBJDIR)/uda.o \
|
$(OBJDIR)/uda.o \
|
||||||
$(OBJDIR)/mscp_server.o \
|
$(OBJDIR)/mscp_server.o \
|
||||||
|
$(OBJDIR)/mscp_drive.o \
|
||||||
$(OBJDIR)/storagedrive.o \
|
$(OBJDIR)/storagedrive.o \
|
||||||
$(OBJDIR)/storagecontroller.o \
|
$(OBJDIR)/storagecontroller.o \
|
||||||
$(OBJDIR)/demo_io.o \
|
$(OBJDIR)/demo_io.o \
|
||||||
@@ -203,6 +204,9 @@ $(OBJDIR)/uda.o : $(DEVICE_SRC_DIR)/uda.cpp $(DEVICE_SRC_DIR)/uda.hpp
|
|||||||
$(OBJDIR)/mscp_server.o : $(DEVICE_SRC_DIR)/mscp_server.cpp $(DEVICE_SRC_DIR)/mscp_server.hpp
|
$(OBJDIR)/mscp_server.o : $(DEVICE_SRC_DIR)/mscp_server.cpp $(DEVICE_SRC_DIR)/mscp_server.hpp
|
||||||
$(CC) $(CCFLAGS) $< -o $@
|
$(CC) $(CCFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(OBJDIR)/mscp_drive.o : $(DEVICE_SRC_DIR)/mscp_drive.cpp $(DEVICE_SRC_DIR)/mscp_drive.hpp
|
||||||
|
$(CC) $(CCFLAGS) $< -o $@
|
||||||
|
|
||||||
$(OBJDIR)/storagedrive.o : $(BASE_SRC_DIR)/storagedrive.cpp $(BASE_SRC_DIR)/storagedrive.hpp
|
$(OBJDIR)/storagedrive.o : $(BASE_SRC_DIR)/storagedrive.cpp $(BASE_SRC_DIR)/storagedrive.hpp
|
||||||
$(CC) $(CCFLAGS) $< -o $@
|
$(CC) $(CCFLAGS) $< -o $@
|
||||||
|
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ void menus_c::menu_devices(void) {
|
|||||||
unibus->emulation_logic_start(); // PRU is active UNIBUS node
|
unibus->emulation_logic_start(); // PRU is active UNIBUS node
|
||||||
|
|
||||||
// 2 demo controller
|
// 2 demo controller
|
||||||
demo_io_c demo_io;
|
//demo_io_c demo_io;
|
||||||
//demo_regs_c demo_regs; // mem at 160000: RT11 crashes?
|
//demo_regs_c demo_regs; // mem at 160000: RT11 crashes?
|
||||||
|
|
||||||
cpu_c cpu;
|
// cpu_c cpu;
|
||||||
|
|
||||||
// create RL11 + drives
|
// create RL11 + drives
|
||||||
RL11_c RL11; // instantiates also 4 RL01/02 drives
|
RL11_c RL11; // instantiates also 4 RL01/02 drives
|
||||||
@@ -89,8 +89,8 @@ void menus_c::menu_devices(void) {
|
|||||||
// Create UDA50
|
// Create UDA50
|
||||||
uda_c UDA50;
|
uda_c UDA50;
|
||||||
|
|
||||||
demo_io.install();
|
// demo_io.install();
|
||||||
demo_io.worker_start();
|
//demo_io.worker_start();
|
||||||
|
|
||||||
//demo_regs.install();
|
//demo_regs.install();
|
||||||
//demo_regs.worker_start();
|
//demo_regs.worker_start();
|
||||||
@@ -105,8 +105,8 @@ void menus_c::menu_devices(void) {
|
|||||||
UDA50.install();
|
UDA50.install();
|
||||||
UDA50.worker_start();
|
UDA50.worker_start();
|
||||||
|
|
||||||
cpu.install();
|
// cpu.install();
|
||||||
cpu.worker_start();
|
// cpu.worker_start();
|
||||||
|
|
||||||
while (!ready) {
|
while (!ready) {
|
||||||
|
|
||||||
@@ -337,24 +337,24 @@ void menus_c::menu_devices(void) {
|
|||||||
cout << "Error : " << e.what() << "\n";
|
cout << "Error : " << e.what() << "\n";
|
||||||
}
|
}
|
||||||
} // ready
|
} // ready
|
||||||
cpu.worker_stop();
|
// cpu.worker_stop();
|
||||||
cpu.uninstall();
|
// cpu.uninstall();
|
||||||
|
|
||||||
RL11.worker_stop();
|
//RL11.worker_stop();
|
||||||
RL11.disconnect_from_panel();
|
//RL11.disconnect_from_panel();
|
||||||
RL11.uninstall();
|
//RL11.uninstall();
|
||||||
|
|
||||||
RK05.worker_stop();
|
RK05.worker_stop();
|
||||||
RK05.uninstall();
|
RK05.uninstall();
|
||||||
|
|
||||||
UDA50.worker_stop();
|
// UDA50.worker_stop();
|
||||||
UDA50.uninstall();
|
// UDA50.uninstall();
|
||||||
|
|
||||||
//demo_regs.worker_stop();
|
//demo_regs.worker_stop();
|
||||||
//demo_regs.uninstall();
|
//demo_regs.uninstall();
|
||||||
|
|
||||||
demo_io.worker_stop();
|
// demo_io.worker_stop();
|
||||||
demo_io.uninstall();
|
// demo_io.uninstall();
|
||||||
|
|
||||||
if (unibus->arbitration_active)
|
if (unibus->arbitration_active)
|
||||||
unibus->emulation_logic_stop(); // undo
|
unibus->emulation_logic_stop(); // undo
|
||||||
|
|||||||
Reference in New Issue
Block a user