/* 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, char *cptr, void *desc); | |
t_stat rtc_show_interval (FILE *st, UNIT *uptr, int32 val, void *desc); | |
t_stat rtc_set_quantum(UNIT *uptr, int32 val, char *cptr, void *desc); | |
t_stat rtc_show_quantum (FILE *st, UNIT *uptr, int32 val, void *desc); | |
t_stat wdt_set_delay (UNIT *uptr, int32 val, char *cptr, void *desc); | |
t_stat wdt_show_delay (FILE *st, UNIT *uptr, int32 val, 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, 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, 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, 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, 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, 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, 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 |