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

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

//
// RTC の共通部分
//

#include "rtc.h"
#include "config.h"
#include "event.h"
#include "mytime.h"
#include "scheduler.h"
#include "syncer.h"

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

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

// 初期化
bool
RTCDevice::Init()
{
	syncer = GetSyncer();

	// TODO: use_localtime を設定可能にするべきかどうか

	// 時刻固定オプション (パフォーマンス測定用)。
	// パフォーマンス測定をする際に、RTC が実時間を返すと
	// ゲストの状態が毎回異なることになるため、
	// いつ何時も同じ値を返すことで、世界から隔絶された
	// VM を作り、パフォーマンス測定を安定させる。
	force_fixed = gConfig->Find(".rtc-force-fixed").AsBool();

	sync_rt = syncer->GetSyncRealtime();

	rtc_tm.tm_year = -1;
	rtc_tm.tm_mon = -1;
	rtc_tm.tm_mday = -1;
	rtc_tm.tm_wday = -1;
	rtc_tm.tm_hour = -1;
	rtc_tm.tm_min = -1;
	rtc_tm.tm_sec = -1;

	// 日付、時刻の直接指定
	const ConfigItem item_date = gConfig->Find(".rtc-date");
	std::string str_date = item_date.AsString();
	if (!str_date.empty()) {
		int yy, mm, dd, w;
		if (ParseDate(str_date, &yy, &mm, &dd, &w) == false) {
			item_date.Err();
			return false;
		}
		rtc_tm.tm_year = yy - 1900;
		rtc_tm.tm_mon = mm - 1;
		rtc_tm.tm_mday = dd;
		if (w != -1) {
			rtc_tm.tm_wday = w;
		}
	}

	const ConfigItem item_time = gConfig->Find(".rtc-time");
	std::string str_time = item_time.AsString();
	if (!str_time.empty()) {
		int hh, mm, ss;
		if (ParseTime(str_time, &hh, &mm, &ss) == false) {
			item_time.Err();
			return false;
		}
		rtc_tm.tm_hour = hh;
		rtc_tm.tm_min  = mm;
		rtc_tm.tm_sec  = ss;
	}

	auto evman = GetEventManager();
	event = evman->Regist(this,
		ToEventCallback(&RTCDevice::Callback),
		"RTC ClockIn");
	event->time = period;

	return true;
}

// 時間の始まり
void
RTCDevice::StartTime()
{
	time_t now_sec;
	uint64 now_usec;
	struct tm tm;

	if (force_fixed) {
		// 固定の時刻を設定
		struct tm fixed {
			.tm_sec = 16,
			.tm_min = 22,
			.tm_hour = 13 - 9,
			.tm_mday = 27,
			.tm_mon = 7 - 1,
			.tm_year = 2018 - 1900,
		};
		now_sec = timegm(&fixed);
		now_usec = 0;
	} else {
		// 起動した時の現在時刻で設定する
		auto usec = get_hosttime_usec();
		now_sec  = usec / 1000'000U;
		now_usec = usec % 1000'000U;
	}

	if (use_localtime) {
		localtime_r(&now_sec, &tm);
	} else {
		gmtime_r(&now_sec, &tm);
	}

	// 日付、時刻の直接指定があればここで差し替える。
	if (rtc_tm.tm_year != -1) {
		tm.tm_year = rtc_tm.tm_year;
		tm.tm_mon = rtc_tm.tm_mon;
		tm.tm_mday = rtc_tm.tm_mday;
		if (rtc_tm.tm_wday != -1) {
			tm.tm_wday = rtc_tm.tm_wday;
		}
	}
	if (rtc_tm.tm_hour != -1) {
		tm.tm_hour = rtc_tm.tm_hour;
		tm.tm_min = rtc_tm.tm_min;
		tm.tm_sec = rtc_tm.tm_sec;
	}

	// 秒未満の初期値もそれなりの精度にしておく (1000*1000 / 32 = 31250)
	cnt = now_usec / 31250;
	SetSec(tm.tm_sec);
	SetMin(tm.tm_min);
	SetHour(tm.tm_hour);
	// wday も tm_wday も日曜が 0
	SetWday(tm.tm_wday);
	SetMday(tm.tm_mday);
	// mon は 1 から、tm_mon は 0 から。
	SetMon(tm.tm_mon + 1);
	SetYear(tm.tm_year + 1900);
	SetLeap(tm.tm_year & 3);

	stime = syncer->GetRealTime();

	scheduler->StartEvent(event);
}

// デバッグオプションの日付文字列を解析する。
// 書式は "yyyy-mm-dd[-w]"。曜日は省略可能、区切り文字は1文字なら何でもいい。
// 入力値の範囲チェックはしない。(出来れば 0A月みたいなのも受け付けたいが)
bool
RTCDevice::ParseDate(const std::string& str, int *y, int *m, int *d, int *w)
{
	const char *p = str.data();
	char *end;

	errno = 0;
	*y = strtoul(p, &end, 10);
	if (end == p || *end == '\0' || errno != 0) {
		return false;
	}

	p = end + 1;
	errno = 0;
	*m = strtoul(p, &end, 10);
	if (end == p || *end == '\0' || errno != 0) {
		return false;
	}

	p = end + 1;
	errno = 0;
	*d = strtoul(p, &end, 10);
	if (end == p || errno != 0) {
		return false;
	}

	if (*end == 0) {
		// 曜日はなくても可
		*w = -1;
	} else {
		// あれば取り出す
		p = end + 1;
		errno = 0;
		*w = strtoul(p, &end, 10);
		if (end == p || errno != 0) {
			return false;
		}
	}

	return true;
}

// デバッグオプションの時刻文字列を解析する。
// 書式は "hh:mm:ss"。区切り文字は1文字なら何でもいい。
// 入力値の範囲チェックはしない。
bool
RTCDevice::ParseTime(const std::string& str, int *h, int *m, int *s)
{
	const char *p = str.data();
	char *end;

	errno = 0;
	*h = strtoul(p, &end, 10);
	if (end == p || *end == '\0' || errno != 0) {
		return false;
	}

	p = end + 1;
	errno = 0;
	*m = strtoul(p, &end, 10);
	if (end == p || *end == '\0' || errno != 0) {
		return false;
	}

	p = end + 1;
	errno = 0;
	*s = strtoul(p, &end, 10);
	if (end == p || errno != 0) {
		return false;
	}

	return true;
}

// イベントハンドラ
void
RTCDevice::Callback(Event *ev)
{
	stime += period;
	ClockIn();
	if (sync_rt) {
		scheduler->StartRealtimeEvent(ev, stime, period);
	} else {
		scheduler->StartEvent(ev);
	}
}

uint64
RTCDevice::ClockIn_PowerOff()
{
	stime += period;

	ClockIn();

	// XXX RealTime ではなく HostTime?
	uint64 rt = syncer->GetRealTime();
	if (rt > stime) {
		// RTC が現実より遅れているので早回し
		return 0;
	} else {
		// clock-sync=virtual のときには stime が現実よりも多く進んで
		// いるため、実時間との差をスケジューラが usleep で待つところで
		// 長時間スリープしてしまう。
		// たちまち、period * 2 で制限しておく。
		uint64 dt = stime - rt;
		if (dt > period * 2) {
			dt = period * 2;
		}
		return dt;
	}
}

// 32Hz のクロック入力。
// イベントから呼び出される。
// 本来の RTC は 32768Hz が入力クロックだが、
// それを 1024 分周したものを基準クロック入力として考える。
void
RTCDevice::ClockIn()
{
	cnt++;

	Tick32Hz();
	if ((cnt & 15) == 0) {
		Tick2Hz();
		if ((cnt & 31) == 0) {
			// 秒カウントアップは Tick1Hz が行う
			// 秒カウントアップ禁止を実装するため、
			// 基本クラスで直接秒カウントアップはしない。
			Tick1Hz();
		}
	}
}

// 32Hz で呼び出される関数。
void
RTCDevice::Tick32Hz()
{
}

// 2Hz で呼び出される関数。
void
RTCDevice::Tick2Hz()
{
}

// 1Hz で呼び出される関数。
void
RTCDevice::Tick1Hz()
{
}

// 秒をカウントアップし、繰り上がりを処理。
void
RTCDevice::CountUpSec()
{
	uint v;

	// BCD の桁ごとにキャリー処理をしてるはずだけど妥協。
	// たぶん内部キャリーはビット途中のキャリーで作られていると
	// 思うんだけど、ソフトでやるのは大変なので妥協。

	v = GetSec() + 1;
	if (v < 60) {
		SetSec(v);
		return;
	}
	SetSec(0);
	CountUpMin();
}

// 分をカウントアップし、繰り上がりを処理。
void
RTCDevice::CountUpMin()
{
	uint v;

	v = GetMin() + 1;
	if (v < 60) {
		SetMin(v);
		return;
	}
	SetMin(0);

	v = GetHour() + 1;
	if (v < 24) {
		SetHour(v);
		return;
	}
	SetHour(0);

	// 曜日は単純7進カウンタ
	SetWday((GetWday() + 1) % 7);

	v = GetMday() + 1;
	if (v <= GetLastMday()) {
		SetMday(v);
		return;
	}
	SetMday(1);

	v = GetMon() + 1;
	if (v <= 12) {
		SetMon(v);
		return;
	}
	SetMon(1);

	SetLeap((GetLeap() + 1) % 4);
	SetYear(GetYear() + 1);
}

// 現在の月の最終日を返す (月の繰り上がり処理用)
uint
RTCDevice::GetLastMday() const
{
	// 下記の理由により 0 月が必要。値は適当。
	static const uint mdays[] = {
		30, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	};

	// GetMon() は不正な範囲を取ると規定していることに注意。
	// (0月、19月などが許されている)
	// 範囲外の値に対するロジックは違うだろうが規定されていないので
	// こっち都合で実装しておく。
	// m は 0..12
	uint m = GetMon() % 13;
	uint lastday = mdays[m];
	if (m == 2 && GetLeap() == 0) {
		lastday++;
	}
	return lastday;
}

// 曜日文字列。
// RP5C15、MK48T02 いずれも曜日カウンタと曜日の対応はデータシートに規定がなく
// 運用依存であり、X680x0 (RP5C15) は 0..6 (0が日曜) としている。
// LUNA (MK48T02) は運用上 0..7(0と7が日曜) となってしまっている。
/*static*/ const char * const
RTCDevice::wdays[8] = {
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
	"Sun",
};

unix.superglobalmegacorp.com

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