--- nono/vm/romemu_luna88k.cpp 2026/04/29 17:04:32 1.1.1.1 +++ nono/vm/romemu_luna88k.cpp 2026/04/29 17:04:53 1.1.1.6 @@ -1,45 +1,116 @@ // // nono -// Copyright (C) 2020 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "aout.h" -#include "bt454.h" +// LUNA88K の ROM エミュレーション + +// IODevice +// | +// +----------+---------------------+ +// v v +// ROMDevice LunaPROMEmuDeivce (ROM エミュレーションの共通部分) +// | | +// | +------------+----------------+ +// v v v +// PROMDevice Luna1PROMEmuDevice Luna88kPROMEmuDevice +// (実ROM) (LUNA-I エミュレーション) (LUNA88K エミュレーション) + +#include "romemu_luna88k.h" +#include "bitmap.h" +#include "iodevstream.h" #include "mainapp.h" #include "memorystream.h" -#include "romemu_luna88k.h" +#include "pio.h" #include "ram.h" -#include "spc.h" +#include "sysctlr.h" // コンストラクタ Luna88kPROMEmuDevice::Luna88kPROMEmuDevice() { devaddr = baseaddr; - mask = 1024 - 1; // サイズは適当 - mem = new uint8 [mask + 1]; + + // ROM の特定アドレスを読み出す人がいるかも知れないので、 + // サイズは実 ROM と同じにしておく。LUNA-I からの類推。 + devlen = 256 * 1024; + romdata.reset(new uint8 [devlen]); + + // プロンプトを初期化。 + // 本物は "O>" というプロンプトでよく分からんけど、こっちは nono の + // 頭文字ということにしておく。 + prompt = "N>"; } // デストラクタ Luna88kPROMEmuDevice::~Luna88kPROMEmuDevice() { - if (mem) { - delete[] mem; - } } bool Luna88kPROMEmuDevice::Init() { - MemoryStreamBE ms(mem); + // 親クラス + if (inherited::Init() == false) { + return false; + } + + MemoryStreamBE ms(romdata.get()); - // リセットベクタは本来2ワードだけだが他のベクタは使わないので - // 直接コードを書く。 - uint32 hi = ROMIO_IPL >> 16; - uint32 lo = ROMIO_IPL & 0xffff; - ms.Write32(0x5c400000 | hi); // or.u r2, r0, (ROMIO_IPL >> 16) - ms.Write32(0x58420000 | lo); // or r2, r2, (ROMIO_IPL & 0xffff) - ms.Write32(0xf4221400); // ld r1, r2, r0 - ms.Write32(0xf400c001); // jmp r1 + // リセット時のブートページには ROM (これ) が見えている。 + // ROMIO_INIT へのアクセスでブートページを RAM に切り替えて、同時に RAM の + // 0番地付近のベクタも作り直す。そのため、ここのベクタ1 以降 ($41000008〜) + // をベクタとして参照することはないので、ベクタ0 の命令列がそのまま使い潰 + // してよい。というか2ワードでは 0番地から ROM まで飛べないので使わないと + // 面倒くさい。一体どういう設計してんだ…。 + + // ; _ROM_START へジャンプ (ここは0番地) + ms.Write32(0x5cc04100); // or.u r6, r0, #0x4100 + ms.Write32(0x58c6000c); // or r6, r6, #0x000c + ms.Write32(0xf400c006); // jmp r6 + + // _ROM_START: (ここは ROM) + // ; 元の PSR を r10 に保存しつつ + // ; 割り込み許可 + ms.Write32(0x81404020); // ldcr r10, psr + ms.Write32(0xf04a8040); // clr r2, r10, 2<1> ; IND,SFRZ + ms.Write32(0x80028022); // stcr r2, psr + // ; r3 <- ROMIO_BASE + ms.Write32(0x5c600000 | // or.u r3, r0, #ROMIO_BASE.Hi + (ROMIO_BASE >> 16)); // + ms.Write32(0x58630000 | // or r3, r0, #ROMIO_BASE.Lo + (ROMIO_BASE & 0xffff)); // + // ; ROMIO_INIT を読み出す + ms.Write32(0x14430000); // ld r2, r3, #+INIT + // ; 起動先があれば GO + ms.Write32(0xe9a20005); // bcnd ne0, r2, _go + // _prompt: + // ; 疑似 STOP でキー入力を待つ + ms.Write32(0xc8000001); // bsr _stop + // ; この時点で r1 = _stop + // _stop: + ms.Write32(0xf400c001); // jmp r1 + // _back: + ms.Write32(0x14430000 | // ld r2, r3, #+ENTRY + (ROMIO_ENTRY - ROMIO_BASE));// + // ; 起動先がなければループ + ms.Write32(0xe842fffd); // bcnd eq0, r2, _prompt + // ; 起動先があれば後始末して + ms.Write32(0x14030000 | // _go: + (ROMIO_CLOSE - ROMIO_BASE));// ld r0, r3, #+CLOSE + // ; 割り込み禁止に戻す + ms.Write32(0x800a802a); // stcr r10, psr + // ; ジャンプ + ms.Write32(0xf400c002); // jmp r2 + + // ここから割り込みハンドラ。割り込みは SIO のみ許可にしてある。 + // キーボード割り込みが上がったら KEYIN をコールして _back に戻る。 + ms.SetPtr(&romdata[0x200]); + ms.Write32(0x14030000 | // ld r0, r3, #+KEYIN + (ROMIO_KEYIN - ROMIO_BASE));// + // ; 戻ったところの jmp を抜ける + ms.Write32(0x60210004); // addu r1, r1, #4 + ms.Write32(0xf400fc00); // rte return true; } @@ -47,47 +118,41 @@ Luna88kPROMEmuDevice::Init() void Luna88kPROMEmuDevice::ResetHard() { - ipl_done = false; -} - -// アドレスデコーダ -inline uint64 -Luna88kPROMEmuDevice::Decoder(uint32 addr) const -{ - return addr - baseaddr; -} - -uint64 -Luna88kPROMEmuDevice::Read32(uint32 addr) -{ - uint64 offset = Decoder(addr); - - // (ロング)ワードアクセスでだけ謎の I/O 空間が見える - switch (offset) { - case ROMIO_IPL - baseaddr: - if (!ipl_done) - return IPL(); - break; - default: - break; - } + inherited::ResetHard(); - return inherited::Read32(addr); + b_entrypoint = 0; } // 起動時の処理を行う。 -// 成功すればエントリポイント、失敗すれば 0 を返す。 +// 次段実行ファイルの読み込みに成功すればそのエントリポイントを返す。 +// そうでなければ 0 を返すので、プロンプトに処理を移すこと。 uint32 -Luna88kPROMEmuDevice::IPL() +Luna88kPROMEmuDevice::ROM_Init() { - uint32 entry = 0; + IODeviceStream rs(gRAM.get()); + uint32 entry; + + // デバイスの初期化 (親クラス) + InitDevice(); - ipl_done = true; + // 割り込みマスクで SIO だけ許可 + gInterrupt->Write32(0x65000000, 0x40000000); + rs.SetAddr(0); // ベクタテーブルを rte で埋めておく - for (int i = 0; i < 512; i++) { - gRAM->Write32(i * 8, 0xf400fc00 /* rte */); + for (int i = 0; i < 512 * 2; i++) { + rs.Write32(0xf400fc00 /* rte */); } + // +$08: 割り込みベクタ + rs.Write32(8, 0xc00003fe); // br $00001000 + + // 割り込みトランポリン (0x1000) + // ベクタテーブルは各2ワードしかないのに、0 番地付近にあるベクタテーブル + // から2ワードでは ROM まで飛べない。一体どういう設計してんだ…。 + rs.SetAddr(0x1000); + rs.Write32(0x5cc04100); // or.u r6, r0, #0x4100 + rs.Write32(0x58c60200); // or r6, r6, #0x0200 + rs.Write32(0xf400c006); // jmp r6 // ROM の機能テーブル // 0x00001100 からあるようだ @@ -96,33 +161,211 @@ Luna88kPROMEmuDevice::IPL() // 0x00001114 ビットマッププレーンの有効状態が入っている。 // bit0: plane0 有効 // bit1..7: plane1..7 有効 - gRAM->Write32(0x0000110c, 0x00001300); - gRAM->Write32(0x00001110, 0x00001400); - gRAM->Write32(0x00001114, 0x00000001); + rs.SetAddr(0x110c); + rs.Write32(0x00001300); + rs.Write32(0x00001400); + rs.Write32(0x00000001); // ROM GETC エミュレータ - gRAM->Write32(0x00001300, 0xfc000c00); // internal inst getc - gRAM->Write32(0x00001304, 0xf400c001); // jmp r1 + rs.SetAddr(0x1300); + rs.Write32(0xfc000c00); // internal inst getc + rs.Write32(0xf400c001); // jmp r1 // ROM PUTC エミュレータ - gRAM->Write32(0x00001400, 0xfc001000); // internal inst putc - gRAM->Write32(0x00001404, 0xf400c001); // jmp r1 + rs.SetAddr(0x1400); + rs.Write32(0xfc001000); // internal inst putc + rs.Write32(0xf400c001); // jmp r1 + + // スクリーンを初期化 + InitScreen("LUNA88K"); + + entry = 0; + + // 表示環境が整ったところで NVRAM を照合 + if (VerifyNVRAM() == false) { + InitNVRAM(); + text.Print("** NVRAM Initialized.\n"); + text.Print("\n"); + // NVRAM をクリアしたら DIPSW によらずプロンプトに降りる + goto done; + } - // 初期パレット - gBT454->Write8(0, 14 << 4); // select palette - gBT454->Write8(1, 15 << 4); // R=15 - gBT454->Write8(1, 15 << 4); // G=15 - gBT454->Write8(1, 15 << 4); // B=15 - - // SCSI - // BDID の書き込みによってホストデバイスがバスにアタッチされる構造なので。 - gSPC->Write8(SPC::BDID, 7); - - if (gMainApp.host_file) { - // ホストのファイルを読み込んでエントリポイントアドレスを - // この Read アクセスの読み出し値として返す。 - entry = gRAM->LoadFile(gMainApp.host_file, AOUT_MID_M88K); + // DIPSW#1-1 が UP なら自動起動、DOWN なら ROM モニタ起動なので、 + // これを真似する。 + if ((gPIO0->GetDIPSW1() & 1)) { + if (gMainApp.host_file) { + // ホストのファイルを読み込んでエントリポイントアドレスを + // この Read アクセスの読み出し値として返す。 + // エラーがあればエラーメッセージは呼び出し先側で表示してある。 + entry = LoadHostFile(); + } else { + // NVRAM の設定に従ってブート先を探す。 + errmsg.clear(); + + bootinfo_t bootinfo; + GetNVRAM(bootinfo); + entry = LoadGuestFile(bootinfo); + if (entry == 0) { + text.Print("** %s\n", errmsg.c_str()); + } + } + } + + // ここまでタイトルなどを Print してはいるが、実際の画面更新は + // ConsoleUpdate() を呼ぶまで行われないことを利用(悪用?)して、 + // ここでエラーなくブートプログラムに制御を渡せる場合には何も表示せずに + // ゲストに処理を移す。意図して或いは意図せずプロンプトに降りる場合だけ + // ConsoleUpdate を呼ぶことでタイトルを含めて画面を表示する。 + done: + if (entry == 0) { + ConsoleUpdate(); } return entry; } + +// ROM 処理。エントリポイントを返す。 +uint32 +Luna88kPROMEmuDevice::ROM_Entry() +{ + if (b_entrypoint != 0) { + return b_entrypoint; + } else { + return 0; + } +} + +// 入力コマンド処理 (入力行が揃ったところで呼ばれる)。 +// +// b 現在のパラメータで起動 +// b ... パラメータを指定して起動 +// nvram 全パラメータを表示 +// nvram [] name のみ表示? +// nvram 値を設定 +// nvram - 削除 +// nvram-- NVRAM 初期化 + +void +Luna88kPROMEmuDevice::Command() +{ + std::vector buf(inputbuf.length() + 1); + std::vector arg; + + // 1ワードごとに分解 + strcpy(&buf[0], inputbuf.c_str()); + 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[%d]=|%s|", i, arg[i].c_str()); + } + } + + // 空行 + if (arg.size() == 0) { + return; + } + + // 1ワード目がコマンド + if (arg[0] == "h") { + // ヘルプメッセージ + text.Print( +" *** Help Message ***\n" +" b : load and execute program\n" +" h : show this message\n" +#if 0 +" nvram : show/set nvram parameters\n" +#endif + ); + return; + } + if (arg[0] == "b") { + CommandB(arg); + return; + } +#if 0 + if (arg[0] == "nvram") { + CommandNvram(arg); + return; + } +#endif + + text.Print("** Unknown command: %s\n", arg[0].c_str()); +} + +// "b" コマンド +// 引数なしなら NVRAM の情報に従ってプログラムをロードして実行。 +// 引数ありのほうは未実装。 +void +Luna88kPROMEmuDevice::CommandB(const std::vector& arg) +{ + errmsg.clear(); + + // XXX とりあえず boot を起動するだけ + bootinfo_t bootinfo; + GetNVRAM(bootinfo); + b_entrypoint = LoadGuestFile(bootinfo); + if (b_entrypoint == 0) { + text.Print("** %s\n", errmsg.c_str()); + } +} + +// "bh" コマンド (勝手に拡張) +// -A オプションで指定されたホストプログラムをロードして実行。 +void +Luna88kPROMEmuDevice::CommandBH() +{ + // 指定されてたら読み込む + if (gMainApp.host_file == NULL) { + text.Print("** Cannot loaded; host program is not specified.\n"); + return; + } + b_entrypoint = LoadHostFile(); +} + +void +Luna88kPROMEmuDevice::CommandNvram(const std::vector& arg) +{ + printf("%s\n", __func__); +} + +// NVRAM のチェックサムを返す +uint8 +Luna88kPROMEmuDevice::CalcNVRAMCsum() +{ + return 0; +} + +// NVRAM のチェックサムを計算して書き込む +void +Luna88kPROMEmuDevice::WriteNVRAMCsum() +{ +} + +// NVRAM のマジックとチェックサムを照合する +bool +Luna88kPROMEmuDevice::VerifyNVRAM() +{ + return true; +} + +// NVRAM を初期化する +bool +Luna88kPROMEmuDevice::InitNVRAM() +{ + return false; +} + +// NVRAM のブート設定を bootinfo に取得する。 +void +Luna88kPROMEmuDevice::GetNVRAM(bootinfo_t& bootinfo) +{ + // XXX NVRAM 未調査につき ID=6 a: boot 固定 + bootinfo.dkunit = 0; + bootinfo.dkpart = 0; + bootinfo.dkfile = "boot"; +}