File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / goldfish_timer.cpp
Revision 1.1.1.7 (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 (の 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);
}

unix.superglobalmegacorp.com

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