File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / sysctlr.cpp
Revision 1.1.1.17 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 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
//

//
// LUNA-88K のシステムコントローラのような何か
//

// IODevice
//    |
//    +-- InterruptDevice (割り込みコントローラの共通部)
//          |
//          +-- M680x0Interrupt (m68k 割り込みコントローラの共通部)
//          |     |
//          |     +-- X68030Interrupt  (X68030 の割り込みコントローラ)
//          |     +-- LunaInterrupt    (LUNA-I の割り込みコントローラ)
//          |
//          +-- PEDECDevice (X68030 の Lv1 担当コントローラ)
//          |
//          |  +---------------+
//          +--| SysCtlrDevice | (LUNA-88K の割り込みコントローラ)
//             +---------------+

// この辺を全部このデバイスで受け付けてある。
//
// 公称アドレス	実態範囲
// $6500'000n	$64'〜$67'	割り込み制御
// $6900'000n	$68'〜$6b'	ソフトウェア割り込み設定
// $6d00'00nn	$6c'〜$6d'	CPUリセット(?)
// ($6e'〜$6f' は不明、$ffffffff が読める)

// $6500'000n: 割り込み制御レジスタ(?)
//	アドレスは
//	  $6500'0000: CPU0
//	  $6500'0004: CPU1
//	  $6500'0008: CPU2
//	  $6500'000c: CPU3
//	$64..$67 から始まるこの 64MB は 16バイトでミラーされている模様。
//
//	書き込み時は
//	     3                   2                   1
//	   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//	  +-+-+-+-+-+-+---------------------------------------------------+
//	  |6|5|4|3|2|1|                                                   |
//	  +-+-+-+-+-+-+---------------------------------------------------+
//	  bit31: %1 = 割り込み 6 イネーブル
//	  bit30: %1 = 割り込み 5 イネーブル
//	  bit29: %1 = 割り込み 4 イネーブル
//	  bit28: %1 = 割り込み 3 イネーブル
//	  bit27: %1 = 割り込み 2 イネーブル
//	  bit26: %1 = 割り込み 1 イネーブル
//
//	m68k と異なり、個別の割り込みごとに許可/禁止できる。
//	該当ビットに 1 を書き込むと許可。空欄部分は無視される模様。
//
//	読み込み時は
//	     3                   2                   1
//	   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//	  +-----+---------+-+-+-+-+-+-+-----------------------------------+
//	  |Level|         |6|5|4|3|2|1|                                   |
//	  +-----+---------+-+-+-+-+-+-+-----------------------------------+
//	  bit31-29: 発生している最上位レベルの割り込み。範囲は7-0。
//	  bit23-18: 書き込んだ割り込みマスク。
//
//	空欄部分は「不定」の模様。
//
//	bit31-29 のレベルは、割り込みコントローラに入力されている割り込み
//	信号線6本をマスク処理した後の、プライオリティバイナリエンコーダの値が
//	そのまま出力されている模様。ラッチ機構は無いようだ。
//	従ってマスクされている割り込み(ビットが 0) の値が出力されることはない。
//	同時に入力があった場合は、数字の大きいほうが出力される。
//	レベル7 はマスクなしでプライオリティエンコーダに入力されているようだ。
//	割り込み入力がないときは 0 になる模様。
//
//	本体 ABRT スイッチを押している間、すべての CPU の Level が %111 (=7)
//	になる。離すと戻る。
//	m88100 には NMI が無いので、通常の割り込みと同じように処理している?
//	(なので IND 状態だとABRT できない。いいのかそれで)
//	(OpenBSD6.7 の割り込みハンドラはABRTには対応していないように読める)
//
//	想定される回路
//
//	  [割り込みソース]
//	   7:ABRT スイッチ
//	   |   6:システムクロック
//	   |   |   5:(シリアル | XP 高レベル | 拡張 PC-98?)
//	   |   |   |   4:(LANCE | 拡張スロット?)
//	   |   |   |   |   3:SPC
//	   |   |   |   |   |   2:(未使用?)
//	   |   |   |   |   |   |   1:(XP 低レベル | SOFTINT レジスタ $6900'000n)
//	   |   |   |   |   |   |   |
//	   |   V   V   V   V   V   V
//	   |  +---------------------+
//	   |  |   割り込みマスク    | (R:bit23-18, W:bit31-26)
//	   |  +---------------------+
//	   |   |   |   |   |   |   |
//	   V   V   V   V   V   V   V
//	  +-------------------------+
//	  |プライオリティエンコーダ |-> (R:bit31-29)
//	  +-------------------------+
//	           | | |
//	           V V V
//	         (Wired-OR)
//	             |
//	         [M88K INT]
//
//	割り込みソース調査状況。(LUNA-I とはレベル配置が違う)
//
//	1:SOFTINT レジスタ $6900'000n
//		ROM のメモリコマンドで確認。OpenBSD machdep.c の記述の通り。
//	1:XP 低レベル
//		OpenBSD mainbus.c および Issue#131 より。
//	3:SPC
//		PROM 1.20, OpenBSD ともに使用。
//	4:LANCE
//		ROM はまだ不明。OpenBSD は使用。
//	4:拡張スロット
//		LUNA-I からの類推のみ。不明。
//	5:拡張PC98
//		LUNA-I からの類推のみ。
//		LUNA-88K にはスロットがないようなので LUNA-88K2 が必要。
//		OpenBSD mainbus.c に記述あり。
//	5:シリアル(uPD7201)
//		OpenBSD mainbus.c に記述。
//	5:XP 高レベル
//		Issue#131 より。
//	6:システムクロック
//		ROM のメモリコマンドではレベル6割り込みが常時上がっているように
//		観測できるので、おそらくシステムクロック割り込みが上がっている。
//		OpenBSD maibus.c にも記述。
//	7:ABRTスイッチ
//		ROM のメモリコマンドで確認。
//
// $6900'000n: ソフトウェア割り込み設定レジスタ(?)
//	アドレスは
//	  $6900'0000: CPU0
//	  $6900'0004: CPU1
//	  $6900'0008: CPU2
//	  $6900'000c: CPU3
//
//	この32ビットのどこかにライトアクセスすると、INT1 をアサートする。
//	この32ビットのどこかをリードアクセスすると、INT1 をネゲートする。
//	この時のデータバスは無視される。0x00 を書いてもアサートされることに注意。
//	リードアクセスで読み込める値も不定のようだ。
//	要はアドレスバスと R/W 信号線しか見ていない。
//
// $6d00'000n: CPU 個別リセットレジスタ(?)
//	このへん触ると CPU がリセットされるというかだんまりになるので調査困難。
//
// $6d00'0010: 全 CPU リセットレジスタ(?)

#include "sysctlr.h"
#include "lance.h"
#include "mpu88xx0.h"
#include "nmi.h"
#include "prom.h"
#include "sio.h"
#include "spc.h"
#include "sysclk.h"

// コンストラクタ
SysCtlrDevice::SysCtlrDevice()
	: inherited(OBJ_INTERRUPT)
{
	SetName("SysCtlr");
	AddAlias("SysCtlr");
	AddAlias("SystemCtlr");

	intmap_status = IntmapSentinel;

	monitor = gMonitorManager->Regist(ID_MONITOR_INTERRUPT, this);
	monitor->SetCallback(&SysCtlrDevice::MonitorScreen);
}

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

// 初期化
bool
SysCtlrDevice::Init()
{
	mpu88xx0 = GetMPU88xx0Device(mpu);

	// 検索順
	RegistINT(IntmapSysClk,	"Clock",	GetSysClkDevice());
	RegistINT(IntmapSIO,	"SIO",		GetSIODevice());
	RegistINT(IntmapLance,	"Lance",	GetLanceDevice());
	RegistINT(IntmapSPC,	"SPC",		GetSPCDevice());
	RegistINT(IntmapXPHigh,	"XP(Hi)",	DEVICE_XPINT_HIGH);
	RegistINT(IntmapXPLow,	"XP(Lo)",	DEVICE_XPINT_LOW);
	RegistINT(IntmapSoft,	"SoftInt",	mpu88xx0);	// ?
	RegistINT(IntmapNMI,	"NMI",		GetNMIDevice());

	// 表示順
	disp_list = {
		IntmapNMI,
		IntmapSysClk,
		IntmapSIO,
		IntmapXPHigh,
		IntmapLance,
		IntmapSPC,
		IntmapXPLow,
		IntmapSoft,
	};

	// 行数が確定したのでサイズ設定
	monitor->SetSize(41, 1 + disp_list.size());

	return true;
}

// リセット
void
SysCtlrDevice::ResetHard(bool poweron)
{
	// さすがにリセットくらいはされるだろう…
	inherited::ResetHard(poweron);

	for (int i = 0; i < 4; i++) {
		// XXX 初期値は分からないがとりあえず割り込み禁止にしておく
		cpu[i].maskval = 0x00000000;
		cpu[i].intmask = MakeIntMask(cpu[i].maskval, i);
		cpu[i].ipl = 0;
		cpu[i].softint = 0;
		cpu[i].softint_count = 0;
	}
	ChangeInterrupt();
}

busdata
SysCtlrDevice::Read(busaddr addr)
{
	uint32 paddr = addr.Addr();
	uint n = (paddr >> 2) & 3;
	busdata data;

	// XXX バイト/ワードアクセスは未対応
	if (addr.GetSize() < 4) {
		putlog(0, "Read $%08x size=%u (NOT IMPLEMENTED)",
			paddr, addr.GetSize());
	}

	if (0x64000000 <= paddr && paddr < 0x68000000) {
		data = Peek4(paddr);
		putlog(2, "Read INT_CTRL(CPU#%u) -> $%08x", n, data.Data());

	} else if (0x69000000 <= paddr && paddr < 0x69000010) {
		// 読み出しで SOFTINT ネゲート (値は関係ない)
		if (cpu[n].softint) {
			putlog(1, "Softint(CPU#%u) Negate", n);
			cpu[n].softint = 0;
			ChangeInterrupt();
		}
		data = 0xffffffff;

	} else if (0x6d000000 <= paddr && paddr < 0x6d000010) {
		putlog(0, "Read CPU%u reset (NOT IMPLEMENTED)", n);
		data = 0xffffffff;

	} else if (paddr == 0x6d000010) {
		putlog(0, "Read All CPU reset (NOT IMPLEMENTED)");
		data = 0xffffffff;

	} else {
		putlog(0, "Read $%08x (NOT IMPLEMENTED)", paddr);
		data.SetBusErr();
	}

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

busdata
SysCtlrDevice::Write(busaddr addr, uint32 data)
{
	uint32 paddr = addr.Addr();
	uint n = (paddr >> 2) & 3U;

	// XXX バイト/ワードアクセスは未対応
	if (addr.GetSize() < 4) {
		putlog(0, "Write $%08x size=%u (NOT IMPLEMENTED)",
			paddr, addr.GetSize());
	}

	if (0x64000000 <= paddr && paddr < 0x68000000) {
		// 割り込みマスクを設定。
		WriteIntMask(n, data);

	} else if (0x69000000 <= paddr && paddr < 0x69000010) {
		// 書き込みで SOFTINT アサート (値は関係ない)
		if (cpu[n].softint == false) {
			putlog(1, "Softint(CPU#%u) Assert", n);
			cpu[n].softint = IntmapSoft;
			cpu[n].softint_count++;
			// 親クラスのカウンタも上げる (4つの和になる)
			int clz = __builtin_clz(IntmapSoft);
			counter[clz]++;
			ChangeInterrupt();
		}

	} else if (0x6d000000 <= paddr && paddr < 0x6d000010) {
		putlog(0, "Write CPU%u reset (NOT IMPLEMENTED)", n);

	} else if (paddr == 0x6d000010) {
		putlog(1, "Reset All CPUs");
		// XXX このあたりもう少し整理する必要がある
		// 今は MPU.ResetHard() が func を ResetCallback に、time をリセット
		// 間隔に書き換えて Start() した後、この書き込み命令から戻ったところの
		// ExecCallback が time だけ所要サイクルに書き換えて再度 Start() する
		// ため、時間はともかく幸い ResetCallback() が呼ばれることで動いている。
		GetPROM0Device()->ResetHard(false);
		mpu->ResetHard(false);
		ResetHard(false);

	} else {
		putlog(0, "Write $%08x <- $%08x (NOT IMPLEMENTED)", paddr, data);
	}

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

// Peek1() の下請け。ロングワードで処理して後でバイトに分割するほうが楽。
uint32
SysCtlrDevice::Peek4(uint32 addr) const
{
	uint n = (addr >> 2) & 3;
	uint32 data;

	if (0x64000000 <= addr && addr < 0x68000000) {
		// bit31-29 に発生している最上位の割り込みレベル。
		// bit23-18 に設定したレベル 6-1 の割り込みマスク。
		data = (cpu[n].ipl << 29) | (cpu[n].maskval >> 8);
	} else {
		data = 0xffffffff;
	}
	return data;
}

busdata
SysCtlrDevice::Peek1(uint32 addr)
{
	uint32 data = Peek4(addr & ~3U);
	switch (addr & 3) {
	 case 0:
		return data >> 24;
	 case 1:
		return (data >> 16) & 0xff;
	 case 2:
		return (data >>  8) & 0xff;
	 case 3:
		return data & 0xff;
	 default:
		__unreachable();
	}
}

void
SysCtlrDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	int y;

	// CPU の割り込み可否は本当は関係ないけど、便宜的に一緒に表示したい
	bool ei = mpu88xx0->IsIntrEnable();

	screen.Clear();

	// 0         1         2         3         4
	// 01234567890123456789012345678901234567890
	// Lv Device CPU0                     Count

	y = 0;
	// XXX CPU の実装数によってウィンドウ幅を変えること。
	screen.Print(0, y, "Lv Device  CPU0");
	screen.Puts(36, y, "Count");
	y++;
	for (uint32 map : disp_list) {
		uint clz = __builtin_clz(map);
		uint lv = (31 - clz) / 4;
		screen.Print(0, y, "%u", lv);
		screen.Puts(3, y, TA::OnOff(intmap_status & map), GetIntName(map));
		screen.Print(15, y, "%26s", format_number(counter[clz]).c_str());

		// CPU ごとのマスク
		// XXX 今は CPU は1つしかない
		for (int n = 0; n < 1; n++) {
			// Softint の他 CPU 分は
			// 動作としては常にマスクされてて通過しないから問題ないが、
			// 見せる分にはマスクされているではなく無効と見せたほうがいい。
			if ((cpu[n].intmask & map) == 0) {
				screen.Puts(11 + n * 5, y, "Mask");
			} else if (!ei) {
				screen.Puts(11 + n * 5, y, "IND");
			}
		}

		y++;
	}

	// ソフトウェア割り込みの CPU ごとのカウンタいる?
}

// 割り込みマスクを設定する。
// data はロングワードレジスタの書き込み値 (bit31-26 のみ有効)。
void
SysCtlrDevice::WriteIntMask(uint32 n, uint32 data)
{
	data &= 0xfc000000;

	// 割り込みレベル変えるたびに表示されるのですごく多い。
	// さらに同レベルが書き込まれることも多い…。
	if (cpu[n].maskval != data) {
		putlog(2, "Interrupt Mask(CPU#%u) <- $%08x", n, data);
	} else {
		putlog(3, "Interrupt Mask(CPU#%u) <- $%08x", n, data);
	}

	// 設定値を保存
	cpu[n].maskval = data;
	// 内部マスクを書き換えて割り込み状態を更新
	cpu[n].intmask = MakeIntMask(data, n);
	ChangeInterrupt();
}

// 割り込みアクノリッジはない
busdata
SysCtlrDevice::InterruptAcknowledge(int lv)
{
	return 0;
}

// 割り込み状態を更新
void
SysCtlrDevice::ChangeInterrupt()
{
	// XXX MPU 側がまだマルチプロセッサに対応してないので CPU#0 だけ
	for (int i = 0; i < 1; i++) {
		uint32 localmap = (intmap_status | cpu[i].softint) & cpu[i].intmask;

		// intmap_status の最下位ビットは常に立ててあり localmap は 0 にならない
		uint newipl = (uint)(31 - __builtin_clz(localmap)) / 4;

		// 割り込みレベルが変わったら、新しいレベルを通知
		if (newipl != cpu[i].ipl) {
			cpu[i].ipl = newipl;
			// MPU 側は 1 レベルしかない
			mpu->Interrupt(cpu[i].ipl ? 1 : 0);
		}
	}
}

// 割り込みマスク設定レジスタの書き込み値から内部用のマスクを作成
// data は bit31-26 のみ使用。他のビットは不問。
// n は CPU# (Softint 用)。
/*static*/ uint32
SysCtlrDevice::MakeIntMask(uint32 data, uint n)
{
	uint32 val = 0xf0000001;	// Lv7 は NMI、最下位は番兵用

	uint32 mask = 0x0f000000;
	for (int i = 6; i >= 1; i--) {
		if ((int32)data < 0) {
			val |= mask;
		}
		mask >>= 4;
		data <<= 1;
	}

	return val;
}

unix.superglobalmegacorp.com

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