File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / romemu_luna88k.cpp
Revision 1.1.1.18 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:01 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 の 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 のブートページ切り替え用プロキシ)

#include "romemu_luna88k.h"
#include "iodevstream.h"
#include "mainapp.h"
#include "mainram.h"
#include "memorystream.h"
#include "mk48t02.h"
#include "mpu88xx0.h"
#include "sysclk.h"
#include "sysctlr.h"

static bool luna88k_fline_callback(MPU88xx0Device *cpu, void *arg);

// m88k の命令コードを少し読みやすく書きたい。
// 必要なものは適宜足すこと。
enum class OPr {
	r0,  r1,  r2,  r3,  r4,  r5,  r6,  r7,
	r8,  r9,  r10, r11, r12, r13, r14, r15,
	r16, r17, r18, r19, r20, r21, r22, r23,
	r24, r25, r26, r27, r28, r29, r30, r31,
};
#define r0	OPr::r0
#define r1	OPr::r1
#define r2	OPr::r2
#define r3	OPr::r3
#define r4	OPr::r4
#define r5	OPr::r5
#define r6	OPr::r6
#define r7	OPr::r7
#define r8	OPr::r8
#define r9	OPr::r9
#define r10	OPr::r10
#define r11	OPr::r11
enum OPm5 {
	eq0 = 0x02,
	ge0 = 0x03,
	lt0 = 0x0c,
	ne0 = 0x0d,
};
enum OPcr {
	psr  = 0x01,
	ssbr = 0x03,
	snip = 0x05,
	sfip = 0x06,
	sr0  = 0x11,
	sr1  = 0x12,
	sr2  = 0x13,
	sr3  = 0x14,
};
enum CMPbits {
	CMP_EQ = 2,
	CMP_NE = 3,
	CMP_LT = 6,
};

#define USC  __unused static constexpr

USC uint32 OP3(uint32 base, uint32 rd, uint32 rs1, uint32 rs2) {
	return (base) | (rd << 21) | (rs1 << 16) | rs2;
}
USC uint32 OP4(uint32 base, uint32 rd, uint32 rs1,
	uint32 w, uint32 o) {
	return (base) | (rd << 21) | (rs1 << 16) | (w << 5) | o;
}

USC uint32 OP_addu(OPr rd, OPr rs1, uint32 imm) {
	return OP3(0x60000000, (uint32)rd, (uint32)rs1, imm);
}
USC uint32 OP_addu(OPr rd, OPr rs1, OPr rs2) {
	return OP3(0xf4006000, (uint32)rd, (uint32)rs1, (uint32)rs2);
}
USC uint32 OP_and(OPr rd, OPr rs1, OPr rs2) {
	return OP3(0xf4004000, (uint32)rd, (uint32)rs1, (uint32)rs2);
}
USC uint32 OP_bb1(uint32 b, OPr rs1, uint32 d16) {
	return OP3(0xd8000000, b, (uint32)rs1, d16 & 0xffff);
}
USC uint32 OP_bcnd(OPm5 m5, OPr rs1, uint32 d16) {
	return OP3(0xe8000000, (uint32)m5, (uint32)rs1, d16 & 0xffff);
}
USC uint32 OP_br(uint32 d26) {
	return (0xc0000000 | (d26 & 0x03ffffff));
}
USC uint32 OP_bsr(uint32 d26) {
	return (0xc8000000 | (d26 & 0x03ffffff));
}
USC uint32 OP_clr(OPr rd, OPr rs1, uint32 w, uint32 o) {
	return OP4(0xf0008000, (uint32)rd, (uint32)rs1, w, o);
}
USC uint32 OP_cmp(OPr rd, OPr rs1, OPr rs2) {
	return OP3(0xf4007c00, (uint32)rd, (uint32)rs1, (uint32)rs2);
}
USC uint32 OP_extu(OPr rd, OPr rs1, uint32 w, uint32 o) {
	return OP4(0xf0009800, (uint32)rd, (uint32)rs1, w, o);
}
USC uint32 OP_jmp(OPr rs2) {
	return OP3(0xf400c000, 0, 0, (uint32)rs2);
}
USC uint32 OP_ld(OPr rd, OPr rs1, uint32 imm) {
	return OP3(0x14000000, (uint32)rd, (uint32)rs1, imm);
}
USC uint32 OP_ldcr(OPr rd, uint32 crs) {
	return OP4(0x80004000, (uint32)rd, 0, crs, 0);
}
USC uint32 OP_mask(OPr rd, OPr rs1, uint32 imm) {
	return OP3(0x48000000, (uint32)rd, (uint32)rs1, imm);
}
USC uint32 __unused OP_or(OPr rd, OPr rs1, OPr rs2) {
	return OP3(0xf4005800, (uint32)rd, (uint32)rs1, (uint32)rs2);
}
USC uint32 OP_or(OPr rd, OPr rs1, uint32 imm) {
	return OP3(0x58000000, (uint32)rd, (uint32)rs1, imm);
}
USC uint32 OP_or_c(OPr rd, OPr rs1, OPr rs2) {
	return OP3(0xf4005c00, (uint32)rd, (uint32)rs1, (uint32)rs2);
}
USC uint32 OP_or_u(OPr rd, OPr rs1, uint32 imm) {
	return OP3(0x5c000000, (uint32)rd, (uint32)rs1, imm);
}
USC uint32 OP_rte() {
	return 0xf400fc00;
}
USC uint32 OP_st(OPr rd, OPr rs1, uint32 imm) {
	return OP3(0x24000000, (uint32)rd, (uint32)rs1, imm);
}
USC uint32 OP_st(OPr rd, OPr rs1, OPr rs2) {
	return OP3(0xf4002400, (uint32)rd, (uint32)rs1, (uint32)rs2);
}
USC uint32 OP_st_b(OPr rd, OPr rs1, OPr rs2) {
	return OP3(0xf4002c00, (uint32)rd, (uint32)rs1, (uint32)rs2);
}
USC uint32 OP_stcr(OPr rs1, uint32 crd) {
	return OP4(0x80008000, 0, (uint32)rs1, crd, (uint32)rs1);
}
USC uint32 OP_xcr(OPr rd, OPr rs1, uint32 cr) {
	return OP4(0x8000c000, (uint32)rd, (uint32)rs1, cr, (uint32)rs1);
}
USC uint32 OP_xmem(OPr rd, OPr rs1, uint32 imm) {
	return OP3(0x04000000, (uint32)rd, (uint32)rs1, imm);
}
USC uint32 OP_doscall(OPr rd, OPr rs1, uint32 imm) {
	return OP3(0xfc000400, (uint32)rd, (uint32)rs1, imm);
}
#define SxIP_VALID	(0x0002)
#define DOS_GETCHAR	(1)
#define DOS_PUTCHAR	(2)


// コンストラクタ
Luna88kPROMEmuDevice::Luna88kPROMEmuDevice()
{
	machine_name = "LUNA-88K";

	// プロンプトを初期化。
	prompt = default_prompt;
}

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

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

	// ROM の特定アドレスを読み出す人がいるかも知れないので、
	// サイズは実 ROM と同じにしておく。LUNA-I からの類推。
	if (AllocROM(256 * 1024, 0) == false) {
		// エラーメッセージは表示済み。
		return false;
	}

	SysClkDevice *sysclk = GetSysClkDevice();

	// DOS call エミュレーションのコールバックを登録
	auto mpu88xx0 = GetMPU88xx0Device(mpu);
	mpu88xx0->SetFLineCallback(luna88k_fline_callback, this);

	MemoryStreamBE roms(imagebuf.get());

	// m88k では ROMIO_BASE は上位ワードだけで表現できれば楽。
	static_assert((ROMIO_BASE & 0xffff) == 0, "");

	// ROM のメモリマップ:
	// $41000000.L*3	リセットベクタから ROM _START へ飛ぶコード。
	// $4100000c		ROM _START
	// $41000200		割り込みハンドラ
	// $41000270		 sysclk
	// $41000300		ROM GETC
	// $41000400		ROM PUTC
	// $41000500		データアクセス例外ハンドラ

	// リセット時のブートページには ROM (これ) が見えている。
	// ROMIO_INIT へのアクセスでブートページを RAM に切り替えて、同時に RAM の
	// 0番地付近のベクタも作り直す。そのため、ここのベクタ1 以降 ($41000008〜)
	// をベクタとして参照することはないので、ベクタ0 の命令列がそのまま使い潰
	// してよい。というか2ワードでは 0番地から ROM まで飛べないので使わないと
	// 面倒くさい。一体どういう設計してんだ…。

	// ここからややアセンブラっぽいインデント

	// _START:
		// ここは 0番地。ここから _ROM_START へジャンプ
		roms.Write4(OP_or_u	(r1, r0, 0x4100));
		roms.Write4(OP_or	(r1, r1, 0x000c));
		roms.Write4(OP_jmp	(r1));

	// _ROM_START: (ここは ROM)
		// r1:  work (最後は戻りアドレスとして使う)
		// r2:  work (最後はジャンプ先として使う)
		// r3:  work
		// r5:  ROMIO_BASE

		// RAM に切り替え
		roms.Write4(OP_st	(r0, r1, r0));

		// 割り込み許可 (INIT の前に必要)。
		// なお PROM が次段に処理を移す時は割り込み許可のまま進んでいるので
		// ここも割り込みを許可したそのまま進む。
		roms.Write4(OP_ldcr	(r1, psr));
		roms.Write4(OP_clr	(r1, r1, 2, 0));	// clear IND,SFRZ
		roms.Write4(OP_stcr	(r1, psr));

		// 先に ROMIO_INIT だけ実行。
		roms.Write4(OP_or_u	(r5, r0, ROMIO_BASE >> 16));
		roms.Write4(OP_ld	(r0, r5, (ROMIO_INIT - ROMIO_BASE)));

	// _rom_main:
		uint32 rom_main = roms.GetOffset();
		// _rom_main 以降で r5 を(再)初期化する必要がある。
		roms.Write4(OP_or_u	(r5, r0, ROMIO_BASE >> 16));
		roms.Write4(OP_st	(r0, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));

		roms.Write4(OP_ld	(r0, r5, (ROMIO_LOAD - ROMIO_BASE)));
		roms.Write4(OP_br	(5));				// _entry
	// _prompt:
		uint32 prompt = roms.GetOffset();
		roms.Write4(OP_or_u	(r1, r0,  0x4100));
		roms.Write4(OP_or	(r1, r1, roms.GetOffset() + 12));
		roms.Write4(OP_st	(r1, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
		// STOP 相当の無限ループ。
		// 抜ける時は割り込みハンドラから (QUITSTOP) に rte で戻ってくる。
		roms.Write4(OP_br	(0));

	// _entry:
		// 起動可能なら ENTRY が -1 以外を返す
		roms.Write4(OP_ld	(r2, r5, (ROMIO_ENTRY - ROMIO_BASE)));
		roms.Write4(OP_or_c	(r1, r0, r0));		// r1 <- $ffffffff
		roms.Write4(OP_cmp	(r1, r2, r1));
		// 起動不可なら _prompt へ戻る
		roms.Write4(OP_bb1	(CMP_EQ, r1, (prompt - roms.GetOffset()) / 4));

	// _go:
		// 起動先があれば後始末して
		roms.Write4(OP_ld	(r0, r5, (ROMIO_CLOSE - ROMIO_BASE)));
		// 使ったレジスタを一応クリア
		roms.Write4(OP_or	(r3, r0, r0));
		roms.Write4(OP_or	(r5, r0, r0));
		// 戻ってきたときのために r1 を用意して
		roms.Write4(OP_or_u	(r1, r0, 0x4100));
		roms.Write4(OP_or	(r1, r1, roms.GetOffset() + 8));
		// ジャンプ
		roms.Write4(OP_jmp	(r2));
		// 戻ってきたら
		roms.Write4(OP_br	((rom_main - roms.GetOffset()) / 4));

	// 割り込みハンドラ。
	// _intr:
	roms.SetOffset(0x200);
		// r1:  work
		// r2:  work
		// r5:  ROMIO_BASE

		// レジスタを退避。r1 は sr2 に退避済み。
		roms.Write4(OP_st	(r2, r0, 0x2000));
		roms.Write4(OP_st	(r5, r0, 0x2004));
		roms.Write4(OP_or_u	(r5, r0, ROMIO_BASE >> 16));

		roms.Write4(OP_or_u	(r1, r0, 0x6500));
		roms.Write4(OP_ld	(r1, r1, 0));
		roms.Write4(OP_extu	(r1, r1, 3, 29));
		roms.Write4(OP_or	(r2, r0, 6));
		roms.Write4(OP_cmp	(r2, r1, r2));
		// 割り込みは sysclk, sio のみ許可しているので必ずどちらか。
		roms.Write4(OP_bb1	(CMP_EQ, r2, (0x270 - roms.GetOffset()) / 4));
	// sio_intr:
		roms.Write4(OP_ld	(r0, r5, (ROMIO_SIOINTR - ROMIO_BASE)));

	// _intr_ret:
		// QUITSTOP があれば rte の戻り先をそっちに書き換える。
		uint32 intr_ret = roms.GetOffset();
		roms.Write4(OP_ld	(r1, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
		roms.Write4(OP_bcnd	(eq0, r1, 6));		// beq @f
		roms.Write4(OP_st	(r0, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
		roms.Write4(OP_or	(r1, r1, SxIP_VALID));
		roms.Write4(OP_stcr	(r1, snip));
		roms.Write4(OP_addu	(r1, r1, 0x0004));
		roms.Write4(OP_stcr	(r1, sfip));
	// @@:
		// レジスタを復帰
		roms.Write4(OP_xcr	(r1, r0, sr2));
		roms.Write4(OP_or	(r2, r0, r0));
		roms.Write4(OP_or	(r5, r0, r0));
		roms.Write4(OP_xmem	(r2, r0, 0x2000));
		roms.Write4(OP_xmem	(r5, r0, 0x2004));
		roms.Write4(OP_stcr	(r0, ssbr));
		roms.Write4(OP_rte	());

	// _sysclk_intr:
	assertmsg(roms.GetOffset() < 0x270, "offset=$%x", roms.GetOffset());
	roms.SetOffset(0x270);
		// ACK
		roms.Write4(OP_or_u	(r1, r0, 0x6300));
		roms.Write4(OP_st_b	(r0, r1, r0));
		// inc
		roms.Write4(OP_ld	(r1, r5, (ROMIO_CLKCNT - ROMIO_BASE)));
		roms.Write4(OP_addu	(r1, r1, 1));
		roms.Write4(OP_st	(r1, r5, (ROMIO_CLKCNT - ROMIO_BASE)));
		roms.Write4(OP_br	((intr_ret - roms.GetOffset()) / 4));

	// ROM GETC エミュレータ
	assertmsg(roms.GetOffset() < 0x300, "offset=$%x", roms.GetOffset());
	roms.SetOffset(0x300);
		roms.Write4(OP_doscall(r2, r0, DOS_GETCHAR));
		roms.Write4(OP_jmp	(r1));

	// ROM PUTC エミュレータ
	// 表示する文字は r2 に置かれている。
	roms.SetOffset(0x400);
		roms.Write4(OP_doscall(r0, r2, DOS_PUTCHAR));
		roms.Write4(OP_jmp	(r1));

	// データアクセス例外ハンドラ。
	// 例外を起こした命令の次の命令 (現状 snip) に戻る。
	roms.SetOffset(0x500);
		// PROM-1.20 互換動作にするため sr2 に退避した r1 はそのままにする。
		// boot のオレオレ machine read コマンドがこの動作を利用している。
		roms.Write4(OP_ldcr	(r1, sr2));
		roms.Write4(OP_stcr	(r0, ssbr));
		roms.Write4(OP_rte	());

	// サブルーチン
	roms.SetOffset(0x900);
		roms.Write4(OP_or_u	(r5, r0, ROMIO_BASE >> 16));
		roms.Write4(OP_ld	(r0, r5, (ROMIO_AP1 - ROMIO_BASE)));
	//@@:
		uint32 subr1 = roms.GetOffset();
		roms.Write4(OP_or_u	(r2, r0, 0x4100));
		roms.Write4(OP_or	(r2, r2, roms.GetOffset() + 12));
		roms.Write4(OP_st	(r2, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
		roms.Write4(OP_br	(0));				// STOP 相当

		roms.Write4(OP_ld	(r2, r5, (ROMIO_CLKCNT - ROMIO_BASE)));
		roms.Write4(OP_mask	(r2, r2, 0x01ff));	// r2 &= 0x1ff;
		uint freq = sysclk->GetFreq();
		roms.Write4(OP_or	(r3, r0, freq * 3));// r5 = freq*3;
		roms.Write4(OP_cmp	(r3, r2, r3));
		roms.Write4(OP_bb1	(CMP_LT, r3, (subr1 - roms.GetOffset()) / 4));
												// blt @b
		roms.Write4(OP_ld	(r0, r5, (ROMIO_AP2 - ROMIO_BASE)));
		roms.Write4(OP_jmp	(r1));

	// 変なインデントここまで

	return true;
}

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

	// 割り込みマスクで Sysclk, SIO だけ許可
	// XXX とりあえず CPU#0 のみ
	auto *sysctlr = dynamic_cast<SysCtlrDevice*>(GetInterruptDevice());
	sysctlr->WriteIntMask(0, 0xc0000000);

	// RAM のメモリマップ:
	// $00000000 .. $00000fff	例外ベクタ
	// $00001000 .. $000010ff	トランポリン用 (PROM もここを使っている)
	// $00001100 .. ?			PROM 用が使う領域があるようだ
	// $00002000 .. 			レジスタ保存用として勝手に使うことにする

	rams.SetAddr(0);
	// ベクタテーブルを rte で埋めておく。
	for (int i = 0; i < 512 * 2; i++) {
		rams.Write4(OP_rte());
	}
	// 例外ベクタの2ワードとトランポリン先の4ワードで PROM まで飛ぶ
	// コードを出力する。
	static const struct {
		uint32 vec;
		uint32 toaddr;
	} vectors [] = {
		{ 1, 0x41000200 },	// 割り込み
		{ 3, 0x41000500 },	// データアクセス例外
	};
	uint32 midaddr = 0x1000;
	for (int i = 0; i < countof(vectors); i++) {
		uint32 vec = vectors[i].vec;
		uint32 toaddr = vectors[i].toaddr;
		rams.SetAddr(vec * 8);
		// ベクタは定型文で書いておく。
		rams.Write4(OP_or	(r0, r0, r0));
		rams.Write4(OP_br	((midaddr - rams.GetAddr()) / 4));
		// 経由地で r1 を sr2 に退避してから、toaddr にジャンプ。
		rams.SetAddr(midaddr);
		rams.Write4(OP_stcr	(r1, sr2));
		rams.Write4(OP_or_u	(r1, r0, toaddr >> 16));
		rams.Write4(OP_or	(r1, r1, toaddr & 0xffff));
		rams.Write4(OP_jmp	(r1));
		midaddr = rams.GetAddr();
	}

	// ROM の機能テーブル
	// 0x00001100 ?
	// 0x00001104 ?
	// 0x00001108 ?
	// 0x0000110c GETC() のアドレス
	// 0x00001110 PUTC(c) のアドレス
	// 0x00001114 ビットマッププレーンの有効状態が入っている。
	//  bit0: plane0 有効
	//  bit1..7: plane1..7 有効
	rams.SetAddr(0x110c);
		rams.Write4(0x41000300);
		rams.Write4(0x41000400);
		rams.Write4(0x00000001);

	uint32 ap = (nvram->PeekPort(0x7fd) << 8) | nvram->PeekPort(0x7fe);
	if (__predict_false(0||(ap & 0x3f1f) == 0x0104)) {
		auto *mpu88xx0 = GetMPU88xx0Device(mpu);
		mpu88xx0->reg.r[1] = mpu88xx0->reg.nip;
		mpu88xx0->DoBranch(0x41000900, false);
	}
}

void
Luna88kPROMEmuDevice::ROM_Close()
{
	inherited::ROM_Close();

	// 割り込みをすべてマスクに戻す。
	// XXX とりあえず CPU#0 のみ
	auto *sysctlr = dynamic_cast<SysCtlrDevice*>(GetInterruptDevice());
	sysctlr->WriteIntMask(0, 0x00000000);
}

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

	if (gMainApp.exec_file) {
		// -X が指定されてたらホストプログラムを読み込む。
		entry = LoadHostFile();
	} else {
		// NVRAM で指定されるゲストプログラムを読み込む。
		// XXX とりあえず boot を起動するだけ
		bootinfo_t bootinfo;
		GetNVRAM(bootinfo);
		// 引数で指定されたらファイル名だけ上書き
		if (fname.empty() == false) {
			bootinfo.dkfile = fname;
		}
		entry = LoadGuestFile(bootinfo);
	}

	return entry;
}

void
Luna88kPROMEmuDevice::ProcessChar(char asciicode)
{
	if (prompt_idx == 0) {
		// 行入力モード (画面更新もしてある)
		inherited::ProcessChar(asciicode);
	} else {
		// 0 以外ならノンブロッキングモード

		// 入力されたキーを表示
		if (' ' <= asciicode && asciicode < 0x7f) {
			Putc(asciicode);
		}

		switch (prompt_idx) {
		 case PROMPT_NVRAM_UPDATE:
			CommandNvramUpdate2(asciicode);
			break;

		 case PROMPT_NVRAM_NEW:
			CommandNvramNew2(asciicode);
			break;

		 case PROMPT_NVRAM_INIT:
			CommandNvramInit2(asciicode);
			break;
		}

		// プロンプトを戻す
		prompt = default_prompt;
		prompt_idx = 0;
		prompt_y = cursor_y;
		ClearPrompt();
	}
}

// 入力コマンド処理 (入力行が揃ったところで呼ばれる)。
//
// b					現在のパラメータで起動
// b ...				パラメータを指定して起動
// nvram				全パラメータを表示
// nvram [<name>]		name のみ表示?
// nvram <name> <val>	値を設定
// nvram - <name>		削除
// nvram--				NVRAM 初期化

void
Luna88kPROMEmuDevice::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());
		}
	}

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

	// 1ワード目がコマンド
	if (arg[0] == "h") {
		CommandH(arg);
		return;
	}
	if (arg[0] == "b") {
		CommandB(arg);
		return;
	}
	if (arg[0] == "nvram") {
		CommandNvram(arg);
		return;
	}

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

// "h" コマンド
// 引数なしなら、全体のヘルプメッセージ。
// 引数ありなら、個別のヘルプメッセージ。
void
Luna88kPROMEmuDevice::CommandH(const std::vector<std::string>& arg)
{
	if (arg.size() < 2) {
		// ヘルプメッセージ
 mainhelp:
		Print(
"         *** Help Message ***\n"
"  b      : load and execute program\n"
"  h      : show this message\n"
"  nvram  : show/set nvram parameters\n"
		);
		return;
	}

	const std::string& key = arg[1];
	if (key == "nvram") {
		Print(
"         *** Help Message ***\n"
" nvram [<symbol>]       : show\n"
" nvram <symbol> <value> : set\n"
" nvram - <symbol>       : delete\n"
" nvram --               : initialize\n"
		);
		return;
	} else {
		goto mainhelp;
	}
}

// "b" コマンド
// 引数なしなら NVRAM の情報 (もしくは -X オプション) に従ってプログラムを
// ロードして実行。引数ありのほうは未実装。
void
Luna88kPROMEmuDevice::CommandB(const std::vector<std::string>& arg)
{
	entrypoint = LoadFile("");
	if (entrypoint == -1) {
		Print("** %s\n", errmsg.c_str());
	} else {
		execute = true;
		Print("%s.  Entry point = $%08x\n",
			(gMainApp.exec_file ? "Host program loaded" : "Loaded"),
			entrypoint);
	}
}

// "nvram" コマンド
// nvram             .. 全表示
// nvram <key>       .. 表示
// nvram <key> <val> .. 追加/更新
// nvram - <key>     .. 削除
// nvram --          .. 初期化
void
Luna88kPROMEmuDevice::CommandNvram(const std::vector<std::string>& arg)
{
	switch (arg.size()) {
	 case 1:
		// 引数なし
		CommandNvramShowAll();
		break;
	 case 2:
		// 引数1つ
		if (arg[1] == "--") {
			CommandNvramInit();
		} else {
			CommandNvramShow(arg);
		}
		break;
	 case 3:
		// 引数2つ
		if (arg[1] == "-") {
			CommandNvramDelete(arg);
		} else {
			CommandNvramUpdate(arg);
		}
		break;
	 default:
		Print("** Syntax error\n");
	}
}

// NVRAM は 32バイトを1エントリとする。
// [0] はヘッダに相当し、フォーマットは別記。
// [1] 以降が key (16バイト)、val(16バイト) の並び。こんな感じ。
//
// 00000020  62 6f 6f 74 5f 64 65 76 69 63 65 00 00 00 00 00 |boot_device.....|
// 00000030  73 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |sd..............|
//
// key, val とも '\0' で終端もしくは16文字で終端のようだ。
// val に17文字を設定しようとすると怒られる。
// key が "" ならこのエントリは空きで val は無視、
// 空きエントリは無視して次へ (FAT のディレクトリエントリみたいな感じ)。
// val が "" は有効。
//
// オフセット 0x540 から始まるエントリが最後で、これ以上追加しようとすると
// "nvram set error: no area" (PROM 1.20) と言われる。
// LUNA-88K にはないが LUNA-I では 0x560 にぽつんと ENADDR があったので、
// その辺りとの互換性だろうか。

// NVRAM 全項目表示
void
Luna88kPROMEmuDevice::CommandNvramShowAll()
{
	putmsg(1, "nvram ShowAll");
	for (uint32 offset = 0x20; offset < 0x560; offset += 0x20) {
		std::string key = nvram->PeekString(offset);
		if (key.empty()) {
			continue;
		}
		std::string val = nvram->PeekString(offset + 16);
		Print("%-15s : %s\n", key.c_str(), val.c_str());
	}
}

// NVRAM 指定項目を表示
void
Luna88kPROMEmuDevice::CommandNvramShow(const std::vector<std::string>& arg)
{
	assert(arg.size() >= 2);
	const std::string& key = arg[1];

	putmsg(1, "nvram Show |%s|", key.c_str());
	uint32 offset = FindNVRAM(key);
	if (offset > 0) {
		std::string val = nvram->PeekString(offset + 16);
		Print("%-15s : %s\n", key.c_str(), val.c_str());
	} else {
		Print("** Not found\n");
	}
}

// NVRAM 項目を追加/更新
void
Luna88kPROMEmuDevice::CommandNvramUpdate(const std::vector<std::string>& arg)
{
	// キーがあれば更新、なければ追加。
	// いずれも事前にプロンプトで確認する。

	assert(arg.size() >= 3);
	nvram_key = arg[1];
	nvram_val = arg[2];
	putmsg(1, "nvram Update |%s|%s|", nvram_key.c_str(), nvram_val.c_str());

	if (nvram_key.size() > 16) {
		Print("** Key too long\n");
		return;
	}
	if (nvram_val.size() > 16) {
		Print("** Val too long\n");
		return;
	}

	nvram_offset = FindNVRAM(nvram_key);
	if (nvram_offset > 0) {
		// キーがすでにあれば更新。
		// Update2() に申し送りするのは nvram_offset と nvram_val のみでいい。

		std::string oldval = nvram->PeekString(nvram_offset + 16);
		prompt = string_format("Update %s : \"%s\" -> \"%s\" (Y/[N]):",
			nvram_key.c_str(), oldval.c_str(), nvram_val.c_str());
		prompt_idx = PROMPT_NVRAM_UPDATE;
	} else {
		// キーがなければ追加
		// New2() に申し送りするのは nvram_offset, nvram_key, nvram_val の3つ。

		// 先に空き領域をチェックしている
		nvram_offset = FindNVRAM("");
		if (nvram_offset == 0) {
			Print("** No area available\n");
			return;
		}

		prompt = string_format("Set new %s : \"%s\" (Y/[N]):",
			nvram_key.c_str(), nvram_val.c_str());
		prompt_idx = PROMPT_NVRAM_NEW;
	}

	// 入力待ちの状態で戻る
	// (prompt_idx != 0 なのでキー入力後に指定の関数へ飛んで続きを実行する)
}

// NVRAM 項目を更新 (キー入力後)
void
Luna88kPROMEmuDevice::CommandNvramUpdate2(char asciicode)
{
	assert(prompt_idx == PROMPT_NVRAM_UPDATE);
	assert(nvram_offset > 0);
	assert(nvram_val.size() <= 16);

	Putc('\n');

	if (asciicode != 'y' && asciicode != 'Y') {
		Print("** Not updated\n");
		return;
	}

	if (nvram->WriteString(nvram_offset + 16, nvram_val) == false) {
		Print("** Write error\n");
		return;
	}
	WriteNVRAMCsum();

	Print("Updated\n");
}

// NVRAM 項目を追加 (キー入力後)
void
Luna88kPROMEmuDevice::CommandNvramNew2(char asciicode)
{
	assert(prompt_idx == PROMPT_NVRAM_NEW);
	assert(nvram_offset > 0);
	assert(!nvram_key.empty());
	assert(nvram_key.size() <= 16);
	assert(nvram_val.size() <= 16);

	Putc('\n');

	if (asciicode != 'y' && asciicode != 'Y') {
		Print("** Not added\n");
		return;
	}

	if (nvram->WriteString(nvram_offset, nvram_key) == false
	 || nvram->WriteString(nvram_offset + 16, nvram_val) == false) {
		Print("** Write error\n");
		return;
	}
	WriteNVRAMCsum();

	Print("Added\n");
}

// NVRAM 項目削除
//
// 削除する時は確認がないらしい… (PROM 1.20)
// 成功しても何も言わないらしい… (PROM 1.20)
// 追加削除に比べてなんか雑じゃない?
void
Luna88kPROMEmuDevice::CommandNvramDelete(const std::vector<std::string>& arg)
{
	assert(arg.size() >= 3);
	assert(arg[1] == "-");
	const std::string& key = arg[2];
	putmsg(1, "nvram Delete |%s|", key.c_str());

	if (key.size() > 16) {
		Print("** key too long\n");
		return;
	}

	uint32 offset = FindNVRAM(key);
	if (offset == 0) {
		Print("** Not found\n");
		return;
	}

	// キーを消せば削除になる (値のほうは消してないようだ)
	nvram->WriteString(offset, "");
	WriteNVRAMCsum();
}

// NVRAM から key を探してそのエントリのオフセットを返す。
// key == "" なら空きエントリを探す。
// 見付からなければ 0 を返す (オフセット 0 はパラメータには使わないため)
uint32
Luna88kPROMEmuDevice::FindNVRAM(const std::string& key) const
{
	for (uint32 offset = 0x20; offset < 0x560; offset += 0x20) {
		std::string k = nvram->PeekString(offset);
		if (k == key) {
			return offset;
		}
		// XXX 同じキーが複数あったらどうなるか
	}
	return 0;
}

// NVRAM 初期化
void
Luna88kPROMEmuDevice::CommandNvramInit()
{
	putmsg(1, "nvram Init");

	prompt = "Initialize (Y/[N]):";
	prompt_idx = PROMPT_NVRAM_INIT;

	// 入力待ちの状態で戻る
}

// NVRAM 初期化 (キー入力後)
void
Luna88kPROMEmuDevice::CommandNvramInit2(char asciicode)
{
	assert(prompt_idx == PROMPT_NVRAM_INIT);

	Putc('\n');

	if (asciicode != 'y' && asciicode != 'Y') {
		Print("** Not initialized\n");
		return;
	}

	InitNVRAM();
	Print("** Initialized\n");
}

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

	nvram->WriteString(0x0004, "<nv>");
	nvram->WriteString(0x0020, "boot_device");
	nvram->WriteString(0x0030, "sd");
	nvram->WriteString(0x0040, "boot_spec");
	nvram->WriteString(0x0050, "0");
	nvram->WriteString(0x0060, "boot_unit");
	nvram->WriteString(0x0070, "0");
	nvram->WriteString(0x0080, "boot_partition");
	nvram->WriteString(0x0090, "0");
	nvram->WriteString(0x00a0, "boot_filename");
	nvram->WriteString(0x00b0, "vmunix");
	nvram->WriteString(0x00c0, "main_mem_check");
	nvram->WriteString(0x00d0, "1");
	nvram->WriteString(0x00e0, "3port_mem_check");
	nvram->WriteString(0x00f0, "1");

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

	return true;
}

// NVRAM のブート設定を bootinfo に取得する。
void
Luna88kPROMEmuDevice::GetNVRAM(bootinfo_t& bootinfo)
{
	// dkunit (装置番号) はたぶん boot_unit (0..)
	// dkpart (パーティション) は boot_partition (0..)
	// dkfile (ファイル名) は boot_filename
	// sd 以外のデバイスは未対応
	bootinfo.dkunit = nvram->PeekPort(0x0070) - '0';
	bootinfo.dkpart = nvram->PeekPort(0x0090) - '0';
	bootinfo.dkfile = nvram->PeekString(0x00b0);
	putmsg(1, "%s unit=%u", __func__, bootinfo.dkunit);
	putmsg(1, "%s part=%c", __func__, bootinfo.dkpart + 'a');
	putmsg(1, "%s file=|%s|", __func__, bootinfo.dkfile.c_str());
}

// コールバック関数 (C のグローバル関数)
bool
luna88k_fline_callback(MPU88xx0Device *cpu, void *arg)
{
	auto *dev = reinterpret_cast<Luna88kPROMEmuDevice *>(arg);
	return dev->FLineCallback(cpu);
}

// コールバック関数 (こっちが実体)
bool
Luna88kPROMEmuDevice::FLineCallback(MPU88xx0Device *cpu)
{
	// 111111_DDDDDSSSSS_000001_00nnnnnnnn
	//
	// 引数は rS1 から順番に格納。
	// m68k 的なバイト値やワード値の場合は下位詰めしてあるが、不要な上位
	// ビットの処置はゲストコードよりホストコードで行うほうが楽なので、
	// すべてこちらで責任持って処理すること。
	// 戻り値がある DOS call では戻り値は rD に格納。

	uint32 D   = (cpu->reg.opX >> 21) & 0x1f;
	uint32 S1  = (cpu->reg.opX >> 16) & 0x1f;
	uint32 num =  cpu->reg.opX        & 0xff;

	switch (num) {
	 case 0x00:	// _EXIT
		break;

	 case 0x01:	// _GETCHAR
		cpu->reg.r[D] = Getc();
		break;

	 case 0x02:	// _PUTCHAR
		Putc(cpu->reg.r[S1++] & 0xff);
		break;

	 default:
		putlog(0, "DOS call #$%02x (NOT IMPLEMENTED)", num);
		cpu->reg.r[D] = 0xffffffff;
		break;
	}

	// 存在しないコール番号、未実装コールも含めて処理済みとする。
	return true;
}

unix.superglobalmegacorp.com

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