File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / goldfish_rtc.cpp
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//

//
// Goldfish (Qemu) RTC+Timer (の RTC のほう)
//

// RTC デバイスのレジスタは次の通り。(QEMU 仕様)
//
//  +$00.L TIME_LOW		RW: ホスト時刻 [nsec] の下位 32 ビット
//  +$04.L TIME_HIGH	RW: ホスト時刻 [nsec] の上位 32 ビット
//
// TIME_LOW の動作は Timer デバイスと同じだが、こちらはホストの RTC 時刻
// (実世界時間) を秒の粒度で返す。つまり time() * 1e9 だと仕様書にはある。
// 書き込みも可能 (当然 VM の RTC を書き換える)。
// アラームと割り込み機能は一切ないが、レジスタマップは Timer デバイスと
// 同じものを使うらしい。

#include "goldfish_rtc.h"
#include "event.h"
#include "mainbus.h"
#include "mytime.h"
#include "textscreen.h"

//
// GFRTC
//

// コンストラクタ
GFRTCDevice::GFRTCDevice()
	: inherited()
{
}

// デストラクタ
GFRTCDevice::~GFRTCDevice()
{
}

// リセット
void
GFRTCDevice::ResetHard(bool poweron)
{
	time_nsec.q = 0;
}

busdata
GFRTCDevice::ReadPort(uint32 offset)
{
	busdata data;

	switch (offset) {
	 case GFRTC::TIME_LOW:
		time_nsec.q = sec_to_nsec(MkTime());
		data = time_nsec.l;
		if (__predict_false(loglevel >= 1)) {
			if (loglevel >= 3) {
				putlogn("TIME_LOW  -> $%08x", data.Data());
			} else {
				putlogn("TIME -> $%08x'%08x", time_nsec.h, data.Data());
			}

			if (loglevel >= 2) {
				struct tm tm, tr;
				time_t t = (time_t)nsec_to_sec(time_nsec.q);
				gmtime_r(&t, &tm);
				putlogn("TIME / 1e9:   %4u/%02u/%02u %02u:%02u:%02u",
					tm.tm_year + 1900,
					tm.tm_mon + 1,
					tm.tm_mday,
					tm.tm_hour,
					tm.tm_min,
					tm.tm_sec);

				time_t u = (time_t)get_hosttime_sec();
				gmtime_r(&u, &tr);
				putlogn("system_clock: %4u/%02u/%02u %02u:%02u:%02u",
					tr.tm_year + 1900,
					tr.tm_mon + 1,
					tr.tm_mday,
					tr.tm_hour,
					tr.tm_min,
					tr.tm_sec);
			}
		}
		break;

	 case GFRTC::TIME_HIGH:
		data = time_nsec.h;
		putlog(3, "TIME_HIGH -> $%08x", data.Data());
		break;

	 case GFRTC::INTR_STATUS:
		putlog(2, "Read INTR_STATUS (No function)");
		data = 0;
		break;

	 case GFRTC::ALARM_STATUS:
		putlog(2, "Read ALARM_STATUS (No function)");
		data = 0;
		break;

	 default:
		putlog(1, "Read unknown $%08x", GetMainbusDevice()->GetPaddr());
		data.SetBusErr();
		break;
	}

	data |= BusData::Size4;
	return data;
}

busdata
GFRTCDevice::WritePort(uint32 offset, uint32 data)
{
	busdata r;

	switch (offset) {
	 case GFRTC::TIME_LOW:
	 {
		time_nsec.l = data;

		if (__predict_false(loglevel >= 1)) {
			if (loglevel >= 2) {
				putlogn("TIME_LOW  <- $%08x", time_nsec.l);
			} else {
				putlogn("TIME <- $%08x'%08x", time_nsec.h, time_nsec.l);
			}
		}

		time_t t = nsec_to_sec(time_nsec.q);
		struct tm tm;
		gmtime_r(&t, &tm);
		SetYear(tm.tm_year + 1900);
		SetMon(tm.tm_mon + 1);
		SetMday(tm.tm_mday);
		SetHour(tm.tm_hour);
		SetMin(tm.tm_min);
		SetSec(tm.tm_sec);
		break;
	 }

	 case GFRTC::TIME_HIGH:
		putlog(2, "TIME_HIGH <- $%08x", data);
		time_nsec.h = data;
		break;

	 case GFRTC::ALARM_LOW:
		putlog(2, "ALARM_LOW  <- $%08x (No function)", data);
		break;

	 case GFRTC::ALARM_HIGH:
		putlog(2, "ALARM_HIGH <- $%08x (No function)", data);
		break;

	 case GFRTC::INTR_STATUS:
		putlog(2, "INTR_STATUS <- $%08x (No function)", data);
		break;

	 case GFRTC::ALARM_CLEAR:
		putlog(2, "ALARM_CLEAR <- $%08x (No function)", data);
		break;

	 case GFRTC::INTR_CLEAR:
		putlog(2, "INTR_CLEAR <- $%08x (No function)", data);
		break;

	 default:
		putlog(1, "Write unknown $%08x <- $%08x",
			GetMainbusDevice()->GetPaddr(), data);
		r.SetBusErr();
		break;
	}

	r |= BusData::Size4;
	return r;
}

busdata
GFRTCDevice::PeekPort(uint32 offset)
{
	switch (offset) {
	 case GFRTC::TIME_LOW:
	 {
		uint64 now = sec_to_nsec(MkTime());
		return now & 0xffffffffU;
	 }
	 case GFRTC::TIME_HIGH:
	 {
		uint64 now = sec_to_nsec(MkTime());
		return now >> 32;
	 }
	 case GFRTC::INTR_STATUS:
	 case GFRTC::ALARM_STATUS:
		return 0;
	 default:
		return BusData::BusErr;
	}
}

// モニタの下請け。(GFTimer から呼ばれる)
int
GFRTCDevice::MonitorScreenRTC(TextScreen& screen, int y) const
{
	screen.Puts(0, y++, "<RTC>");
	screen.Print(0, y++, "%04u/%02u/%02u(%s) %02u:%02u:%02u",
		year, mon, day, wdays[GetWday()], hour, min, sec);
	screen.Puts(0, y++, "TimeZone: UTC");

	return y;
}

void
GFRTCDevice::Tick1Hz()
{
	CountUpSec();
}

// 内部時刻から time_t を作成する。
time_t
GFRTCDevice::MkTime() const
{
	struct tm tm;

	tm.tm_year = year - 1900;
	tm.tm_mon  = mon - 1;
	tm.tm_mday = day;
	tm.tm_hour = hour;
	tm.tm_min  = min;
	tm.tm_sec  = sec;

	return timegm(&tm);
}

uint
GFRTCDevice::GetWday() const
{
	// 曜日は保持していないので一旦日付を作ってから求める。
	struct tm tm;
	time_t t = MkTime();

	gmtime_r(&t, &tm);
	return tm.tm_wday;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.