These changes facilitate more robust parameter type checking and helps to identify unexpected coding errors. Most simulators can now also be compiled with a C++ compiler without warnings. Additionally, these changes have also been configured to facilitate easier backporting of simulator and device simulation modules to run under the simh v3.9+ SCP framework.
384 lines
14 KiB
C
384 lines
14 KiB
C
/* h316_rtc.c- BBN ARPAnet IMP/TIP Real Time Clock and Watch Dog Timer
|
|
Based on the SIMH simulator package written by Robert M Supnik.
|
|
|
|
Copyright (c) 2013 Robert Armstrong, bob@jfcl.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:
|
|
|
|
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
|
|
ROBERT ARMSTRONG 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.
|
|
|
|
Except as contained in this notice, the name of Robert Armstrong shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from Robert Armstrong.
|
|
|
|
rtc real time clock
|
|
wdt watch dog timer
|
|
|
|
21-May-13 RLA New file
|
|
|
|
OVERVIEW
|
|
|
|
The IMP and TIP used a custom real time clock that was apparently created
|
|
by BBN just for those devices. The IMP/TIP RTC is NOT the same as the
|
|
official Honeywell real time clock option, H316-12. When emulating an IMP
|
|
or TIP, this RTC device must be enabled and the standard simh H316 CLK
|
|
device must be disabled.
|
|
|
|
The IMP and TIP also had a watch dog timer which, if ever allowed to time
|
|
out, would cause a non-maskable interrupt via location 62(8) - this is the
|
|
same trap location used by the memory lockout option, which the IMP/TIP
|
|
lacked. Not much is known about the WDT, and the current implementation is
|
|
simply a place holder that doesn't do much.
|
|
|
|
RTC state is maintained in a set of state variables:
|
|
|
|
ENA RTC is enabled
|
|
COUNT current count
|
|
IEN RTC interrupt enabled
|
|
IRQ RTC interrupt pending
|
|
TPS effective ticks per second
|
|
WAIT simulator time until the next tick
|
|
|
|
WDT state is maintained in a set of state variables:
|
|
|
|
COUNT current countdown
|
|
TMO WDT timed out
|
|
LIGHTS last "set status lights"
|
|
WAIT simulator time until the next tick
|
|
|
|
TODO
|
|
|
|
Implement the WDT!!
|
|
*/
|
|
#ifdef VM_IMPTIP
|
|
#include "h316_defs.h" // H316 emulator definitions
|
|
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
|
|
|
// Locals ...
|
|
uint32 rtc_interval = RTC_INTERVAL; // RTC tick interval (in microseconds)
|
|
uint32 rtc_quantum = RTC_QUANTUM; // RTC update interval (in ticks)
|
|
uint32 rtc_tps = 1000000UL / (RTC_INTERVAL * RTC_QUANTUM);
|
|
uint16 rtc_enabled = 1; // RTC enabled
|
|
uint32 rtc_count = 0; // current RTC count
|
|
uint32 wdt_delay = WDT_DELAY; // WDT timeout (in milliseconds, 0=none)
|
|
uint32 wdt_count = 0; // current WDT countdown
|
|
uint16 wdt_lights = 0; // last "set status lights" output
|
|
|
|
// Externals from other parts of simh ...
|
|
extern uint16 dev_ext_int, dev_ext_enb; // current IRQ and IEN bit vectors
|
|
extern int32 PC; // current PC (for debug messages)
|
|
extern int32 stop_inst; // needed by IOBADFNC()
|
|
|
|
// Forward declarations ...
|
|
int32 rtc_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
|
t_stat rtc_service (UNIT *uptr);
|
|
t_stat rtc_reset (DEVICE *dptr);
|
|
int32 wdt_io (int32 inst, int32 fnc, int32 dat, int32 dev);
|
|
t_stat wdt_service (UNIT *uptr);
|
|
t_stat wdt_reset (DEVICE *dptr);
|
|
t_stat rtc_set_interval (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
t_stat rtc_show_interval (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
t_stat rtc_set_quantum(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
t_stat rtc_show_quantum (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
t_stat wdt_set_delay (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
t_stat wdt_show_delay (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////// R T C D A T A S T R U C T U R E S //////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// RTC device information block ...
|
|
DIB rtc_dib = { RTC, 1, IOBUS, IOBUS, INT_V_RTC, INT_V_NONE, &rtc_io, 0 };
|
|
|
|
// RTC unit data block (we have only one unit!) ...
|
|
UNIT rtc_unit = { UDATA (&rtc_service, 0, 0), (RTC_INTERVAL*RTC_QUANTUM) };
|
|
|
|
// RTC device registers (for "EXAMINE RTC STATE") ...
|
|
REG rtc_reg[] = {
|
|
{ FLDATA (ENA, rtc_enabled, 0) },
|
|
{ DRDATA (COUNT, rtc_count, 16), PV_LEFT },
|
|
{ FLDATA (IEN, dev_ext_enb, INT_V_RTC-INT_V_EXTD) },
|
|
{ FLDATA (IRQ, dev_ext_int, INT_V_RTC-INT_V_EXTD) },
|
|
{ DRDATA (TPS, rtc_tps, 32), PV_LEFT },
|
|
{ DRDATA (WAIT, rtc_unit.wait, 24), REG_NZ + PV_LEFT },
|
|
{ NULL }
|
|
};
|
|
|
|
// RTC device modifiers (for "SET/SHOW RTC xxx") ...
|
|
MTAB rtc_mod[] = {
|
|
{ MTAB_XTD|MTAB_VDV, 0, "INTERVAL", "INTERVAL", &rtc_set_interval, &rtc_show_interval, NULL },
|
|
{ MTAB_XTD|MTAB_VDV, 0, "QUANTUM", "QUANTUM", &rtc_set_quantum, &rtc_show_quantum, NULL },
|
|
{ 0 }
|
|
};
|
|
|
|
// RTC debugging flags (for "SET RTC DEBUG=xxx") ...
|
|
DEBTAB rtc_debug[] = {
|
|
{"WARN", IMP_DBG_WARN},
|
|
{"IO", IMP_DBG_IOT},
|
|
{0}
|
|
};
|
|
|
|
// And finally tie it all together ...
|
|
DEVICE rtc_dev = {
|
|
"RTC", &rtc_unit, rtc_reg, rtc_mod,
|
|
1, 0, 0, 0, 0, 0,
|
|
NULL, NULL, &rtc_reset, NULL, NULL, NULL,
|
|
&rtc_dib, DEV_DIS|DEV_DISABLE|DEV_DEBUG, 0, rtc_debug, NULL, NULL
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////// W D T D A T A S T R U C T U R E S //////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// WDT device information block ...
|
|
DIB wdt_dib = { WDT, 1, IOBUS, IOBUS, INT_V_NONE, INT_V_NONE, &wdt_io, 0 };
|
|
|
|
// WDT unit data block (it has only one) ...
|
|
UNIT wdt_unit = { UDATA (&wdt_service, 0, 0), 1000 };
|
|
|
|
// WDT device registers (for "EXAMINE WDT STATE") ...
|
|
REG wdt_reg[] = {
|
|
{ DRDATA (COUNT, wdt_count, 16), PV_LEFT },
|
|
{ DRDATA (WAIT, wdt_unit.wait, 24), REG_NZ | PV_LEFT },
|
|
{ ORDATA (LIGHTS, wdt_lights, 16), REG_RO | PV_LEFT },
|
|
{ NULL }
|
|
};
|
|
|
|
// WDT device modifiers (for "SET/SHOW WDT xxx") ...
|
|
MTAB wdt_mod[] = {
|
|
{ MTAB_XTD | MTAB_VDV, 0, "DELAY", "DELAY", &wdt_set_delay, &wdt_show_delay, NULL },
|
|
{ 0 }
|
|
};
|
|
|
|
// WDT debugging flags (for "SET WDT DEBUG=xxx") ...
|
|
DEBTAB wdt_debug[] = {
|
|
{"WARN", IMP_DBG_WARN},
|
|
{"IO", IMP_DBG_IOT},
|
|
{"LIGHTS", WDT_DBG_LIGHTS},
|
|
{0}
|
|
};
|
|
|
|
// And summarize ...
|
|
DEVICE wdt_dev = {
|
|
"WDT", &wdt_unit, wdt_reg, wdt_mod,
|
|
1, 0, 0, 0, 0, 0,
|
|
NULL, NULL, &wdt_reset, NULL, NULL, NULL,
|
|
&wdt_dib, DEV_DIS|DEV_DISABLE|DEV_DEBUG, 0, wdt_debug, NULL, NULL
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////// R T C I / O A N D S E R V I C E R O U T I N E S //////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Set and clear the RTC IRQ and IEN ...
|
|
#define SET_RTC_IRQ() SET_EXT_INT((1u << (rtc_dib.inum - INT_V_EXTD)))
|
|
#define CLR_RTC_IRQ() CLR_EXT_INT((1u << (rtc_dib.inum - INT_V_EXTD)))
|
|
#define CLR_RTC_IEN() CLR_EXT_ENB((1u << (rtc_dib.inum - INT_V_EXTD)))
|
|
|
|
// RTC IO routine ...
|
|
int32 rtc_io (int32 inst, int32 fnc, int32 dat, int32 dev)
|
|
{
|
|
switch (inst) {
|
|
case ioOCP:
|
|
if (fnc == 010) {
|
|
// CLKOFF - turn the RTC off ...
|
|
sim_cancel(&rtc_unit); rtc_enabled = 0; CLR_RTC_IRQ();
|
|
sim_debug(IMP_DBG_IOT, &rtc_dev, "disabled (PC=%06o)\n", PC-1);
|
|
return dat;
|
|
} else if (fnc == 000) {
|
|
// CLKON - turn the RTC on ...
|
|
rtc_enabled = 1; CLR_RTC_IRQ();
|
|
if (sim_is_active(&rtc_unit) == 0)
|
|
sim_activate (&rtc_unit, sim_rtc_init (rtc_unit.wait));
|
|
sim_debug(IMP_DBG_IOT, &rtc_dev, "enabled (PC=%06o)\n", PC-1);
|
|
return dat;
|
|
}
|
|
break;
|
|
|
|
case ioINA:
|
|
if ((fnc == 010) || (fnc == 000)) {
|
|
// RDCLOK - return the current count
|
|
sim_debug(IMP_DBG_IOT, &rtc_dev, "read clock (PC=%06o, RTC=%06o)\n", PC-1, (rtc_count & DMASK));
|
|
return IOSKIP((rtc_count & DMASK));
|
|
}
|
|
break;
|
|
}
|
|
|
|
sim_debug(IMP_DBG_WARN, &rtc_dev, "UNIMPLEMENTED I/O (PC=%06o, instruction=%o, function=%02o)\n", PC-1, inst, fnc);
|
|
return IOBADFNC(dat);
|
|
}
|
|
|
|
// RTC unit service ...
|
|
t_stat rtc_service (UNIT *uptr)
|
|
{
|
|
// Add the current quantum to the clock register and, if the clock register
|
|
// has overflowed, request an interrupt. The real hardware interrupts when
|
|
// there is a carry out of the low byte (in other words, every 256 clocks).
|
|
// Note that we can't simply check the low byte for zero to detect overflows
|
|
// because of the quantum. Since we aren't necessarily incrementing by 1, we
|
|
// may never see a value of exactly zero. We'll have to be more clever.
|
|
uint8 rtc_high = HIBYTE(rtc_count);
|
|
rtc_count = (rtc_count + rtc_quantum) & DMASK;
|
|
if (HIBYTE(rtc_count) != rtc_high) {
|
|
sim_debug(IMP_DBG_IOT, &rtc_dev, "interrupt request\n");
|
|
SET_RTC_IRQ();
|
|
}
|
|
mi_tx_service(rtc_quantum);
|
|
uptr->wait = sim_rtc_calb (rtc_tps); /* recalibrate */
|
|
sim_activate_after (uptr, 1000000/rtc_tps); /* reactivate unit */
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////// W D T I / O A N D S E R V I C E R O U T I N E S //////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// WDT IO routine ...
|
|
int32 wdt_io (int32 inst, int32 fnc, int32 dat, int32 dev)
|
|
{
|
|
if ((inst == ioOCP) && (fnc == 0)) {
|
|
// Reset WDT ...
|
|
sim_debug(IMP_DBG_IOT, &wdt_dev, "reset (PC=%06o)\n", PC-1);
|
|
return dat;
|
|
} else if ((inst == ioOTA) && (fnc == 0)) {
|
|
// Set status lights ...
|
|
if (wdt_lights != dat) {
|
|
sim_debug(WDT_DBG_LIGHTS, &wdt_dev, "changed to %06o\n", dat);
|
|
}
|
|
sim_debug(IMP_DBG_IOT, &wdt_dev, "set status lights (PC=%06o, LIGHTS=%06o)\n", PC-1, dat);
|
|
wdt_lights = dat; return dat;
|
|
}
|
|
|
|
sim_debug(IMP_DBG_WARN, &wdt_dev, "UNIMPLEMENTED I/O (PC=%06o, instruction=%o, function=%02o)\n", PC-1, inst, fnc);
|
|
return IOBADFNC(dat);
|
|
}
|
|
|
|
// WDT unit service ...
|
|
t_stat wdt_service (UNIT *uptr)
|
|
{
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/////////////// D E V I C E A C T I O N C O M M A N D S ////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// RTC reset routine ...
|
|
t_stat rtc_reset (DEVICE *dptr)
|
|
{
|
|
// Clear the interrupt enable and any pending interrupts, reset the count
|
|
// and enable the clock. At least I assume that's what a reset does - the
|
|
// docs aren't too specific on this point...
|
|
rtc_enabled = 1; rtc_count = 0;
|
|
CLR_RTC_IRQ(); CLR_RTC_IEN();
|
|
sim_cancel (&rtc_unit);
|
|
sim_register_clock_unit ((dptr->flags & DEV_DIS) ? NULL : &rtc_unit);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
// WDT reset routine ...
|
|
t_stat wdt_reset (DEVICE *dptr)
|
|
{
|
|
// Clear the WDT countdown and turn off all the lights ...
|
|
wdt_count = 0; wdt_lights = 0;
|
|
sim_cancel (&wdt_unit);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///////// D E V I C E S E T A N D S H O W C O M M A N D S //////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Set/Show RTC interval ...
|
|
t_stat rtc_set_interval (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
uint32 newint, newtps; t_stat ret;
|
|
if (cptr == NULL) return SCPE_ARG;
|
|
newint = get_uint (cptr, 10, 1000000, &ret);
|
|
if (ret != SCPE_OK) return ret;
|
|
if (newint == 0) return SCPE_ARG;
|
|
newtps = 1000000UL / (newint * rtc_quantum);
|
|
if ((newtps == 0) || (newtps >= 100000)) return SCPE_ARG;
|
|
rtc_interval = newint; rtc_tps = newtps;
|
|
uptr->wait = sim_rtc_calb (rtc_tps);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat rtc_show_interval (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
fprintf(st,"interval=%d (us)", rtc_interval);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
// Set/Show RTC quantum ...
|
|
t_stat rtc_set_quantum (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
uint32 newquant, newtps; t_stat ret;
|
|
if (cptr == NULL) return SCPE_ARG;
|
|
newquant = get_uint (cptr, 10, 1000000, &ret);
|
|
if (ret != SCPE_OK) return ret;
|
|
if (newquant == 0) return SCPE_ARG;
|
|
newtps = 1000000UL / (rtc_interval * newquant);
|
|
if ((newtps == 0) || (newtps >= 100000)) return SCPE_ARG;
|
|
rtc_quantum = newquant; rtc_tps = newtps;
|
|
uptr->wait = sim_rtc_calb (rtc_tps);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat rtc_show_quantum (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
fprintf(st,"quantum=%d (ticks)", rtc_quantum);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
// Set/Show WDT delay ...
|
|
t_stat wdt_set_delay (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
uint32 newint; t_stat ret;
|
|
if (cptr == NULL) return SCPE_ARG;
|
|
newint = get_uint (cptr, 10, 65535, &ret);
|
|
if (ret != SCPE_OK) return ret;
|
|
if (newint != 0) {
|
|
fprintf(stderr,"WDT - timeout not yet implemented\n");
|
|
return SCPE_IERR;
|
|
}
|
|
wdt_delay = newint;
|
|
// TBA add calculations here???
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat wdt_show_delay (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
if (wdt_delay > 0)
|
|
fprintf(st,"delay=%d (ms)", wdt_delay);
|
|
else
|
|
fprintf(st,"no timeout");
|
|
return SCPE_OK;
|
|
}
|
|
|
|
#endif // #ifdef VM_IMPTIP from the very top
|