File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / romemu_luna.cpp
Revision 1.1.1.20 (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 シリーズの 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_luna.h"
#include "bt45x.h"
#include "builtinrom.h"
#include "dipsw.h"
#include "lance.h"
#include "lunafb.h"
#include "mainapp.h"
#include "mainram.h"
#include "mk48t02.h"
#include "mpu.h"
#include "pio.h"
#include "prom.h"
#include "rtc.h"
#include "scsidev.h"
#include "scsidomain.h"
#include "sio.h"
#include "spc.h"
#include "ufs.h"

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

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

// 初期化
bool
LunaPROMEmuDevice::Init()
{
	bt45x = GetBT45xDevice();
	lance = GetLanceDevice();
	lunafb = GetLunafbDevice();
	mainram = GetMainRAMDevice();
	nvram = GetMK48T02Device();
	sio = GetSIODevice();
	spc = GetSPCDevice();

	return true;
}

// ROMIO から読み出す。
// ROMIO が応答すべきでなければ (uint64)-1 を返す。
uint64
LunaPROMEmuDevice::ReadROMIO(busaddr addr)
{
	// ROM からのロングワードアクセスでだけ謎の I/O 空間が見える。
	if ((mpu->GetPPC() & 0xff000000) == baseaddr && addr.GetSize() == 4) {
		switch (addr.Addr()) {
		 case ROMIO_INIT:
			ROM_Init();
			return 0;

		 case ROMIO_LOAD:
			entrypoint = ROM_Load();
			putlog(2, "ROMIO_LOAD entrypoint=$%08x", entrypoint);
			return 0;

		 case ROMIO_SIOINTR:
			putlog(2, "ROMIO_SIOINTR");
			ROM_SIOIntr();
			return 0;

		 case ROMIO_CLOSE:
			putlog(2, "ROMIO_CLOSE");
			ROM_Close();
			return 0;

		 case ROMIO_ENTRY:
		 {
			uint32 rv = ROM_Entry();
			putlog(2, "ROMIO_ENTRY entrypoint=$%08x", rv);
			return rv;
		 }

		 case ROMIO_AP1:
			ROM_AP1();
			return 0;

		 case ROMIO_AP2:
			ROM_AP2();
			return 0;

		 case ROMIO_CLKCNT:
			return clkcnt;

		 case ROMIO_QUITSTOP:
			return quitstop;

		 default:
			break;
		}
	}

	return (uint64)-1;
}

// ROMIO に書き込む。
// 反応すれば true を返す。そうでなければ false を返す。
bool
LunaPROMEmuDevice::WriteROMIO(busaddr addr, uint32 data)
{
	// ROM からのロングワードアクセスでだけ謎の I/O 空間が見える。
	if ((mpu->GetPPC() & 0xff000000) == baseaddr && addr.GetSize() == 4) {
		switch (addr.Addr()) {
		 case ROMIO_CLKCNT:
			clkcnt = data;
			return true;

		 case ROMIO_QUITSTOP:
			quitstop = data;
			return true;

		 default:
			break;
		}
	}

	return false;
}

// ROM 処理の初期化。
void
LunaPROMEmuDevice::ROM_Init()
{
	// 初期パレット
	InitPalette();

	// SCSI
	// BDID の書き込みによってホストデバイスがバスにアタッチされる構造なので。
	spc->WritePort(SPC::BDID, 7);

	// LANCE。
	lance->WritePort(0, 0x00);	// RAP=CSR0
	lance->WritePort(1, AM7990::CSR0_STOP);

	// DIP-SW の #1-2 が down(=false) ならシリアル。
	auto dipsw = GetDipswDevice();
	use_serial = (dipsw->Get(1) == false);

	if (use_serial) {
		// シリアル(chA)を初期化
		sio->WritePort(1, 0x01);	// CR1
		sio->WritePort(1, 0x12);	//    RX Int(all char) | TXIntEnable
		sio->WritePort(1, 0x04);	// CR4
		sio->WritePort(1, 0x44);	//    Stop 1bit
		sio->WritePort(1, 0x03);	// CR3
		sio->WritePort(1, 0xc1);	//    RX Enable, 8bit
		sio->WritePort(1, 0x05);	// CR5
		sio->WritePort(1, 0x68);	//    TX Enable, 8bit

		sio_txq.Clear();
	} else {
		// キーボード(chB)を初期化
		sio->WritePort(3, 0x01);	// CR1
		sio->WritePort(3, 0x10);	//    RX Int(all char)
		sio->WritePort(3, 0x04);	// CR4
		sio->WritePort(3, 0x44);	//    Stop 1bit
		sio->WritePort(3, 0x03);	// CR3
		sio->WritePort(3, 0xc1);	//    RX Enable, 8bit
		sio->WritePort(3, 0x05);	// CR5
		sio->WritePort(3, 0x68);	//    TX Enable, 8bit

		// LED を(明示的に)オフ、マウスサンプリングをオフ
		sio->WritePort(2, 0x00);
		sio->WritePort(2, 0x01);
		sio->WritePort(2, 0x20);
	}

	// 電源オフをキャンセル。
	// 実 PROM では、リセット後に PIO のモードセットを行っている。
	// モードセットを行うと PortC のビットが %0 になる = 電源オフなので、
	// 即座にこれをキャンセルしている。
	// ROMEmu ではモードセットを行っていないが、キャンセルだけ発行しておく。
	auto pio1 = GetPIO1Device();
	pio1->WritePort(3, 0x09);

	// 機種別の初期化
	ROM_InitMD();

	// スクリーンを初期化
	InitScreen();
}

// 起動時の実行ファイル読み込み処理を行う。
// 次段実行ファイルの読み込みに成功すれば execute を true にして、
// エントリポイントを返す。
// そうでなければ -1 を返す。
uint32
LunaPROMEmuDevice::ROM_Load()
{
	uint32 entry;

	entry = -1;
	execute = false;

	// 表示環境が整ったところで NVRAM を照合
	if (VerifyNVRAM()) {
		// DIPSW#1-1 が UP なら自動起動、DOWN なら ROM モニタ起動なので、
		// これを真似する。
		auto pio0 = GetPIO0Device();
		if (pio0->IsDIPSW11Up()) {
			entry = LoadFile("");
			if (entry != -1) {
				execute = true;
				return entry;
			}
		}
	} else {
		// LUNA-88K の PROM 1.20 は初期化するかどうか確認してくるけど、
		// とりあえず。
		InitNVRAM();
		errmsg = "NVRAM Initialized.";
		// NVRAM をクリアしたら DIPSW によらずプロンプトに降りる
	}

	PrintTitle();
	if (errmsg.empty() == false) {
		Print("** %s\n\n", errmsg.c_str());
	}
	ClearPrompt();

	return entry;
}

// 実行ファイルに処理を移すならそのエントリポイントを返す。
// そうでなければ -1 を返す。
uint32
LunaPROMEmuDevice::ROM_Entry()
{
	if (execute == false) {
		return -1;
	}
	return entrypoint;
}

// パレットを初期化
void
LunaPROMEmuDevice::InitPalette()
{
	// 偶数番は白(R/G/B=15/15/15)、奇数番は黒(0/0/0)。
	uint8 val = 0xff;
	bt45x->WritePort(0, 0);	// select palette
	for (int i = 0; i < 16; i++) {
		bt45x->WritePort(1, val);	// R
		bt45x->WritePort(1, val);	// G
		bt45x->WritePort(1, val);	// B
		val ^= 0xff;
	}
}

// スクリーンを初期化
void
LunaPROMEmuDevice::InitScreen()
{
	lunafb->EmuInitScreen();

	screen_w = 80;
	screen_h = 32;
	cursor_x = 0;
	cursor_y = 0;
	cursor_on = true;

	// 表示範囲はざっくりセンタリングする
	origin_px = (1280 - screen_w * 12) / 2;
	origin_py = (1024 - screen_h * 24) / 2;
	// 横は 48ドット境界から始めておく。
	// こうすると1(,2)文字目の24ビットが必ずワード境界から始まるため。
	// 今となっては任意の場所から文字が描画できるようになったので揃える
	// 必要はなくなったが、依然このほうがアクセス効率はいいので。
	origin_px = (origin_px / 48) * 48;

	cursor_on = true;

	inputbuf.clear();
	inputpos = 0;
	is_shift = false;
	mousecnt = 0;
}

// タイトルを出力
void
LunaPROMEmuDevice::PrintTitle()
{
	// シリアルモードなら、画面に何か出しておく。
	// 実機 PROM は、LUNA-I (4.22) はカーソルが出るだけ、
	// LUNA-88K (1.20) は " Use UART-A.\n" が表示される。
	if (use_serial) {
		PrintDisplay("Use HostCom0.\n");
	}

	Print("NONO %u.%u.%u Emulated ROM Monitor for %s\n\n",
		NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER, machine_name);
}

// 1文字出力
void
LunaPROMEmuDevice::Putc(uint ch)
{
	if (use_serial) {
		PutcSerial(ch);
	} else {
		PutcDisplay(ch);
	}
}

// シリアルコンソールに1文字出力。
// 実際にはキューに1文字追加して、キュー先頭の1文字を可能なら送信する。
void
LunaPROMEmuDevice::PutcSerial(uint ch)
{
	sio_txq.Enqueue(ch);
	ProcessSerial();
}

// シリアルコンソールにキュー先頭の1文字を可能なら送信する。
void
LunaPROMEmuDevice::ProcessSerial()
{
	// コントロールレジスタポインタを確実に 0 にするため読み捨てる。
	sio->ReadPort(1);

	// 送信完了なら割り込みを下ろす。
	uint sr0 = sio->ReadPort(1);
	if ((sr0 & MPSCC::SR0_INTPEND)) {
		sio->WritePort(1, 5 << 3);	// Reset TXIntr Pending
	}

	if ((sr0 & MPSCC::SR0_TXEMPTY)) {
		uint8 ch;
		if (sio_txq.Dequeue(&ch)) {
			sio->WritePort(0, ch);
		}
	}
}

// 画面に1文字出力。
void
LunaPROMEmuDevice::PutcDisplay(uint ch)
{
	uint px = origin_px + cursor_x * 12;
	uint py = origin_py + cursor_y * 24;

	if (cursor_on) {
		// カーソル位置の反転を元に戻す
		lunafb->EmuPutc(px, py, ' ', ROP::INV2);
	}

	// いくつかの制御コードを勝手に使う。
	switch (ch) {
	 case 0x0f:	// SI
		bold = true;
		break;
	 case 0x0e:	// SO
		bold = false;
		break;

	 case 0x10: // カラーコード変更
		lunafb->SetBMSEL(0);
		break;
	 case 0x11:
		lunafb->SetBMSEL(1);
		break;
	 case 0x12:
		lunafb->SetBMSEL(3);
		break;
	 case 0x13:
		lunafb->SetBMSEL(5);
		break;

	 case CR:
		break;

	 case LF:
	 put_LF:
		cursor_x = 0;
		if (cursor_y < screen_h - 1) {
			cursor_y++;
		} else {
			// 上スクロールして..
			lunafb->EmuScrollY(origin_py, origin_py + 24, (screen_h - 1) * 24);
			// 最終行を消す
			lunafb->EmuFill(0, py, 1280, 24, 0);
		}
		break;

	 case LEFT:
		if (cursor_x > 0) {
			cursor_x--;
		} else {
			cursor_y--;
			cursor_x = screen_w - 1;
		}
		break;

	 case RIGHT:
		if (cursor_x < screen_w - 1) {
			cursor_x++;
		} else {
			cursor_y++;
			cursor_x = 0;
		}
		break;

	 default:
		if (ch < 0x80) {
			lunafb->EmuPutc(px, py, ch);
			if (bold) {
				for (int i = 1; i < 2; i++) {
					lunafb->EmuPutc(px + i, py, ch, ROP::OR1);
				}
			}
		} else {
			lunafb->EmuPutc(px, py, ' ');
			lunafb->EmuPutc(px, py + 10, ch);
			if (bold) {
				for (int i = 1; i < 2; i++) {
					lunafb->EmuPutc(px + i, py + 10, ch, ROP::OR1);
				}
			}
		}
		cursor_x++;
		if (cursor_x >= screen_w) {
			goto put_LF;
		}
		break;
	}

	if (cursor_on) {
		// カーソル位置を再計算して描画 (文字を反転)
		px = origin_px + cursor_x * 12;
		py = origin_py + cursor_y * 24;
		lunafb->EmuPutc(px, py, ' ', ROP::INV2);
	}
}

// 文字列 s を画面かシリアルに出力。
void
LunaPROMEmuDevice::Print(const std::string& s)
{
	for (auto *p = s.c_str(); *p; p++) {
		Putc(*p);
	}
}

// 文字列 fmt... を画面かシリアルに出力。
void
LunaPROMEmuDevice::Print(const char *fmt, ...)
{
	char buf[1024];
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);

	for (char *p = buf; *p; p++) {
		Putc(*p);
	}
}

// 文字列 s を画面に出力。
void
LunaPROMEmuDevice::PrintDisplay(const char *s)
{
	for (; *s; s++) {
		PutcDisplay(*s);
	}
}

// ROM 処理のクローズ
void
LunaPROMEmuDevice::ROM_Close()
{
	// SIO をリセットしておく。
	// SIO のバッファにデータが残っていると、NetBSD のブートローダが
	// 起動してこないようだ。リセットして RXEN を落としておけば大丈夫ぽい。
	sio->ResetHard(false);

	// 実行ファイルにジャンプ後、NMI でプロンプトにもう一度戻ってこれるので
	// その時のためにエントリポイント等も初期化しておく。
	entrypoint = -1;
	execute = false;
	clkcnt = 0;
	quitstop = 0;
}

// SIO 割り込みハンドラ。
// シリアル送信の進行と、シリアルもしくはキーボード(どちらもSIO)からの入力を
// 受け取って、コマンド処理を進行させる。
// コマンド処理の進行は、キー入力で行を構成し、行が完成すればそのコマンドを
// 実行する、実行結果により出力(画面もしくはシリアル)を行ったり、メンバ変数を
// 更新したり、までを含む。要するにここがメインループみたいなところ。
void
LunaPROMEmuDevice::ROM_SIOIntr()
{
	// シリアル送信を進行。
	if (use_serial) {
		ProcessSerial();
	}

	// 入力を処理。
	int asciicode = Getc();
	if (cursor_on && asciicode != -1) {
		ProcessChar(asciicode);
	}
}

// キー入力。
int
LunaPROMEmuDevice::Getc()
{
	int asciicode;

	if (use_serial) {
		asciicode = GetcSerial();
	} else {
		asciicode = GetcKeyboard();
	}

	return asciicode;
}

// シリアルポートからのキー入力。
int
LunaPROMEmuDevice::GetcSerial()
{
	// コントロールレジスタポインタを確実に 0 にするため読み捨てる
	sio->ReadPort(1);

	uint8 sr0 = sio->ReadPort(1);
	if ((sr0 & MPSCC::SR0_RXAVAIL) == 0) {
		return -1;
	}

	int asciicode = sio->ReadPort(0);
	return asciicode;
}

// キーボードからのキー入力。
// 押されているキーの ASCII コードを返す。
// キー入力が無いときは -1 を返す。
int
LunaPROMEmuDevice::GetcKeyboard()
{
	// コントロールレジスタポインタを確実に 0 にするため読み捨てる
	sio->ReadPort(3);

	uint8 sr0 = sio->ReadPort(3);
	if ((sr0 & MPSCC::SR0_RXAVAIL) == 0) {
		return -1;
	}

	uint32 lunakey = sio->ReadPort(2);

	// マウス入力の 2, 3バイト目は無視
	if (mousecnt > 0) {
		mousecnt--;
		return -1;
	}

	// SHIFT キーだけ状態を持つので先に処理
	if (lunakey == 0x0c || lunakey == 0x0d) {	// 左右SHIFT押下
		is_shift = true;
		return -1;
	}
	if (lunakey == 0x8c || lunakey == 0x8d) {	// 左右SHIFT開放
		is_shift = false;
		return -1;
	}
	// マウスデータの1バイト目なら、後続2バイトをスキップ
	if ((lunakey & 0xf8) == 0x80) {
		mousecnt = 2;
		return -1;
	}
	// キー開放は無視してよい
	if ((lunakey & 0x80)) {
		return -1;
	}

	// キーコードから文字を取得
	int asciicode;
	if (is_shift) {
		asciicode = lunakey2shifttable[lunakey];
	} else {
		asciicode = lunakey2asciitable[lunakey];
	}
	if (asciicode == 0) {
		return -1;
	}

	return asciicode;
}

// キー入力ハンドラ (キー入力割り込みによって呼ばれる)。
// 引数 asciicode は概ね ASCII コード (ただし 0x1c..0x1f が上下左右)。
void
LunaPROMEmuDevice::ProcessChar(char asciicode)
{
	// LUNA-I の場合、
	// 実機 PROM にはカーソル左右移動の機能はなく、カーソルは常に行末にいる。
	// そのためかどうか Delete も BackSpace と同じく1文字前を消す動作をして
	// いる。カーソル位置に文字はないので、Delete をそうするのはまあ分からん
	// でもない。
	// 一方、エミュレーション ROM では、カーソルの左右移動をサポートしてみた
	// ため Delete を「カーソル位置の文字を消す」にすることは出来るが、それは
	// それで実機と乖離が広がるので、どうしたもんか。

	switch (asciicode) {
	 case BS:
		if (inputbuf.empty() == false && inputpos != 0) {
			inputbuf.erase(--inputpos, 1);
			Putc(LEFT);
			for (int i = inputpos; i < inputbuf.size(); i++) {
				Putc(inputbuf[i]);
			}
			Putc(' ');
			for (int i = 0; i < 1 + inputbuf.size() - inputpos; i++) {
				Putc(LEFT);
			}
		}
		break;

	 case LF:
		// 改行
		Putc(asciicode);
		prompt_y = cursor_y;
		// コマンド実行
		Command();
		// プロンプトを更新
		ClearPrompt();
		break;

	 case LEFT:
		if (inputpos > 0) {
			inputpos--;
			Putc(LEFT);
		}
		break;
	 case RIGHT:
		if (inputpos < inputbuf.length()) {
			inputpos++;
			Putc(RIGHT);
		}
		break;

	 case UP:
	 case DOWN:
	 case 0:	// キー割り当てなし、これは来ないはずだが一応
		return;

	 default:	// 通常文字のはず
		inputbuf.insert(inputbuf.begin() + inputpos, asciicode);
		for (int i = inputpos; i < inputbuf.size(); i++) {
			Putc(inputbuf[i]);
		}
		for (int i = 0; i < inputbuf.size() - inputpos - 1; i++) {
			Putc(LEFT);
		}
		inputpos++;
		break;
	}
}

// 入力バッファを初期化する。
void
LunaPROMEmuDevice::ClearPrompt()
{
	prompt_y = cursor_y;
	// 入力行をクリア
	inputbuf.clear();
	inputpos = 0;

	Print(prompt);
}

// NVRAM のチェックサムを返す
uint8
LunaPROMEmuDevice::CalcNVRAMCsum() const
{
	uint8 eor = 0;
	for (uint32 addr = 0x20; addr < 0x560; addr++) {
		eor ^= nvram->PeekPort(addr);
	}
	return eor;
}

// NVRAM のチェックサムを計算して書き込む
void
LunaPROMEmuDevice::WriteNVRAMCsum()
{
	uint8 eor = CalcNVRAMCsum();

	nvram->WritePort(0x001c, eor);
	nvram->WritePort(0x001d, eor);
	nvram->WritePort(0x001e, eor);
}

// NVRAM のマジックとチェックサムを照合する
bool
LunaPROMEmuDevice::VerifyNVRAM() const
{
	if (nvram->PeekString(0x0004) != "<nv>") {
		return false;
	}

	uint8 eor = CalcNVRAMCsum();
	for (uint32 addr = 0x1c; addr < 0x1f; addr++) {
		if (nvram->PeekPort(addr) != eor) {
			return false;
		}
	}
	return true;
}

static const uint8 __unused test0[] = {
	0x12, 0x0f, 0x4e, 0x4f, 0x4e, 0x4f, 0x0e, 0x1e, 0x13, 0x00,
};
static const uint8 __unused test1[] = {
	0xcd, 0xd5, 0xcc, 0xd4, 0xc9, 0xad, 0xd2, 0xc9, 0xd3, 0xc3,
	0x20, 0xd7, 0xcf, 0xd2, 0xcb, 0xd3, 0xd4, 0xc1, 0xd4, 0xc9,
	0xcf, 0xce, 0x20, 0x11, 0x0f, 0x4c, 0x55, 0x4e, 0x41, 0x2d,
	0x38, 0x38, 0x4b, 0x00, 0xaa, 0x12, 0x43, 0x58, 0xc2, 0x00,
};
static const uint8 __unused test2[] = {
	0xc8, 0xcf, 0xcc, 0xcf, 0xce, 0xc9, 0xc3, 0x20, 0xd7, 0xcf,
	0xd2, 0xcb, 0xd3, 0xd4, 0xc1, 0xd4, 0xc9, 0xcf, 0xce, 0x20,
	0x11, 0x0f, 0x4c, 0x55, 0x4e, 0x41, 0x00, 0x21, 0xd8, 0x67,
};
static const uint8 __unused test3[] = {
	0x0e, 0x1e, 0x13, 0xd3, 0xc5, 0xd2, 0xc9, 0xc5, 0xd3, 0x0a,
	0x00, 0x0f, 0xd0, 0x4f, 0x7e, 0x40, 0x41, 0xcc, 0xdf, 0x09,
};

// -X オプションで指定されたホストファイルをロードする。
// ロードできればエントリポイントを返す。
// 失敗ならエラーメッセージを errmsg にセットして (uint32)-1 を返す。
// exec_file が指定されている時に呼ぶこと。
uint32
LunaPROMEmuDevice::LoadHostFile()
{
	assert(gMainApp.exec_file);

	LoadInfo info(gMainApp.exec_file);
	if (BootLoader::LoadExec(mainram, &info)) {
		return info.entry;
	} else {
		errmsg = "Couldn't load specified host program.";
		return -1;
	}
}

// bootinfo に従ってゲストから起動先をロードする。
// ロードできればエントリポイントを返す。
// 失敗ならエラーメッセージを errmsg にセットして (uint32)-1 を返す。
uint32
LunaPROMEmuDevice::LoadGuestFile(const bootinfo_t& bootinfo)
{
	const int& dkpart = bootinfo.dkpart;
	const int& dkunit = bootinfo.dkunit;
	const std::string& dkfile = bootinfo.dkfile;

	// ユニット (今は SCSI ID=6 決め打ち)
	uint id = 6;
	SCSITarget *target = spc->GetDomain()->GetTarget(id);
	if (target == NULL) {
		errmsg = "No bootable disks";
		putmsg(0, "%s", errmsg.c_str());
		return -1;
	}
	if (target->GetDevType() != SCSI::DevType::HD) {
		errmsg = "No bootable disks";
		putmsg(0, "%s", errmsg.c_str());
		return -1;
	}

	SCSIDisk *hd = dynamic_cast<SCSIDisk*>(target);
	putmsg(2, "%s SCSIHD[%u] found", __func__, id);

	// +0 セクタ目(512byte)に Sun disklabel
	scd_dk_label dk;
	if (hd->Peek(&dk, 0, sizeof(dk)) == false) {
		putmsg(0, "SCSIHD[%u] ReadSector failed", id);
		errmsg = string_format("dk%u Read disklabel failed.", dkunit);
		return -1;
	}

	// +$1fc の MAGIC をチェック
	uint32 magic = be16toh(dk.magic);
	if (magic != scd_dk_label::DKL_MAGIC) {
		putmsg(0, "SCSIHD[%d] Bad magic 0x%04x (!= 0x%x)",
			id, magic, scd_dk_label::DKL_MAGIC);
		errmsg = string_format("dk%d Bad disklabel magic.", dkunit);
		return -1;
	}

	// チェックサム照合は手抜きで省略

	// 指定パーティションの開始位置とサイズ
	if (loglevel >= 2) {
		// デバッグ用に全部表示
		for (int i = 0; i < 8; i++) {
			putmsgn("%s partition %c: { start=%8u size=%8u }", __func__,
				'a' + i,
				be32toh(dk.map[i].blkno),
				be32toh(dk.map[i].nblk));
		}
	}
	uint32 part_start = be32toh(dk.map[dkpart].blkno);
	uint32 part_size  = be32toh(dk.map[dkpart].nblk);
	putmsg(1, "%s partition=%c, start=%u size=%u", __func__,
		dkpart + 'a', part_start, part_size);

	// ファイルシステムをオープン (マウントっぽいイメージ)
	Filesys fsys(this);
	if (fsys.Mount(hd, part_start, part_size) == false) {
		errmsg = string_format("dk%u,%c Open ffs failed: %s",
			dkunit, dkpart + 'a', fsys.errstr.c_str());
		putmsg(0, "%s: %s", __func__, errmsg.c_str());
		return -1;
	}

	// ファイルを探してオープンする
	inodefile f(this);
	if (fsys.OpenFile(f, dkfile) == false) {
		errmsg = string_format("dk%u,%c,%s open failed: %s",
			dkunit, dkpart + 'a', dkfile.c_str(), fsys.errstr.c_str());
		putmsg(0, "%s: %s", __func__, errmsg.c_str());
		return -1;
	}

	// ファイル本体を読み込み
	fsys.ReadData(f);

	// ロード
	std::string name = string_format("SCSIHD %u,%c:%s",
		id, dkpart + 'a', dkfile.c_str());
	LoadInfo info(name.c_str(), f.data.data(), f.data.size());
	if (BootLoader::LoadExec(mainram, &info) == false) {
		errmsg = string_format("dk%u,%c,%s load failed",
			dkunit, dkpart + 'a', dkfile.c_str());
		putmsg(0, "%s: %s", __func__, errmsg.c_str());
		return -1;
	}

	if (info.entry == -1) {
		errmsg = string_format("dk%u,%c,%s not executable",
			dkunit, dkpart + 'a', dkfile.c_str());
		putmsg(0, "%s: %s", __func__, errmsg.c_str());
		return -1;
	}

	return info.entry;
}

void
LunaPROMEmuDevice::ROM_AP1()
{
	char buf[100];
	int n;

	cursor_on = false;

	static uint8 ap1pal[] = {
		0x00, 0x00, 0x00,
		0x00, 0xf8, 0xf8,
		0, 0, 0,
		0xf8, 0xf8, 0x00,
		0, 0, 0,
		0xf8, 0xf8, 0xf8,
		0, 0, 0,  0, 0, 0,
		0, 0, 0,  0, 0, 0,
		0, 0, 0,  0, 0, 0,
		0, 0, 0,  0, 0, 0,
		0x00, 0x00, 0x00,
		0xf8, 0xf8, 0xf8
	};
	bt45x->WritePort(0, 0);
	for (int i = 0; i < countof(ap1pal); i++) {
		bt45x->WritePort(1, ap1pal[i]);
	}

	PrintDisplay((const char *)test0);
	if (gMainApp.IsLUNA88K()) {
		PrintDisplay((const char *)test1);
	} else {
		PrintDisplay((const char *)test2);
	}
	PrintDisplay((const char *)test3);

	n = strlcpy(buf, (const char *)&Builtin::IPLROM30[0x111e3], sizeof(buf));
	snprintf(buf + n, sizeof(buf) - n, "%d.%d.%d '%02d.%02d.%02d\n",
		NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER,
		GetRTCDevice()->GetYear() % 100, 4, 1);
	PrintDisplay(buf);

	memcpy(buf, &Builtin::IPLROM30[0x111f5], 0x28);
	memcpy(buf + 0x1e, mpu->GetMPUName(), 7);
	n = 0x27;
	n += snprintf(buf + n, sizeof(buf) - n, "%4.1f",
		(double)mpu->GetClockSpeed() / 1000);
	strlcpy(buf + n, (const char *)&Builtin::IPLROM30[0x1121f],
		sizeof(buf) - n);
	PrintDisplay(buf);

	strlcpy(buf, (const char *)&Builtin::IPLROM30[0x11226], sizeof(buf));
	if (gMainApp.Has(VMCap::M88K)) {
		memcpy(buf + 0x1e, mpu->GetMPUName(), 7);
		memcpy(buf + 0x25, &Builtin::IPLROM30[0x11251], 3);
	}
	PrintDisplay(buf);

	strlcpy(buf, (const char *)&Builtin::IPLROM30[0x11254], sizeof(buf));
	buf[0x0c] = 'e';
	if (gMainApp.Has(VMCap::M88K)) {
		memcpy(buf + 0x1e, mpu->GetMPUName(), 7);
		memcpy(buf + 0x25, &Builtin::IPLROM30[0x1127d], 3);
		buf[0x22] = 0x32;
	}
	PrintDisplay(buf);

	strlcpy(buf, (const char *)&Builtin::IPLROM30[0x11280], sizeof(buf));
	n = 0x1e;
	n += snprintf(buf + n, sizeof(buf) - n, "%u", mainram->GetSizeMB());
	strlcpy(buf + n, (const char *)&Builtin::IPLROM30[0x1129f],
		sizeof(buf) - n);
	PrintDisplay(buf);
}

void
LunaPROMEmuDevice::ROM_AP2()
{
	InitPalette();
	InitScreen();
}

// キーコードから文字への変換
/*static*/ const uint8
LunaPROMEmuDevice::lunakey2asciitable[] = {
	 0,   0,   0,   0,   0,   0,   0,   0,
	 0,   0,   0,   0,   0,   0,   0,   0,
	 0,  BS,  LF,   0, ' ',   0,   0,   0,
	 0,   0,   0,   0,  UP,LEFT,RIGHT,DOWN,
	 0,   0,  '1', '2', '3', '4', '5', '6',
	'7', '8', '9', '0', '-', '^', '\\', 0,
	 0,   0,  'q', 'w', 'e', 'r', 't', 'y',
	'u', 'i', 'o', 'p', '@', '[',  0,   0,
	 0,   0,  'a', 's', 'd', 'f', 'g', 'h',
	'j', 'k', 'l', ';', ':', ']',  0,   0,
	 0,   0,  'z', 'x', 'c', 'v', 'b', 'n',
	'm', ',', '.', '/',  0,   0,   0,   0,
	 0,  '+', '-', '7', '8', '9', '4', '5',
	'6', '1', '2', '3', '0', '.', LF,   0,
	 0,   0,   0,   0,   0,   0,   0,   0,
	 0,   0,   0,   0,  '*', '/', '=', ',',
};
/*static*/ const uint8
LunaPROMEmuDevice::lunakey2shifttable[] = {
	 0,   0,   0,   0,   0,   0,   0,   0,
	 0,   0,   0,   0,   0,   0,   0,   0,
	 0,  BS,  LF,   0, ' ',   0,   0,   0,
	 0,   0,   0,   0,  UP,LEFT,RIGHT,DOWN,
	 0,   0,  '!', '\"','#', '$', '%', '&',
	'\'','(', ')', ' ', '=', '~', '|',  0,		// XXX SHIFT+'0'は?
	 0,   0,  'Q', 'W', 'E', 'R', 'T', 'Y',
	'U', 'I', 'O', 'P', '`', '{',  0,   0,
	 0,   0,  'A', 'S', 'D', 'F', 'G', 'H',
	'J', 'K', 'L', '+', '*', '}',  0,   0,
	 0,   0,  'Z', 'X', 'C', 'V', 'B', 'N',
	'M', '<', '>', '?', '_',  0,   0,   0,
	 0,  '+', '-', '7', '8', '9', '4', '5',
	'6', '1', '2', '3', '0', '.', LF,   0,
	 0,   0,   0,   0,   0,   0,   0,   0,
	 0,   0,   0,   0,  '*', '/', '=', ',',
};

unix.superglobalmegacorp.com

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