diff --git a/3B2/3b2_sysdev.c b/3B2/3b2_sysdev.c index 3070398f..ec89966f 100644 --- a/3B2/3b2_sysdev.c +++ b/3B2/3b2_sysdev.c @@ -38,6 +38,8 @@ - tod MM58174A Real-Time-Clock */ +#include + #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 \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 \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; } diff --git a/3B2/3b2_sysdev.h b/3B2/3b2_sysdev.h index 078fdc08..2d4a8332 100644 --- a/3B2/3b2_sysdev.h +++ b/3B2/3b2_sysdev.h @@ -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