--- nono/vm/romemu_luna1.cpp 2026/04/29 17:04:50 1.1 +++ nono/vm/romemu_luna1.cpp 2026/04/29 17:05:09 1.1.1.6 @@ -4,22 +4,54 @@ // Licensed under nono-license.txt // +// // LUNA-I の ROM エミュレーション +// -// IODevice -// | -// +----------+---------------------+ -// v v -// ROMDevice LunaPROMEmuDeivce (ROM エミュレーションの共通部分) -// | | -// | +------------+----------------+ -// v v v -// PROMDevice Luna1PROMEmuDevice Luna88kPROMEmuDevice -// (実ROM) (LUNA-I エミュレーション) (LUNA88K エミュレーション) +// IODevice +// | +// +-- ROMDevice (LoadROM()、ウェイト、マスク等を持つ) +// | | +// | +-- PROMDevice (LUNA* の PROM) +// | +-- IPLROM1Device (X680x0 の IPLROM 後半) +// | +-- IPLROM2Device (X680x0 の IPLROM 前半) +// | +-- CGROMDevice (X680x0 の CGROM) +// | +-- LunaPROMEmuDevice (LUNA PROM エミュレーションの共通部分) +// | | +// | | +--------------------+ +// | +--| Luna1PROMEmuDevice | (LUNA1 の PROM エミュレーション) +// | | +--------------------+ +// | | +// | +-- Luna88kPROMEmuDevice (LUNA88K の PROM エミュレーション) +// | +// +-- 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'0400 リセットハンドラ +// 4100'0600 Lv6 (SIO) 割り込みハンドラ +// 4100'0700 Lv7 (NMI) 割り込みハンドラ +// 4100'0800 デフォルトハンドラ +// +// *4101'ffd8〜 "ENADDR",0,0 +// *4101'ffe0〜 MACアドレス(文字列形式、英大文字、区切りなし) +// 0123456789ab c +// "0011223344AA",0 #include "romemu_luna1.h" -#include "bitmap.h" #include "ethernet.h" +#include "lunafb.h" #include "mainapp.h" #include "memorystream.h" #include "mk48t02.h" @@ -33,7 +65,11 @@ Luna1PROMEmuDevice::Luna1PROMEmuDevice() // LUNA-I の ROM の特定アドレスを読み出す人がいるので、 // サイズは実 ROM と同じにしておく必要がある。 devlen = 128 * 1024; - romdata.reset(new uint8 [devlen]); + imagebuf.reset(new uint8 [devlen]); + memset(&imagebuf[0], 0, devlen); + mem = &imagebuf[0]; + + mask = devlen - 1; } // デストラクタ @@ -49,18 +85,25 @@ Luna1PROMEmuDevice::Init() return false; } - MemoryStreamBE ms(romdata.get()); + MemoryStreamBE ms(imagebuf.get()); ms.Write32(0x00002000); // +00: リセットベクタ SP ms.Write32(0x41000400); // +04: リセットベクタ PC ms.Write32(0); // +08 - ms.Write32(0x41000500); // +0c: メモリサイズ格納先 + ms.Write32(0x41000010); // +0c: メモリサイズ格納先 + // +10: 値は ROM_Init() でセット + ms.SetOffset(0xb8); + ms.Write32(0x410000bc); // +b8: プレーンマスク格納先 + // +bc: 値は ROM_Init() でセット // リセット時に実行する命令 - ms.SetPtr(&romdata[0x400]); + ms.SetOffset(0x400); + ms.Write16(0x13c0); // move.b d0,#0x41000000 ; RAMに切り替え + ms.Write32(0x41000000); ms.Write16(0x2039); // move.l (ROMIO_INIT),d0 ms.Write32(ROMIO_INIT); ms.Write16(0x660c); // bne _go ; 起動先あればGO //_prompt: + uint32 _prompt = ms.GetOffset(); ms.Write32(0x4e722500); // stop #0x2500 ; キー入力を待つ ms.Write16(0x2039); // move.l (ROMIO_ENTRY),d0 ms.Write32(ROMIO_ENTRY); // ; キー入力処理 @@ -73,107 +116,116 @@ Luna1PROMEmuDevice::Init() // Lv6 (SIO) 割り込みハンドラ // キーボード割り込みが上がったら KEYIN をコールする - ms.SetPtr(&romdata[0x600]); + ms.SetOffset(0x600); ms.Write16(0x4ab9); // tst.l (ROMIO_KEYIN) ms.Write32(ROMIO_KEYIN); // ; キー入力処理 ms.Write16(0x4e73); // rte + // NMI のハンドラ + ms.SetOffset(0x700); + ms.Write32(0x46fc2700); // move.w #0x2700,sr + ms.Write16(0x91c8); // suba.l a0,a0 + ms.Write32(0x4e7b8801); // movec a0,vbr + ms.Write16(0x2e50); // movea.l (a0),sp + ms.Write16(0x4ef9); // jmp _prompt + ms.Write32(0x41000000 + _prompt); + + // デフォルトのハンドラ + ms.SetOffset(0x800); + ms.Write16(0x4e73); // rte + // MAC アドレス const macaddr_t macaddr = gEthernet->GetMacAddr(); - strcpy((char *)&romdata[0x1ffd8], "ENADDR"); - memcpy(&romdata[0x1ffe0], macaddr.to_string().c_str(), 12); - - // RAM にベクタを書き込む - // XXX VBR を 41000000 に向けるのとどっちが楽かはある - gRAM->Write32(0x78, 0x41000600); // +78: Lv6 割り込み + strcpy((char *)&imagebuf[0x1ffd8], "ENADDR"); + memcpy(&imagebuf[0x1ffe0], macaddr.ToString().c_str(), 12); return true; } -void -Luna1PROMEmuDevice::ResetHard() -{ - inherited::ResetHard(); - - x_executed = false; - x_entrypoint = 0; -} - // 起動時の処理を行う。 // 次段実行ファイルの読み込みに成功すればそのエントリポイントを返す。 // そうでなければ 0 を返すので、プロンプトに処理を移すこと。 uint32 Luna1PROMEmuDevice::ROM_Init() { - MemoryStreamBE ms; + MemoryStreamBE ms(imagebuf.get()); uint32 entry; // デバイスの初期化 (親クラス) InitDevice(); - // ROM 0x41000000.L リセットベクタ(SP,PC)もカーネルが参照している - // (これは Init() が用意している) - // ROM 0x4100000c.L メモリサイズ(バイト数)格納先アドレス - // ROM 0x410000b8.L プレーンマスク格納先アドレス - - // メモリサイズはとりあえず 0x41000500 に格納 - ms.SetPtr(&romdata[0x500]); + // +0010: メモリサイズ + ms.SetOffset(0x10); ms.Write32(gRAM->GetSize()); // プレーン数(1..4)をプレーンマスク(0x01..0x0f)に - int nplane = gBitmap->GetPlaneCount(); + int nplane = gPlaneVRAM->GetPlaneCount(); int planemask = (1 << nplane) - 1; - // 実際は RAM 上を指しているのだが、とりあえずすぐ隣に置いておく。 - ms.SetPtr(&romdata[0xb8]); - ms.Write32(0x410000bc); + // プレーンマスク。実際は RAM に書き込まれるがとりあえず + ms.SetOffset(0xbc); ms.Write32(planemask); + // RAM にベクタを書き込む + ms.SetOffset(0); + gRAM->Write32(0, ms.Read32()); // ROM のリセットベクタをコピー + gRAM->Write32(4, ms.Read32()); + + for (int i = 2; i < 256; i++) { // デフォルトベクタで埋める + gRAM->Write32(i * 4, 0x41000800); + } + gRAM->Write32(0x78, 0x41000600); // +78: Lv6 割り込み + gRAM->Write32(0x7c, 0x41000700); // +7c: Lv7 割り込み = RESET + prompt_idx = 0; prompt = default_prompt; // スクリーンを初期化 - InitScreen("LUNA-I"); + InitScreen(); entry = 0; + x_executed = false; + x_entrypoint = 0; // 表示環境が整ったところで NVRAM を照合 if (VerifyNVRAM() == false) { InitNVRAM(); - text.Print("** NVRAM Initialized.\n"); - text.Print("\n"); + errmsg = "NVRAM Initialized."; // NVRAM をクリアしたら DIPSW によらずプロンプトに降りる goto done; } - // DIPSW#1-1 が UP なら自動起動、DOWN なら ROM モニタ起動なので、 - // これを真似する。 - if ((gPIO0->GetDIPSW1() & 1)) { - if (gMainApp.host_file) { - // ホストのファイルを読み込んでエントリポイントアドレスを - // この Read アクセスの読み出し値として返す。 - // エラーがあればエラーメッセージは呼び出し先側で表示してある。 - entry = LoadHostFile(); - } else { + // -X file + // DIPSW の状態に関わらず、ファイルをロードし実行する。 + // --load-only file + // DIPSW の状態に関わらず、ファイルをロードし、プロンプトで止まる。 + if (gMainApp.exec_file) { + // ホストのファイルを読み込んでエントリポイントアドレスを + // この Read アクセスの読み出し値として返す。 + // エラーがあればエラーメッセージは呼び出し先側で表示してある。 + x_entrypoint = LoadHostFile(); + if (gMainApp.load_and_exec) { + entry = x_entrypoint; + } + } else { + // DIPSW#1-1 が UP なら自動起動、DOWN なら ROM モニタ起動なので、 + // これを真似する。 + if (gPIO0->IsDIPSW11Up()) { // 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(); + PrintTitle("LUNA-I"); + if (errmsg.empty() == false) { + Print("** %s\n\n", errmsg.c_str()); + } + ClearPrompt(); } return entry; @@ -218,6 +270,9 @@ Luna1PROMEmuDevice::ROM_Close() m68kcpu *cpu = gMPU680x0->GetCPU(); RegD(6) = 0xa0060000; RegD(7) = 0; + + x_entrypoint = 0; + x_executed = false; } // 入力コマンド処理 (入力行が揃ったところで呼ばれる)。 @@ -230,19 +285,11 @@ Luna1PROMEmuDevice::Command() // 1ワードごとに分解 strcpy(&buf[0], inputbuf.c_str()); char *p = &buf[0]; - char *s = p; - while (*p != '\0') { - if (*p == ' ') { - *p++ = '\0'; - arg.push_back(std::string(s)); - s = p; - } else { - p++; + for (char *ap; (ap = strsep(&p, " ")) != NULL; ) { + if (*ap != '\0') { + arg.push_back(std::string(ap)); } } - if (p != s) { - arg.push_back(std::string(s)); - } if (loglevel >= 1) { for (int i = 0; i < arg.size(); i++) { putmsgn("arg[%d]=|%s|", i, arg[i].c_str()); @@ -263,10 +310,10 @@ Luna1PROMEmuDevice::Command() // 1ワード目がコマンド if (arg[0] == "h") { // ヘルプメッセージ - text.Print("Commands\n"); - text.Print( + Print("Commands\n"); + Print( " g [] : load program\n" -" gh : load host program (specified by -A option)\n" +" gh : load host program (specified by --load-only option)\n" " h : show this message\n" " k : show/set boot constants\n" " x : execute loaded program\n" @@ -290,7 +337,7 @@ Luna1PROMEmuDevice::Command() return; } - text.Print("** Unknown command: %s\n", arg[0].c_str()); + Print("** Unknown command: %s\n", arg[0].c_str()); } // "g" コマンド @@ -311,7 +358,7 @@ Luna1PROMEmuDevice::CommandG(const std:: fname = arg[1]; break; default: - text.Print("** Invalid argument.\n"); + Print("** Invalid argument.\n"); return; } errmsg.clear(); @@ -324,34 +371,34 @@ Luna1PROMEmuDevice::CommandG(const std:: } x_entrypoint = LoadGuestFile(bootinfo); if (x_entrypoint == 0) { - text.Print("** %s\n", errmsg.c_str()); + Print("** %s\n", errmsg.c_str()); } } else if (ctlr == "et" || // netboot ctlr == "sd" || // cassette tape ctlr == "fl" ) // floppy { - text.Print("** %s boot not supported.\n", ctlr.c_str()); + Print("** %s boot not supported.\n", ctlr.c_str()); return; } else { // 起きないはず - text.Print("** Invalid boot controller name.\n"); + Print("** Invalid boot controller name.\n"); return; } if (x_entrypoint) { - text.Print("Loaded. Entry point = $%08x\n", x_entrypoint); + Print("Loaded. Entry point = $%08x\n", x_entrypoint); } } // "gh" コマンド (勝手に拡張) -// -A オプションで指定されたホストプログラムをロードする。 +// -X オプションで指定されたホストプログラムをロードする。 void Luna1PROMEmuDevice::CommandGH() { // 指定されてたら読み込む - if (gMainApp.host_file == NULL) { - text.Print("** Cannot loaded; host program is not specified.\n"); + if (gMainApp.exec_file == NULL) { + Print("** Cannot loaded; host program is not specified.\n"); return; } x_entrypoint = LoadHostFile(); @@ -423,7 +470,7 @@ Luna1PROMEmuDevice::CommandK(const std:: next = 5; } else { // 起きることはないはず - text.Print("** Inconsistent boot parameter: %s\n", + Print("** Inconsistent boot parameter: %s\n", val.c_str()); } } else { @@ -532,55 +579,18 @@ void Luna1PROMEmuDevice::CommandX() { if (x_entrypoint == 0) { - text.Print("** Program is not loaded.\n"); + Print("** Program is not loaded.\n"); } else { x_executed = true; } } -// NVRAM のチェックサムを返す -uint8 -Luna1PROMEmuDevice::CalcNVRAMCsum() -{ - uint8 eor = 0; - for (uint32 addr = 0x20; addr < 0x560; addr++) { - eor ^= nvram->Peek(addr); - } - return eor; -} - -// NVRAM のチェックサムを計算して書き込む -void -Luna1PROMEmuDevice::WriteNVRAMCsum() -{ - uint8 eor = CalcNVRAMCsum(); - - nvram->Write(0x001c, eor); - nvram->Write(0x001d, eor); - nvram->Write(0x001e, eor); -} - -// NVRAM のマジックとチェックサムを照合する -bool -Luna1PROMEmuDevice::VerifyNVRAM() -{ - if (nvram->PeekString(0x0004) != "") { - return false; - } - - uint8 eor = CalcNVRAMCsum(); - for (uint32 addr = 0x1c; addr < 0x1f; addr++) { - if (nvram->Peek(addr) != eor) { - return false; - } - } - return true; -} - // NVRAM を初期化する bool Luna1PROMEmuDevice::InitNVRAM() { + nvram->ClearAll(); + // 初期値どうしたもんか nvram->WriteString(0x0004, ""); nvram->WriteString(0x0020, "BOOT");