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!
This commit is contained in:
Christopher Bonhage 2021-07-12 09:29:05 -04:00 committed by Simon Eriksson
parent 202d2359c1
commit 8f64dcd8b3
11 changed files with 221 additions and 111 deletions

View file

@ -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

View file

@ -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) {

View file

@ -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,

51
os/common/local_time.c Normal file
View file

@ -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 <time.h>
#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;
}

View file

@ -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

View file

@ -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 <time.h>
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;
}

View file

@ -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 <time.h>
#include <windows.h>
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;
}

View file

@ -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:

View file

@ -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];
};

115
si/rtc.c
View file

@ -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;
}

View file

@ -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