1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-02-26 16:54:04 +00:00

Enable devices individually over param "enabled"

UNIBUS addr, intr vector, level setable
This commit is contained in:
Joerg Hoppe
2019-06-20 21:58:04 +02:00
parent ccd6747892
commit 3952cb93b0
38 changed files with 1341 additions and 1427 deletions

View File

@@ -1,27 +1,27 @@
/* device.cpp - abstract base class for devices
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
12-nov-2018 JH entered beta phase
Abstract device, with or without UNIBUS registers.
maybe mass storage controller, storage drive or other UNIBUS device
@@ -32,7 +32,7 @@
- has a worker()
- has a logger
- has parameters
*/
*/
#define _DEVICE_CPP_
#include <string.h>
@@ -68,17 +68,17 @@ static void device_worker_pthread_cleanup_handler(void *context) {
static void *device_worker_pthread_wrapper(void *context) {
device_c *device = (device_c *) context;
int oldstate ; // not used
int oldstate; // not used
#define this device // make INFO work
// call real worker
INFO("%s::worker() started", device->name.value.c_str());
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate) ;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldstate) ; //ASYNCH not allowed!
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldstate); //ASYNCH not allowed!
device->worker_terminate = false;
device->worker_terminated = false;
pthread_cleanup_push(device_worker_pthread_cleanup_handler, device) ;
device->worker();
pthread_cleanup_pop(1) ; // call cleanup_handler on regular exit
pthread_cleanup_push(device_worker_pthread_cleanup_handler, device);
device->worker();
pthread_cleanup_pop(1); // call cleanup_handler on regular exit
// not reached on pthread_cancel()
#undef this
return NULL;
@@ -99,10 +99,13 @@ device_c::device_c() {
// creation order of vector vs params?
name.parameterized = this;
type_name.parameterized = this;
enabled.parameterized = this;
verbosity.parameterized = this;
verbosity.value = *log_level_ptr; // global default value from logger->logsource
enabled.value = false; // must be activated by emulation logic/user interaction
param_add(&name);
param_add(&type_name);
param_add(&enabled);
param_add(&emulation_speed);
param_add(&verbosity);
emulation_speed.value = 1;
@@ -125,7 +128,27 @@ device_c::~device_c() {
mydevices.erase(p);
}
bool device_c::on_param_changed(parameter_c *param) {
if (param == &enabled) {
if (enabled.new_value)
worker_start();
else
worker_stop();
}
// all devices forward their "on_param_changed" to parent classes,
// until a class rejects a value.
// device_c is the grand pratnes and produces "OK" for unknown or passive parameters
return true;
}
// search device in global list mydevices[]
device_c *device_c::find_by_name(char *name) {
list<device_c *>::iterator it;
for (it = device_c::mydevices.begin(); it != device_c::mydevices.end(); ++it)
if (!strcasecmp((*it)->name.value.c_str(), name))
return *it;
return NULL;
}
// set priority to max, keep policy, return current priority
// do not change worker_sched_priority
@@ -262,12 +285,13 @@ void device_c::worker_start(void) {
pthread_attr_t attr;
pthread_attr_init(&attr);
// pthread_attr_setstacksize(&attr, 1024*1024);
assert(worker_terminated); // do not srtat device worker twiche in parallel!
int status = pthread_create(&worker_pthread, &attr, &device_worker_pthread_wrapper,
(void *) this);
if (status != 0) {
FATAL("Failed to create pthread with status = %d", status);
}
pthread_attr_destroy(&attr) ; // why?
pthread_attr_destroy(&attr); // why?
}
}
@@ -287,7 +311,8 @@ void device_c::worker_stop(void) {
// if thread is hanging in pthread_cond_wait(): send a cancellation request
status = pthread_cancel(worker_pthread);
if (status != 0)
FATAL("Failed to send cancellation request to worker_pthread with status = %d", status);
FATAL("Failed to send cancellation request to worker_pthread with status = %d",
status);
}
// !! If crosscompling: this causes a crash in the worker thread

View File

@@ -1,28 +1,28 @@
/* device.hpp - abstract base class for devices
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
*/
12-nov-2018 JH entered beta phase
*/
#ifndef _DEVICE_HPP_
#define _DEVICE_HPP_
@@ -40,70 +40,74 @@ using namespace std;
// sets device register values depending on internal status,
// reacts on register read/write over UNIBUS by evaluation of PRU events.
class device_c: public logsource_c, public parameterized_c {
private:
void worker_start(void);
void worker_stop(void);
public:
// the class holds a list of pointers to instantiated devices
// also needed to have a list of threads
static list<device_c *> mydevices;
device_c *parent ; // example: storagedrive_c.parent is storage-controller
device_c *parent; // example: storagedrive_c.parent is storage-controller
// name of instance: "RL3"
parameter_string_c name = parameter_string_c(NULL, "name", "name", /*readonly*/
true, "Unique identifier of device");
true, "Unique identifier of device");
// name of type: "RL02". normally readonly.
parameter_string_c type_name = parameter_string_c(NULL, "type", "type", /*readonly*/
true, "Type");
// NULL: do not link params to this device automatically over param constructor
parameter_unsigned_c emulation_speed = parameter_unsigned_c(NULL,
"emulation_speed", "es", false, "", "%d",
"1 = original speed, > 1: mechanics is this factor faster", 8, 10);
// "enabled": controls device installation to PRU and worker() state.
parameter_bool_c enabled = parameter_bool_c(NULL, "enabled", "en", true,
"device installed and ready to use?");
parameter_unsigned_c emulation_speed = parameter_unsigned_c(NULL, "emulation_speed", "es",
false, "", "%d", "1 = original speed, > 1: mechanics is this factor faster", 8, 10);
// 1 = original speed, > 1: mechanics is this factor faster
parameter_unsigned_c verbosity = parameter_unsigned_c(NULL,
"verbosity", "v", false, "", "%d",
"1 = fatal, 2 = error, 3 = warning, 4 = info, 5 = debug", 8, 10);
parameter_unsigned_c verbosity = parameter_unsigned_c(NULL, "verbosity", "v", false, "",
"%d", "1 = fatal, 2 = error, 3 = warning, 4 = info, 5 = debug", 8, 10);
// make data exchange with worker atomic
std::mutex worker_mutex ;
std::mutex worker_mutex;
// scheduler settings for worker thread
int worker_sched_policy;
int worker_sched_priority;
enum worker_priority_e {
none_rt, // under all RT priorities
rt_device, // all controeller and storage worker
rt_max // 100% CPU, uninterruptable
} ;
void worker_init_realtime_priority(enum worker_priority_e priority) ;
void worker_boost_realtime_priority(void) ;
void worker_restore_realtime_priority(void) ;
};
void worker_init_realtime_priority(enum worker_priority_e priority);
void worker_boost_realtime_priority(void);
void worker_restore_realtime_priority(void);
device_c();
virtual ~device_c(); // class with virtual functions should have virtual destructors
virtual bool on_param_changed(parameter_c *param);
// search in mydevices
static device_c *find_by_name(char *name);
// a device can be powered down. use this to define power-up values
volatile bool power_down ;
volatile bool power_down;
virtual void on_power_changed(void) = 0; // reset device, UNIBUS DC_LO
// every device has a INIT signal, which can be active (asserted) or inactive
// set/release device from INIT state
volatile bool init_asserted ;
volatile bool init_asserted;
virtual void on_init_changed(void) = 0; // reset device, like UNIBUS INIT
// worker thread
volatile bool worker_terminate; // cmd flag to worker()
volatile bool worker_terminated; // ACK flag from worker()
pthread_t worker_pthread;
void worker_start(void);
void worker_stop(void);
virtual void worker(void) = 0; // background worker function
};

View File

@@ -73,16 +73,22 @@ parameter_string_c::parameter_string_c(parameterized_c *parameterized, string na
parameter_string_c::~parameter_string_c() {
}
void parameter_string_c::set(string new_value) {
if (value == new_value)
return ; // call "on_change" only on change
this->new_value = new_value ;
// reject parsed value, if device parameter check complains
if (parameterized == NULL || parameterized->on_param_changed(this))
value = new_value;
}
// string parsing is just copying
void parameter_string_c::parse(string text) {
if (readonly)
throw bad_parameter_readonly("Parameter \"" + name + "\" is read-only");
new_value = text;
// reject parsed value, if device parameter check complains
if (parameterized == NULL || parameterized->on_param_changed(this))
value = new_value;
set(text) ;
}
string *parameter_string_c::render() {
@@ -96,7 +102,19 @@ parameter_bool_c::parameter_bool_c(parameterized_c *parameterized, string name,
value = false;
}
// bool accepty 0/1, y*/n*, t*/f*
void parameter_bool_c::set(bool new_value) {
if (value == new_value)
return ; // call "on_change" only on change
// reject parsed value, if device parameter check complains
this->new_value = new_value ;
if (parameterized == NULL || parameterized->on_param_changed(this))
value = new_value;
}
// bool accepts 0/1, y*/n*, t*/f*
void parameter_bool_c::parse(string text) {
char c;
if (readonly)
@@ -112,10 +130,7 @@ void parameter_bool_c::parse(string text) {
new_value = false;
else
throw bad_parameter_parse("Illegal boolean expression \"" + text + "\"");
// reject parsed value, if device parameter check complains
if (parameterized == NULL || parameterized->on_param_changed(this))
value = new_value;
set(new_value) ;
}
string *parameter_bool_c::render() {
@@ -134,6 +149,18 @@ parameter_unsigned_c::parameter_unsigned_c(parameterized_c *parameterized, strin
this->base = base;
value = 0;
}
void parameter_unsigned_c::set(unsigned new_value) {
if (value == new_value)
return ; // call "on_change" only on change
this->new_value = new_value ;
// reject parsed value, if device parameter check complains
if (parameterized == NULL || parameterized->on_param_changed(this))
value = new_value;
}
void parameter_unsigned_c::parse(string text) {
char *endptr;
if (readonly)
@@ -146,10 +173,7 @@ void parameter_unsigned_c::parse(string text) {
if (new_value & ~BitmaskFromLen32[bitwidth]) //
throw bad_parameter_parse(
"Number " + to_string(new_value) + " exceeds bitwidth " + to_string(bitwidth));
// reject parsed value, if device parameter check complains
if (parameterized == NULL || parameterized->on_param_changed(this))
value = new_value;
set(new_value) ;
}
string *parameter_unsigned_c::render() {
@@ -168,6 +192,16 @@ parameter_unsigned64_c::parameter_unsigned64_c(parameterized_c *parameterized, s
value = 0;
}
void parameter_unsigned64_c::set(uint64_t new_value) {
if (value == new_value)
return ; // call "on_change" only on change
this->new_value = new_value ;
// reject parsed value, if device parameter check complains
if (parameterized == NULL || parameterized->on_param_changed(this))
value = new_value;
}
void parameter_unsigned64_c::parse(string text) {
char *endptr;
if (readonly)
@@ -180,10 +214,7 @@ void parameter_unsigned64_c::parse(string text) {
if (new_value & ~BitmaskFromLen64[bitwidth]) //
throw bad_parameter_parse(
"Number " + to_string(new_value) + " exceeds bitwidth " + to_string(bitwidth));
// reject parsed value, if device parameter check complains
if (parameterized == NULL || parameterized->on_param_changed(this))
value = new_value;
set(new_value) ;
}
string *parameter_unsigned64_c::render() {

View File

@@ -93,25 +93,26 @@ class parameter_string_c: public parameter_c {
public:
// dynamic state
string value;
string new_value ; // after parse, checked by device.on_param_change_check()
string new_value;
parameter_string_c(parameterized_c *parameterized, string name, string shortname, bool readonly, string info);
~parameter_string_c();
string *render(void) override;
void parse(string text) override;
void set(string new_value) ;
};
class parameter_bool_c: public parameter_c {
public:
// dynamic state
bool value;
bool new_value ; // after parse, checked by device.on_param_change_check()
bool new_value;
parameter_bool_c(parameterized_c *parameterized, string name, string shortname, bool readonly, string info);
parameter_bool_c();
string *render(void) override;
void parse(string text) override;
void set(bool new_value) ;
};
class parameter_unsigned_c: public parameter_c {
@@ -123,12 +124,13 @@ public:
// dynamic state
unsigned value;
unsigned new_value ; // after parse, checked by device.on_param_change_check()
unsigned new_value;
parameter_unsigned_c(parameterized_c *parameterized, string name,string shortname, bool readonly, string unit, string format,
string info, unsigned bitwidth, unsigned base);
string *render(void) override;
void parse(string text) override;
void set(unsigned new_value) ;
};
class parameter_unsigned64_c: public parameter_c {
@@ -140,12 +142,13 @@ public:
// dynamic state
uint64_t value;
uint64_t new_value ; // after parse, checked by device.on_param_change_check()
uint64_t new_value ;
parameter_unsigned64_c(parameterized_c *parameterized, string name, string shortname, bool readonly, string unit, string format,
string info, unsigned bitwidth, unsigned base);
string *render(void) override;
void parse(string text) override;
void set(uint64_t new_value) ;
};

View File

@@ -1,30 +1,30 @@
/* storagecontroller.cpp: a unibus device with several "storagedrives" attached
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
12-nov-2018 JH entered beta phase
A unibus device with several "storagedrives"
supports the "attach" command
A unibus device with several "storagedrives"
supports the "attach" command
*/
#include "utils.hpp"
@@ -41,8 +41,15 @@ storagecontroller_c::~storagecontroller_c() {
// implements params, so must handle "change"
bool storagecontroller_c::on_param_changed(parameter_c *param) {
UNUSED(param) ;
return true ;
if (param == &enabled) {
if (!enabled.new_value)
// power/up/down attached drives, then plug to UNIBUS
// if disable, disable also the drives ("contreolelr plugged from UNIBUS)")
// on enable, leave them disabled (user may decide which to use)
for (unsigned i = 0; i < drivecount; i++)
storagedrives[i]->enabled.set(false);
}
return unibusdevice_c::on_param_changed(param); // more actions (for enable)
}
// forward BUS events to connected storage drives
@@ -65,21 +72,3 @@ void storagecontroller_c::on_init_changed() {
}
}
// start/stop threads of this and all drives
void storagecontroller_c::worker_start() {
vector<storagedrive_c*>::iterator it;
for (it = storagedrives.begin(); it != storagedrives.end(); it++) {
(*it)->worker_start();
}
device_c::worker_start();
}
void storagecontroller_c::worker_stop() {
device_c::worker_stop();
vector<storagedrive_c*>::iterator it;
for (it = storagedrives.begin(); it != storagedrives.end(); it++) {
(*it)->worker_stop();
}
}

View File

@@ -1,27 +1,27 @@
/* storagecontroller.hpp: a unibus device with several "storagedrives" attached
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
12-nov-2018 JH entered beta phase
*/
@@ -41,15 +41,12 @@ public:
// does not instantiate the drives
storagecontroller_c(void);
virtual ~storagecontroller_c() ; // classes with virtual functions shoudl have virtual destructors
virtual ~storagecontroller_c(); // classes with virtual functions shoudl have virtual destructors
virtual bool on_param_changed(parameter_c *param) override;
virtual void on_power_changed() override ;
virtual void on_init_changed() override ;
virtual void on_drive_status_changed(storagedrive_c *drive) = 0 ;
void worker_start() ; // start/stop threads of this and all drives
void worker_stop() ;
virtual void on_power_changed() override;
virtual void on_init_changed() override;
virtual void on_drive_status_changed(storagedrive_c *drive) = 0;
};

View File

@@ -49,8 +49,8 @@ storagedrive_c::storagedrive_c(storagecontroller_c *controller) :
// implements params, so must handle "change"
bool storagedrive_c::on_param_changed(parameter_c *param) {
UNUSED(param);
return true;
// no own "enable" logic
return device_c::on_param_changed(param);
}
// http://www.cplusplus.com/doc/tutorial/files/
@@ -121,11 +121,11 @@ void storagedrive_c::file_write(uint8_t *buffer, uint64_t position, unsigned len
assert(!file_readonly); // caller must take care
// enlarge file in chunks until filled up to "position"
f.clear() ; // clear fail bit
f.clear(); // clear fail bit
f.seekp(0, ios::end); // move to current EOF
file_size = f.tellp(); // current file len
if (file_size < 0)
file_size = 0 ; // -1 on emtpy files
file_size = 0; // -1 on emtpy files
while (file_size < write_pos) {
// fill in '00' 'chunks up to desired end, but limit to max_chunk_size
int chunk_size = std::min(max_chunk_size, (int) (write_pos - file_size));
@@ -135,7 +135,7 @@ void storagedrive_c::file_write(uint8_t *buffer, uint64_t position, unsigned len
assert(fillbuff);
memset(fillbuff, 0, max_chunk_size);
}
f.clear() ; // clear fail bit
f.clear(); // clear fail bit
f.seekp(file_size, ios::beg); // move to end
f.write((const char *) fillbuff, chunk_size);
file_size += chunk_size;
@@ -148,7 +148,7 @@ void storagedrive_c::file_write(uint8_t *buffer, uint64_t position, unsigned len
assert(write_pos == 0);
else {
// move write pointer to target position
f.clear() ; // clear fail bit
f.clear(); // clear fail bit
f.seekp(write_pos, ios::beg);
p = f.tellp(); // position now < target?
assert(p == write_pos);

View File

@@ -67,47 +67,29 @@ using namespace std;
#include "iopageregister.h"
#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_start_addr(unibus_addr),
_unibus_end_addr(0),
_buffer(buffer),
_wordcount(wordcount),
_isComplete(false),
_success(false)
{
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_start_addr(unibus_addr), _unibus_end_addr(0), _buffer(
buffer), _wordcount(wordcount), _isComplete(false), _success(false) {
}
dma_request_c::~dma_request_c()
{
dma_request_c::~dma_request_c() {
}
irq_request_c::irq_request_c(
unsigned level,
unsigned vector) :
_level(level),
_vector(vector),
_isComplete(false)
{
irq_request_c::irq_request_c(unsigned level, unsigned vector) :
_level(level), _vector(vector), _isComplete(false) {
}
irq_request_c::~irq_request_c()
{
irq_request_c::~irq_request_c() {
}
void* bus_worker(
void *context)
{
void* bus_worker(void *context) {
unibusadapter_c* bus = reinterpret_cast<unibusadapter_c*>(context);
bus->dma_worker();
bus->dma_worker();
return nullptr;
}
@@ -115,11 +97,8 @@ unibusadapter_c *unibusadapter; // another Singleton
// is registered in device_c.list<devices> ... order of static constructor calls ???
unibusadapter_c::unibusadapter_c() :
device_c(),
_busWakeup_cond(PTHREAD_COND_INITIALIZER),
_requestFinished_cond(PTHREAD_COND_INITIALIZER),
_busWorker_mutex(PTHREAD_MUTEX_INITIALIZER)
{
device_c(), _busWakeup_cond(PTHREAD_COND_INITIALIZER), _requestFinished_cond(
PTHREAD_COND_INITIALIZER), _busWorker_mutex(PTHREAD_MUTEX_INITIALIZER) {
unsigned i;
log_label = "UNAPT";
@@ -137,26 +116,20 @@ unibusadapter_c::unibusadapter_c() :
pthread_attr_t attribs;
pthread_attr_init(&attribs);
int status = pthread_create(
&_busWorker_pthread,
&attribs,
&bus_worker,
reinterpret_cast<void*>(this));
int status = pthread_create(&_busWorker_pthread, &attribs, &bus_worker,
reinterpret_cast<void*>(this));
if (status != 0)
{
if (status != 0) {
FATAL("Failed to start unibus worker thread. Status 0x%x", status);
}
}
bool unibusadapter_c::on_param_changed(parameter_c *param) {
UNUSED(param);
return true ;
// no own parameter or "enable" logic
return device_c::on_param_changed(param); // more actions (for enable)
}
void unibusadapter_c::on_power_changed(void)
{
void unibusadapter_c::on_power_changed(void) {
}
@@ -175,9 +148,8 @@ void unibusadapter_c::worker_init_event() {
device->on_init_changed();
}
// Clear bus request queues
rundown_bus_requests();
// Clear bus request queues
rundown_bus_requests();
}
void unibusadapter_c::worker_power_event() {
@@ -410,7 +382,7 @@ bool unibusadapter_c::register_device(unibusdevice_c& device) {
for (i = 0; i < device.register_count; i++) {
unibusdevice_register_t *device_reg = &(device.registers[i]);
device_reg->addr = device.base_addr.value + 2 * i;
if ( IOPAGE_REGISTER_ENTRY(*deviceregisters,device_reg->addr) != 0 )
if ( IOPAGE_REGISTER_ENTRY(*deviceregisters,device_reg->addr)!= 0 )
FATAL("IO page address conflict: %s implements register at %06o, belongs already to other device.",
device.name.value.c_str(), device_reg->addr);
}
@@ -544,27 +516,19 @@ bool unibusadapter_c::request_INTR_active(const char *error_info) {
// unibus_control = UNIBUS_CONTROL_DATI or _DATO
// unibus_end_addr = last accessed address (success or timeout) and timeout condition
// result: false on UNIBUS timeout
bool unibusadapter_c::request_client_DMA(
uint8_t unibus_control,
uint32_t unibus_addr,
uint16_t *buffer,
uint32_t wordcount,
uint32_t *unibus_end_addr) {
bool unibusadapter_c::request_client_DMA(uint8_t unibus_control, uint32_t unibus_addr,
uint16_t *buffer, uint32_t wordcount, uint32_t *unibus_end_addr) {
//
// Acquire bus mutex; append new request to queue.
// bus worker will wake and service the request in due time.
//
dma_request_c request(
unibus_control,
unibus_addr,
buffer,
wordcount);
dma_request_c request(unibus_control, unibus_addr, buffer, wordcount);
pthread_mutex_lock(&_busWorker_mutex);
_dmaRequests.push(&request);
pthread_cond_signal(&_busWakeup_cond);
pthread_mutex_unlock(&_busWorker_mutex);
pthread_mutex_lock(&_busWorker_mutex);
_dmaRequests.push(&request);
pthread_cond_signal(&_busWakeup_cond);
pthread_mutex_unlock(&_busWorker_mutex);
DEBUG("DMA start: %s @ %06o, len=%d", unibus->control2text(unibus_control), unibus_addr,
wordcount);
@@ -573,59 +537,48 @@ bool unibusadapter_c::request_client_DMA(
// Wait for request to finish.
//
pthread_mutex_lock(&_busWorker_mutex);
while (!request.IsComplete())
{
pthread_cond_wait(&_requestFinished_cond, &_busWorker_mutex);
while (!request.IsComplete()) {
pthread_cond_wait(&_requestFinished_cond, &_busWorker_mutex);
}
pthread_mutex_unlock(&_busWorker_mutex);
if (unibus_end_addr)
*unibus_end_addr = request.GetUnibusEndAddr() ;
*unibus_end_addr = request.GetUnibusEndAddr();
return request.GetSuccess() ;
return request.GetSuccess();
}
void unibusadapter_c::dma_worker()
{
void unibusadapter_c::dma_worker() {
//worker_init_realtime_priority(rt_device);
while(true)
{
while (true) {
dma_request_c* dmaReq = nullptr;
irq_request_c* irqReq = nullptr;
irq_request_c* irqReq = nullptr;
//
// Wait for the next request.
//
pthread_mutex_lock(&_busWorker_mutex);
while(_dmaRequests.empty() && _irqRequests.empty())
{
pthread_cond_wait(
&_busWakeup_cond,
&_busWorker_mutex);
while (_dmaRequests.empty() && _irqRequests.empty()) {
pthread_cond_wait(&_busWakeup_cond, &_busWorker_mutex);
}
//
// We have a request: prioritize IRQ over DMA, dequeue from the requisite
// queue and get to work.
//
if (!_irqRequests.empty())
{
if (!_irqRequests.empty()) {
irqReq = _irqRequests.front();
_irqRequests.pop();
}
else
{
dmaReq = _dmaRequests.front();
} else {
dmaReq = _dmaRequests.front();
_dmaRequests.pop();
}
pthread_mutex_unlock(&_busWorker_mutex);
// Sanity check: Should be no active DMA or interrupt requests on the PRU.
assert (!request_DMA_active(nullptr) && !request_INTR_active(nullptr));
assert(!request_DMA_active(nullptr) && !request_INTR_active(nullptr));
if (dmaReq)
{
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.
@@ -636,22 +589,17 @@ void unibusadapter_c::dma_worker()
uint32_t unibusAddr = dmaReq->GetUnibusStartAddr();
uint32_t bufferOffset = 0;
while (wordCount > 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;
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);
if (dmaReq->GetUnibusControl() == UNIBUS_CONTROL_DATO) {
memcpy((void*) mailbox->dma.words, dmaReq->GetBuffer() + bufferOffset,
2 * chunkSize);
}
//
@@ -662,11 +610,10 @@ void unibusadapter_c::dma_worker()
// Wait for the transfer to complete.
// TODO: we're polling the mailbox; is there a more efficient way to do this?
timeout_c timeout;
int retries = 0;
while (request_DMA_active(nullptr) && retries < 10000)
{
int retries = 0;
while (request_DMA_active(nullptr) && retries < 10000) {
timeout.wait_us(50);
retries++;
retries++;
}
//
@@ -677,18 +624,14 @@ void unibusadapter_c::dma_worker()
// Nothing to do in that case but give up.
// And log the issue. Should get to the root of this..
//
if (retries == 10000)
{
if (retries == 10000) {
ERROR("dma timeout");
}
if (dmaReq->GetUnibusControl() == UNIBUS_CONTROL_DATI)
{
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);
memcpy(dmaReq->GetBuffer() + bufferOffset, (void *) mailbox->dma.words,
2 * chunkSize);
}
wordCount -= chunkSize;
bufferOffset += chunkSize;
@@ -698,8 +641,9 @@ void unibusadapter_c::dma_worker()
dmaReq->SetSuccess(mailbox->dma.cur_status == DMA_STATE_READY);
// no success: UnibusEndAddr is first failed address
assert(dmaReq->GetUnibusStartAddr() + dmaReq->GetWordCount() * 2 ==
mailbox->dma.cur_addr + 2);
assert(
dmaReq->GetUnibusStartAddr() + dmaReq->GetWordCount() * 2
== mailbox->dma.cur_addr + 2);
//
// Signal that the request is complete.
@@ -708,30 +652,28 @@ void unibusadapter_c::dma_worker()
dmaReq->SetComplete();
pthread_cond_signal(&_requestFinished_cond);
pthread_mutex_unlock(&_busWorker_mutex);
}
else
{
} else {
// Handle interrupt request
switch(irqReq->GetInterruptLevel())
{
case 4:
switch (irqReq->GetInterruptLevel()) {
case 4:
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B4;
break;
case 5:
case 5:
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B5;
break;
case 6:
case 6:
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B6;
break;
case 7:
case 7:
mailbox->intr.priority_bit = ARBITRATION_PRIORITY_BIT_B7;
break;
default:
ERROR("Request_INTR(): Illegal priority %u, aborting", irqReq->GetInterruptLevel());
default:
ERROR("Request_INTR(): Illegal priority %u, aborting",
irqReq->GetInterruptLevel());
return;
}
@@ -747,68 +689,60 @@ void unibusadapter_c::dma_worker()
pthread_cond_signal(&_requestFinished_cond);
pthread_mutex_unlock(&_busWorker_mutex);
// Wait for the transfer to complete.
// TODO: we're polling the mailbox; is there a more efficient way to
// Wait for the transfer to complete.
// TODO: we're polling the mailbox; is there a more efficient way to
// do this? (as w/dma)
timeout_c timeout;
while(request_INTR_active(nullptr))
{
timeout.wait_us(50);
}
}
timeout_c timeout;
while (request_INTR_active(nullptr)) {
timeout.wait_us(50);
}
}
}
}
void unibusadapter_c::rundown_bus_requests()
{
void unibusadapter_c::rundown_bus_requests() {
//
// Cancel all pending DMA and IRQ requests, freeing threads waiting
// on completion.
//
pthread_mutex_lock(&_busWorker_mutex);
while (!_dmaRequests.empty())
{
while (!_dmaRequests.empty()) {
dma_request_c* dmaReq = _dmaRequests.front();
dmaReq->SetSuccess(false);
dmaReq->SetComplete();
pthread_cond_signal(&_requestFinished_cond);
_dmaRequests.pop();
}
while (!_irqRequests.empty())
{
while (!_irqRequests.empty()) {
irq_request_c* irqReq = _irqRequests.front();
irqReq->SetComplete();
pthread_cond_signal(&_requestFinished_cond);
_irqRequests.pop();
}
pthread_mutex_unlock(&_busWorker_mutex);
pthread_mutex_unlock(&_busWorker_mutex);
}
void unibusadapter_c::request_INTR(uint32_t level, uint32_t vector) {
//
// Acquire bus mutex; append new request to queue.
// bus worker will wake and service the request in due time.
//
irq_request_c request(
level,
vector);
//
// Acquire bus mutex; append new request to queue.
// bus worker will wake and service the request in due time.
//
irq_request_c request(level, vector);
pthread_mutex_lock(&_busWorker_mutex);
_irqRequests.push(&request);
pthread_cond_signal(&_busWakeup_cond);
pthread_mutex_unlock(&_busWorker_mutex);
pthread_mutex_lock(&_busWorker_mutex);
_irqRequests.push(&request);
pthread_cond_signal(&_busWakeup_cond);
pthread_mutex_unlock(&_busWorker_mutex);
//
// Wait for request to finish.
//
pthread_mutex_lock(&_busWorker_mutex);
while (!request.IsComplete())
{
pthread_cond_wait(&_requestFinished_cond, &_busWorker_mutex);
}
pthread_mutex_unlock(&_busWorker_mutex);
//
// Wait for request to finish.
//
pthread_mutex_lock(&_busWorker_mutex);
while (!request.IsComplete()) {
pthread_cond_wait(&_requestFinished_cond, &_busWorker_mutex);
}
pthread_mutex_unlock(&_busWorker_mutex);
//
// And we're done.

View File

@@ -1,27 +1,27 @@
/* unibusadapter.hpp: connects multiple "unibusdevices" to the PRU UNIBUS interface
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
12-nov-2018 JH entered beta phase
*/
#ifndef _UNIBUSADAPTER_HPP_
@@ -33,63 +33,83 @@
#include "iopageregister.h"
#include "unibusdevice.hpp"
class dma_request_c
{
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 unibus_control, uint32_t unibus_addr, uint16_t *buffer,
uint32_t wordcount);
~dma_request_c();
~dma_request_c();
uint8_t GetUnibusControl() { return _unibus_control; }
uint32_t GetUnibusStartAddr() { return _unibus_start_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; }
uint8_t GetUnibusControl() {
return _unibus_control;
}
uint32_t GetUnibusStartAddr() {
return _unibus_start_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; }
bool IsComplete() {
return _isComplete;
}
bool GetSuccess() {
return _success;
}
void SetComplete() { _isComplete = true; }
void SetSuccess(bool success) { _success = success; }
void SetComplete() {
_isComplete = true;
}
void SetSuccess(bool success) {
_success = success;
}
private:
uint8_t _unibus_control;
uint32_t _unibus_start_addr;
uint32_t _unibus_end_addr;
uint16_t* _buffer;
uint32_t _wordcount;
uint8_t _unibus_control;
uint32_t _unibus_start_addr;
uint32_t _unibus_end_addr;
uint16_t* _buffer;
uint32_t _wordcount;
bool _isComplete;
bool _success;
bool _isComplete;
bool _success;
};
class irq_request_c
{
class irq_request_c {
public:
irq_request_c(
uint32_t level,
uint32_t vector);
irq_request_c(uint32_t level, uint32_t vector);
~irq_request_c();
~irq_request_c();
uint32_t GetInterruptLevel() { return _level; }
uint32_t GetVector() { return _vector; }
bool IsComplete() { return _isComplete; }
uint32_t GetInterruptLevel() {
return _level;
}
uint32_t GetVector() {
return _vector;
}
bool IsComplete() {
return _isComplete;
}
void SetComplete() { _isComplete = true; }
void SetComplete() {
_isComplete = true;
}
private:
uint32_t _level;
uint32_t _vector;
bool _isComplete;
uint32_t _level;
uint32_t _vector;
bool _isComplete;
};
// is a device_c. need a thread (but no params)
class unibusadapter_c: public device_c {
@@ -97,45 +117,46 @@ class unibusadapter_c: public device_c {
public:
unibusadapter_c();
bool on_param_changed(parameter_c *param) override; // must implement
// list of registered devices.
// Defines GRANT priority:
// Lower index = "nearer to CPU" = higher priority
unibusdevice_c *devices[MAX_DEVICE_HANDLE + 1];
volatile bool line_INIT ; // current state of these UNIBUS signals
volatile bool line_DCLO ;
volatile bool line_INIT; // current state of these UNIBUS signals
volatile bool line_DCLO;
bool on_param_changed(parameter_c *param) override; // must implement
void on_power_changed(void) override; // must implement
void on_init_changed(void) override; // must implement
void worker_init_event(void) ;
void worker_power_event(void) ;
void worker_deviceregister_event(void) ;
void worker_init_event(void);
void worker_power_event(void);
void worker_deviceregister_event(void);
void worker(void) override; // background worker function
void dma_worker(void); // background DMA worker
void dma_worker(void); // background DMA worker
bool register_device(unibusdevice_c& device);
void unregister_device(unibusdevice_c& device);
bool request_DMA_active(const char *error_info) ;
bool request_INTR_active(const char *error_info) ;
bool request_DMA_active(const char *error_info);
bool request_INTR_active(const char *error_info);
bool request_client_DMA(uint8_t unibus_control, uint32_t unibus_addr,
uint16_t *buffer, uint32_t wordcount, uint32_t *unibus_end_addr);
bool request_client_DMA(uint8_t unibus_control, uint32_t unibus_addr, uint16_t *buffer,
uint32_t wordcount, uint32_t *unibus_end_addr);
void request_INTR(uint32_t level, uint32_t vector);
void rundown_bus_requests(void);
void rundown_bus_requests(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;
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

View File

@@ -1,34 +1,34 @@
/* unibusdevice.cpp: abstract device with interface to unibusadapter
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
12-nov-2018 JH entered beta phase
abstract unibus device
maybe mass storage controller or other device implementing
UNIBUS IOpage registers.
sets device register values depending on internal status,
reacts on register read/write over UNIBUS by evaluation of PRU events.
*/
abstract unibus device
maybe mass storage controller or other device implementing
UNIBUS IOpage registers.
sets device register values depending on internal status,
reacts on register read/write over UNIBUS by evaluation of PRU events.
*/
//#include <string>
//using namespace std;
#include "logger.hpp"
@@ -39,19 +39,51 @@ unibusdevice_c::unibusdevice_c() :
device_c() {
handle = 0;
register_count = 0;
// device is not yet enabled, UNIBUS properties can be set
base_addr.readonly = false;
intr_vector.readonly = false;
intr_level.readonly = false;
default_base_addr = 0;
default_intr_vector = 0;
default_intr_level = 0;
log_channelmask = 0; // no logging until set
}
unibusdevice_c::~unibusdevice_c() {
}
void unibusdevice_c::install(uint32_t base_addr, unsigned intr_vector, uint8_t intr_level) {
this->base_addr.value = base_addr;
this->intr_vector.value = intr_vector;
this->intr_level.value = intr_level;
// implements params, so must handle "change"
bool unibusdevice_c::on_param_changed(parameter_c *param) {
if (param == &enabled) {
// plug/unplug device into UNIBUS:
if (enabled.new_value) {
// enable: lock UNIBUS config
base_addr.readonly = true;
intr_vector.readonly = true;
intr_level.readonly = true;
install(); // visible on UNIBUS
} else {
// disable
uninstall();
base_addr.readonly = false;
intr_vector.readonly = false;
intr_level.readonly = false;
}
}
return device_c::on_param_changed(param); // more actions (for enable)
}
// define default values for device BASE address and INTR
void unibusdevice_c::set_default_bus_params(uint32_t default_base_addr, unsigned default_intr_vector, unsigned default_intr_level) {
this->default_base_addr = this->base_addr.value = default_base_addr;
this->default_intr_vector = this->intr_vector.value = default_intr_vector ;
this->default_intr_level = this->intr_level.value = default_intr_level ;
}
void unibusdevice_c::install(void) {
unibusadapter->register_device(*this); // -> device_c ?
// now has handle
@@ -62,10 +94,6 @@ void unibusdevice_c::install(uint32_t base_addr, unsigned intr_vector, uint8_t i
on_power_changed();
}
void unibusdevice_c::install(void) {
install(default_base_addr, default_intr_vector, default_intr_level);
}
void unibusdevice_c::uninstall(void) {
unibusadapter->unregister_device(*this);
}

View File

@@ -1,28 +1,28 @@
/* unibusdevice.hpp: abstract device with interface to unibusadapter
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
*/
12-nov-2018 JH entered beta phase
*/
#ifndef _UNIBUSDEVICE_HPP_
#define _UNIBUSDEVICE_HPP_
@@ -38,7 +38,7 @@ class unibusdevice_c;
typedef struct {
// backlink
unibusdevice_c *device;
char name[40] ; // for display
char name[40]; // for display
unsigned index; // # of register in device register list
uint32_t addr; // unibus address
// so addr = device_base_addr + 2 * index
@@ -77,26 +77,33 @@ typedef struct {
} unibusdevice_register_t;
class unibusdevice_c: public device_c {
private:
// setup address tables, also in shared memory
// start both threads
void install(void);
void uninstall(void);
bool is_installed() {
return (handle > 0);
}
public:
uint8_t handle; // assigned by "unibus.adapter.register
// 0 = not "Plugged" in to UNIBUS
parameter_unsigned_c base_addr = parameter_unsigned_c(this,
"base_addr", "addr", true, "", "%06o",
"controller base address in IO page", 18, 8);
parameter_unsigned_c intr_vector = parameter_unsigned_c(this,
"intr_vector", "iv", true, "", "%03o",
"interrupt vector address", 9, 8);
parameter_unsigned_c intr_level = parameter_unsigned_c(this,
"intr_level", "il", true, "", "%o",
"interrupt bus request level", 3, 8);
parameter_unsigned_c base_addr = parameter_unsigned_c(this, "base_addr", "addr", true, "",
"%06o", "controller base address in IO page", 18, 8);
parameter_unsigned_c intr_vector = parameter_unsigned_c(this, "intr_vector", "iv", true, "",
"%03o", "interrupt vector address", 9, 8);
parameter_unsigned_c intr_level = parameter_unsigned_c(this, "intr_level", "il", true, "",
"%o", "interrupt bus request level", 3, 8);
// DEC defaults as defined by device type
uint32_t default_base_addr;
unsigned default_intr_vector;
unsigned default_intr_level;
void set_default_bus_params(uint32_t default_base_addr, unsigned default_intr_vector, unsigned default_intr_level) ;
// controller register data as pointer to registers in shared PRU RAM
// UNIBUS addr of register[i] = base_addr + 2*i
@@ -104,35 +111,28 @@ public:
unsigned register_count;
unibusdevice_register_t registers[MAX_REGISTERS_PER_DEVICE];
unsigned log_channelmask ; // channelmask for DEBUG logging
unsigned log_channelmask; // channelmask for DEBUG logging
// device is the log channel. one of logger::LC_*
unibusdevice_c();
virtual ~unibusdevice_c(); // class with virtual functions should have virtual destructors
// setup address tables, also in shared memory
// start both threads
void install(uint32_t base_addr, unsigned intr_vector, uint8_t intr_level);
void install(void); // defaults
void uninstall(void);
bool is_installed() {
return (handle > 0);
}
bool on_param_changed(parameter_c *param) override;
// reset device
// virtual void init() override ;
// access the value of a register in shared UNIBUS PRU space
void set_register_dati_value(unibusdevice_register_t *device_reg, uint16_t value, const char *debug_info);
void set_register_dati_value(unibusdevice_register_t *device_reg, uint16_t value,
const char *debug_info);
uint16_t get_register_dato_value(unibusdevice_register_t *device_reg);
void reset_unibus_registers();
unibusdevice_register_t *register_by_name(string name) ;
unibusdevice_register_t *register_by_unibus_address(uint32_t addr) ;
unibusdevice_register_t *register_by_name(string name);
unibusdevice_register_t *register_by_unibus_address(uint32_t addr);
// set an UNIBUS interrupt condition with intr_vector and intr_level
void interrupt(void) ;
void interrupt(void);
// callback to be called on controller register DATI/DATO events.
// must ACK mailbox.event.signal. Asynchron!
@@ -146,8 +146,7 @@ public:
pthread_cond_t on_after_register_access_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t on_after_register_access_mutex = PTHREAD_MUTEX_INITIALIZER;
void log_register_event(const char *change_info, unibusdevice_register_t *changed_reg) ;
void log_register_event(const char *change_info, unibusdevice_register_t *changed_reg);
};

View File

@@ -52,24 +52,36 @@ cpu_c::cpu_c() :
init.value = false;
// current CPU does not publish registers to the bus
// must be unibusdevice_c then!
register_count = 0;
memset(&bus, 0, sizeof(bus)) ;
memset(&ka11, 0, sizeof(ka11)) ;
ka11.bus = &bus ;
memset(&bus, 0, sizeof(bus));
memset(&ka11, 0, sizeof(ka11));
ka11.bus = &bus;
}
cpu_c::~cpu_c() {
}
bool cpu_c::on_param_changed(parameter_c *param) {
if (param == &enabled) {
if (!enabled.new_value) {
// HALT disabled CPU
runmode.value = false;
init.value = false;
}
}
return device_c::on_param_changed(param); // more actions (for enable)
}
extern "C" {
// functions to be used by Angelos CPU emulator
// Result: 1 = OK, 0 = bus timeout
int cpu_dato(unsigned addr, unsigned data) {
bool timeout;
mailbox->dma.words[0] = data;
timeout = !unibus->dma(unibus_c::ARBITRATION_MODE_MASTER , UNIBUS_CONTROL_DATO, addr, 1);
timeout = !unibus->dma(unibus_c::ARBITRATION_MODE_MASTER, UNIBUS_CONTROL_DATO, addr, 1);
return !timeout;
}
@@ -80,7 +92,6 @@ int cpu_datob(unsigned addr, unsigned data) {
mailbox->dma.words[0] = data;
timeout = !unibus->dma(unibus_c::ARBITRATION_MODE_MASTER, UNIBUS_CONTROL_DATOB, addr, 1);
return !timeout;
}
int cpu_dati(unsigned addr, unsigned *data) {
@@ -88,12 +99,12 @@ int cpu_dati(unsigned addr, unsigned *data) {
timeout = !unibus->dma(unibus_c::ARBITRATION_MODE_MASTER, UNIBUS_CONTROL_DATI, addr, 1);
*data = mailbox->dma.words[0];
// printf("DATI; ba=%o, data=%o\n", addr, *data) ;
return !timeout;
}
}
// background worker.
// udpate LEDS, poll switches direct to register flipflops
void cpu_c::worker(void) {
timeout_c timeout;
bool nxm;
@@ -115,11 +126,10 @@ void cpu_c::worker(void) {
if (init.value) {
// user wants CPU reset
reset(&ka11) ;
init.value = 0 ;
reset(&ka11);
init.value = 0;
}
#if 0
if (runmode.value) {
// simulate a fetch
@@ -155,11 +165,6 @@ void cpu_c::on_after_register_access(unibusdevice_register_t *device_reg,
UNUSED(unibus_control);
}
bool cpu_c::on_param_changed(parameter_c *param) {
UNUSED(param) ;
return true ;
}
void cpu_c::on_power_changed(void) {
if (power_down) { // power-on defaults
}

View File

@@ -21,7 +21,7 @@
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23-nov-2018 JH created
23-nov-2018 JH created
*/
#ifndef _CPU_HPP_
#define _CPU_HPP_
@@ -44,17 +44,17 @@ private:
public:
cpu_c();
~cpu_c() ;
~cpu_c();
bool on_param_changed(parameter_c *param) override; // must implement
parameter_bool_c runmode = parameter_bool_c(this, "run", "r",/*readonly*/
false, "1 = CPU running, 0 = halt");
parameter_bool_c init = parameter_bool_c(this, "init", "i",/*readonly*/
false, "1 = CPU initalizing");
struct Bus bus ; // UNIBU Sinterface of CPU
struct KA11 ka11 ; // Angelos CPU state
struct Bus bus; // UNIBU Sinterface of CPU
struct KA11 ka11; // Angelos CPU state
// background worker function
void worker(void) override;
@@ -63,7 +63,6 @@ public:
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
override;
bool on_param_changed(parameter_c *param) override; // must implement
void on_power_changed(void) override;
void on_init_changed(void) override;
};

View File

@@ -355,7 +355,6 @@ step(KA11 *cpu)
INA(PC, cpu->ir);
PC += 2; /* don't increment on bus error! */
by = !!(cpu->ir&B15);
br = sxt(cpu->ir)<<1;
src = cpu->ir>>6 & 077;

View File

@@ -45,15 +45,14 @@ demo_io_c::demo_io_c() :
name.value = "DEMO_IO";
type_name.value = "demo_io_c";
log_label = "di";
default_base_addr = 0760100; // overwritten in install()?
default_intr_vector = 0;
default_intr_level = 0;
set_default_bus_params(0760100, 0, 0) ; // base addr, intr-vector, intr level
// init parameters
switch_feedback.value = false ;
switch_feedback.value = false;
// controller has only 2 register
register_count = 2 ;
register_count = 2;
switch_reg = &(this->registers[0]); // @ base addr
strcpy(switch_reg->name, "SR"); // "Switches and Display"
@@ -95,13 +94,17 @@ demo_io_c::demo_io_c() :
demo_io_c::~demo_io_c() {
// close all gpio value files
unsigned i ;
for (i=0 ; i < 5 ; i++)
gpio_inputs[i].close() ;
for (i=0 ; i < 4 ; i++)
gpio_outputs[i].close() ;
unsigned i;
for (i = 0; i < 5; i++)
gpio_inputs[i].close();
for (i = 0; i < 4; i++)
gpio_outputs[i].close();
}
bool demo_io_c::on_param_changed(parameter_c *param) {
// no own parameter or "enable" logic
return unibusdevice_c::on_param_changed(param); // more actions (for enable)
}
/* helper: opens the control file for a gpio
* exports, programs directions, assigns stream
@@ -113,7 +116,7 @@ void demo_io_c::gpio_open(fstream& value_stream, bool is_input, unsigned gpio_nu
// 1. export pin, so it appears as .../gpio<nr>
char export_filename[80];
ofstream export_file ;
ofstream export_file;
sprintf(export_filename, "%s/export", gpio_class_path);
export_file.open(export_filename);
@@ -127,7 +130,7 @@ void demo_io_c::gpio_open(fstream& value_stream, bool is_input, unsigned gpio_nu
// 2. Now we have directory /sys/class/gpio<number>
// Set to input or output
char direction_filename[80];
ofstream direction_file ;
ofstream direction_file;
sprintf(direction_filename, "%s/gpio%d/direction", gpio_class_path, gpio_number);
direction_file.open(direction_filename);
if (!direction_file.is_open()) {
@@ -201,7 +204,7 @@ void demo_io_c::worker(void) {
// into /sys/class/gpio<n>/value pseudo files
// LED control from switches or UNIBUS "DR" register?
if (! switch_feedback.value)
if (!switch_feedback.value)
register_value = get_register_dato_value(display_reg);
for (i = 0; i < 4; i++) {
register_bitmask = (1 << i);
@@ -224,11 +227,6 @@ void demo_io_c::on_after_register_access(unibusdevice_register_t *device_reg,
UNUSED(unibus_control);
}
bool demo_io_c::on_param_changed(parameter_c *param) {
UNUSED(param) ;
return true ;
}
void demo_io_c::on_power_changed(void) {
if (power_down) { // power-on defaults
}

View File

@@ -50,12 +50,13 @@ private:
public:
demo_io_c();
~demo_io_c() ;
~demo_io_c();
bool on_param_changed(parameter_c *param) override; // must implement
parameter_bool_c switch_feedback = parameter_bool_c(this, "switch_feedback", "sf",/*readonly*/
false, "1 = hard wire Switches to LEDs, PDP-11 can not set LEDs");
// background worker function
void worker(void) override;
@@ -63,7 +64,6 @@ public:
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
override;
bool on_param_changed(parameter_c *param) override; // must implement
void on_power_changed(void) override;
void on_init_changed(void) override;
};

View File

@@ -54,9 +54,9 @@ demo_regs_c::demo_regs_c() :
name.value = "DEMO_REGS";
type_name.value = "demo_regs_c";
log_label = "dr";
default_base_addr = 0760000; // overwritten in install()
default_intr_vector = 0;
default_intr_level = 0;
set_default_bus_params(0760000, 0, 0) ; // base addr, intr-vector, intr level
register_count = 32; // up to 760076
// registers are "active": receive "on_after_register_access"
@@ -73,6 +73,11 @@ demo_regs_c::demo_regs_c() :
}
bool demo_regs_c::on_param_changed(parameter_c *param) {
// no own parameter or "enable" logic
return unibusdevice_c::on_param_changed(param); // more actions (for enable)
}
// background worker.
// Just print a heart beat
void demo_regs_c::worker(void) {
@@ -107,11 +112,6 @@ void demo_regs_c::on_after_register_access(unibusdevice_register_t *device_reg,
// device_reg->addr, unibus_c::control2text(unibus_control));
}
bool demo_regs_c::on_param_changed(parameter_c *param) {
UNUSED(param) ;
return true ;
}
void demo_regs_c::on_power_changed(void) {
if (power_down) { // power-on defaults
}

View File

@@ -35,12 +35,13 @@ private:
public:
parameter_unsigned_c access_count = parameter_unsigned_c(this, "access_count",
"ac",/*readonly*/
true, "", "%u", "Total # of register accesses", 32, 10);
parameter_unsigned_c access_count = parameter_unsigned_c(this, "access_count", "ac",/*readonly*/
true, "", "%u", "Total # of register accesses", 32, 10);
demo_regs_c();
bool on_param_changed(parameter_c *param) override; // must implement
// background worker function
void worker(void) override;
@@ -48,7 +49,6 @@ public:
void on_after_register_access(unibusdevice_register_t *device_reg, uint8_t unibus_control)
override;
bool on_param_changed(parameter_c *param) override; // must implement
void on_power_changed(void) override;
void on_init_changed(void) override;
};

View File

@@ -1,19 +1,19 @@
/*
mscp_drive.cpp: Implementation of MSCP disks.
mscp_drive.cpp: Implementation of MSCP disks.
Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA.
Contributed under the BSD 2-clause license.
This provides the logic for reads and writes to the data and RCT space
for a given drive, as well as configuration for different standard DEC
drive types.
Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA.
Contributed under the BSD 2-clause license.
Disk data is backed by an image file on disk. RCT data exists only in
memory and is not saved -- it is provided to satisfy software that
expects the RCT area to exist. Since no bad sectors will ever actually
exist, the RCT area has no real purpose, so it is ephemeral in this
implementation.
*/
This provides the logic for reads and writes to the data and RCT space
for a given drive, as well as configuration for different standard DEC
drive types.
Disk data is backed by an image file on disk. RCT data exists only in
memory and is not saved -- it is provided to satisfy software that
expects the RCT area to exist. Since no bad sectors will ever actually
exist, the RCT area has no real purpose, so it is ephemeral in this
implementation.
*/
#include <assert.h>
#include <memory>
@@ -25,26 +25,43 @@ using namespace std;
#include "mscp_drive.hpp"
#include "mscp_server.hpp"
mscp_drive_c::mscp_drive_c(
storagecontroller_c *controller,
uint32_t driveNumber) :
storagedrive_c(controller),
_useImageSize(false)
{
log_label = "MSCPD";
SetDriveType("RA81");
SetOffline();
mscp_drive_c::mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber) :
storagedrive_c(controller), _useImageSize(false) {
log_label = "MSCPD";
SetDriveType("RA81");
SetOffline();
// Calculate the unit's ID:
_unitDeviceNumber = driveNumber + 1;
// Calculate the unit's ID:
_unitDeviceNumber = driveNumber + 1;
}
mscp_drive_c::~mscp_drive_c()
{
if (file_is_open())
{
file_close();
}
mscp_drive_c::~mscp_drive_c() {
if (file_is_open()) {
file_close();
}
}
// on_param_changed():
// Handles configuration parameter changes.
bool mscp_drive_c::on_param_changed(parameter_c *param) {
// no own "enable" logic
if (&type_name == param) {
return SetDriveType(type_name.new_value.c_str());
} else if (&image_filepath == param) {
// Try to open the image file.
if (file_open(image_filepath.new_value, true)) {
image_filepath.value = image_filepath.new_value;
UpdateCapacity();
return true;
}
// TODO: if file is a nonstandard size?
} else if (&use_image_size == param) {
_useImageSize = use_image_size.new_value;
use_image_size.value = use_image_size.new_value;
UpdateCapacity();
return true;
}
return device_c::on_param_changed(param); // more actions (for enable)false;
}
//
@@ -52,12 +69,11 @@ mscp_drive_c::~mscp_drive_c()
// Returns the size, in bytes, of a single block on this drive.
// This is either 512 or 576 bytes.
//
uint32_t mscp_drive_c::GetBlockSize()
{
//
// For the time being this is always 512 bytes.
//
return 512;
uint32_t mscp_drive_c::GetBlockSize() {
//
// For the time being this is always 512 bytes.
//
return 512;
}
//
@@ -65,65 +81,56 @@ uint32_t mscp_drive_c::GetBlockSize()
// Get the size of the data space (not including RCT area) of this
// drive, in blocks.
//
uint32_t mscp_drive_c::GetBlockCount()
{
if (_useImageSize)
{
// Return the image size / Block size (rounding down).
return file_size() / GetBlockSize();
}
else
{
//
// Use the size defined by the drive type.
//
return _driveInfo.BlockCount;
}
uint32_t mscp_drive_c::GetBlockCount() {
if (_useImageSize) {
// Return the image size / Block size (rounding down).
return file_size() / GetBlockSize();
} else {
//
// Use the size defined by the drive type.
//
return _driveInfo.BlockCount;
}
}
//
// GetRCTBlockCount():
// Returns the total size of the RCT area in blocks.
//
uint32_t mscp_drive_c::GetRCTBlockCount()
{
return _driveInfo.RCTSize * GetRCTCopies();
uint32_t mscp_drive_c::GetRCTBlockCount() {
return _driveInfo.RCTSize * GetRCTCopies();
}
//
// GetMediaID():
// Returns the media ID specific to this drive's type.
//
uint32_t mscp_drive_c::GetMediaID()
{
return _driveInfo.MediaID;
uint32_t mscp_drive_c::GetMediaID() {
return _driveInfo.MediaID;
}
//
// GetDeviceNumber():
// Returns the unique device number for this drive.
//
uint32_t mscp_drive_c::GetDeviceNumber()
{
return _unitDeviceNumber;
uint32_t mscp_drive_c::GetDeviceNumber() {
return _unitDeviceNumber;
}
//
// GetClassModel():
// Returns the class and model information for this drive.
//
uint16_t mscp_drive_c::GetClassModel()
{
return _unitClassModel;
uint16_t mscp_drive_c::GetClassModel() {
return _unitClassModel;
}
//
// GetRCTSize():
// Returns the size of one copy of the RCT.
//
uint16_t mscp_drive_c::GetRCTSize()
{
return _driveInfo.RCTSize;
uint16_t mscp_drive_c::GetRCTSize() {
return _driveInfo.RCTSize;
}
//
@@ -131,9 +138,8 @@ uint16_t mscp_drive_c::GetRCTSize()
// Returns the number of replacement blocks per track for
// this drive.
//
uint8_t mscp_drive_c::GetRBNs()
{
return 0;
uint8_t mscp_drive_c::GetRBNs() {
return 0;
}
//
@@ -141,9 +147,8 @@ uint8_t mscp_drive_c::GetRBNs()
// Returns the number of copies of the RCT present in the RCT
// area.
//
uint8_t mscp_drive_c::GetRCTCopies()
{
return 1;
uint8_t mscp_drive_c::GetRCTCopies() {
return 1;
}
//
@@ -151,9 +156,8 @@ uint8_t mscp_drive_c::GetRCTCopies()
// Indicates whether this drive is available (i.e. has an image
// assigned to it and can thus be used by the controller.)
//
bool mscp_drive_c::IsAvailable()
{
return file_is_open();
bool mscp_drive_c::IsAvailable() {
return file_is_open();
}
//
@@ -161,51 +165,41 @@ bool mscp_drive_c::IsAvailable()
// Indicates whether this drive has been placed into an Online
// state (for example by the ONLINE command).
//
bool mscp_drive_c::IsOnline()
{
return _online;
bool mscp_drive_c::IsOnline() {
return _online;
}
//
// SetOnline():
// Brings the drive online.
//
void mscp_drive_c::SetOnline()
{
_online = true;
void mscp_drive_c::SetOnline() {
_online = true;
//
// Once online, the drive's type and image cannot be changed until
// the drive is offline.
//
// type_name.readonly = true;
// image_filepath.readonly = true;
//
// Once online, the drive's type and image cannot be changed until
// the drive is offline.
//
// type_name.readonly = true;
// image_filepath.readonly = true;
}
//
// SetOffline():
// Takes the drive offline.
//
void mscp_drive_c::SetOffline()
{
_online = false;
type_name.readonly = false;
image_filepath.readonly = false;
void mscp_drive_c::SetOffline() {
_online = false;
type_name.readonly = false;
image_filepath.readonly = false;
}
//
// Writes the specified number of bytes from the provided buffer,
// starting at the specified logical block.
//
void mscp_drive_c::Write(
uint32_t blockNumber,
size_t lengthInBytes,
uint8_t* buffer)
{
file_write(
buffer,
blockNumber * GetBlockSize(),
lengthInBytes);
void mscp_drive_c::Write(uint32_t blockNumber, size_t lengthInBytes, uint8_t* buffer) {
file_write(buffer, blockNumber * GetBlockSize(), lengthInBytes);
}
//
@@ -213,20 +207,14 @@ void mscp_drive_c::Write(
// block. Returns a pointer to a buffer containing the data read.
// Caller is responsible for freeing this buffer.
//
uint8_t* mscp_drive_c::Read(
uint32_t blockNumber,
size_t lengthInBytes)
{
uint8_t* buffer = new uint8_t[lengthInBytes];
uint8_t* mscp_drive_c::Read(uint32_t blockNumber, size_t lengthInBytes) {
uint8_t* buffer = new uint8_t[lengthInBytes];
assert(nullptr != buffer);
assert(nullptr != buffer);
file_read(
buffer,
blockNumber * GetBlockSize(),
lengthInBytes);
file_read(buffer, blockNumber * GetBlockSize(), lengthInBytes);
return buffer;
return buffer;
}
//
@@ -234,16 +222,11 @@ uint8_t* mscp_drive_c::Read(
// RCT area at the specified RCT block. Buffer must be at least as large
// as the disk's block size.
//
void mscp_drive_c::WriteRCTBlock(
uint32_t rctBlockNumber,
uint8_t* buffer)
{
assert (rctBlockNumber < GetRCTBlockCount());
void mscp_drive_c::WriteRCTBlock(uint32_t rctBlockNumber, uint8_t* buffer) {
assert(rctBlockNumber < GetRCTBlockCount());
memcpy(
reinterpret_cast<void *>(_rctData.get() + rctBlockNumber * GetBlockSize()),
reinterpret_cast<void *>(buffer),
GetBlockSize());
memcpy(reinterpret_cast<void *>(_rctData.get() + rctBlockNumber * GetBlockSize()),
reinterpret_cast<void *>(buffer), GetBlockSize());
}
//
@@ -251,84 +234,42 @@ void mscp_drive_c::WriteRCTBlock(
// block offset). Returns a pointer to a buffer containing the data read.
// Caller is responsible for freeing this buffer.
//
uint8_t* mscp_drive_c::ReadRCTBlock(
uint32_t rctBlockNumber)
{
assert (rctBlockNumber < GetRCTBlockCount());
uint8_t* mscp_drive_c::ReadRCTBlock(uint32_t rctBlockNumber) {
assert(rctBlockNumber < GetRCTBlockCount());
uint8_t* buffer = new uint8_t[GetBlockSize()];
assert (nullptr != buffer);
uint8_t* buffer = new uint8_t[GetBlockSize()];
assert(nullptr != buffer);
memcpy(
reinterpret_cast<void *>(buffer),
reinterpret_cast<void *>(_rctData.get() + rctBlockNumber * GetBlockSize()),
GetBlockSize());
return buffer;
}
memcpy(reinterpret_cast<void *>(buffer),
reinterpret_cast<void *>(_rctData.get() + rctBlockNumber * GetBlockSize()),
GetBlockSize());
return buffer;
}
//
// UpdateCapacity():
// Updates the capacity parameter of the drive based on the block count and block size.
//
void mscp_drive_c::UpdateCapacity()
{
capacity.value =
GetBlockCount() * GetBlockSize();
void mscp_drive_c::UpdateCapacity() {
capacity.value = GetBlockCount() * GetBlockSize();
}
//
// UpdateMetadata():
// Updates the Unit Class / Model info and RCT area based on the selected drive type.
//
void mscp_drive_c::UpdateMetadata()
{
_unitClassModel = 0x0200 | _driveInfo.Model;
void mscp_drive_c::UpdateMetadata() {
_unitClassModel = 0x0200 | _driveInfo.Model;
// Initialize the RCT area
size_t rctSize = _driveInfo.RCTSize * GetBlockSize();
_rctData.reset(new uint8_t[rctSize]);
assert(_rctData != nullptr);
memset(reinterpret_cast<void *>(_rctData.get()), 0, rctSize);
// Initialize the RCT area
size_t rctSize = _driveInfo.RCTSize * GetBlockSize();
_rctData.reset(new uint8_t[rctSize]);
assert(_rctData != nullptr);
memset(reinterpret_cast<void *>(_rctData.get()), 0, rctSize);
}
//
// on_param_changed():
// Handles configuration parameter changes.
//
bool mscp_drive_c::on_param_changed(
parameter_c *param)
{
if (&type_name == param)
{
return SetDriveType(type_name.new_value.c_str());
}
else if (&image_filepath == param)
{
//
// Try to open the image file.
//
if (file_open(image_filepath.new_value, true))
{
image_filepath.value = image_filepath.new_value;
UpdateCapacity();
return true;
}
//
// TODO: if file is a nonstandard size?
}
else if (&use_image_size == param)
{
_useImageSize = use_image_size.new_value;
use_image_size.value = use_image_size.new_value;
UpdateCapacity();
return true;
}
return false;
}
//
// SetDriveType():
// Updates this drive's type to the specified type (i.e.
@@ -337,57 +278,50 @@ bool mscp_drive_c::on_param_changed(
// drive types, the drive's type is not changed and false
// is returned.
//
bool mscp_drive_c::SetDriveType(const char* typeName)
{
//
// Search through drive data table for name,
// and if valid, set the type appropriately.
//
int index = 0;
while (g_driveTable[index].BlockCount != 0)
{
if (!strcasecmp(typeName, g_driveTable[index].TypeName))
{
_driveInfo = g_driveTable[index];
type_name.value = _driveInfo.TypeName;
UpdateCapacity();
UpdateMetadata();
return true;
}
bool mscp_drive_c::SetDriveType(const char* typeName) {
//
// Search through drive data table for name,
// and if valid, set the type appropriately.
//
int index = 0;
while (g_driveTable[index].BlockCount != 0) {
if (!strcasecmp(typeName, g_driveTable[index].TypeName)) {
_driveInfo = g_driveTable[index];
type_name.value = _driveInfo.TypeName;
UpdateCapacity();
UpdateMetadata();
return true;
}
index++;
}
index++;
}
// Not found
return false;
// Not found
return false;
}
//
// worker():
// worker method for this drive. No work is necessary.
//
void mscp_drive_c::worker(void)
{
// Nothing to do here at the moment.
void mscp_drive_c::worker(void) {
// Nothing to do here at the moment. Thread will terminate immediately.
}
//
// on_power_changed():
// Handle power change notifications.
//
void mscp_drive_c::on_power_changed(void)
{
// Take the drive offline due to power change
SetOffline();
void mscp_drive_c::on_power_changed(void) {
// Take the drive offline due to power change
SetOffline();
}
//
// on_init_changed():
// Handle INIT signal.
void mscp_drive_c::on_init_changed(void)
{
// Take the drive offline due to reset
SetOffline();
void mscp_drive_c::on_init_changed(void) {
// Take the drive offline due to reset
SetOffline();
}

View File

@@ -1,10 +1,10 @@
/*
mscp_drive.hpp: Implementation of MSCP drive, used with MSCP controller.
mscp_drive.hpp: Implementation of MSCP drive, used with MSCP controller.
Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA.
Contributed under the BSD 2-clause license.
Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA.
Contributed under the BSD 2-clause license.
*/
*/
#pragma once
@@ -16,114 +16,92 @@
//
// Implements the backing store for MSCP disk images
//
class mscp_drive_c : public storagedrive_c
{
class mscp_drive_c: public storagedrive_c {
public:
mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber);
~mscp_drive_c(void);
mscp_drive_c(storagecontroller_c *controller, uint32_t driveNumber);
~mscp_drive_c(void);
uint32_t GetBlockSize(void);
uint32_t GetBlockCount(void);
uint32_t GetRCTBlockCount(void);
uint32_t GetMediaID(void);
uint32_t GetDeviceNumber(void);
uint16_t GetClassModel(void);
uint16_t GetRCTSize(void);
uint8_t GetRBNs(void);
uint8_t GetRCTCopies(void);
bool on_param_changed(parameter_c *param) override;
void SetOnline(void);
void SetOffline(void);
bool IsOnline(void);
bool IsAvailable(void);
uint32_t GetBlockSize(void);
uint32_t GetBlockCount(void);
uint32_t GetRCTBlockCount(void);
uint32_t GetMediaID(void);
uint32_t GetDeviceNumber(void);
uint16_t GetClassModel(void);
uint16_t GetRCTSize(void);
uint8_t GetRBNs(void);
uint8_t GetRCTCopies(void);
void Write(
uint32_t blockNumber,
size_t lengthInBytes,
uint8_t* buffer);
void SetOnline(void);
void SetOffline(void);bool IsOnline(void);bool IsAvailable(void);
uint8_t* Read(
uint32_t blockNumber,
size_t lengthInBytes);
void Write(uint32_t blockNumber, size_t lengthInBytes, uint8_t* buffer);
void WriteRCTBlock(
uint32_t rctBlockNumber,
uint8_t* buffer);
uint8_t* Read(uint32_t blockNumber, size_t lengthInBytes);
uint8_t* ReadRCTBlock(
uint32_t rctBlockNumber);
void WriteRCTBlock(uint32_t rctBlockNumber, uint8_t* buffer);
uint8_t* ReadRCTBlock(uint32_t rctBlockNumber);
public:
bool on_param_changed(parameter_c *param) override;
void on_power_changed(void) override;
void on_init_changed(void) override;
void on_power_changed(void) override;
void on_init_changed(void) override;
void worker(void) override;
void worker(void) override;
public:
parameter_bool_c use_image_size = parameter_bool_c(
this, "useimagesize", "uis", false, "Determine unit size from image file instead of drive type");
parameter_bool_c use_image_size = parameter_bool_c(this, "useimagesize", "uis", false,
"Determine unit size from image file instead of drive type");
private:
struct DriveInfo
{
char TypeName[16];
size_t BlockCount;
uint32_t MediaID;
uint8_t Model;
uint16_t RCTSize;
bool Removable;
bool ReadOnly;
};
//
// TODO: add a lot more drive types.
// Does it make sense to support drive types not native to Unibus machines (SCSI types, for example?)
// Need to add a ClassID table entry in that eventuality...
// Also TODO: RCTSize parameters taken from SIMH rq source; how valid are these?
DriveInfo g_driveTable[21]
{
struct DriveInfo {
char TypeName[16];
size_t BlockCount;
uint32_t MediaID;
uint8_t Model;
uint16_t RCTSize;bool Removable;bool ReadOnly;
};
//
// TODO: add a lot more drive types.
// Does it make sense to support drive types not native to Unibus machines (SCSI types, for example?)
// Need to add a ClassID table entry in that eventuality...
// Also TODO: RCTSize parameters taken from SIMH rq source; how valid are these?
DriveInfo g_driveTable[21] {
// Name Blocks MediaID Model RCTSize Removable ReadOnly
{ "RX50", 800, 0x25658032, 7, 0, true, false },
{ "RX33", 2400, 0x25658021, 10, 0, true, false },
{ "RD51", 21600, 0x25644033, 6, 36, false, false },
{ "RD31", 41560, 0x2564401f, 12, 3, false, false },
{ "RC25", 50902, 0x20643019, 2, 0, true, false },
{ "RC25F", 50902, 0x20643319, 3, 0, true, false },
{ "RD52", 60480, 0x25644034, 8, 4, false, false },
{ "RD32", 83236, 0x25641047, 15, 4, false, false },
{ "RD53", 138672, 0x25644035, 9, 5, false, false },
{ "RA80", 237212, 0x20643019, 1, 0, false, false },
{ "RD54", 311200, 0x25644036, 13, 7, false, false },
{ "RA60", 400176, 0x22a4103c, 4, 1008, true, false },
{ "RA70", 547041, 0x20643019, 18, 198, false, false },
{ "RA81", 891072, 0x25641051, 5, 2856, false, false },
{ "RA82", 1216665, 0x25641052, 11, 3420, false, false },
{ "RA71", 1367310, 0x25641047, 40, 1428, false, false },
{ "RA72", 1953300, 0x25641048, 37, 2040, false, false },
{ "RA90", 2376153, 0x2564105a, 19, 1794, false, false },
{ "RA92", 2940951, 0x2564105c, 29, 949, false, false },
{ "RA73", 3920490, 0x25641049, 47, 198, false, false },
{ "", 0, 0, 0, 0, false, false }
};
{ "RX50", 800, 0x25658032, 7, 0, true, false }, { "RX33", 2400, 0x25658021, 10, 0,
true, false }, { "RD51", 21600, 0x25644033, 6, 36, false, false }, { "RD31",
41560, 0x2564401f, 12, 3, false, false }, { "RC25", 50902, 0x20643019, 2, 0,
true, false }, { "RC25F", 50902, 0x20643319, 3, 0, true, false }, { "RD52",
60480, 0x25644034, 8, 4, false, false }, { "RD32", 83236, 0x25641047, 15, 4,
false, false }, { "RD53", 138672, 0x25644035, 9, 5, false, false }, {
"RA80", 237212, 0x20643019, 1, 0, false, false }, { "RD54", 311200,
0x25644036, 13, 7, false, false }, { "RA60", 400176, 0x22a4103c, 4, 1008,
true, false }, { "RA70", 547041, 0x20643019, 18, 198, false, false }, {
"RA81", 891072, 0x25641051, 5, 2856, false, false }, { "RA82", 1216665,
0x25641052, 11, 3420, false, false }, { "RA71", 1367310, 0x25641047, 40,
1428, false, false },
{ "RA72", 1953300, 0x25641048, 37, 2040, false, false }, { "RA90", 2376153,
0x2564105a, 19, 1794, false, false }, { "RA92", 2940951, 0x2564105c, 29,
949, false, false }, { "RA73", 3920490, 0x25641049, 47, 198, false, false },
{ "", 0, 0, 0, 0, false, false } };
bool SetDriveType(const char* typeName);
void UpdateCapacity(void);
void UpdateMetadata(void);
DriveInfo _driveInfo;
bool _online;
uint32_t _unitDeviceNumber;
uint16_t _unitClassModel;
bool _useImageSize;
bool SetDriveType(const char* typeName);
void UpdateCapacity(void);
void UpdateMetadata(void);
DriveInfo _driveInfo;bool _online;
uint32_t _unitDeviceNumber;
uint16_t _unitClassModel;bool _useImageSize;
//
// RCT ("Replacement and Caching Table") data:
// The size of this area varies depending on the drive. This is
// provided only to appease software that expects the RCT to exist --
// since there will never be any bad sectors in our disk images
// there is no other purpose.
// This data is not persisted to disk as it is unnecessary.
//
unique_ptr<uint8_t> _rctData;
//
// RCT ("Replacement and Caching Table") data:
// The size of this area varies depending on the drive. This is
// provided only to appease software that expects the RCT to exist --
// since there will never be any bad sectors in our disk images
// there is no other purpose.
// This data is not persisted to disk as it is unnecessary.
//
unique_ptr<uint8_t> _rctData;
};

View File

@@ -71,9 +71,13 @@ mscp_server::mscp_server(
{
name.value = "mscp_server" ;
type_name.value = "mscp_server_c" ;
log_label = "MSSVR" ;
// Alias the port pointer. We do not own the port, we merely reference it.
_port = port;
enabled.set(true) ;
enabled.readonly = true ; // always active
StartPollingThread();
}
@@ -83,6 +87,18 @@ mscp_server::~mscp_server()
AbortPollingThread();
}
bool mscp_server::on_param_changed(parameter_c *param) {
// no own parameter or "enable" logic
if (param == &enabled) {
// accpet, but do not react on enable/disable, always active
return true ;
}
return device_c::on_param_changed(param) ; // more actions (for enable)
}
//
// StartPollingThread():
// Initializes the MSCP polling thread and starts it running.

View File

@@ -143,6 +143,7 @@ class mscp_server : public device_c
public:
mscp_server(uda_c *port);
~mscp_server();
bool on_param_changed(parameter_c *param) override ;
public:
void Reset(void);
@@ -153,7 +154,6 @@ public:
void on_power_changed(void) override {}
void on_init_changed(void) override {}
void worker(void) override {}
bool on_param_changed(parameter_c *param) override { UNUSED(param); return true; }
private:
uint32_t Abort(void);

View File

@@ -1,43 +1,43 @@
/* panels.cpp: a device to access lamps & buttons connected over I2C bus
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
12-nov-2018 JH entered beta phase
a device to access lamps & buttons connected over I2C bus
Up to 8 MC23017 GPIO extenders each with 16 I/Os can be connected.
a device to access lamps & buttons connected over I2C bus
Up to 8 MC23017 GPIO extenders each with 16 I/Os can be connected.
Other devices register some of their bit-Parameters with IOs.
Other devices register some of their bit-Parameters with IOs.
1 i2c driver - n panels
1 paneldriver - controls for many devices of same type (4 buttons for each of 4 RL02s)
1 control - identifed by unique combination of device name and control name
("rl1", "load_button")
device name ideally same as device-> name
control name ideally same as deviceparameter->name
1 i2c driver - n panels
1 paneldriver - controls for many devices of same type (4 buttons for each of 4 RL02s)
1 control - identifed by unique combination of device name and control name
("rl1", "load_button")
device name ideally same as device-> name
control name ideally same as deviceparameter->name
! Static list of panel controls is consntat,
but device parameters connected to controls is dynamic
(run time device creation/deletion)
! Static list of panel controls is consntat,
but device parameters connected to controls is dynamic
(run time device creation/deletion)
*/
#include <stdint.h>
@@ -159,6 +159,11 @@ paneldriver_c::~paneldriver_c() {
unregister_controls();
}
bool paneldriver_c::on_param_changed(parameter_c *param) {
// no own parameter logic
return device_c::on_param_changed(param);
}
/* low level access to I2C bus slaves */
// https://elinux.org/Interfacing_with_I2C_Devices#Opening_the_Bus
// result: true = success
@@ -208,7 +213,7 @@ bool paneldriver_c::i2c_write_byte(uint8_t slave_addr, uint8_t reg_addr, uint8_t
// reprogram the I2C chips and restart the worker
void paneldriver_c::reset(void) {
timeout_c timeout;
worker_stop();
enabled.set(false); // worker_stop();
// pulse "panel_reset_l"
// MC32017: at least 1 us
@@ -245,12 +250,7 @@ void paneldriver_c::reset(void) {
i2c_write_byte(slave_addr, MC23017_GPPUB, 0xff);
}
worker_start();
}
bool paneldriver_c::on_param_changed(parameter_c *param) {
UNUSED(param);
return true ;
enabled.set(true); // worker_start();
}
void paneldriver_c::on_power_changed(void) {
@@ -468,7 +468,7 @@ void paneldriver_c::test_moving_ones(void) {
clear_all_outputs();
timeout.wait_ms(delay_ms);
// all "OFF" on exit
worker_stop() ;
enabled.set(false); // worker_stop();
INFO("Worker stopped.");
}
@@ -490,7 +490,7 @@ void paneldriver_c::test_manual_loopback(void) {
(*it)->associate->value = (*it)->value;
timeout.wait_ms(10);
}
worker_stop() ;
enabled.set(false); // worker_stop();
INFO("Worker stopped.");
}

View File

@@ -1,28 +1,28 @@
/* panels.hpp: a device to access lamps & buttons connected over I2C bus
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
*/
12-nov-2018 JH entered beta phase
*/
#include "utils.hpp"
#include "device.hpp"
@@ -72,7 +72,7 @@ public:
unsigned value; // input or output
// output lamps can be linekd to their including buttons
panelcontrol_c *associate ;
panelcontrol_c *associate;
panelcontrol_c(string device_name, string control_name,
bool is_input, uint8_t chip_addr, uint8_t reg_addr, uint8_t reg_bitmask);
@@ -94,6 +94,8 @@ public:
paneldriver_c();
~paneldriver_c();
bool on_param_changed(parameter_c *param) override; // must implement
vector<panelcontrol_c *> controls;
void unregister_controls(void); // clear static list of all connected controls
@@ -112,16 +114,13 @@ public:
// background worker function
void worker(void) override;
bool on_param_changed(parameter_c *param) override; // must implement
void on_power_changed(void) override; // must implement
void on_init_changed(void) override; // must implement
void clear_all_outputs(void) ;
void clear_all_outputs(void);
// link_control_to_parameter(new panelcontrol_c()) ;
void link_control_to_parameter(parameter_c *deviceparameter,
panelcontrol_c *panelcontrol);
void link_control_to_parameter(parameter_c *deviceparameter, panelcontrol_c *panelcontrol);
void unlink_controls_from_device(device_c *device);
void refresh_params(device_c *device);

View File

@@ -1,10 +1,10 @@
/*
rk05.cpp: implementation of RK05 disk drive, attached to RK11D controller
rk05.cpp: implementation of RK05 disk drive, attached to RK11D controller
Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA.
Contributed under the BSD 2-clause license.
Copyright Vulcan Inc. 2019 via Living Computers: Museum + Labs, Seattle, WA.
Contributed under the BSD 2-clause license.
*/
*/
#include <assert.h>
@@ -16,304 +16,249 @@ using namespace std;
#include "rk05.hpp"
rk05_c::rk05_c(storagecontroller_c *controller) :
storagedrive_c(controller),
_current_cylinder(0),
_seek_count(0),
_sectorCount(0),
_wps(false),
_rwsrdy(true),
_dry(false),
_sok(false),
_sin(false),
_dru(false),
_rk05(true),
_dpl(false),
_scp(false)
{
type_name.value = "RK05" ;
log_label = "RK05";
_geometry.Cylinders = 203; // Standard RK05
_geometry.Heads = 2;
_geometry.Sectors = 12;
_geometry.Sector_Size_Bytes = 512;
storagedrive_c(controller), _current_cylinder(0), _seek_count(0), _sectorCount(0), _wps(
false), _rwsrdy(true), _dry(false), _sok(false), _sin(false), _dru(false), _rk05(
true), _dpl(false), _scp(false) {
name.value = "RK05";
type_name.value = "RK05";
log_label = "RK05";
_geometry.Cylinders = 203; // Standard RK05
_geometry.Heads = 2;
_geometry.Sectors = 12;
_geometry.Sector_Size_Bytes = 512;
}
//
// Status registers
//
uint32_t rk05_c::get_sector_counter(void)
{
return _sectorCount;
uint32_t rk05_c::get_sector_counter(void) {
return _sectorCount;
}
bool rk05_c::get_write_protect(void)
{
return _wps;
bool rk05_c::get_write_protect(void) {
return _wps;
}
bool rk05_c::get_rws_ready(void)
{
return _rwsrdy;
bool rk05_c::get_rws_ready(void) {
return _rwsrdy;
}
bool rk05_c::get_drive_ready(void)
{
return _dry;
bool rk05_c::get_drive_ready(void) {
return _dry;
}
bool rk05_c::get_sector_counter_ok(void)
{
return _sok;
bool rk05_c::get_sector_counter_ok(void) {
return _sok;
}
bool rk05_c::get_seek_incomplete(void)
{
return _sin;
bool rk05_c::get_seek_incomplete(void) {
return _sin;
}
bool rk05_c::get_drive_unsafe(void)
{
return _dru;
bool rk05_c::get_drive_unsafe(void) {
return _dru;
}
bool rk05_c::get_rk05_disk_online(void)
{
return _rk05;
bool rk05_c::get_rk05_disk_online(void) {
return _rk05;
}
bool rk05_c::get_drive_power_low(void)
{
return _dpl;
bool rk05_c::get_drive_power_low(void) {
return _dpl;
}
bool rk05_c::get_search_complete(void)
{
bool scp = _scp;
_scp = false;
return scp;
bool rk05_c::get_search_complete(void) {
bool scp = _scp;
_scp = false;
return scp;
}
bool rk05_c::on_param_changed(
parameter_c *param)
{
if (&image_filepath == param)
{
if (file_open(image_filepath.new_value, true))
{
_dry = true;
controller->on_drive_status_changed(this);
image_filepath.value = image_filepath.new_value;
return true;
}
}
return false;
bool rk05_c::on_param_changed(parameter_c *param) {
if (param == &enabled) {
if (!enabled.new_value) {
// disable switches power OFF.
drive_reset();
}
} else if (&image_filepath == param) {
if (file_open(image_filepath.new_value, true)) {
_dry = true;
controller->on_drive_status_changed(this);
image_filepath.value = image_filepath.new_value;
return true;
}
}
return storagedrive_c::on_param_changed(param); // more actions (for enable)
}
//
// Reset / Power handlers
//
void rk05_c::on_power_changed(void)
{
// called at high priority.
if (power_down)
{
// power-on defaults
drive_reset();
}
void rk05_c::on_power_changed(void) {
// called at high priority.
if (power_down) {
// power-on defaults
drive_reset();
}
}
void rk05_c::on_init_changed(void)
{
// called at high priority.
void rk05_c::on_init_changed(void) {
// called at high priority.
if (init_asserted)
{
drive_reset();
}
if (init_asserted) {
drive_reset();
}
}
//
// Disk actions (read/write/seek/reset)
//
void rk05_c::read_sector(
uint32_t cylinder,
uint32_t surface,
uint32_t sector,
uint16_t* out_buffer)
{
assert(cylinder < _geometry.Cylinders);
assert(surface < _geometry.Heads);
assert(sector < _geometry.Sectors);
void rk05_c::read_sector(uint32_t cylinder, uint32_t surface, uint32_t sector,
uint16_t* out_buffer) {
assert(cylinder < _geometry.Cylinders);
assert(surface < _geometry.Heads);
assert(sector < _geometry.Sectors);
_current_cylinder = cylinder;
_current_cylinder = cylinder;
// SCP is cleared at the start of any function.
_scp = false;
// SCP is cleared at the start of any function.
_scp = false;
//
// reset Read/Write/Seek Ready flag while we do this operation
//
_rwsrdy = false;
controller->on_drive_status_changed(this);
//
// reset Read/Write/Seek Ready flag while we do this operation
//
_rwsrdy = false;
controller->on_drive_status_changed(this);
timeout_c delay;
// Delay for seek / read.
// TODO: maybe base this on real drive specs.
delay.wait_ms(10);
timeout_c delay;
// Read the sector into the buffer passed to us.
file_read(
reinterpret_cast<uint8_t*>(out_buffer),
get_disk_byte_offset(cylinder, surface, sector),
_geometry.Sector_Size_Bytes);
// Delay for seek / read.
// TODO: maybe base this on real drive specs.
delay.wait_ms(10);
// Set RWS ready now that we're done.
_rwsrdy = true;
// Read the sector into the buffer passed to us.
file_read(reinterpret_cast<uint8_t*>(out_buffer),
get_disk_byte_offset(cylinder, surface, sector), _geometry.Sector_Size_Bytes);
controller->on_drive_status_changed(this);
// Set RWS ready now that we're done.
_rwsrdy = true;
controller->on_drive_status_changed(this);
}
void rk05_c::write_sector(
uint32_t cylinder,
uint32_t surface,
uint32_t sector,
uint16_t* in_buffer)
{
assert(cylinder < _geometry.Cylinders);
assert(surface < _geometry.Heads);
assert(sector < _geometry.Sectors);
void rk05_c::write_sector(uint32_t cylinder, uint32_t surface, uint32_t sector,
uint16_t* in_buffer) {
assert(cylinder < _geometry.Cylinders);
assert(surface < _geometry.Heads);
assert(sector < _geometry.Sectors);
_current_cylinder = cylinder;
_current_cylinder = cylinder;
// SCP is cleared at the start of any function.
_scp = false;
// SCP is cleared at the start of any function.
_scp = false;
//
// reset Read/Write/Seek Ready flag while we do this operation
//
_rwsrdy = false;
controller->on_drive_status_changed(this);
//
// reset Read/Write/Seek Ready flag while we do this operation
//
_rwsrdy = false;
controller->on_drive_status_changed(this);
timeout_c delay;
timeout_c delay;
// Delay for seek / read.
// TODO: maybe base this on real drive specs.
delay.wait_ms(10);
// Delay for seek / read.
// TODO: maybe base this on real drive specs.
delay.wait_ms(10);
// Read the sector into the buffer passed to us.
file_write(
reinterpret_cast<uint8_t*>(in_buffer),
get_disk_byte_offset(cylinder, surface, sector),
_geometry.Sector_Size_Bytes);
// Read the sector into the buffer passed to us.
file_write(reinterpret_cast<uint8_t*>(in_buffer),
get_disk_byte_offset(cylinder, surface, sector), _geometry.Sector_Size_Bytes);
// Set RWS ready now that we're done.
_rwsrdy = true;
// Set RWS ready now that we're done.
_rwsrdy = true;
controller->on_drive_status_changed(this);
controller->on_drive_status_changed(this);
}
void rk05_c::seek(
uint32_t cylinder)
{
assert(cylinder < _geometry.Cylinders);
void rk05_c::seek(uint32_t cylinder) {
assert(cylinder < _geometry.Cylinders);
_seek_count = abs((int32_t)_current_cylinder - (int32_t)cylinder) + 1;
_current_cylinder = cylinder;
_seek_count = abs((int32_t) _current_cylinder - (int32_t) cylinder) + 1;
_current_cylinder = cylinder;
if (_seek_count > 0)
{
// We'll be busy for awhile:
_rwsrdy = false;
_scp = false;
}
else
{
_rwsrdy = true;
_scp = true;
}
controller->on_drive_status_changed(this);
if (_seek_count > 0) {
// We'll be busy for awhile:
_rwsrdy = false;
_scp = false;
} else {
_rwsrdy = true;
_scp = true;
}
controller->on_drive_status_changed(this);
}
void rk05_c::set_write_protect(bool protect)
{
UNUSED(protect);
void rk05_c::set_write_protect(bool protect) {
UNUSED(protect);
// Not implemented at the moment.
_scp = false;
// Not implemented at the moment.
_scp = false;
}
void rk05_c::drive_reset(void)
{
//
// "The controller directs the selected disk drive to move its
// head mechanism to cylinder address 000 and reset all active
// error status lines."
//
// This is basically the same as a seek to cylinder 0 plus
// a reset of error status.
//
_sin = false;
_dru = false;
_dpl = false;
controller->on_drive_status_changed(this);
void rk05_c::drive_reset(void) {
//
// "The controller directs the selected disk drive to move its
// head mechanism to cylinder address 000 and reset all active
// error status lines."
//
// This is basically the same as a seek to cylinder 0 plus
// a reset of error status.
//
_sin = false;
_dru = false;
_dpl = false;
controller->on_drive_status_changed(this);
seek(0);
// SCP change will be posted when the seek instigated above is completed.
seek(0);
// SCP change will be posted when the seek instigated above is completed.
}
void rk05_c::worker(void)
{
timeout_c timeout;
void rk05_c::worker(void) {
timeout_c timeout;
while(true)
{
if (_seek_count > 0)
{
// A seek is active. Wait at least 10ms and decrement
// The seek count by a certain amount. This is completely fudged.
timeout.wait_ms(3);
_seek_count -= 25;
// since simultaneous interrupts
// confuse me right now
while (true) {
if (_seek_count > 0) {
// A seek is active. Wait at least 10ms and decrement
// The seek count by a certain amount. This is completely fudged.
timeout.wait_ms(3);
_seek_count -= 25;
// since simultaneous interrupts
// confuse me right now
if (_seek_count < 0)
{
// Out of seeks to do, let the controller know we're done.
_scp = true;
controller->on_drive_status_changed(this);
if (_seek_count < 0) {
// Out of seeks to do, let the controller know we're done.
_scp = true;
controller->on_drive_status_changed(this);
// Set RWSRDY only after posting status change / interrupt...
_rwsrdy = true;
}
}
else
{
// Move SectorCounter to next sector
// every 1/300th of a second (or so).
// (1600 revs/min = 25 revs / sec = 300 sectors / sec)
timeout.wait_ms(3);
if (file_is_open())
{
_sectorCount = (_sectorCount + 1) % 12;
_sok = true;
controller->on_drive_status_changed(this);
}
}
}
// Set RWSRDY only after posting status change / interrupt...
_rwsrdy = true;
}
} else {
// Move SectorCounter to next sector
// every 1/300th of a second (or so).
// (1600 revs/min = 25 revs / sec = 300 sectors / sec)
timeout.wait_ms(3);
if (file_is_open()) {
_sectorCount = (_sectorCount + 1) % 12;
_sok = true;
controller->on_drive_status_changed(this);
}
}
}
}
uint64_t rk05_c::get_disk_byte_offset(
uint32_t cylinder,
uint32_t surface,
uint32_t sector)
{
return _geometry.Sector_Size_Bytes *
((cylinder * _geometry.Heads * _geometry.Sectors) +
(surface * _geometry.Sectors) +
sector);
uint64_t rk05_c::get_disk_byte_offset(uint32_t cylinder, uint32_t surface, uint32_t sector) {
return _geometry.Sector_Size_Bytes
* ((cylinder * _geometry.Heads * _geometry.Sectors) + (surface * _geometry.Sectors)
+ sector);
}

View File

@@ -93,7 +93,7 @@ public:
rk05_c(storagecontroller_c *controller);
bool on_param_changed(parameter_c* param) override;
bool on_param_changed(parameter_c* param) override;
void on_power_changed(void) override;

View File

@@ -25,10 +25,8 @@ rk11_c::rk11_c() :
name.value = "rk";
type_name.value = "RK11";
log_label = "rk";
default_base_addr = 0777400; // overwritten in install()?
default_intr_vector = 0220; // TODO: make configurable
default_intr_level = 5; // TODO: make configurable
set_default_bus_params(0777400, 0220, 5) ; // base addr, intr-vector, intr level
// The RK11 controller has seven registers,
// We allocate 8 because one address in the address space is unused.
@@ -117,6 +115,14 @@ rk11_c::~rk11_c()
}
}
// return false, if illegal parameter value.
// verify "new_value", must output error messages
bool rk11_c::on_param_changed(parameter_c *param) {
// no own parameter or "enable" logic
return storagecontroller_c::on_param_changed(param) ; // more actions (for enable)
}
void rk11_c::dma_transfer(DMARequest &request)
{
timeout_c timeout;

View File

@@ -162,6 +162,8 @@ public:
unibusdevice_register_t *device_reg,
uint8_t unibus_control) override;
bool on_param_changed(parameter_c *param) override;
void on_power_changed(void) override;
void on_init_changed(void) override;

View File

@@ -1,28 +1,28 @@
/* rl02.cpp: implementation of RL01/RL02 disk drive, attached to RL11 controller
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
*/
12-nov-2018 JH entered beta phase
*/
#include <assert.h>
@@ -44,22 +44,28 @@ RL0102_c::RL0102_c(storagecontroller_c *controller) :
}
// return false, if illegal parameter value.
// verify "new_value", must output error messages
bool RL0102_c::on_param_changed(parameter_c *param) {
if (param == &type_name) {
if (param == &enabled) {
if (!enabled.new_value) {
// disable switches power OFF.
// must be power on by caller or user after enable
power_switch.value = false;
change_state(RL0102_STATE_power_off);
}
} else if (param == &type_name) {
if (!strcasecmp(type_name.new_value.c_str(), "RL01"))
set_type(1) ;
set_type(1);
else if (!strcasecmp(type_name.new_value.c_str(), "RL02"))
set_type(2) ;
set_type(2);
else {
// throw bad_parameter_check("drive type must be RL01 or RL02") ;
ERROR("drive type must be RL01 or RL02") ;
return false ;
ERROR("drive type must be RL01 or RL02");
return false;
}
}
return true ;
return storagedrive_c::on_param_changed(param); // more actions (for enable)
}
void RL0102_c::set_type(uint8_t drivetype) {
@@ -168,15 +174,15 @@ void RL0102_c::change_state(unsigned new_state) {
state.value = new_state;
update_status_word(); // contains state
if (old_state != new_state)
DEBUG("Change drive %s state from %d to %d. Status word %06o -> %06o.", name.value.c_str(),
old_state, state.value, old_status_word, status_word);
DEBUG("Change drive %s state from %d to %d. Status word %06o -> %06o.",
name.value.c_str(), old_state, state.value, old_status_word, status_word);
}
/*** state functions, called repeatedly ***/
void RL0102_c::state_power_off() {
// drive_ready_line = false; // verified
// drive_error_line = true; // real RL02: RL11 show a DRIVE ERROR after power on / DC_LO
type_name.readonly = false ; // may be changed between RL01/RL02
type_name.readonly = false; // may be changed between RL01/RL02
volume_check = true; // starts with volume check?
cover_open.readonly = true;
update_status_word(/*drive_ready_line*/false, /*drive_error_line*/true);
@@ -195,7 +201,7 @@ void RL0102_c::state_power_off() {
// drive stop, door unlocked, cartridge can be loaded
void RL0102_c::state_load_cartridge() {
// drive_ready_line = false; // verified
type_name.readonly = true ; // must be powered of to changed between RL01/RL02
type_name.readonly = true; // must be powered of to changed between RL01/RL02
update_status_word(/*drive_ready_line*/false, drive_error_line);
load_lamp.value = 1;
ready_lamp.value = 0;
@@ -256,7 +262,7 @@ void RL0102_c::state_spin_up() {
load_lamp.value = 0;
ready_lamp.value = 0;
writeprotect_lamp.value = writeprotect_button.value || file_readonly ;
writeprotect_lamp.value = writeprotect_button.value || file_readonly;
image_filepath.readonly = true; // "door locked", disk can not be changed
state_timeout.wait_ms(calcperiod_ms);
@@ -377,7 +383,7 @@ void RL0102_c::state_lock_on() {
load_lamp.value = 0;
ready_lamp.value = 1;
writeprotect_lamp.value = writeprotect_button.value|| file_readonly;
writeprotect_lamp.value = writeprotect_button.value || file_readonly;
// fast polling, if ZRLI tests time of 0 cly seek with head switch
state_timeout.wait_ms(1);
@@ -501,7 +507,7 @@ bool RL0102_c::header_on_track(uint16_t header) {
} while(0)
#ifdef OLD
#define NEXT_SECTOR_SEGMENT_ADVANCE do { \
#define NEXT_SECTOR_SEGMENT_ADVANCE do { \
next_sector_segment_under_heads = (next_sector_segment_under_heads + 1) % 80 ; \
if (next_sector_segment_under_heads & 1) \
/* time to pass one sector 600 data, 25 header? */ \
@@ -526,7 +532,8 @@ bool RL0102_c::cmd_read_next_sector_header(uint16_t *buffer, unsigned buffer_siz
if (next_sector_segment_under_heads & 1) {
// odd: next is data, let it pass the head
NEXT_SECTOR_SEGMENT_ADVANCE;
NEXT_SECTOR_SEGMENT_ADVANCE
;
// nanosleep() for rotational delay?
}
@@ -542,7 +549,8 @@ bool RL0102_c::cmd_read_next_sector_header(uint16_t *buffer, unsigned buffer_siz
buffer[2] = calc_crc(2, &buffer[0]); // header CRC
// circular advance to next header: 40x headers, 40x data
NEXT_SECTOR_SEGMENT_ADVANCE;
NEXT_SECTOR_SEGMENT_ADVANCE
;
// nanosleep() for rotational delay?
return true;
}
@@ -558,7 +566,8 @@ bool RL0102_c::cmd_read_next_sector_data(uint16_t *buffer, unsigned buffer_size_
if (!(next_sector_segment_under_heads & 1)) {
// even: next segment is header, let it pass the head
NEXT_SECTOR_SEGMENT_ADVANCE;
NEXT_SECTOR_SEGMENT_ADVANCE
;
// nanosleep() for rotational delay?
}
unsigned sectorno = next_sector_segment_under_heads >> 1; // LSB is header/data phase
@@ -570,13 +579,12 @@ bool RL0102_c::cmd_read_next_sector_data(uint16_t *buffer, unsigned buffer_size_
// LSB saved before MSB -> word/byte conversion on ARM (little endian) is easy
file_read((uint8_t *) buffer, offset, sector_size_bytes);
DEBUG("File Read 0x%x words from c/h/s=%d/%d/%d, file pos=0x%llx, words = %06o, %06o, ...",
sector_size_bytes/2,
cylinder,head, sectorno,
offset, (unsigned)(buffer[0]), (unsigned)(buffer[1])) ;
sector_size_bytes / 2, cylinder, head, sectorno, offset, (unsigned )(buffer[0]),
(unsigned )(buffer[1]));
// circular advance to next header: 40x headers, 40x data
NEXT_SECTOR_SEGMENT_ADVANCE;
NEXT_SECTOR_SEGMENT_ADVANCE
;
// nanosleep() for rotational delay?
return true;
}
@@ -600,7 +608,8 @@ bool RL0102_c::cmd_write_next_sector_data(uint16_t *buffer, unsigned buffer_size
if (!(next_sector_segment_under_heads & 1)) {
// even: next segment is header, let it pass the head
NEXT_SECTOR_SEGMENT_ADVANCE;
NEXT_SECTOR_SEGMENT_ADVANCE
;
// nanosleep() for rotational delay?
}
unsigned sectorno = next_sector_segment_under_heads >> 1; // LSB is header/data phase
@@ -612,13 +621,12 @@ bool RL0102_c::cmd_write_next_sector_data(uint16_t *buffer, unsigned buffer_size
// LSB saved before MSB -> word/byte conversion on ARM (little endian) is easy
file_write((uint8_t *) buffer, offset, sector_size_bytes);
DEBUG("File Write 0x%x words from c/h/s=%d/%d/%d, file pos=0x%llx, words = %06o, %06o, ...",
sector_size_bytes/2,
cylinder,head, sectorno,
offset, (unsigned)(buffer[0]), (unsigned)(buffer[1])) ;
sector_size_bytes / 2, cylinder, head, sectorno, offset, (unsigned )(buffer[0]),
(unsigned )(buffer[1]));
// circular advance to next header: 40x headers, 40x data
NEXT_SECTOR_SEGMENT_ADVANCE;
NEXT_SECTOR_SEGMENT_ADVANCE
;
// nanosleep() for rotational delay?
return true;
@@ -637,8 +645,13 @@ void RL0102_c::worker(void) {
update_status_word(drive_ready_line, drive_error_line);
// global stuff for all states
if (enabled.value && (!controller || !controller->enabled.value))
// RL drive powered, but no controller: no clock -> FAULT
fault_lamp.value = true;
if (power_switch.value == false)
change_state(RL0102_STATE_power_off);
switch (state.value) {
case RL0102_STATE_power_off:
state_power_off();

View File

@@ -1,28 +1,28 @@
/* rl02.cpp: implementation of RL01/RL02 disk drive, attached to RL11 controller
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
*/
12-nov-2018 JH entered beta phase
*/
#ifndef _RL0102_HPP_
#define _RL0102_HPP_
@@ -120,8 +120,8 @@ public:
volatile uint16_t status_word; // visible to controller
parameter_unsigned_c rotation_umin = parameter_unsigned_c(this, "rotation",
"rot",/*readonly*/true, "rpm", "%d", "Current speed of disk", 32, 10);
parameter_unsigned_c rotation_umin = parameter_unsigned_c(this, "rotation", "rot",/*readonly*/
true, "rpm", "%d", "Current speed of disk", 32, 10);
// RL0102_STATE_*. no enum, is param
parameter_unsigned_c state = parameter_unsigned_c(this, "state", "st", /*readonly*/
true, "", "%d", "Internal state", 32, 10);
@@ -137,17 +137,16 @@ public:
true, "State of READY lamp");
parameter_bool_c fault_lamp = parameter_bool_c(this, "faultlamp", "fl", /*readonly*/
true, "State of FAULT lamp");
parameter_bool_c writeprotect_lamp = parameter_bool_c(this, "writeprotectlamp",
"wpl", /*readonly*/true, "State of WRITE PROTECT lamp");
parameter_bool_c writeprotect_button = parameter_bool_c(this,
"writeprotectbutton", "wpb", /*readonly*/false, "Writeprotect button pressed");
parameter_bool_c writeprotect_lamp = parameter_bool_c(this, "writeprotectlamp", "wpl", /*readonly*/
true, "State of WRITE PROTECT lamp");
parameter_bool_c writeprotect_button = parameter_bool_c(this, "writeprotectbutton", "wpb", /*readonly*/
false, "Writeprotect button pressed");
// cover normally always "closed", need to get opened for ZRLI
parameter_bool_c cover_open = parameter_bool_c(this, "coveropen", "co", /*readonly*/
false, "1, if RL cover is open");
// not readonly only in "load" state
RL0102_c(storagecontroller_c *controller);
bool on_param_changed(parameter_c *param) override;

View File

@@ -1,86 +1,86 @@
/* rl11.cpp: Implementation of the RL11 controller
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
12-nov-2018 JH entered beta phase
- implements a 4 UNIBUS register interface, which are shared with PRU.
- gets notified of UNIBUS register access on_after_register_access()
- starts 4 RL01/02 drives
on_after_register_access() is a high priority RT thread.
It may ONLY update the settings of UNIBUS interface registers by swapping in
several internal registers (status for each drive, MP multipuprpose for different
Commands) to UNIBUS registers.
- execution of commands, access to drives etc is made in worker()
worker() is waked by a signal from on_after_register_access()
- implements a 4 UNIBUS register interface, which are shared with PRU.
- gets notified of UNIBUS register access on_after_register_access()
- starts 4 RL01/02 drives
on_after_register_access() is a high priority RT thread.
It may ONLY update the settings of UNIBUS interface registers by swapping in
several internal registers (status for each drive, MP multipuprpose for different
Commands) to UNIBUS registers.
- execution of commands, access to drives etc is made in worker()
worker() is waked by a signal from on_after_register_access()
Todo:
- operation, when drive power OFF? error DSE drive select?
1) RL0 powered off: CS =200, nach NOP
Get status; DA=013, write 00004 Read: CS 102204 (ERR,OPI, DRVready=0
MP = 006050 (3x identisch)
Seek: Write 0006, read: 102206 (Spnerror, coveropen,brush home)
MP = 20210
2) RL0 powered on, LOAD
NOOP: CS write 0, read 200
Get Status: DA=013,write 0004, read 204. MP = 20217 (spinn down), dann 20210 (LOAD)
Seek: Write CS=0006 , read 102206. MP = unchanged
3) RL02 on track
NOOP: CS write 0, read 140201 (Driverer, do Getstatus
Get Status: DA=013, write 0004, read 205. MP = 020235
Seek: DA=0377 (255)100, read = 207
Todo:
- operation, when drive power OFF? error DSE drive select?
1) RL0 powered off: CS =200, nach NOP
Get status; DA=013, write 00004 Read: CS 102204 (ERR,OPI, DRVready=0
MP = 006050 (3x identisch)
Seek: Write 0006, read: 102206 (Spnerror, coveropen,brush home)
MP = 20210
2) RL0 powered on, LOAD
NOOP: CS write 0, read 200
Get Status: DA=013,write 0004, read 204. MP = 20217 (spinn down), dann 20210 (LOAD)
Seek: Write CS=0006 , read 102206. MP = unchanged
3) RL02 on track
NOOP: CS write 0, read 140201 (Driverer, do Getstatus
Get Status: DA=013, write 0004, read 205. MP = 020235
Seek: DA=0377 (255)100, read = 207
- Which errors raise "OPI" (operation incomplete)
NXM?
- Mismatch DMA wordcount and sector buffer
word len != sector border ? => no problem?
end of track before worldcount == 0 ?
"DA register is not incrmeneted in multisector transfer"
=> OPI?
- Which errors raise "OPI" (operation incomplete)
NXM?
- Mismatch DMA wordcount and sector buffer
word len != sector border ? => no problem?
end of track before worldcount == 0 ?
"DA register is not incrmeneted in multisector transfer"
=> OPI?
- "Read header: ": select which sector to read?
Simulate disk rotation???
How to generate CRC? -> simh!
- "Read header: ": select which sector to read?
Simulate disk rotation???
How to generate CRC? -> simh!
- "read data without header"
-> wait for sector pulse? disk rotation?
- "read data without header"
-> wait for sector pulse? disk rotation?
Communication between on_after_register_access and worker():
- use pthread condition variable pthrad_cond_*
- normally a mutex show protect worker() against variable change
by interrupting on_after_register_access()
- the signal "controller_ready" is that mutex already:
set by cmd-start in on_after_register_access(),
released by worker() on completion
- still a mutex needed, only for the thread condition variable as shown in
- mutex in on_after_register_access() and worker()
- all refgsier access are atomic 32bit anyhow
http://openbook.rheinwerk-verlag.de/linux_unix_programmierung/Kap10-006.htm#RxxKap10006040003201F02818E
https://docs.oracle.com/cd/E19455-01/806-5257/6je9h032r/index.html#sync-59145
Communication between on_after_register_access and worker():
- use pthread condition variable pthrad_cond_*
- normally a mutex show protect worker() against variable change
by interrupting on_after_register_access()
- the signal "controller_ready" is that mutex already:
set by cmd-start in on_after_register_access(),
released by worker() on completion
- still a mutex needed, only for the thread condition variable as shown in
- mutex in on_after_register_access() and worker()
- all refgsier access are atomic 32bit anyhow
http://openbook.rheinwerk-verlag.de/linux_unix_programmierung/Kap10-006.htm#RxxKap10006040003201F02818E
https://docs.oracle.com/cd/E19455-01/806-5257/6je9h032r/index.html#sync-59145
*/
@@ -136,9 +136,7 @@ RL11_c::RL11_c(void) :
type_name.value = "RL11";
log_label = "rl";
default_base_addr = 0774400;
default_intr_vector = 0160;
default_intr_level = 5;
set_default_bus_params(0774400, 0160, 5) ; // base addr, intr-vector, intr level
// add 4 RL disk drives
drivecount = 4;
@@ -194,6 +192,19 @@ RL11_c::~RL11_c() {
delete storagedrives[i];
}
bool RL11_c::on_param_changed(parameter_c *param) {
if (param == &enabled) {
if (enabled.new_value) {
// enabled
connect_to_panel();
} else {
// disabled
disconnect_from_panel();
}
}
return storagecontroller_c::on_param_changed(param); // more actions (for enable)
}
/* connect parameters of drives to i2c paneldriver
* Changes to parameter values after user panel operation
* or refresh_params_from_panel()
@@ -451,12 +462,6 @@ void RL11_c::on_after_register_access(unibusdevice_register_t *device_reg,
// now SSYN goes inactive !
}
bool RL11_c::on_param_changed(parameter_c *param) {
UNUSED(param) ;
return true ;
}
void RL11_c::on_power_changed(void) {
// storagecontroller_c forwards to drives
storagecontroller_c::on_power_changed();
@@ -718,25 +723,25 @@ void RL11_c::state_readwrite() {
//logger.debug_hexdump(LC_RL, "Read data between disk access and DMA",
// (uint8_t *) silo, sizeof(silo), NULL);
// start DMA transmission of SILO into memory
error_dma_timeout = !unibusadapter->request_client_DMA(UNIBUS_CONTROL_DATO, unibus_address, silo,
dma_wordcount, &unibus_address);
error_dma_timeout = !unibusadapter->request_client_DMA(UNIBUS_CONTROL_DATO,
unibus_address, silo, dma_wordcount, &unibus_address);
} else if (function_code == CMD_WRITE_CHECK) {
// read sector data to compare with sector data
drive->cmd_read_next_sector_data(silo, 128);
// logger.debug_hexdump(LC_RL, "Read data between disk access and DMA",
// (uint8_t *) silo, sizeof(silo), NULL);
// start DMA transmission of memory to compare with SILO
error_dma_timeout = !unibusadapter->request_client_DMA(UNIBUS_CONTROL_DATI, unibus_address, silo_compare,
dma_wordcount, &unibus_address);
error_dma_timeout = !unibusadapter->request_client_DMA(UNIBUS_CONTROL_DATI,
unibus_address, silo_compare, dma_wordcount, &unibus_address);
} else if (function_code == CMD_WRITE_DATA) {
// start DMA transmission of memory into SILO
error_dma_timeout = !unibusadapter->request_client_DMA(UNIBUS_CONTROL_DATI, unibus_address, silo,
dma_wordcount, &unibus_address);
error_dma_timeout = !unibusadapter->request_client_DMA(UNIBUS_CONTROL_DATI,
unibus_address, silo, dma_wordcount, &unibus_address);
}
// request_client_DMA() was blocking, DMA processed now.
// unibus_address updated to last accesses address
unibus_address += 2; // was last address, is now next to fill
// request_client_DMA() was blocking, DMA processed now.
// unibus_address updated to last accesses address
unibus_address += 2; // was last address, is now next to fill
// if timeout: addr AFTER illegal address (verified)
update_unibus_address(unibus_address); // set addr msb to cs

View File

@@ -1,40 +1,40 @@
/* rl11.hpp: Implementation of the RL11 controller
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
*/
12-nov-2018 JH entered beta phase
*/
#ifndef _RL11_HPP_
#define _RL11_HPP_
#include "storagecontroller.hpp"
class RL0102_c ;
class RL0102_c;
class RL11_c: public storagecontroller_c {
private:
/** everything sharead between different threads must be "volatile" */
volatile unsigned state; // one of RL11_STATE_*
timeout_c timeout ;
timeout_c timeout;
// which drive will communicate with the controeller via the drive bus.
volatile uint8_t selected_drive_unitno;
@@ -47,8 +47,8 @@ private:
volatile bool error_operation_incomplete; // OPI. operation aborted because of error
volatile bool error_dma_timeout; // DMA operation addresses non existing memory
volatile bool error_writecheck ; // mismatch between memory and disk data
volatile bool error_header_not_found ; // sector address notfound on track
volatile bool error_writecheck; // mismatch between memory and disk data
volatile bool error_header_not_found; // sector address notfound on track
// after "read header": MP register show different values on successive reads
volatile uint16_t mpr_silo[3]; // max 3 word deep buffer
@@ -66,12 +66,10 @@ private:
void update_unibus_address(uint32_t addr);
uint16_t get_MP_wordcount(void);
void set_MP_wordcount(uint16_t wordcount);
void set_MP_dati_value(uint16_t w, const char *debug_info) ;
void set_MP_dati_silo(const char *debug_info) ;
void set_MP_dati_value(uint16_t w, const char *debug_info);
void set_MP_dati_silo(const char *debug_info);
void clear_errors(void) ;
void clear_errors(void);
// controller can accept commands again
void do_command_done(void);
@@ -80,10 +78,14 @@ private:
void do_operation_incomplete(const char *info);
// state machines
void change_state(unsigned new_state) ;
void change_state(unsigned new_state);
void state_seek(void);
void state_readwrite(void);
void connect_to_panel(void);
void disconnect_from_panel(void);
void refresh_params_from_panel(void);
public:
// register interface to RL11 controller
@@ -95,27 +97,23 @@ public:
RL11_c(void);
~RL11_c(void);
bool on_param_changed(parameter_c *param) override;
void reset(void);
void connect_to_panel(void) ;
void disconnect_from_panel(void) ;
void refresh_params_from_panel(void) ;
// background worker function
void worker(void) override;
RL0102_c *selected_drive(void) ;
RL0102_c *selected_drive(void);
// 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)
override;
bool on_param_changed(parameter_c *param) override;
void on_power_changed(void) override;
void on_init_changed(void) override;
void on_drive_status_changed(storagedrive_c *drive) ;
void on_drive_status_changed(storagedrive_c *drive);
};
#endif

View File

@@ -46,9 +46,7 @@ uda_c::uda_c() :
type_name.value = "UDA50";
log_label = "uda";
default_base_addr = 0772150;
default_intr_vector = 0154;
default_intr_level = 5;
set_default_bus_params(0772150, 0154, 5) ; // base addr, intr-vector, intr level
// The UDA50 controller has two registers.
register_count = 2;
@@ -94,6 +92,12 @@ uda_c::~uda_c()
storagedrives.clear();
}
bool uda_c::on_param_changed(parameter_c *param) {
// no own parameter or "enable" logic
return storagecontroller_c::on_param_changed(param) ; // more actions (for enable)
}
//
// Reset():
// Resets the UDA controller state.

View File

@@ -61,6 +61,8 @@ public:
uda_c();
virtual ~uda_c();
bool on_param_changed(parameter_c *param) override;
void worker(void) override;
void on_after_register_access(

View File

@@ -41,8 +41,8 @@
#include "unibus.h"
#include "memoryimage.hpp"
#include "unibusadapter.hpp"
#include "unibusdevice.hpp"
//#include "unibusadapter.hpp"
//#include "unibusdevice.hpp"
#include "devexer_rl.hpp"
@@ -60,12 +60,8 @@ void application_c::menu_device_exercisers(void) {
// UNIBUS activity
hardware_startup(pru_c::PRUCODE_UNIBUS);
buslatches_output_enable(true);
// no device emulation, no CPU arbitration
unibusadapter->worker_stop();
//unibus->arbitrator_client = false;
// instantiate differebt device exercisers
// instantiate different device exercisers
devexer::rl_c rl;
USE(rl); // not accessed diretcly, but is registered to exerciser list

View File

@@ -85,10 +85,6 @@ static void load_memory(enum unibus_c::arbitration_mode_enum arbitration_mode, c
// CPU is enabled, act as ARBITRATION_MASTER
void application_c::menu_devices(bool with_CPU) {
/** list of usable devices ***/
bool with_DEMOIO = true;
bool with_RL = true;
bool with_RK = true; // SIGINT on exit?
bool with_MSCP = true;
bool with_storage_file_test = false;
enum unibus_c::arbitration_mode_enum arbitration_mode;
@@ -117,54 +113,27 @@ void application_c::menu_devices(bool with_CPU) {
// now PRU executing UNIBUS master/slave code, physical PDP-11 CPU as arbitrator required.
buslatches_output_enable(true);
unibusadapter->worker_start();
unibusadapter->enabled.set(true);
// 2 demo controller
cur_device = NULL;
demo_io_c *demo_io = NULL;
demo_io_c *demo_io = new demo_io_c();
//demo_regs_c demo_regs; // mem at 160000: RT11 crashes?
cpu_c *cpu = NULL;
RL11_c *RL11 = NULL;
// create RL11 + also 4 RL01/02 drives
RL11_c *RL11 = new RL11_c();
paneldriver->reset(); // reset I2C, restart worker()
rk11_c *RK11 = NULL;
uda_c *UDA50 = NULL;
if (with_DEMOIO) {
demo_io = new demo_io_c();
demo_io->install();
demo_io->worker_start();
}
// create RK11 + drives
rk11_c *RK11 = new rk11_c();
// Create UDA50
uda_c *UDA50 = new uda_c();
// //demo_regs.install();
// //demo_regs.worker_start();
if (with_RL) {
// create RL11 + drives
// instantiates also 4 RL01/02 drives
RL11 = new RL11_c();
RL11->install();
RL11->connect_to_panel();
RL11->worker_start();
}
if (with_RK) {
// create RK11 + drives
RK11 = new rk11_c();
RK11->install();
RK11->worker_start();
}
if (with_CPU) {
cpu = new cpu_c();
cpu->install();
cpu->worker_start();
}
if (with_MSCP) {
// Create UDA50
UDA50 = new uda_c();
UDA50->install();
UDA50->worker_start();
cpu->enabled.set(true);
}
if (with_storage_file_test) {
@@ -206,7 +175,10 @@ void application_c::menu_devices(bool with_CPU) {
printf("m ll Reload last memory content from file \"%s\"\n",
memory_filename);
printf("ld List all defined devices\n");
printf("en <dev> Enable a device\n");
printf("dis <dev> Disable device\n");
printf("sd <dev> Select \"current device\"\n");
if (cur_device) {
printf("p <param> <val> Set parameter value of current device\n");
printf("p <param> Get parameter value of current device\n");
@@ -300,25 +272,54 @@ void application_c::menu_devices(bool with_CPU) {
// m ll
load_memory(arbitration_mode, memory_filename, "start");
} else if (!strcasecmp(s_opcode, "ld") && n_fields == 1) {
unsigned n;
list<device_c *>::iterator it;
cout << "Registered devices:\n";
for (it = device_c::mydevices.begin(); it != device_c::mydevices.end(); ++it) {
cout << "- " << (*it)->name.value << " (type is " << (*it)->type_name.value
<< ")\n";
if ((*it)->name.value.empty())
printf("EMPTY\n");
}
} else if (!strcasecmp(s_opcode, "sd") && n_fields == 2) {
list<device_c *>::iterator it;
bool found = false;
for (it = device_c::mydevices.begin(); it != device_c::mydevices.end(); ++it)
if (!strcasecmp((*it)->name.value.c_str(), s_param[0])) {
found = true;
cur_device = *it;
for (n = 0, it = device_c::mydevices.begin(); it != device_c::mydevices.end();
++it)
if ((*it)->enabled.value) {
if (n == 0)
cout << "Enabled devices:\n";
n++;
cout << "- " << (*it)->name.value << " (type is "
<< (*it)->type_name.value << ")\n";
// if ((*it)->name.value.empty())
// printf("EMPTY\n");
}
if (!found)
if (n == 0)
cout << "No enabled devices.\n";
for (n = 0, it = device_c::mydevices.begin(); it != device_c::mydevices.end();
++it)
if (!(*it)->enabled.value) {
if (n == 0)
cout << "Disabled devices:\n";
n++;
cout << "- " << (*it)->name.value << " (type is "
<< (*it)->type_name.value << ")\n";
}
if (n == 0)
cout << "No disabled devices.\n";
} else if (!strcasecmp(s_opcode, "en") && n_fields == 2) {
device_c *dev = device_c::find_by_name(s_param[0]);
if (!dev) {
cout << "Device \"" << s_param[0] << "\" not found.\n";
else {
show_help = true;
} else
dev->enabled.set(true);
} else if (!strcasecmp(s_opcode, "dis") && n_fields == 2) {
device_c *dev = device_c::find_by_name(s_param[0]);
if (!dev) {
cout << "Device \"" << s_param[0] << "\" not found.\n";
show_help = true;
} else
dev->enabled.set(false);
} else if (!strcasecmp(s_opcode, "sd") && n_fields == 2) {
cur_device = device_c::find_by_name(s_param[0]);
if (!cur_device) {
cout << "Device \"" << s_param[0] << "\" not found.\n";
show_help = true;
} else {
printf("Current device is \"%s\"\n", cur_device->name.value.c_str());
// find base address of assoiated UNIBUS unibuscontroller
if (cur_device != NULL && dynamic_cast<unibusdevice_c *>(cur_device))
@@ -383,7 +384,7 @@ void application_c::menu_devices(bool with_CPU) {
printf("Bus timeout at %06o.\n", mailbox->dma.cur_addr);
} else if (unibuscontroller && !strcasecmp(s_opcode, "e") && n_fields <= 2) {
unsigned blocksize = 0; // default: no EXAM
bool timeout ;
bool timeout;
uint32_t addr;
unibusdevice_register_t *reg;
if (n_fields == 2) { // single reg number given
@@ -410,7 +411,7 @@ void application_c::menu_devices(bool with_CPU) {
reg->addr, mailbox->dma.words[i]);
}
} else {
timeout = false ;
timeout = false;
printf("Device has no UNIBUS registers.\n");
}
}
@@ -428,40 +429,26 @@ void application_c::menu_devices(bool with_CPU) {
} // ready
if (with_CPU) {
cpu->worker_stop();
cpu->uninstall();
cpu->enabled.set(false);
delete cpu;
}
if (with_RL) {
RL11->worker_stop();
RL11->disconnect_from_panel();
RL11->uninstall();
delete RL11;
}
RL11->enabled.set(false);
delete RL11;
if (with_RK) {
RK11->worker_stop();
RK11->uninstall();
delete RK11;
}
RK11->enabled.set(false);
delete RK11;
if (with_MSCP) {
UDA50->worker_stop();
UDA50->uninstall();
delete UDA50;
}
UDA50->enabled.set(false);
delete UDA50;
// //demo_regs.worker_stop();
// //demo_regs.uninstall();
if (with_DEMOIO) {
demo_io->worker_stop();
demo_io->uninstall();
delete demo_io;
}
demo_io->enabled.set(false);
delete demo_io;
unibusadapter->worker_stop();
unibusadapter->enabled.set(false);
buslatches_output_enable(false);
hardware_shutdown(); // stop PRU

View File

@@ -74,7 +74,7 @@ void application_c::menu_interrupts(void) {
printf("***\n");
printf("*** Starting full UNIBUS master/slave logic on PRU\n");
printf("***\n");
unibusadapter->worker_start();
unibusadapter->enabled.set(true) ;
active = true;
}
@@ -170,7 +170,7 @@ void application_c::menu_interrupts(void) {
printf("***\n");
printf("*** Stopping UNIBUS logic on PRU\n");
printf("***\n");
unibusadapter->worker_stop();
unibusadapter->enabled.set(false) ;
active = false;
}

View File

@@ -71,7 +71,7 @@ void application_c::menu_masterslave(enum unibus_c::arbitration_mode_enum arbitr
// PRUCODE_UNIBUS can raise events (INIT,ACLO,DCLO)
// handle & clear these
unibusadapter->worker_start() ;
unibusadapter->enabled.set(true) ;
ready = false;
@@ -95,8 +95,7 @@ void application_c::menu_masterslave(enum unibus_c::arbitration_mode_enum arbitr
printf("*** Starting full UNIBUS master/slave logic on PRU\n");
printf("***\n");
if (testcontroller_enabled) {
demo_regs.install();
demo_regs.worker_start();
demo_regs.enabled.set(true);
}
unibusadapter->print_shared_register_map();
active = true;
@@ -371,12 +370,11 @@ void application_c::menu_masterslave(enum unibus_c::arbitration_mode_enum arbitr
printf("***\n");
if (testcontroller_enabled) {
demo_regs.worker_stop();
demo_regs.uninstall();
demo_regs.enabled.set(false);
}
active = false;
}
unibusadapter->worker_stop();
unibusadapter->enabled.set(false) ;
// Switch off bus drivers
buslatches_output_enable(false);