--- nono/vm/rtc.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/rtc.cpp 2026/04/29 17:05:29 1.1.1.10 @@ -1,194 +1,411 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "rtc.h" -#include - -// XXX rtc の読み書きはロックしたほうがいいような気がする +// +// RTC の共通部分 +// -RTCDevice *gRTC; +#include "rtc.h" +#include "config.h" +#include "scheduler.h" +#include "syncer.h" +// コンストラクタ RTCDevice::RTCDevice() + : inherited(OBJ_RTC) { - rtc = (RTC){}; - // 秒への繰り上がりを許可。 - rtc.enable_sec = true; } +// デストラクタ RTCDevice::~RTCDevice() { } -// ホストの現在時刻を rtc に取得。 -// (派生クラスの InitRTC() から呼ばれる) -// localtime を内部時刻にするときは use_localtime == true -// UTC を内部時刻にするときは use_localtime == false にすること。 +// 初期化 +bool +RTCDevice::Init() +{ + if (inherited::Init() == false) { + return false; + } + + syncer = GetSyncer(); + + // TODO: use_localtime を設定可能にするべきかどうか + + // 時刻固定オプション (パフォーマンス測定用)。 + // パフォーマンス測定をする際に、RTC が実時間を返すと + // ゲストの状態が毎回異なることになるため、 + // いつ何時も同じ値を返すことで、世界から隔絶された + // VM を作り、パフォーマンス測定を安定させる。 + force_fixed = gConfig->Find(".rtc-force-fixed").AsInt(); + + // クロックの同期方法 + const ConfigItem& item_sync = gConfig->Find("clock-sync"); + auto clock_sync = item_sync.AsString(); + if (clock_sync == "real") { + sync_rt = true; + } else if (clock_sync == "virtual") { + sync_rt = false; + } else { + item_sync.Err(); + return false; + } + + 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; + } + + event.func = ToEventCallback(&RTCDevice::Callback); + event.time = period; + event.SetName("RTC ClockIn"); + scheduler->RegistEvent(event); + + return true; +} + +// 時間の始まり void -RTCDevice::LoadHostRTC(bool use_localtime) +RTCDevice::StartTime() { struct timeval tv; struct tm tm; - // 起動した時の現在時刻で設定する - gettimeofday(&tv, NULL); + 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, + }; + tv.tv_sec = timegm(&fixed); + tv.tv_usec = 0; + } else { + // 起動した時の現在時刻で設定する + gettimeofday(&tv, NULL); + } + if (use_localtime) { localtime_r(&tv.tv_sec, &tm); } else { gmtime_r(&tv.tv_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) - rtc.cnt = tv.tv_usec / 31250; - rtc.sec = tm.tm_sec; - rtc.min = tm.tm_min; - rtc.hour = tm.tm_hour; + cnt = tv.tv_usec / 31250; + SetSec(tm.tm_sec); + SetMin(tm.tm_min); + SetHour(tm.tm_hour); // wday も tm_wday も日曜が 0 - rtc.wday = tm.tm_wday; - rtc.mday = tm.tm_mday; - rtc.mon = tm.tm_mon + 1; - rtc.year = tm.tm_year + 1900; + 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); } -// 32Hz カウンタの処理。 -// 戻り値は更新が起きた位。 -// 派生クラスで用意した ClockIn() から呼び出すこと。 -uint32 -RTCDevice::CountUp() +// デバッグオプションの日付文字列を解析する。 +// 書式は "yyyy-mm-dd[-w]"。曜日は省略可能、区切り文字は1文字なら何でもいい。 +// 入力値の範囲チェックはしない。(出来れば 0A月みたいなのも受け付けたいが) +bool +RTCDevice::ParseDate(const std::string& str, int *y, int *m, int *d, int *w) { - uint32 updates; + const char *p = str.data(); + char *end; - updates = 0; + errno = 0; + *y = strtoul(p, &end, 10); + if (end == p || *end == '\0' || errno != 0) { + return false; + } - // 内部カウンタアップ - rtc.cnt++; - rtc.cnt &= 63; + p = end + 1; + errno = 0; + *m = strtoul(p, &end, 10); + if (end == p || *end == '\0' || errno != 0) { + return false; + } - // RP5C15 の Timer Enable は内部カウンタは動作していて、 - // 秒へのカウントアップをするかどうかのフラグ。 - if (rtc.enable_sec == false) { - return updates; + p = end + 1; + errno = 0; + *d = strtoul(p, &end, 10); + if (end == p || errno != 0) { + return false; } - // 秒の繰り上がり処理。 - // 32カウントで1秒だが、RP5C15 はタイマー停止中でも 1秒への繰り上がり - // 1回は内部で覚えているので、ここだけ 32 以上だったら 32 を引く運用。 - if (rtc.cnt >= 32) { - rtc.cnt -= 32; - updates |= CarrySec(updates); + if (*end == 0) { + // 曜日はなくても可 + *w = -1; + } else { + // あれば取り出す + p = end + 1; + errno = 0; + *w = strtoul(p, &end, 10); + if (end == p || errno != 0) { + return false; + } } - return updates; + + return true; } -// 秒への繰り上がり処理。 -// 戻り値は更新が起きた位。 -uint32 -RTCDevice::CarrySec(uint32 updates) +// デバッグオプションの時刻文字列を解析する。 +// 書式は "hh:mm:ss"。区切り文字は1文字なら何でもいい。 +// 入力値の範囲チェックはしない。 +bool +RTCDevice::ParseTime(const std::string& str, int *h, int *m, int *s) { - rtc.sec++; - updates |= RTCDevice::UPDATE_SEC; + const char *p = str.data(); + char *end; - if (rtc.sec >= 60) { - rtc.sec = 0; - updates |= CarryMin(updates); + 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); } - return updates; } -// 分への繰り上がり処理。 -// 戻り値は更新が起きた位。 -uint32 -RTCDevice::CarryMin(uint32 updates) +uint64 +RTCDevice::ClockIn_PowerOff() { - rtc.min++; - updates |= RTCDevice::UPDATE_MIN; + stime += period; + + ClockIn(); - if (rtc.min >= 60) { - rtc.min = 0; - updates |= CarryHour(updates); + // 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; } - return updates; } -// 時への繰り上がり処理。時間は24時間計。 -// 戻り値は更新が起きた位。 -uint32 -RTCDevice::CarryHour(uint32 updates) +// 32Hz のクロック入力。 +// イベントから呼び出される。 +// 本来の RTC は 32768Hz が入力クロックだが、 +// それを 1024 分周したものを基準クロック入力として考える。 +void +RTCDevice::ClockIn() { - rtc.hour++; - updates |= RTCDevice::UPDATE_HOUR; + cnt++; - if (rtc.hour >= 24) { - rtc.hour = 0; - updates |= CarryDay(updates); + Tick32Hz(); + if ((cnt & 15) == 0) { + Tick2Hz(); + if ((cnt & 31) == 0) { + // 秒カウントアップは Tick1Hz が行う + // 秒カウントアップ禁止を実装するため、 + // 基本クラスで直接秒カウントアップはしない。 + Tick1Hz(); + } } - return updates; } -// 日への繰り上がり処理。 -// 戻り値は更新が起きた位。 -uint32 -RTCDevice::CarryDay(uint32 updates) +// 32Hz で呼び出される関数。 +void +RTCDevice::Tick32Hz() { - // まず曜日 - rtc.wday = (rtc.wday + 1) % 7; +} - // 日にち - rtc.mday++; - updates |= RTCDevice::UPDATE_DAY; +// 2Hz で呼び出される関数。 +void +RTCDevice::Tick2Hz() +{ +} - // 日が繰り上がった結果、月が繰り上がるかどうか - static const int mdays[] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 - }; - if (rtc.mday > (mdays[rtc.mon] + (IsLeapMon() ? 1 : 0))) { - rtc.mday = 1; - updates |= CarryMon(updates); - } - return updates; +// 1Hz で呼び出される関数。 +void +RTCDevice::Tick1Hz() +{ } -// 月への繰り上がり処理。 -// 戻り値は更新が起きた位。 -uint32 -RTCDevice::CarryMon(uint32 updates) +// 秒をカウントアップし、繰り上がりを処理。 +void +RTCDevice::CountUpSec() { - rtc.mon++; - updates |= RTCDevice::UPDATE_MON; + uint v; - if (rtc.mon >= 12) { - rtc.mon = 1; - updates |= CarryYear(updates); + // BCD の桁ごとにキャリー処理をしてるはずだけど妥協。 + // たぶん内部キャリーはビット途中のキャリーで作られていると + // 思うんだけど、ソフトでやるのは大変なので妥協。 + + v = GetSec() + 1; + if (v < 60) { + SetSec(v); + return; } - return updates; + SetSec(0); + CountUpMin(); } -// 年への繰り上がり処理。 -// 戻り値は更新が起きた位。 -uint32 -RTCDevice::CarryYear(uint32 updates) +// 分をカウントアップし、繰り上がりを処理。 +void +RTCDevice::CountUpMin() { - rtc.year++; - updates |= RTCDevice::UPDATE_YEAR; + 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); - return updates; + 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); } -// 閏年の2月なら true を返す。 -bool -RTCDevice::IsLeapMon() const +// 現在の月の最終日を返す (月の繰り上がり処理用) +uint +RTCDevice::GetLastMday() const { - // 1900年から2100年までの間は 4 で割るだけでよい。 - if ((rtc.year % 4 == 0) && (rtc.mon == 2)) { - return true; + // 下記の理由により 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 false; + return lastday; } -// 曜日文字列 +// 曜日文字列。 +// RP5C15、MK48T02 いずれも曜日カウンタと曜日の対応はデータシートに規定がなく +// 運用依存であり、X680x0 (RP5C15) は 0..6 (0が日曜) としている。 +// LUNA (MK48T02) は運用上 0..7(0と7が日曜) となってしまっている。 /*static*/ const char * const -RTCDevice::wdays[7] = { +RTCDevice::wdays[8] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sun", };