mirror of
https://github.com/n64dev/cen64.git
synced 2024-05-11 08:55:44 -04:00
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:
parent
202d2359c1
commit
8f64dcd8b3
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
51
os/common/local_time.c
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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
115
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;
|
||||
}
|
||||
|
|
11
si/rtc.h
11
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
|
||||
|
|
Loading…
Reference in a new issue