File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / romemu_luna1.cpp
Revision 1.1.1.14 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 2026 UTC (3 months 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-I の ROM エミュレーション
//

// 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 のブートページ切り替え用プロキシ)

// ROM マップ
//
//	先頭の "*" は仕様または実機と揃える必要があるアドレスを示す。
//	そうでないものはこっちが適当に割り振っているアドレスで、変更可。
//
//	*4100'0000.L	リセットベクタ(SP)
//	*4100'0004.L	リセットベクタ(PC)
//	*4100'000c.L	メモリサイズ格納先アドレス   (実 ROM は RAM をさしている)
//	 4100'0010.L	メモリサイズ
//	*4100'00b8.L	プレーンマスク格納先アドレス (実 ROM は RAM をさしている)
//	 4100'00bc.L	プレーンマスク
//	*4100'00d4.L	'x' コマンドの引数バッファアドレス
//
//	 4100'0400		リセットハンドラ
//	 4100'0500		Lv5 (Sysclk) 割り込みハンドラ
//	 4100'0600		Lv6 (SIO) 割り込みハンドラ
//	 4100'0700		Lv7 (NMI) 割り込みハンドラ
//	 4100'0800		デフォルトハンドラ
//	 4100'0900		サブルーチン
//	 4100'1000.B[64] 'x' コマンドの引数バッファ(ダミー)
//
//	*4101'ffd8〜	"ENADDR",0,0
//	*4101'ffe0〜	MACアドレス(文字列形式、英大文字、区切りなし)
//	            	 0123456789ab  c
//	            	"0011223344AA",0

#include "romemu_luna1.h"
#include "bt45x.h"
#include "ethernet.h"
#include "iodevstream.h"
#include "mainapp.h"
#include "mainram.h"
#include "memorystream.h"
#include "mk48t02.h"
#include "mpu680x0.h"
#include "sysclk.h"

// コンストラクタ
Luna1PROMEmuDevice::Luna1PROMEmuDevice()
{
	machine_name = "LUNA-I";
}

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

// 初期化
bool
Luna1PROMEmuDevice::Init()
{
	if (inherited::Init() == false) {
		return false;
	}

	// LUNA-I の ROM の特定アドレスを読み出す人がいるので、
	// サイズは実 ROM と同じにしておく必要がある。
	if (AllocROM(128 * 1024, 0) == false) {
		return false;
	}

	SysClkDevice *sysclk = GetSysClkDevice();

	MemoryStreamBE roms(imagebuf.get());
	roms.Write4(0x00002000);	// +00: リセットベクタ SP
	roms.Write4(0x41000400);	// +04: リセットベクタ PC
	roms.Write4(0);				// +08
	roms.Write4(0x41000010);	// +0c: メモリサイズ格納先
								// +10: 値は ROM_Init() でセット
	roms.SetOffset(0xb8);
	roms.Write4(0x410000bc);	// +b8: プレーンマスク格納先
								// +bc: 値は ROM_Init() でセット
	roms.SetOffset(0xd4);
	roms.Write4(0x41001000);	// +d4: 'x' コマンドの引数(実際はRAM)

	// リセット時に実行する命令
	roms.SetOffset(0x400);
	roms.Write2(0x13c0);		//	move.b	d0,#0x41000000	; RAMに切り替え
	roms.Write4(0x41000000);
	roms.Write2(0x2039);		//	move.l	(ROMIO_INIT),d0
	roms.Write4(ROMIO_INIT);
	roms.Write2(0x2039);		//	move.l	(ROMIO_LOAD),d0
	roms.Write4(ROMIO_LOAD);
	roms.Write2(0x72ff);		//	moveq.l	#0xffffffff,d1
	roms.Write2(0x6004);		//	bra		_entry
								//_prompt:
	uint32 _prompt = roms.GetOffset();
	roms.Write4(0x4e722500);	//	stop	#0x2500	; キー入力を待つ
								//_entry:
	roms.Write2(0x2039);		//	move.l	(ROMIO_ENTRY),d0
	roms.Write4(ROMIO_ENTRY);	//					; エントリポイント取得
	roms.Write2(0xb081);		//	cmp.l	d1,d0
	roms.Write2(0x67f2);		//	beq		_prompt	; 起動先なければループ
								//_go:
	roms.Write2(0x2040);		//	move.l	d0,a0	; ジャンプ先を a0 にセット
	roms.Write2(0x2039);		//	move.l	(ROMIO_CLOSE),d0
	roms.Write4(ROMIO_CLOSE);	//					; 後始末
	roms.Write2(0x4ed0);		//	jmp		(a0)	; ジャンプ

	// Lv5 (Sysclk) 割り込みハンドラ。
	roms.SetOffset(0x500);
	roms.Write2(0x13c0);		//	move.b	d0,#0x63000000	; ACK
	roms.Write4(0x63000000);
	roms.Write2(0x52b9);		//	addq.l	#1,(ROMIO_CLKCNT)
	roms.Write4(ROMIO_CLKCNT);
	roms.Write2(0x4e73);		//	rte

	// Lv6 (SIO) 割り込みハンドラ。
	// この中でコマンドなどを処理する。
	roms.SetOffset(0x600);
	roms.Write2(0x4ab9);		//	tst.l	(ROMIO_SIOINTR)
	roms.Write4(ROMIO_SIOINTR);	//					; キー入力処理
	roms.Write2(0x4e73);		//	rte

	// NMI のハンドラ
	roms.SetOffset(0x700);
	roms.Write4(0x46fc2700);	//	move.w	#0x2700,sr
	roms.Write2(0x91c8);		//	suba.l	a0,a0
	roms.Write4(0x4e7b8801);	//	movec	a0,vbr
	roms.Write2(0x2e50);		//	movea.l	(a0),sp
	roms.Write2(0x4ef9);		//	jmp		_prompt
	roms.Write4(0x41000000 + _prompt);

	// デフォルトのハンドラ
	roms.SetOffset(0x800);
	roms.Write2(0x4e73);		//  rte

	// サブルーチン
	roms.SetOffset(0x900);		//_ap1:
	roms.Write2(0x2039);		//	move.l	(ROMIO_AP1),d0
	roms.Write4(ROMIO_AP1);		//
	uint freq = sysclk->GetFreq();
	roms.Write2(0x7200 + freq * 3);
								//	moveq.l	#(CLK_FREQ*3),d1
								//@@:
	roms.Write4(0x4e722400);	//	stop	#0x2400	; クロック入力を待つ
	roms.Write2(0x2039);		//	move.l	(ROMIO_CLKCNT),d0
	roms.Write4(ROMIO_CLKCNT);
	roms.Write2(0xb001);		//	cmp.b	d1,d0
	roms.Write2(0x66f2);		//	bne		@b
	roms.Write2(0x2039);		//	move.l	(ROMIO_AP2),d0
	roms.Write4(ROMIO_AP2);		//
	roms.Write2(0x4e75);		//	rts

	// MAC アドレス
	MacAddr macaddr;
	if (EthernetDevice::GetConfigMacAddr(0, &macaddr, false) == false) {
		// エラーメッセージは表示済み
		return false;
	}
	putmsg(1, "macaddr=%s", macaddr.ToString(':').c_str());
	strcpy((char *)&imagebuf[0x1ffd8], "ENADDR");
	memcpy(&imagebuf[0x1ffe0], macaddr.ToString().c_str(), 12);

	return true;
}

// ROM 起動時の初期化 (機種依存部分) を行う。
void
Luna1PROMEmuDevice::ROM_InitMD()
{
	IODeviceStream rams(mainram);
	MemoryStreamBE roms(imagebuf.get());

	// +0010: メモリサイズ
	roms.SetOffset(0x10);
	roms.Write4((uint32)mainram->GetSize());

	// プレーン数(1,4,8)をプレーンマスク(0x01, 0x0f, 0xff)に
	int nplane = bt45x->GetPlaneCount();
	int planemask = (1U << nplane) - 1;
	// プレーンマスク。実際は RAM に書き込まれるがとりあえず
	roms.SetOffset(0xbc);
	roms.Write4(planemask);

	// RAM にベクタを書き込む
	roms.SetOffset(0);
	rams.SetAddr(0);
	rams.Write4(roms.Read4());		// ROM のリセットベクタをコピー
	rams.Write4(roms.Read4());
	for (uint i = 2; i < 256; i++) {	// デフォルトベクタで埋める
		rams.Write4(0x41000800);
	}
	rams.Write4(0x74, 0x41000500);		// +74: Lv5 割り込み
	rams.Write4(0x78, 0x41000600);		// +78: Lv6 割り込み
	rams.Write4(0x7c, 0x41000700);		// +7c: Lv7 割り込み = RESET

	prompt_idx = 0;
	prompt = default_prompt;

	uint32 ap = (nvram->PeekPort(0x7fd) << 8) | nvram->PeekPort(0x7fe);
	if (__predict_false(0||(ap & 0x3f1f) == 0x0104)) {
		auto mpu680x0 = GetMPU680x0Device(mpu);
		m68kreg& reg = mpu680x0->reg;
		reg.A[7] -= 4;
		rams.Write4(reg.A[7], reg.pc);
		reg.pc = 0x41000900;
	}
}

// ROM 処理のクローズ。
// 次段プログラムにジャンプするための処理も含む。
void
Luna1PROMEmuDevice::ROM_Close()
{
	inherited::ROM_Close();

	// NetBSD のブートローダがカーネルを読み込む時にはいくつかレジスタ
	// 渡しのパラメータがあるので、ここで用意する。本当はターゲットが
	// NetBSD カーネルの時に限定すべきのような気もするけど、とりあえず。
	// ROM がブートローダを読み込む時にはそのような取り決め事は一切ない
	// ので、ブートローダを読む時にも同じ値が入ってしまうが気にしない
	// ことにする。
	// 必要なパラメータは
	// %d6 に bootdev、NetBSD/luna68k の場合は
	// bootdev = $MACUPP00
	//            ||||++-- パーティション番号
	//            |||+---- SCSI ID (XXX ただしここでは 6 決め打ち)
	//            ||+----- unit (spc0 なら 0 のやつのはず)
	//            |+------ アダプタ (spc = 0、lance = 1)
	//            +------- magic ($a)
	//
	// %d7 に howto (0 でよい)
	// (と %a0 にジャンプ先を入れるがこれは Init() が書き出した ROM
	// アセンブラコード側で行っている)
	m68kreg& reg = GetMPU680x0Device(mpu)->reg;
	reg.D[6] = 0xa0060000;
	reg.D[7] = 0;
}

// ファイルをロードする。
// -X が指定されていればそのホストファイル。
// そうでなければゲストファイル。この場合引数 fname があれば
// NVRAM で指定されたデバイスから読み込む際のファイル名とする。
// 空なら NVRAM で指定されたファイル名。
// ロードできればエントリポイントを返す。
// ロードできなければ errmsg にメッセージをセットし -1 を返す。
uint32
Luna1PROMEmuDevice::LoadFile(const std::string& fname)
{
	uint32 entry = -1;

	if (gMainApp.exec_file) {
		// -X が指定されてたらホストプログラムを読み込む。
		entry = LoadHostFile();
	} else {
		// NVRAM で指定されるゲストプログラムを読み込む。
		std::string ctlr = nvram->PeekString(0x0030);
		if (ctlr == "dk") {			// harddisk
			bootinfo_t bootinfo;
			GetNVRAM(bootinfo);
			// 引数で指定されたらファイル名だけ上書き
			if (fname.empty() == false) {
				bootinfo.dkfile = fname;
			}
			entry = LoadGuestFile(bootinfo);

		} else if (ctlr == "et" ||	// netboot
		           ctlr == "sd" ||	// cassette tape
		           ctlr == "fl"   )	// floppy
		{
			errmsg = ctlr + " boot not supported.";
		} else {
			// 起きないはず
			errmsg = ctlr + ": Invalid boot controller name.";
		}
	}

	return entry;
}

// 入力コマンド処理 (入力行が揃ったところで呼ばれる)。
void
Luna1PROMEmuDevice::Command()
{
	std::vector<char> buf(inputbuf.length() + 1);
	std::vector<std::string> arg;

	// 1ワードごとに分解
	strlcpy(buf.data(), inputbuf.c_str(), buf.size());
	char *p = &buf[0];
	for (char *ap; (ap = strsep(&p, " ")) != NULL; ) {
		if (*ap != '\0') {
			arg.push_back(std::string(ap));
		}
	}
	if (loglevel >= 1) {
		for (int i = 0; i < arg.size(); i++) {
			putmsgn("arg[%u]=|%s|", i, arg[i].c_str());
		}
	}

	// "k" コマンド中ならここで先に処理する
	if (prompt_idx != 0) {
		CommandK(arg);
		return;
	}

	// 空行
	if (arg.size() == 0) {
		return;
	}

	// 1ワード目がコマンド
	if (arg[0] == "h") {
		// ヘルプメッセージ
		Print("Commands\n");
		Print(
"  g [<filename>]     : load program\n"
"  h                  : show this message\n"
"  k                  : show/set boot constants\n"
"  x                  : execute loaded program\n"
		);
		return;
	}
	if (arg[0] == "g") {
		CommandG(arg);
		return;
	}
	if (arg[0] == "k") {
		CommandK(arg);
		return;
	}
	if (arg[0] == "x") {
		CommandX();
		return;
	}

	Print("** Unknown command: %s\n", arg[0].c_str());
}

// "g" コマンド
// g なら NVRAM の情報 (もしくは -X オプション) に従ってプログラムをロード。
// g <fname> なら NVRAM の fname だけ指定の名前を使ってロード。NVRAM には
// 書き込まない。
void
Luna1PROMEmuDevice::CommandG(const std::vector<std::string>& arg)
{
	std::string fname {};

	switch (arg.size()) {
	 case 1:
		break;
	 case 2:
		fname = arg[1];
		break;
	 default:
		Print("** Invalid argument.\n");
		return;
	}

	entrypoint = LoadFile(fname);
	if (entrypoint == -1) {
		Print("** %s\n", errmsg.c_str());
	} else {
		Print("%s.  Entry point = $%08x\n",
			(gMainApp.exec_file ? "Host program loaded" : "Loaded"),
			entrypoint);
	}
}

// k コマンドのパラメータ表
struct kinfo {
	uint32 offset;				// NVRAM 上のオフセット
	const char * const name;	// 表示名
	int next;					// 次インデックス
};
static const struct kinfo kinfo[] = {
	{ 0 },	// 0 は通常プロンプトを指すのでここでは使わない
	{ 0x0030,	"controller",  0 },	// [1] BOOT (next は動的判定する)
	{ 0x0090,	"drive unit",  3 },	// [2] DKUNIT
	{ 0x00b0,	"partition ",  4 },	// [3] DKPART
	{ 0x00d0,	"filename  ",  0 },	// [4] FLNAME
	{ 0x0050,	"hostname  ",  6 },	// [5] HOST
	{ 0x0070,	"servername",  7 },	// [6] SERVER
	{ 0x0190,	"filename  ",  8 },	// [7] ETBOOT
	{ 0x01b0,	"",			   0 },	// [8] ETFILE (ETBOOT と1行で処理する)
};

// "k" コマンド
void
Luna1PROMEmuDevice::CommandK(const std::vector<std::string>& arg)
{
	uint32 offset;
	const char *name;
	int next;

	// 入力を処理
	if (prompt_idx == 0) {
		// ここは1行目表示前なので前行の入力はない。
		next = 1;
	} else {
		std::string val;
		bool rv;

		// 入力があれば更新
		if (arg.size() != 0) {
			rv = CommandKInput(arg[0]);
		} else {
			// [7] の時に入力があれば CommandKInput() の中で更新されるが、
			// 入力がなければそこを通らないので、こっちでやらないといけない。
			// うーん。
			if (prompt_idx == 7) {
				prompt_idx = kinfo[prompt_idx].next;
			}
			rv = true;
		}

		// これ以降は更新後の prompt_idx を使う

		// 入力エラーがあった時はこの行を再入力するため
		next = prompt_idx;

		if (rv == true) {
			// 入力があっても空でも次インデックスへ。
			 if (prompt_idx == 1) {
				// BOOT なら現在値によって次のインデックスが変わる
				// "dk" なら [2]、"et" なら [5]。他は未対応
				offset = kinfo[prompt_idx].offset;
				val = nvram->PeekString(offset);
				if (val == "dk") {
					next = 2;
				} else if (val == "et") {
					next = 5;
				} else {
					// 起きることはないはず
					Print("** Inconsistent boot parameter: %s\n",
						val.c_str());
				}
			} else {
				// それ以外はテーブルにかかれている通り
				next = kinfo[prompt_idx].next;
			}
		}
	}

	// この行のプロンプト文字列を作成
	prompt_idx = next;
	if (prompt_idx == 0) {
		// 最終行なら元に戻す
		prompt = default_prompt;
	} else {
		// 次のプロンプトに変更
		name = kinfo[prompt_idx].name;
		offset = kinfo[prompt_idx].offset;
		std::string val = nvram->PeekString(offset);

		// [7] ETBOOT だけ1行で ETBOOT:ETFILE 形式
		if (prompt_idx == 7) {
			next = kinfo[prompt_idx].next;	// ETFILE
			offset = kinfo[next].offset;
			val += ":";
			val += nvram->PeekString(offset);
		}

		prompt = string_format("%s: %s  ?", name, val.c_str());
	}
}

// k コマンドの入力値を処理する部分。
// 成功すれば true を返す。
// 失敗すれば false を返す。
// 実機 ROM は入力値にエラーがあると黙って再入力を促す。
// エラーメッセージくらい表示してもいいかもだがとりあえず真似しておく。
bool
Luna1PROMEmuDevice::CommandKInput(const std::string& val)
{
	int offset;
	bool rv = true;

	offset = kinfo[prompt_idx].offset;

	switch (prompt_idx) {
	 case 1:	// BOOT
		// 正常な入力値は "dk", "et", "sd", "fl" のみ。
		if (val != "dk" && val != "et" && val != "sd" && val != "fl") {
			return false;
		}
		rv = nvram->WriteString(offset, val);
		if (rv == false) {
			return false;
		}
		break;

	 case 7:	// ETBOOT
	 {
		// こいつだけ ETBOOT:ETFILE 形式で2つのパラメータを同時に処理する

		// ':' で前後に分ける
		int sep = val.find(':');
		if (sep == std::string::npos) {
			return false;
		}
		std::string etboot = val.substr(0, sep);
		std::string etfile = val.substr(sep + 1, val.size() - sep + 1);
		if (etboot.empty() || etfile.empty()) {
			return false;
		}

		// ETBOOT
		rv = nvram->WriteString(offset, etboot);
		if (rv == false) {
			return false;
		}

		// 次へ
		prompt_idx = kinfo[prompt_idx].next;

		// ETFILE
		offset = kinfo[prompt_idx].offset;
		rv = nvram->WriteString(offset, etfile);
		if (rv == false) {
			return false;
		}
		break;
	 }

	 default:
		rv = nvram->WriteString(offset, val);
		if (rv == false) {
			return false;
		}
		break;
	}

	WriteNVRAMCsum();
	return rv;
}

// "x" コマンド
// 読み込まれていたらフラグを立てるだけ。
void
Luna1PROMEmuDevice::CommandX()
{
	if (entrypoint == -1) {
		Print("** Program is not loaded.\n");
		return;
	}
	execute = true;
}

// NVRAM を初期化する
bool
Luna1PROMEmuDevice::InitNVRAM()
{
	nvram->ClearAll();

	// 初期値どうしたもんか
	nvram->WriteString(0x0004, "<nv>");
	nvram->WriteString(0x0020, "BOOT");
	nvram->WriteString(0x0030, "dk");
	nvram->WriteString(0x0040, "HOST");
	nvram->WriteString(0x0050, "nono");	// 偽札的なアレ
	nvram->WriteString(0x0060, "SERVER");
	nvram->WriteString(0x0070, "servername");
	nvram->WriteString(0x0080, "DKUNIT");
	nvram->WriteString(0x0090, "0");
	nvram->WriteString(0x00a0, "DKPART");
	nvram->WriteString(0x00b0, "c");
	nvram->WriteString(0x00c0, "DKFILE");
	nvram->WriteString(0x00d0, "vmunix");
	nvram->WriteString(0x00e0, "FLUNIT");
	nvram->WriteString(0x00f0, "2");
	nvram->WriteString(0x0100, "FLFILE");
	nvram->WriteString(0x0110, "vmunix");
	nvram->WriteString(0x0120, "STUNIT");
	nvram->WriteString(0x0130, "0");
	nvram->WriteString(0x0140, "STFLNO");
	nvram->WriteString(0x0150, "1");
	nvram->WriteString(0x0160, "STFILE");
	nvram->WriteString(0x0170, "vmunix");
	nvram->WriteString(0x0180, "ETBOOT");
	nvram->WriteString(0x0190, "server");
	nvram->WriteString(0x01a0, "ETFILE");
	nvram->WriteString(0x01b0, "/vmunix");
	nvram->WriteString(0x0560, "ENADDR");
	nvram->WriteString(0x0570, "");	// どうするか

	// チェックサム再計算
	WriteNVRAMCsum();

	return true;
}

// NVRAM のブート設定を bootinfo に取得する。
void
Luna1PROMEmuDevice::GetNVRAM(bootinfo_t& bootinfo)
{
	// 起動先情報を NVRAM (mk48t02) に問い合わせる
	// DKUNIT が装置番号(今はまだ SCSI ID=6 固定)。
	// DKPART が 'a'..'h' でパーティション。
	// DKFILE が読み込む次段プログラム (NetBSD では通常 /boot のこと)。
	bootinfo.dkunit = nvram->PeekPort(0x0090) - '0';
	bootinfo.dkpart = nvram->PeekPort(0x00b0) - 'a';
	bootinfo.dkfile = nvram->PeekString(0x00d0);
	putmsg(1, "%s DKUNIT=%u", __func__, bootinfo.dkunit);
	putmsg(1, "%s DKPART=%c", __func__, bootinfo.dkpart + 'a');
	putmsg(1, "%s DKFILE=|%s|", __func__, bootinfo.dkfile.c_str());
}

unix.superglobalmegacorp.com

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