|
|
nono 1.7.0
//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//
//
// Goldfish (Qemu) RTC+Timer (の Timer のほう)
//
// Google Goldfish RTC/Timer 仕様と QEMU 実装の仕様には互換性がない。
// オリジナルもまあまあ首をかしげる仕様だったので、それを QEMU が跡形なく
// 変更して使っているのかも知れない。知らんけど。
// ここで実装しているのは QEMU 仕様のほう。
//
// デバイスには 2 種類あり、Timer と RTC という名前で区別する。
// で、これはどこの仕様なのか QEMU の実装なのか分からないが、何故か
// RTC デバイスのベースアドレスは Timer デバイスのベースアドレス + 0x1000 の
// 位置らしい。
// どうして別デバイスにして BootInfo で別々に渡すよう設計しなかったのか…。
//
// 元仕様書にエンディアンのエの字もないので、たぶん何の考察もしてないのだと
// 思うが、ここではゲストエンディアン (ビッグエンディアン) デバイスに見せる。
// NetBSD/virt68k もそのように動いているようなので。
// Timer デバイスのレジスタは次の通り。
//
// +$00.L TIME_LOW R-: ホスト時刻 [nsec] の下位 32 ビット
// +$04.L TIME_HIGH R-: ホスト時刻 [nsec] の上位 32 ビット
// +$08.L ALARM_LOW -W: アラーム時刻 [nsec] の下位 32 ビットとアラーム開始
// +$0c.L ALARM_HIGH -W: アラーム時刻 [nsec] の上位 32 ビット
// +$10.L INTR_STATUS RW: 割り込み許可禁止の取得(R)/設定(W)
// +$14.L ALARM_CLEAR -W: 動作中のアラームクリア
// +$18.L ALARM_STATUS R-: アラーム動作中なら $1、そうでなければ $0
// +$1c.L INTR_CLEAR -W: 割り込みクリア
//
// TIME_LOW、TIME_HIGH はホスト時刻を [nsec] で返す。
// TIME_LOW 読み込み時に 64 ビットをラッチするので必ず TIME_LOW、HIGH の順で
// 読み出すこと。ここでいうホスト時刻は、ホストのある時点 (大抵は VM の開始
// 時点) からの相対時刻。
//
// ALARM_LOW、ALARM_HIGH はアラーム時刻 [nsec] の設定。
// 現時刻からの間隔ではなくこのホスト時間軸上の時刻。
// 過去の時刻だったらどうなるとかの定義は一切ない…。
// ALARM_LOW がアラーム開始を兼ねているので必ず HIGH、LOW の順で書き込むこと。
//
// INTR_STATUS は書き込み時は最下位ビットが %1 なら割り込み許可、%0 なら禁止。
// 読み込み時は割り込み許可なら $1、禁止なら $0。
#include "goldfish_timer.h"
#include "event.h"
#include "goldfish_pic.h"
#include "goldfish_rtc.h"
#include "mainbus.h"
#include "monitor.h"
#include "scheduler.h"
#include "syncer.h"
//
// GFTimer
//
// コンストラクタ
GFTimerDevice::GFTimerDevice()
: inherited(OBJ_GFTIMER)
{
// モニタは GFRTC + GFTimer で共有。
monitor = gMonitorManager->Regist(ID_MONITOR_RTC, this);
monitor->SetCallback(&GFTimerDevice::MonitorScreen);
monitor->SetSize(38, 7);
}
// デストラクタ
GFTimerDevice::~GFTimerDevice()
{
}
// 初期化
bool
GFTimerDevice::Init()
{
// 割り込み信号線の接続先
auto gfpic = GetGFPICDevice(6);
gfpic->RegistIRQ(1, "GFTimer", this);
interrupt = gfpic;
gfrtc = dynamic_cast<GFRTCDevice *>(GetRTCDevice());
syncer = GetSyncer();
auto evman = GetEventManager();
event = evman->Regist(this, NULL, "Goldfish Timer");
// クロックの同期方法
sync_rt = syncer->GetSyncRealtime();
// それによってイベントを分けてある。(time はどちらも都度セットする)
if (sync_rt) {
event->SetCallback(&GFTimerDevice::CallbackRT);
} else {
event->SetCallback(&GFTimerDevice::CallbackVT);
}
return true;
}
// 電源オン/リセット
void
GFTimerDevice::ResetHard(bool poweron)
{
scheduler->StopEvent(event);
elapsed.q = 0;
alarm.q = 0;
mtime = 0;
mtime_epoch = 0;
intr_enable = false;
intr_assert = false;
ChangeInterrupt();
}
busdata
GFTimerDevice::ReadPort(uint32 offset)
{
busdata data;
switch (offset) {
case GFRTC::TIME_LOW:
elapsed.q = Now();
data = elapsed.l;
if (__predict_false(loglevel >= 2)) {
if (loglevel >= 3) {
putlogn("TIME_LOW -> $%08x", data.Data());
} else {
putlogn("TIME -> $%08x'%08x", elapsed.h, data.Data());
}
}
break;
case GFRTC::TIME_HIGH:
data = elapsed.h;
putlog(3, "TIME_HIGH -> $%08x", data.Data());
break;
case GFRTC::INTR_STATUS:
data = intr_enable ? 1 : 0;
putlog(2, "INTR_STATUS -> $%08x", data.Data());
break;
case GFRTC::ALARM_STATUS:
data = event->IsRunning() ? 1 : 0;
putlog(2, "ALARM_STATUS -> $%08x", data.Data());
break;
default:
putlog(1, "Read unknown $%08x", GetMainbusDevice()->GetPaddr());
data.SetBusErr();
break;
}
data |= BusData::Size4;
return data;
}
busdata
GFTimerDevice::WritePort(uint32 offset, uint32 data)
{
busdata r;
switch (offset) {
case GFRTC::ALARM_LOW:
alarm.h = alarm_high;
alarm.l = data;
alarm_high = 0;
if (__predict_false(loglevel >= 2)) {
if (loglevel >= 3) {
putlogn("ALARM_LOW <- $%08x", data);
} else {
putlogn("ALARM <- $%08x'%08x", alarm.h, alarm.l);
}
}
StartAlarm();
break;
case GFRTC::ALARM_HIGH:
putlog(3, "ALARM_HIGH <- $%08x", data);
alarm_high = data;
break;
case GFRTC::INTR_STATUS:
intr_enable = data & 1;
if (__predict_false(loglevel >= 1)) {
if (intr_enable) {
putlogn("Enable interrupt");
} else {
putlogn("Disable interrupt");
}
}
ChangeInterrupt();
break;
case GFRTC::ALARM_CLEAR:
putlog(1, "Clear alarm");
alarm.q = 0;
break;
case GFRTC::INTR_CLEAR:
putlog(2, "Clear interrupt");
intr_assert = false;
ChangeInterrupt();
break;
default:
putlog(1, "Write unknown $%08x <- $%08x",
GetMainbusDevice()->GetPaddr(), data);
r = BusData::BusErr;
break;
}
r |= BusData::Size4;
return r;
}
busdata
GFTimerDevice::PeekPort(uint32 offset)
{
switch (offset) {
case GFRTC::TIME_LOW:
return Now() & 0xffffffffU;
case GFRTC::TIME_HIGH:
return Now() >> 32;
case GFRTC::INTR_STATUS:
return intr_assert ? 1 : 0;
case GFRTC::ALARM_STATUS:
return event->IsRunning() ? 1 : 0;
default:
return BusData::BusErr;
}
}
void
GFTimerDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
int y = 0;
screen.Clear();
uint64 now = Now();
screen.Puts(0, y++, "<Timer>");
screen.Print(0, y++, "Time: %26s", TimeToStr(now).c_str());
screen.Print(0, y++, "Alarm: %26s", TimeToStr(alarm.q).c_str());
y++;
gfrtc->MonitorScreenRTC(screen, y);
}
// 現在時刻を返す
uint64
GFTimerDevice::Now() const
{
uint64 now;
if (sync_rt) {
now = syncer->GetRealTime();
} else {
now = scheduler->GetVirtTime();
}
return now;
}
// アラーム開始。(ALARM_LOW の書き込みで呼ばれる)
void
GFTimerDevice::StartAlarm()
{
uint64 now = Now();
if (__predict_false(mtime_epoch == 0)) {
// 最初のアラーム書き込み時を修正実時間軸の基準点にする。
mtime_epoch = now;
mtime = 0;
}
// 仮想時間モードなら ALARM 時刻に発火するイベントをセットするだけ。
// 実時間モードならひとまず仮に本来の仮想時刻にイベントは起こすが、
// 割り込みを上げるかどうかは向こうで調整する。
// event->code は VT モードの時には不要だが気にせず立てる。
// 指定されたアラームがすでに過去の時刻なら QEMU は即発火するようだ。
if (__predict_true(alarm.q > now)) {
event->time = alarm.q - now;
} else {
if (__predict_false(loglevel >= 1)) {
uint64 delta = now - alarm.q;
putlogn("ALARM: past alarm -%s", TimeToStr(delta).c_str());
}
event->time = 0;
}
event->code = 1;
scheduler->RestartEvent(event);
}
// 実時間モードでのコールバック。
void
GFTimerDevice::CallbackRT(Event *ev)
{
// アラーム開始後最初の1回だけ自分の時間軸をアラーム分だけ進める。
if (event->code) {
mtime += event->time;
event->code = 0;
}
uint64 mnow = syncer->GetRealTime() - mtime_epoch;
if (mtime >= mnow) {
// システムの実時間軸を追い越していたら一回休んで
// もう一度このイベントに来る。
scheduler->RestartEvent(event);
} else {
// システムの実時間軸に追い付いてなければ(通常動作)、アラーム発報。
intr_assert = true;
ChangeInterrupt();
}
}
// 仮想時間モードでのコールバック。
void
GFTimerDevice::CallbackVT(Event *ev)
{
// 仮想時間モードなら、イベントが起きたここがゴール。
// アラームは単発イベントなのでリスタート不要。
intr_assert = true;
ChangeInterrupt();
}
void
GFTimerDevice::ChangeInterrupt()
{
bool irq = (intr_enable && intr_assert);
interrupt->ChangeINT(this, irq);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.