3b2: Time of Day Clock

- This change adds support for storing time between boots in the Time
  of Day clock. An effort has been made to keep the Time of Day clock
  in sync with the wall clock between boots, so that user-set time
  advances properly between boots.

- Added a couple of custom help routines for TOD and NVRAM.
  I will be filling in more help routines as time permits.
This commit is contained in:
Seth Morabito 2017-11-26 18:26:05 -08:00
parent 3147c30b80
commit 00f8bbcef1
2 changed files with 393 additions and 26 deletions

View file

@ -38,6 +38,8 @@
- tod MM58174A Real-Time-Clock
*/
#include <time.h>
#include "3b2_sysdev.h"
#include "3b2_iu.h"
@ -82,7 +84,7 @@ BITFIELD csr_bits[] = {
};
UNIT csr_unit = {
UDATA(&csr_svc, UNIT_FIX, CSRSIZE)
UDATA(NULL, UNIT_FIX, CSRSIZE)
};
REG csr_reg[] = {
@ -135,12 +137,6 @@ uint32 csr_read(uint32 pa, size_t size)
}
}
/* TODO: Remove once we confirm we don't need it */
t_stat csr_svc(UNIT *uptr)
{
return SCPE_OK;
}
void csr_write(uint32 pa, uint32 val, size_t size)
{
uint32 reg = pa - CSRBASE;
@ -239,7 +235,7 @@ DEVICE nvram_dev = {
&nvram_ex, &nvram_dep, &nvram_reset,
NULL, &nvram_attach, &nvram_detach,
NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL,
NULL, NULL, NULL,
&nvram_help, NULL, NULL,
&nvram_description
};
@ -294,7 +290,24 @@ t_stat nvram_reset(DEVICE *dptr)
const char *nvram_description(DEVICE *dptr)
{
return "Non-volatile memory";
return "Non-volatile memory, used to store system state between boots.\n";
}
t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf(st,
"The NVRAM holds system state between boots. On initial startup,\n"
"if no valid NVRAM file is attached, you will see the message:\n"
"\n"
" FW ERROR 1-01: NVRAM SANITY FAILURE\n"
" DEFAULT VALUES ASSUMED\n"
" IF REPEATED, CHECK THE BATTERY\n"
"\n"
"To avoid this message on subsequent boots, attach a new NVRAM file\n"
"with the SIMH command:\n"
"\n"
" sim> ATTACH NVRAM <filename>\n");
return SCPE_OK;
}
t_stat nvram_attach(UNIT *uptr, CONST char *cptr)
@ -661,64 +674,272 @@ void timer_write(uint32 pa, uint32 val, size_t size)
* MM58174A Real-Time-Clock
*/
UNIT tod_unit = { UDATA(&tod_svc, UNIT_IDLE+UNIT_FIX, 0) };
/* The year is NOT stored in the TOD clock. However, it does store an
* integer indicating which year in a 4-year Leap-Year cycle the
* current year is. To simplify converting from seconds into a TOD
* structure, we will map this into the range 1985...1988, for
* internal purposes only.
*/
uint32 tod_reg = 0;
int tod_years[4] = {85, 86, 87, 88};
UNIT tod_unit = {
UDATA(&tod_svc, UNIT_IDLE+UNIT_FIX+UNIT_BINK, sizeof(TOD_DATA))
};
DEVICE tod_dev = {
"TOD", &tod_unit, NULL, NULL,
1, 16, 8, 4, 16, 32,
NULL, NULL, &tod_reset,
NULL, NULL, NULL, NULL,
DEV_DEBUG, 0, sys_deb_tab
NULL, &tod_attach, &tod_detach,
NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL,
&tod_help, NULL, NULL,
&tod_description
};
t_stat tod_reset(DEVICE *dptr)
{
int32 t;
if (!sim_is_running) {
t = sim_rtcn_init_unit(&tod_unit, TPS_TOD, TMR_TOD);
sim_activate_after(&tod_unit, 1000000 / t);
t = sim_rtcn_init_unit(&tod_unit, TPS_TOD, TMR_TOD);
sim_activate_after(&tod_unit, 1000000 / TPS_TOD);
if (tod_unit.filebuf == NULL) {
tod_unit.filebuf = calloc(sizeof(TOD_DATA), 1);
if (tod_unit.filebuf == NULL) {
return SCPE_MEM;
}
}
return SCPE_OK;
}
t_stat tod_attach(UNIT *uptr, CONST char *cptr)
{
t_stat r;
TOD_DATA *td = (TOD_DATA *)uptr->filebuf;
uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE);
r = attach_unit(uptr, cptr);
if (r != SCPE_OK) {
uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE);
} else {
uptr->hwmark = (uint32) uptr->capac;
if (!td->ssec) {
tod_init_time();
} else {
tod_sync();
}
tod_update_tdata();
}
return r;
}
t_stat tod_detach(UNIT *uptr)
{
t_stat r;
TOD_DATA *td = (TOD_DATA *)uptr->filebuf;
/* We're about to detach, so update the current tod clock */
tod_update_tdata();
r = detach_unit(uptr);
if ((uptr->flags & UNIT_ATT) == 0) {
uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE);
}
return r;
}
/*
* If no time is set in the system clock, grab it from the
* real wall clock.
*/
void tod_init_time()
{
TOD_DATA *td = (TOD_DATA *)tod_unit.filebuf;
int result;
struct timespec now;
sim_rtcn_get_time(&now, TMR_TOD);
td->rsec = (time_t)now.tv_sec;
td->ssec = (t_uint64)now.tv_sec * 10;
}
/*
* Re-set the tod_data registers based on the current value of ssec
*/
void tod_update_tdata()
{
int result;
struct timespec now;
struct tm tm;
TOD_DATA *td = (TOD_DATA *)tod_unit.filebuf;
time_t sec = td->ssec / 10;
sim_debug(EXECUTE_MSG, &tod_dev,
">>> Epoch time from tod_update_tdata() = %lu\n",
sec);
/* Populate the tm struct based on current sim_time */
localtime_r(&sec, &tm);
td->tsec = 0;
td->unit_sec = tm.tm_sec % 10;
td->ten_sec = tm.tm_sec / 10;
td->unit_min = tm.tm_min % 10;
td->ten_min = tm.tm_min / 10;
td->unit_hour = tm.tm_hour % 10;
td->ten_hour = tm.tm_hour / 10;
/* tm struct stores as 0-11, tod struct as 1-12 */
td->unit_mon = (tm.tm_mon + 1) % 10;
td->ten_mon = (tm.tm_mon + 1) / 10;
td->unit_day = tm.tm_mday % 10;
td->ten_day = tm.tm_mday / 10;
td->year = 1 << ((tm.tm_year - 1) % 4);
sim_rtcn_get_time(&now, TMR_TOD);
sim_debug(EXECUTE_MSG, &tod_dev,
">>> Updated rsec to %lu\n",
now.tv_sec);
td->rsec = now.tv_sec;
}
void tod_sync()
{
int result;
struct timespec now;
TOD_DATA *td = (TOD_DATA *)tod_unit.filebuf;
sim_rtcn_get_time(&now, TMR_TOD);
sim_debug(EXECUTE_MSG, &tod_dev,
">>> Syncing clock. Clock gained %lu seconds\n",
now.tv_sec - td->rsec);
td->ssec += ((time_t)now.tv_sec - td->rsec) * (t_uint64)10;
}
/*
* Re-set the value of ssec based on the current values of the
* tod_data registers.
*/
void tod_update_ssec()
{
struct tm tm = {0};
time_t simtime = 0;
TOD_DATA *td = (TOD_DATA *)tod_unit.filebuf;
/* We've been asked to re-set the TOD register values based on the
* current 1/10th seconds since the epoch (ssec) */
tm.tm_sec = (td->ten_sec * 10) + td->unit_sec;
tm.tm_min = (td->ten_min * 10) + td->unit_min;
tm.tm_hour = (td->ten_hour * 10) + td->unit_hour;
/* tm struct stores as 0-11, tod struct as 1-12 */
tm.tm_mon = ((td->ten_mon * 10) + td->unit_mon) - 1;
tm.tm_mday = (td->ten_day * 10) + td->unit_day;
switch(td->year) {
case 1: /* Leap Year - 3 */
tm.tm_year = 85;
case 2: /* Leap Year - 2 */
tm.tm_year = 86;
case 4: /* Leap Year - 1 */
tm.tm_year = 87;
case 8: /* Leap Year */
tm.tm_year = 88;
}
tm.tm_isdst = 0;
simtime = mktime(&tm);
sim_debug(EXECUTE_MSG, &tod_dev,
">>> Epoch time from tod_update_ssec() = %lu\n",
simtime);
td->ssec = (t_uint64)simtime * 10;
}
t_stat tod_svc(UNIT *uptr)
{
int32 t;
TOD_DATA *td = (TOD_DATA *)(uptr->filebuf);
td->ssec++;
t = sim_rtcn_calb(TPS_TOD, TMR_TOD);
sim_activate_after(&tod_unit, 1000000 / TPS_TOD);
sim_activate_after(&tod_unit, (uint32) (1000000 / TPS_TOD));
tod_reg++;
return SCPE_OK;
}
uint32 tod_read(uint32 pa, size_t size)
{
uint32 reg;
uint8 reg;
TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf);
reg = pa - TODBASE;
sim_debug(READ_MSG, &tod_dev,
"[%08x] READ TOD: reg=%02x\n",
R[NUM_PC], reg);
switch(reg) {
case 0x04: /* 1/10 Sec */
return td->tsec;
case 0x08: /* 1 Sec */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->unit_sec=%d\n",
td->unit_sec);
return td->unit_sec;
case 0x0c: /* 10 Sec */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->ten_sec=%d\n",
td->ten_sec);
return td->ten_sec;
case 0x10: /* 1 Min */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->unit_min=%d\n",
td->unit_min);
return td->unit_min;
case 0x14: /* 10 Min */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->ten_min=%d\n",
td->ten_min);
return td->ten_min;
case 0x18: /* 1 Hour */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->unit_hour=%d\n",
td->unit_hour);
return td->unit_hour;
case 0x1c: /* 10 Hour */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->ten_hour=%d\n",
td->ten_hour);
return td->ten_hour;
case 0x20: /* 1 Day */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->unit_day=%d\n",
td->unit_day);
return td->unit_day;
case 0x24: /* 10 Day */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->ten_day=%d\n",
td->ten_day);
return td->ten_day;
case 0x28: /* Day of Week */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->wday=%d\n",
td->wday);
return td->wday;
case 0x2c: /* 1 Month */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->unit_mon=%d\n",
td->unit_mon);
return td->unit_mon;
case 0x30: /* 10 Month */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->ten_mon=%d\n",
td->ten_mon);
return td->ten_mon;
case 0x34: /* Year */
sim_debug(READ_MSG, &tod_dev,
"READ TOD: td->year=%d\n",
td->year);
return td->year;
default:
break;
}
@ -729,10 +950,122 @@ uint32 tod_read(uint32 pa, size_t size)
void tod_write(uint32 pa, uint32 val, size_t size)
{
uint32 reg;
TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf);
reg = pa - TODBASE;
sim_debug(WRITE_MSG, &tod_dev,
"[%08x] WRITE TOD: reg=%02x val=%d\n",
R[NUM_PC], reg, val);
switch(reg) {
case 0x04: /* 1/10 Sec */
td->tsec = (uint8) val;
break;
case 0x08: /* 1 Sec */
td->unit_sec = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->unit_sec=%d\n",
td->unit_sec);
break;
case 0x0c: /* 10 Sec */
td->ten_sec = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->ten_sec=%d\n",
td->ten_sec);
break;
case 0x10: /* 1 Min */
td->unit_min = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->unit_min=%d\n",
td->unit_min);
break;
case 0x14: /* 10 Min */
td->ten_min = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->ten_min=%d\n",
td->ten_min);
break;
case 0x18: /* 1 Hour */
td->unit_hour = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->unit_hour=%d\n",
td->unit_hour);
break;
case 0x1c: /* 10 Hour */
td->ten_hour = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->ten_hour=%d\n",
td->ten_hour);
break;
case 0x20: /* 1 Day */
td->unit_day = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->unit_day=%d\n",
td->unit_day);
break;
case 0x24: /* 10 Day */
td->ten_day = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->ten_day=%d\n",
td->ten_day);
break;
case 0x28: /* Day of Week */
td->wday = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->wday=%d\n",
td->wday);
break;
case 0x2c: /* 1 Month */
td->unit_mon = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->unit_mon=%d\n",
td->unit_mon);
break;
case 0x30: /* 10 Month */
td->ten_mon = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->ten_mon=%d\n",
td->ten_mon);
break;
case 0x34: /* Year */
td->year = (uint8) val;
sim_debug(WRITE_MSG, &tod_dev,
"WRITE TOD: td->year=%d\n",
td->year);
case 0x38:
if (val & 1) {
/* Start the clock */
sim_debug(WRITE_MSG, &tod_dev, "Start the clock and resync TOD registers.\n");
tod_update_ssec();
} else {
/* Stop the clock */
sim_debug(WRITE_MSG, &tod_dev, "Stop the clock\n");
}
break;
default:
break;
}
}
const char *tod_description(DEVICE *dptr)
{
return "Time-of-Day clock, used to store system time between boots.\n";
}
t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf(st,
"The TOD is a battery-backed time-of-day clock that holds system\n"
"time between boots. In order to store the time, a file must be\n"
"attached to the TOD device with the SIMH command:\n"
"\n"
" sim> ATTACH TOD <filename>\n"
"\n"
"On a newly installed System V Release 3 UNIX system, no system\n"
"time will be stored in the TOD clock. In order to set the system\n"
"time, run the following command from within UNIX (as root):\n"
"\n"
" # sysadm datetime\n"
"\n"
"On subsequent boots, the correct system time will restored from\n"
"from the TOD.\n");
return SCPE_OK;
}

View file

@ -59,6 +59,7 @@ uint32 nvram_read(uint32 pa, size_t size);
t_stat nvram_attach(UNIT *uptr, CONST char *cptr);
t_stat nvram_detach(UNIT *uptr);
const char *nvram_description(DEVICE *dptr);
t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
void nvram_write(uint32 pa, uint32 val, size_t size);
/* 8253 Timer */
@ -78,8 +79,41 @@ uint32 csr_read(uint32 pa, size_t size);
void csr_write(uint32 pa, uint32 val, size_t size);
/* TOD */
typedef enum tod_action {
TOD_SAVE,
TOD_RESTORE
} TOD_ACTION;
typedef struct tod_data {
time_t rsec; /* Real clock time from the host */
t_uint64 ssec; /* Simulated time in 1/10th second since the epoch */
uint8 tsec; /* 1/10 seconds */
uint8 unit_sec; /* 1's column seconds */
uint8 ten_sec; /* 10's column seconds */
uint8 unit_min; /* 1's column minutes */
uint8 ten_min; /* 10's column minutes */
uint8 unit_hour; /* 1's column hours */
uint8 ten_hour; /* 10's column hours */
uint8 unit_day; /* 1's column day of month */
uint8 ten_day; /* 10's column day of month */
uint8 wday; /* Day of week (0-6) */
uint8 unit_mon; /* 1's column month */
uint8 ten_mon; /* 10's column month */
uint8 year; /* 1, 2, 4, 8 shift register */
uint8 pad[3]; /* Padding to 32 bytes */
} TOD_DATA;
void tod_init_time();
void tod_update_tdata();
void tod_update_ssec();
void tod_sync();
t_stat tod_svc(UNIT *uptr);
t_stat tod_reset(DEVICE *dptr);
t_stat tod_attach(UNIT *uptr, CONST char *cptr);
t_stat tod_detach(UNIT *uptr);
const char *tod_description(DEVICE *dptr);
t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
uint32 tod_read(uint32 pa, size_t size);
void tod_write(uint32, uint32 val, size_t size);
#endif