From 8f64dcd8b303eb5e4715145b32606fc9680e4269 Mon Sep 17 00:00:00 2001 From: Christopher Bonhage Date: Mon, 12 Jul 2021 09:29:05 -0400 Subject: [PATCH] Implement RTC write support * Set local time offset when writing to Joybus or 64DD RTC. * Refactor get_local_time to use ISO C Time APIs. Special thanks to @jago85 and @LuigiBlood for their research! --- CMakeLists.txt | 3 +- dd/controller.c | 54 ++++++++++++------- dd/controller.h | 2 + os/common/local_time.c | 51 ++++++++++++++++++ os/common/local_time.h | 28 ++++++---- os/posix/local_time.c | 25 --------- os/winapi/local_time.c | 26 ---------- si/controller.c | 9 ++-- si/controller.h | 8 +++ si/rtc.c | 115 +++++++++++++++++++++++++++++++++-------- si/rtc.h | 11 ++-- 11 files changed, 221 insertions(+), 111 deletions(-) create mode 100644 os/common/local_time.c delete mode 100644 os/posix/local_time.c delete mode 100644 os/winapi/local_time.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ee1ba5..e942856 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,12 +293,12 @@ set(GDB_SOURCES set(OS_SOURCES ${PROJECT_SOURCE_DIR}/os/common/gl_hints.c ${PROJECT_SOURCE_DIR}/os/common/input.c + ${PROJECT_SOURCE_DIR}/os/common/local_time.c ) set(OS_POSIX_SOURCES ${PROJECT_SOURCE_DIR}/os/posix/alloc.c ${PROJECT_SOURCE_DIR}/os/posix/cpuid.c - ${PROJECT_SOURCE_DIR}/os/posix/local_time.c ${PROJECT_SOURCE_DIR}/os/posix/main.c ${PROJECT_SOURCE_DIR}/os/posix/rom_file.c ${PROJECT_SOURCE_DIR}/os/posix/save_file.c @@ -310,7 +310,6 @@ set(OS_WINAPI_SOURCES ${PROJECT_SOURCE_DIR}/os/winapi/cpuid.c ${PROJECT_SOURCE_DIR}/os/winapi/gl_config.c ${PROJECT_SOURCE_DIR}/os/winapi/gl_window.c - ${PROJECT_SOURCE_DIR}/os/winapi/local_time.c ${PROJECT_SOURCE_DIR}/os/winapi/main.c ${PROJECT_SOURCE_DIR}/os/winapi/rom_file.c ${PROJECT_SOURCE_DIR}/os/winapi/save_file.c diff --git a/dd/controller.c b/dd/controller.c index 0edfd99..90a8c59 100644 --- a/dd/controller.c +++ b/dd/controller.c @@ -116,7 +116,6 @@ static void dd_update_bm(struct dd_controller *dd); static void dd_write_sector(struct dd_controller *dd); static void dd_read_sector(struct dd_controller *dd); static void set_offset(struct dd_controller *dd); -static void get_dd_time(uint8_t *out); // Initializes the DD. int dd_init(struct dd_controller *dd, struct bus_controller *bus, @@ -129,6 +128,8 @@ int dd_init(struct dd_controller *dd, struct bus_controller *bus, dd->retail = true; dd->regs[DD_ASIC_ID_REG] = 0x00030000; + dd->rtc_offset_seconds = 0; + return 0; } @@ -187,21 +188,47 @@ int write_dd_regs(void *opaque, uint32_t address, uint32_t word, uint32_t dqm) { // Command register written: do something. if (reg == DD_ASIC_CMD_STATUS) { - uint8_t now[6]; switch (word) { + // set time + case DD_CMD_SET_YEAR_MONTH: + case DD_CMD_SET_DAY_HOUR: + case DD_CMD_SET_MIN_SEC: { + struct time_stamp now; + get_local_time(&now, dd->rtc_offset_seconds); + + if (word == DD_CMD_SET_YEAR_MONTH) { + uint8_t year = bcd2byte(dd->regs[DD_ASIC_DATA] >> 24); + /* 96-99 map to the 1990's, 00-95 map to 2000+ */ + now.year = (year >= 96 ? 0 : 100) + year; + now.month = bcd2byte(dd->regs[DD_ASIC_DATA] >> 16); + } else if (word == DD_CMD_SET_DAY_HOUR) { + now.day = bcd2byte(dd->regs[DD_ASIC_DATA] >> 24); + now.hour = bcd2byte(dd->regs[DD_ASIC_DATA] >> 16); + } else if (word == DD_CMD_SET_MIN_SEC) { + now.min = bcd2byte(dd->regs[DD_ASIC_DATA] >> 24); + now.sec = bcd2byte(dd->regs[DD_ASIC_DATA] >> 16); + } + + dd->rtc_offset_seconds = get_offset_seconds(&now); + break; + } + // get time case DD_CMD_GET_YEAR_MONTH: case DD_CMD_GET_DAY_HOUR: - case DD_CMD_GET_MIN_SEC: - get_dd_time(now); + case DD_CMD_GET_MIN_SEC: { + struct time_stamp now; + get_local_time(&now, dd->rtc_offset_seconds); + if (word == DD_CMD_GET_YEAR_MONTH) - dd->regs[DD_ASIC_DATA] = (now[0] << 24) | (now[1] << 16); + dd->regs[DD_ASIC_DATA] = (byte2bcd(now.year) << 24) | (byte2bcd(now.month) << 16); else if (word == DD_CMD_GET_DAY_HOUR) - dd->regs[DD_ASIC_DATA] = (now[2] << 24) | (now[3] << 16); + dd->regs[DD_ASIC_DATA] = (byte2bcd(now.day) << 24) | (byte2bcd(now.hour) << 16); else if (word == DD_CMD_GET_MIN_SEC) - dd->regs[DD_ASIC_DATA] = (now[4] << 24) | (now[5] << 16); + dd->regs[DD_ASIC_DATA] = (byte2bcd(now.min) << 24) | (byte2bcd(now.sec) << 16); break; + } case DD_CMD_SEEK_READ: dd->regs[DD_ASIC_CUR_TK] = dd->regs[DD_ASIC_DATA] >> 16; @@ -575,19 +602,6 @@ void set_offset(struct dd_controller *dd) { tr_off * zone_sec_size[dd->zone] * SECTORS_PER_BLOCK * BLOCKS_PER_TRACK; } -void get_dd_time(uint8_t *out) { - struct time_stamp now; - get_local_time(&now); - - out[0] = byte2bcd(now.year); - out[1] = byte2bcd(now.month); - out[2] = byte2bcd(now.day); - out[3] = byte2bcd(now.hour); - out[4] = byte2bcd(now.min); - out[5] = byte2bcd(now.sec); - -} - #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) const struct dd_variant *dd_identify_variant(struct rom_file *ipl) { diff --git a/dd/controller.h b/dd/controller.h index 76fa13d..41fe3ff 100644 --- a/dd/controller.h +++ b/dd/controller.h @@ -51,6 +51,8 @@ struct dd_controller { uint8_t c2s_buffer[DD_C2S_BUFFER_LEN]; uint8_t ds_buffer[DD_DS_BUFFER_LEN]; uint8_t ms_ram[DD_MS_RAM_LEN]; + + int32_t rtc_offset_seconds; }; cen64_cold int dd_init(struct dd_controller *dd, struct bus_controller *bus, diff --git a/os/common/local_time.c b/os/common/local_time.c new file mode 100644 index 0000000..86b70ee --- /dev/null +++ b/os/common/local_time.c @@ -0,0 +1,51 @@ +// +// os/unix/rom_file.c +// +// Functions for mapping ROM images into the address space. +// +// This file is subject to the terms and conditions defined in +// 'LICENSE', which is part of this source code package. +// + +#include +#include "local_time.h" + +void get_local_time(struct time_stamp *ts, int32_t offset_seconds) { + time_t now = time(NULL); + now += offset_seconds; + + struct tm tm = { 0, }; +#ifdef _WIN32 + localtime_s(&now, &tm); +#else + localtime_r(&now, &tm); +#endif + + // Copy tm into time_stamp struct + ts->year = tm.tm_year; + ts->month = tm.tm_mon + 1; // time_stamp month is zero-indexed + ts->day = tm.tm_mday; + ts->hour = tm.tm_hour; + ts->min = tm.tm_min; + ts->sec = tm.tm_sec; + ts->week_day = tm.tm_wday; +} + +int32_t get_offset_seconds(const struct time_stamp * ts) { + struct tm tm = { 0, }; + + // Copy time_stamp into tm struct + tm.tm_year = ts->year; + tm.tm_mon = ts->month - 1; // time_stamp month is zero-indexed + tm.tm_mday = ts->day; + tm.tm_hour = ts->hour; + tm.tm_min = ts->min; + tm.tm_sec = ts->sec; + tm.tm_wday = ts->week_day; + tm.tm_isdst = -1; // Auto-adjust for DST + + time_t then = mktime(&tm); + time_t now = time(NULL); + + return then - now; +} diff --git a/os/common/local_time.h b/os/common/local_time.h index 22345c5..f52f7fb 100644 --- a/os/common/local_time.h +++ b/os/common/local_time.h @@ -17,22 +17,30 @@ #endif struct time_stamp { - unsigned year; - unsigned month; - unsigned day; - unsigned hour; - unsigned min; - unsigned sec; - - unsigned week_day; + // Used by 64DD and Joybus RTC + unsigned year; /* Year starting from 1900 [96-195] */ + unsigned month; /* Month [1-12] */ + unsigned day; /* Day [1-31] */ + unsigned hour; /* Hour [0-23] */ + unsigned min; /* Minute [0-59] */ + unsigned sec; /* Second [0-59] */ + // Used only by Joybus RTC + unsigned week_day; /* Day of Week [0-6] (Sun-Sat) */ }; -void get_local_time(struct time_stamp *ts); +void get_local_time(struct time_stamp *ts, int32_t offset_seconds); + +int32_t get_offset_seconds(const struct time_stamp * ts); static inline uint8_t byte2bcd(unsigned byte) { byte %= 100; return ((byte / 10) << 4) | (byte % 10); } -#endif +static inline uint8_t bcd2byte(uint8_t bcd) { + uint8_t hi = (bcd & 0xF0) >> 4; + uint8_t lo = bcd & 0x0F; + return (hi * 10) + lo; +} +#endif diff --git a/os/posix/local_time.c b/os/posix/local_time.c deleted file mode 100644 index c918b32..0000000 --- a/os/posix/local_time.c +++ /dev/null @@ -1,25 +0,0 @@ -// -// os/unix/rom_file.c -// -// Functions for mapping ROM images into the address space. -// -// This file is subject to the terms and conditions defined in -// 'LICENSE', which is part of this source code package. -// - -#include "local_time.h" -#include - -void get_local_time(struct time_stamp *ts) { - time_t now = time(NULL); - struct tm time = { 0, }; - localtime_r(&now, &time); - - ts->year = time.tm_year; - ts->month = time.tm_mon + 1; // month is zero-indexed in this struct - ts->day = time.tm_mday; - ts->hour = time.tm_hour; - ts->min = time.tm_min; - ts->sec = time.tm_sec; - ts->week_day = time.tm_wday; -} diff --git a/os/winapi/local_time.c b/os/winapi/local_time.c deleted file mode 100644 index d16c845..0000000 --- a/os/winapi/local_time.c +++ /dev/null @@ -1,26 +0,0 @@ -// -// os/winapi/local_time.c: Time functions for Windows. -// -// CEN64: Cycle-Accurate Nintendo 64 Emulator. -// Copyright (C) 2015, Tyler J. Stachecki. -// -// This file is subject to the terms and conditions defined in -// 'LICENSE', which is part of this source code package. -// - -#include "local_time.h" -#include -#include - -void get_local_time(struct time_stamp *ts) { - SYSTEMTIME sysTime; - GetLocalTime(&sysTime); - - ts->year = sysTime.wYear; - ts->month = sysTime.wMonth; - ts->day = sysTime.wDay; - ts->hour = sysTime.wHour; - ts->min = sysTime.wMinute; - ts->sec = sysTime.wSecond; - ts->week_day = sysTime.wDayOfWeek - 1; -} diff --git a/si/controller.c b/si/controller.c index 4c276d5..1fe9cc0 100644 --- a/si/controller.c +++ b/si/controller.c @@ -80,6 +80,9 @@ int si_init(struct si_controller *si, struct bus_controller *bus, si->eeprom.data = eeprom; si->eeprom.size = eeprom_size; + // initialize RTC + rtc_init(&si->rtc); + // controllers memcpy(si->controller, controller, sizeof(struct controller) * 4); @@ -207,7 +210,7 @@ int pif_perform_command(struct si_controller *si, assert(0 && "Invalid channel for RTC status"); return 1; } - return rtc_status(send_buf, send_bytes, recv_buf, recv_bytes); + return rtc_status(&si->rtc, send_buf, send_bytes, recv_buf, recv_bytes); // RTC read case 0x07: @@ -215,7 +218,7 @@ int pif_perform_command(struct si_controller *si, assert(0 && "Invalid channel for RTC read"); return 1; } - return rtc_read(send_buf, send_bytes, recv_buf, recv_bytes); + return rtc_read(&si->rtc, send_buf, send_bytes, recv_buf, recv_bytes); // RTC write case 0x08: @@ -223,7 +226,7 @@ int pif_perform_command(struct si_controller *si, assert(0 && "Invalid channel for RTC write"); return 1; } - return rtc_write(send_buf, send_bytes, recv_buf, recv_bytes); + return rtc_write(&si->rtc, send_buf, send_bytes, recv_buf, recv_bytes); // Unimplemented command: default: diff --git a/si/controller.h b/si/controller.h index 2e8a4ff..78a30c4 100644 --- a/si/controller.h +++ b/si/controller.h @@ -11,6 +11,7 @@ #ifndef __si_controller_h__ #define __si_controller_h__ #include "common.h" +#include "local_time.h" #include "si/pak.h" #include "dd/controller.h" @@ -30,6 +31,12 @@ struct eeprom { size_t size; }; +struct rtc { + uint16_t control; + struct time_stamp now; + int32_t offset_seconds; +}; + struct si_controller { struct bus_controller *bus; const uint8_t *rom; @@ -40,6 +47,7 @@ struct si_controller { uint32_t pif_status; uint8_t input[4]; struct eeprom eeprom; + struct rtc rtc; struct controller controller[4]; }; diff --git a/si/rtc.c b/si/rtc.c index ffb7db3..ae19f25 100644 --- a/si/rtc.c +++ b/si/rtc.c @@ -10,56 +10,127 @@ #include "common.h" #include "local_time.h" +#include "si/controller.h" -int rtc_status(uint8_t *send_buf, uint8_t send_bytes, +static inline uint8_t rtc_status_byte(struct rtc * rtc) { + return (rtc->control & 0x0004) ? 0x80 : 0x00; +} + +void rtc_init(struct rtc * rtc) { + // Write-protected, not stopped + rtc->control = 0x0300; + rtc->offset_seconds = 0; + get_local_time(&rtc->now, rtc->offset_seconds); +} + +int rtc_status(struct rtc * rtc, + uint8_t *send_buf, uint8_t send_bytes, uint8_t *recv_buf, uint8_t recv_bytes) { + // Check send/recv buffer lengths + assert(send_bytes == 1); + assert(recv_bytes == 3); + recv_buf[0] = 0x00; recv_buf[1] = 0x10; - recv_buf[2] = 0x00; + recv_buf[2] = rtc_status_byte(rtc); return 0; } -int rtc_read(uint8_t *send_buf, uint8_t send_bytes, +int rtc_read(struct rtc * rtc, + uint8_t *send_buf, uint8_t send_bytes, uint8_t *recv_buf, uint8_t recv_bytes) { - struct time_stamp now; + // Check send/recv buffer lengths + assert(send_bytes == 2); + assert(recv_bytes == 9); - // FIXME is this needed? + // Zero out the response buffer memset(recv_buf, 0, recv_bytes); // read RTC block switch (send_buf[1]) { case 0: - recv_buf[0] = 0x02; + recv_buf[0] = rtc->control >> 8; + recv_buf[1] = rtc->control; break; case 1: - debug("RTC cannot read block 1\n"); - return 1; + debug("RTC read block 1 is not implemented\n"); + break; case 2: - get_local_time(&now); - recv_buf[0] = byte2bcd(now.sec); - recv_buf[1] = byte2bcd(now.min); - recv_buf[2] = 0x80 + byte2bcd(now.hour); - recv_buf[3] = byte2bcd(now.day); - recv_buf[4] = byte2bcd(now.week_day); - recv_buf[5] = byte2bcd(now.month); - recv_buf[6] = byte2bcd(now.year); - recv_buf[7] = byte2bcd(now.year / 100); - recv_buf[8] = 0x00; // status + // update the time if the clock is not stopped + if ((rtc->control & 0x0004) == 0) { + get_local_time(&rtc->now, rtc->offset_seconds); + } + recv_buf[0] = byte2bcd(rtc->now.sec); + recv_buf[1] = byte2bcd(rtc->now.min); + recv_buf[2] = byte2bcd(rtc->now.hour) + 0x80; + recv_buf[3] = byte2bcd(rtc->now.day); + recv_buf[4] = byte2bcd(rtc->now.week_day); + recv_buf[5] = byte2bcd(rtc->now.month); + recv_buf[6] = byte2bcd(rtc->now.year); + recv_buf[7] = byte2bcd(rtc->now.year / 100); break; default: - debug("RTC unknown block\n"); + debug("RTC read invalid block\n"); return 1; } + recv_buf[8] = rtc_status_byte(rtc); + return 0; } -int rtc_write(uint8_t *send_buf, uint8_t send_bytes, +int rtc_write(struct rtc * rtc, + uint8_t *send_buf, uint8_t send_bytes, uint8_t *recv_buf, uint8_t recv_bytes) { - debug("RTC write not implemented\n"); - return 1; + // Check send/recv buffer lengths + assert(send_bytes == 10); + assert(recv_bytes == 1); + + // write RTC block + switch (send_buf[1]) { + case 0: + rtc->control = ((uint16_t)send_buf[2] << 8) | send_buf[3]; + break; + + case 1: + if (rtc->control & 0x0100) { + debug("RTC write block 1 is write-protected\n"); + } else { + debug("RTC write block 1 is not implemented\n"); + } + break; + + case 2: + if ((rtc->control & 0x0004) == 0) { + debug("RTC write block 2 while clock is running\n"); + break; + } + if (rtc->control & 0x0200) { + debug("RTC write block 2 is write-protected\n"); + break; + } + rtc->now.sec = bcd2byte(send_buf[2]); + rtc->now.min = bcd2byte(send_buf[3]); + rtc->now.hour = bcd2byte(send_buf[4] - 0x80); + rtc->now.day = bcd2byte(send_buf[5]); + rtc->now.week_day = bcd2byte(send_buf[6]); + rtc->now.month = bcd2byte(send_buf[7]); + rtc->now.year = bcd2byte(send_buf[8]); + rtc->now.year += bcd2byte(send_buf[9]) * 100; + // Set the clock offset based on current local time + rtc->offset_seconds = get_offset_seconds(&rtc->now); + break; + + default: + debug("RTC write invalid block\n"); + return 1; + } + + recv_buf[0] = rtc_status_byte(rtc); + + return 0; } diff --git a/si/rtc.h b/si/rtc.h index 4266c18..d7d8e5c 100644 --- a/si/rtc.h +++ b/si/rtc.h @@ -11,12 +11,17 @@ #ifndef __si_rtc_h__ #define __si_rtc_h__ #include "common.h" +#include "si/controller.h" -int rtc_status(uint8_t *send_buf, uint8_t send_bytes, +void rtc_init(struct rtc * rtc); +int rtc_status(struct rtc * rtc, + uint8_t *send_buf, uint8_t send_bytes, uint8_t *recv_buf, uint8_t recv_bytes); -int rtc_read(uint8_t *send_buf, uint8_t send_bytes, +int rtc_read(struct rtc * rtc, + uint8_t *send_buf, uint8_t send_bytes, uint8_t *recv_buf, uint8_t recv_bytes); -int rtc_write(uint8_t *send_buf, uint8_t send_bytes, +int rtc_write(struct rtc * rtc, + uint8_t *send_buf, uint8_t send_bytes, uint8_t *recv_buf, uint8_t recv_bytes); #endif