File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / prom.cpp
Revision 1.1.1.19 (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) 2020 nono project
// Licensed under nono-license.txt
//

//
// PROM (LUNA-I, LUNA-88K 共通)
//

// IODevice
//  |
//  +- ROMDevice (LoadROM()、ウェイト、マスク等を持つ)
//  |   |
//  |   | +------------+
//  |   +-| PROMDevice |  (LUNA* の PROM)
//  |   | +------------+
//  |   |
//  |   +- IPLROM1Device (X680x0 の IPLROM 後半)
//  |   +- IPLROM2Device (X680x0 の IPLROM 前半)
//  |   +- CGROMDevice   (X680x0 の CGROM)
//  |   |
//  |   +- ROMEmuDevice
//  |       +- LunaPROMEmuDevice (LUNA PROM エミュレーションの共通部分)
//  |       |   +- Luna1PROMEmuDevice   (LUNA-I の PROM エミュレーション)
//  |       |   +- Luna88kPROMEmuDevice (LUNA-88K の PROM エミュレーション)
//  |       +- NewsROMEmuDevice    (NEWS の ROM エミュレーション)
//  |       +- ROM30EmuDevice      (X68030 の ROM30 エミュレーション)
//  |       +- Virt68kROMEmuDevice (virt-m68k の IPLROM 相当の何か)
//  |
//  +- PROM0Device   (LUNA* のブートページ切り替え用プロキシ)
//  +- IPLROM0Device (X680x0 のブートページ切り替え用プロキシ)

#include "prom.h"
#include "config.h"
#include "ethernet.h"
#include "mainapp.h"
#include "mainbus.h"
#include "mpu.h"

//
// PROM0
//
// PROM0 は PROM のブートページ担当。
// これ自身はメモリ領域を持たず PROM へのプロキシとして動作する。
//
// リセット後最初の ROM 領域への書き込みアクセスでブートページを RAM に
// 切り替える。
// LUNA のブートページのアドレス範囲は不明なので、とりあえず devtable の
// 1区画である 16MB ($0000'0000..$00ff'ffff) とする。実害はない。

// コンストラクタ
PROM0Device::PROM0Device()
	: inherited(OBJ_PROM_BOOT)
{
	// 通常ここからログを出すことはない
	ClearAlias();
}

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

// 初期化
bool
PROM0Device::Init()
{
	prom = GetPROMDevice();

	return true;
}

// リセット
void
PROM0Device::ResetHard(bool poweron)
{
	auto mainbus = GetMainbusDevice();
	mainbus->SwitchBootPageToROM();
}

// アドレスデコーダ
/*static*/ inline uint32
PROM0Device::Decoder(uint32 addr)
{
	// ここはブートページか ROM かの比較だけで十分 (それ以外はここに来ない)
	if (addr < 0x0100'0000) {
		addr += 0x4100'0000;
	}
	return addr;
}

busdata
PROM0Device::Read(busaddr addr)
{
	addr.ChangeAddr(Decoder(addr.Addr()));
	return prom->Read(addr);
}

// LUNA では PROM へのライトアクセスでブートページを RAM に切り替えている。
// - LUNA-I では最初に $41000000.B への書き込みがあるが、
//   それ以降も $41000000.L への書き込みが(複数回)あるようだ。
// - LUNA-88K では $4100000c.L への書き込みがある。
//
// よってアドレスを定めず PROM 全域への書き込みで最初の1回だけ発動する
// ようにしておく。サイズも今の所ワード(16bit)アクセスは確認してないけど
// 統一的に対処しておく。
busdata
PROM0Device::Write(busaddr addr, uint32 data)
{
	auto mainbus = GetMainbusDevice();
	mainbus->SwitchBootPageToRAM();

	// バスエラーは起きない
	busdata r = 0;
	r |= busdata::Size(addr.GetSize());
	return r;
}

busdata
PROM0Device::Peek1(uint32 addr)
{
	addr = Decoder(addr);
	return prom->Peek1(addr);
}


//
// PROM
//

// コンストラクタ
PROMDevice::PROMDevice()
	: inherited(OBJ_PROM)
{
	// LUNA では ROM への書き込みは何も起きない
	write_op = 0;
}

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

// 初期化
bool
PROMDevice::Init()
{
	VMType vmtype = gMainApp.GetVMType();

	switch (vmtype) {
	 case VMType::LUNA1:
		if (gMainApp.msxdos_mode) {
			return true;
		}
		return InitLuna1();

	 case VMType::LUNA88K:
		return InitLuna88k();

	 default:
		PANIC("unknown vmtype %u", (uint)vmtype);
	}
}

// LUNA-I の場合
bool
PROMDevice::InitLuna1()
{
	uint devlen = 128 * 1024;
	mask = devlen - 1;

	// ファイル名が存在してる時しかここに来ない
	const ConfigItem& item = gConfig->Find("prom-image");
	if (LoadROM(item.AsString().c_str(), devlen) == false) {
		return false;
	}

	// XXX アクセスウェイトは未調査。とりあえず RAM と同程度にしておく
	wait = busdata::Wait(1 * mpu->GetClock_tsec());

	// ROM バージョンを確認。確認されてるのは
	// V4.02 (Wed Oct 12 17:07:11 1988)
	// V4.22 (Thu Jul 27 11:45:42 1989)
	// V4.25
	//
	// とりあえず V4.22 は +$00d390 からにタイムスタンプ文字列があるので
	// それで判別してみる。
	//00d390 54 68 75 20 4a 75 6c 20 32 37 20 31 31 3a 34 35 |Thu Jul 27 11:45|
	//00d3a0 3a 34 32 20 31 39 38 39 00 00 00 00 4e 56 fd f8 |:42 1989....NV..|

	if (strcmp((const char *)&mem[0xd390], "Thu Jul 27 11:45:42 1989") == 0) {
		romver = 422;
	}

	if (romver == 0) {
		putmsg(0, "Unknown PROM version");
	} else {
		putmsg(1, "v%u.%02u detected", romver / 100, romver % 100);
	}

	if (romver == 422) {
		// V4.22。他の ROM は何も分からないので何もしない。

		// MAC アドレスは ROM に書き込まれているので、ここで上書き
		if (strcmp((const char *)&mem[0x1ffd8], "ENADDR") != 0) {
			warnx("ENADDR not found; unknown ROM V4.22 variants?");
			return false;
		}

		// MAC アドレスは12桁の16進数文字列として書き込まれている。例えば
		// 前半の 00:00:0A 部分は 30 30 30 30 30 41 "00000A" という感じ。
		MacAddr macaddr {};
		if (EthernetDevice::GetConfigMacAddr(0, &macaddr, true) == false) {
			// エラーメッセージは表示済み
			return false;
		}

		const char *msg;
		if (macaddr.Empty()) {
			// "rom" なら元イメージのままにする。
			msg = "ROM";
		} else {
			// 指定されたら上書き。
			memcpy(&imagebuf[0x1ffe0], macaddr.ToString().c_str(), 12);
			msg = "config";
		}
		putmsg(1, "macaddr=%c%c:%c%c:%c%c:%c%c:%c%c:%c%c (from %s)",
			std::tolower(imagebuf[0x1ffe0]),
			std::tolower(imagebuf[0x1ffe1]),
			std::tolower(imagebuf[0x1ffe2]),
			std::tolower(imagebuf[0x1ffe3]),
			std::tolower(imagebuf[0x1ffe4]),
			std::tolower(imagebuf[0x1ffe5]),
			std::tolower(imagebuf[0x1ffe6]),
			std::tolower(imagebuf[0x1ffe7]),
			std::tolower(imagebuf[0x1ffe8]),
			std::tolower(imagebuf[0x1ffe9]),
			std::tolower(imagebuf[0x1ffea]),
			std::tolower(imagebuf[0x1ffeb]),
			msg);

		// SCSI デバッグビット
		bool scsi_debug_bit = gConfig->Find(".prom-scsi-debug").AsBool();
		imagebuf[0x4100981d - 0x41000000] = (scsi_debug_bit) ? 0x01 : 0;
	}

	return true;
}

// LUNA-88K の場合
bool
PROMDevice::InitLuna88k()
{
	uint devlen = 256 * 1024;
	mask = devlen - 1;

	// ファイル名が存在してる時しかここに来ない
	const ConfigItem& item = gConfig->Find("prom-image");
	if (LoadROM(item.AsString().c_str(), devlen) == false) {
		return false;
	}

	// XXX アクセスウェイトは未調査。とりあえず RAM と同程度にしておく
	wait = busdata::Wait(3 * mpu->GetClock_tsec());

	// ROM バージョンを照合。どこかに日付が埋まってるっぽい。
	//
	// 1.20:
	//02c190 00 00 04 b0 28 0d 2f e1 28 0d 2f e1 54 68 75 20 |....(./.(./.Thu |
	//02c1a0 41 70 72 20 31 38 20 31 34 3a 33 34 3a 32 35 20 |Apr 18 14:34:25 |
	//02c1b0 31 39 39 31 00 00 00 00 41 02 c1 9c 00 00 00 00 |1991....A.......|
	//
	// 1.31:
	//03c940 00 00 05 1e 2a 0f 28 78 2a 0f 28 78 54 75 65 20 |....*.(x*.(xTue |
	//03c950 4d 61 79 20 31 32 20 31 31 3a 30 37 3a 35 32 20 |May 12 11:07:52 |
	//03c960 31 39 39 32 00 00 00 00 41 03 c9 4c 00 00 00 00 |1992....A..L....|
	//
	// 1.37:
	//03cf30 00 00 05 5a 2b 5a 4a 7d 2b 5a 4a 7d 4d 6f 6e 20 |...Z+ZJ}+ZJ}Mon |
	//03cf40 4a 61 6e 20 31 38 20 31 35 3a 31 33 3a 31 37 20 |Jan 18 15:13:17 |
	//03cf50 31 39 39 33 00 00 00 00 41 03 cf 3c 00 00 00 00 |1993....A..<....|

	if (strcmp((const char *)&mem[0x2c19c], "Thu Apr 18 14:34:25 1991") == 0) {
		romver = 120;
	} else
	if (strcmp((const char *)&mem[0x3c94c], "Tue May 12 11:07:52 1992") == 0) {
		romver = 131;
	} else
	if (strcmp((const char *)&mem[0x3cf3c], "Mon Jan 18 15:13:17 1993") == 0) {
		romver = 137;
	}

	if (romver == 0) {
		putmsg(0, "Unknown PROM version");
	} else {
		putmsg(1, "v%u.%02u detected", romver / 100, romver % 100);
	}

	if (romver == 120) {
		// m88100 コアのパイプラインが未実装なのをごまかすハック。
		//
		// PROM 1.20 のプローブルーチン(かな?) はこうなっている。
		//  410005f8: f5421400   ld    r10,r2, r0
		//  410005fc: f4405800   or    r2, r0, r0
		//  41000600: c0000002   br    0x41000608
		//  41000604: 58400001   or    r2, r0, #0x0001
		//  41000608: f1a0e800   tcnd  ne0,r0, #0x000
		//  :
		// ここで 410005f8 の ld 命令がバスエラーになると以下の例外ハンドラに
		// 飛ぶ。
		//  0000102c: 80008060   stcr  r0, ssbr		; Clear SSBR
		//  00001030: 80204080   ldcr  r1, sxip
		//  00001034: 800180c1   stcr  r1, sfip		; sfip <- sxip
		//  00001038: f0218021   clr   r1, r1, 1<1>	; &= ~VALID
		//  0000103c: 80018081   stcr  r1, sxip		; ただし sxip は readonly
		//  00001040: 800080a0   stcr  r0, snip		; snip <- 0 (Invalidate)
		//  00001044: 80204260   ldcr  r1, sr2
		//  00001048: f400fc00   rte
		//
		// 例外ハンドラは特に何もせず sxip の位置から実行を再開しようとする。
		// そのまま読むと例外発生前に実行した命令(ld 命令)を再実行するように
		// 読めるが、ld 命令直後の OR 命令は整数ユニット担当であり更に ld
		// 命令の rD と OR 命令の rD, rS は依存関係もないため、データユニット
		// が ld 命令のバス応答を待っている間に整数ユニットが OR 命令を実行
		// できる。もっと言えばバスの応答時間次第で少なくとも同期命令である
		// tcnd 命令の直前までは進むことが出来る。
		// この例外ハンドラはおそらくこれを踏まえて、例外発生時の sxip が
		// 少なくとも ld 命令よりも進んでいることを前提にしているようだ。
		//
		// ところがうちではこのパイプラインを実装しておらず、xip が ld 命令を
		// 指したまま例外が発生するため、このままでは rte 命令で再び ld 命令に
		// 戻って行ってしまい無限ループになる。
		//
		// この DAE ハンドラは OpenBSD のブートローダからも呼ばれるので、
		// MPU 側で呼び出し元を特定して動作を書き換えるハックでは十分でない。
		// そのため PROM 1.20 であれば ROM のコードを変更して snip から
		// 再実行するようにする。
		//
		// OpenBSD (カーネルのほう) でも例外発生時の sxip が実機よりも遅い
		// ところを指している (可能性がある) という現象は変わらないが、
		// これによる齟齬は発生しないはず。
		// ld/st 命令中の例外なら、xip がどこを指していても xip は実行済み
		// で snip から再実行することには変わりないので問題は起きないはず。
		// xmem 命令中の例外なら、xmem は同期命令なので xip はここに留まる
		// ためおそらく実機と同じ動作になってるはず。
		//
		// ROM上     RAMコピー後
		// 410016c0: 00001030:	80204080  ldcr r1,sxip
		// ↓
		// 410016c0: 00001030:  802040c0  ldcr r1,snip
		if (be32toh(*(const uint32 *)&imagebuf[0x16c0]) == 0x80204080) {
			imagebuf[0x16c3] = 0xa0;
		}
	}

	return true;
}

unix.superglobalmegacorp.com

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