--- nono/vm/romemu_luna88k.cpp 2026/04/29 17:04:32 1.1.1.1 +++ nono/vm/romemu_luna88k.cpp 2026/04/29 17:05:43 1.1.1.16 @@ -1,128 +1,891 @@ // // nono -// Copyright (C) 2020 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "aout.h" -#include "bt454.h" +// +// 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 "romemu_luna88k.h" -#include "ram.h" -#include "spc.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, + snip = 0x05, + sfip = 0x06, +}; +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 __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_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() { - devaddr = baseaddr; - mask = 1024 - 1; // サイズは適当 - mem = new uint8 [mask + 1]; + machine_name = "LUNA-88K"; + + // プロンプトを初期化。 + prompt = default_prompt; } // デストラクタ Luna88kPROMEmuDevice::~Luna88kPROMEmuDevice() { - if (mem) { - delete[] mem; - } } +// 初期化 bool Luna88kPROMEmuDevice::Init() { - MemoryStreamBE ms(mem); + 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()); + + // リセット時のブートページには ROM (これ) が見えている。 + // ROMIO_INIT へのアクセスでブートページを RAM に切り替えて、同時に RAM の + // 0番地付近のベクタも作り直す。そのため、ここのベクタ1 以降 ($41000008〜) + // をベクタとして参照することはないので、ベクタ0 の命令列がそのまま使い潰 + // してよい。というか2ワードでは 0番地から ROM まで飛べないので使わないと + // 面倒くさい。一体どういう設計してんだ…。 + + // ここからややアセンブラっぽいインデント + + // _START: + // ここは 0番地。ここから _ROM_START へジャンプ + roms.Write4(OP_or_u (r6, r0, 0x4100)); + roms.Write4(OP_or (r6, r6, 0x000c)); + roms.Write4(OP_jmp (r6)); + + // _ROM_START: (ここは ROM) + // RAM に切り替え + roms.Write4(OP_st (r0, r6, r0)); + + // 元の PSR を r10 に保存し、r9 に割り込み許可の値を作る + roms.Write4(OP_ldcr (r10, psr)); + roms.Write4(OP_clr (r9, r10, 2, 0)); // clear IND,SFRZ + roms.Write4(OP_stcr (r9, psr)); + roms.Write4(OP_or (r11, r0, r0)); // r11 <- 0 + + roms.Write4(OP_or_u (r3, r0, ROMIO_BASE >> 16)); + roms.Write4(OP_or (r3, r3, ROMIO_BASE & 0xffff)); + // ROMIO_INIT を実行 + roms.Write4(OP_ld (r0, r3, (ROMIO_INIT - ROMIO_BASE))); + roms.Write4(OP_ld (r0, r3, (ROMIO_LOAD - ROMIO_BASE))); + roms.Write4(OP_br (5)); // _entry + // _prompt: + uint32 prompt = roms.GetOffset(); + roms.Write4(OP_or_u (r11, r0, 0x4100)); + roms.Write4(OP_or (r11, r11, roms.GetOffset() + 8)); + roms.Write4(OP_br (0)); // STOP 相当 + roms.Write4(OP_or (r11, r0, r0)); + + // _entry: + roms.Write4(OP_ld (r2, r3, (ROMIO_ENTRY - ROMIO_BASE))); + roms.Write4(OP_or_c (r5, r0, r0)); // r5 <- $ffffffff + roms.Write4(OP_cmp (r5, r2, r5)); + roms.Write4(OP_bb1 (CMP_EQ, r5, -7)); // 起動先なければループ + // _go: + // 起動先があれば後始末して + roms.Write4(OP_ld (r0, r3, (ROMIO_CLOSE - ROMIO_BASE))); + // 割り込み禁止に戻して + roms.Write4(OP_stcr (r10, psr)); + // 戻ってきたときのために 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 ((prompt - roms.GetOffset()) / 4)); + + // 割り込みハンドラ。 + // _intr: + roms.SetOffset(0x200); + roms.Write4(OP_or_u (r6, r0, 0x6500)); + roms.Write4(OP_ld (r6, r6, 0)); + roms.Write4(OP_extu (r6, r6, 3, 29)); + roms.Write4(OP_or (r7, r0, 6)); + roms.Write4(OP_cmp (r7, r6, r7)); + roms.Write4(OP_bb1 (CMP_EQ, r7, (0x240 - roms.GetOffset()) / 4)); + // key_intr: + roms.Write4(OP_ld (r0, r3, (ROMIO_KEYIN - ROMIO_BASE))); + // _intr_ret: + // r11 があればそこに戻る + uint32 intr_ret = roms.GetOffset(); + roms.Write4(OP_bcnd (eq0, r11, 5)); // beq @f + roms.Write4(OP_or (r6, r11, SxIP_VALID)); + roms.Write4(OP_stcr (r6, snip)); + roms.Write4(OP_addu (r6, r6, 0x0004)); + roms.Write4(OP_stcr (r6, sfip)); + // @@: + roms.Write4(OP_rte ()); + + // Sysclk 割り込みハンドラ。 + roms.SetOffset(0x240); + // ACK + roms.Write4(OP_or_u (r6, r0, 0x6300)); + roms.Write4(OP_st_b (r0, r6, r0)); + // inc + roms.Write4(OP_ld (r6, r3, (ROMIO_CLKCNT - ROMIO_BASE))); + roms.Write4(OP_addu (r6, r6, 1)); + roms.Write4(OP_st (r6, r3, (ROMIO_CLKCNT - ROMIO_BASE))); + roms.Write4(OP_br ((intr_ret - roms.GetOffset()) / 4)); + + // ROM GETC エミュレータ + 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)); + + // サブルーチン + roms.SetOffset(0x900); + roms.Write4(OP_ld (r2, r3, (ROMIO_AP1 - ROMIO_BASE))); + //@@: + roms.Write4(OP_or_u (r11, r0, 0x4100)); + roms.Write4(OP_or (r11, r11, roms.GetOffset() + 8)); + roms.Write4(OP_br (0)); // STOP 相当 + roms.Write4(OP_or (r11, r0, r0)); + + roms.Write4(OP_ld (r2, r3, (ROMIO_CLKCNT - ROMIO_BASE))); + roms.Write4(OP_or (r5, r0, 0x01ff)); + roms.Write4(OP_and (r2, r2, r5)); // r2 &= 0x1ff; + uint freq = sysclk->GetFreq(); + roms.Write4(OP_or (r5, r0, freq * 3));// r5 = freq*3; + roms.Write4(OP_cmp (r5, r2, r5)); + roms.Write4(OP_bb1 (CMP_LT, r5, -9)); // blt @b + roms.Write4(OP_ld (r0, r3, (ROMIO_AP2 - ROMIO_BASE))); + roms.Write4(OP_jmp (r1)); - // リセットベクタは本来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 + // 変なインデントここまで return true; } +// ROM 起動時の初期化 (機種依存部分) を行う。 void -Luna88kPROMEmuDevice::ResetHard() +Luna88kPROMEmuDevice::ROM_InitMD() { - ipl_done = false; + IODeviceStream rams(mainram); + + // 割り込みマスクで Sysclk, SIO だけ許可 + // XXX とりあえず CPU#0 のみ + auto *sysctlr = dynamic_cast(GetInterruptDevice()); + sysctlr->WriteIntMask(0, 0xc0000000); + + rams.SetAddr(0); + // ベクタテーブルを rte で埋めておく + for (int i = 0; i < 512 * 2; i++) { + rams.Write4(OP_rte()); + } + // +$08: 割り込みベクタ + rams.Write4(8, OP_br(0x003fe)); // br $00001000 + + // 割り込みトランポリン (0x1000) + // ベクタテーブルは各2ワードしかないのに、0 番地付近にあるベクタテーブル + // から2ワードでは ROM まで飛べない。一体どういう設計してんだ…。 + rams.SetAddr(0x1000); + rams.Write4(OP_or_u (r6, r0, 0x4100)); + rams.Write4(OP_or (r6, r6, 0x0200)); + rams.Write4(OP_jmp (r6)); + + // 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); + } +} + +// ファイルをロードする。 +// -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 のみ表示? +// nvram 値を設定 +// nvram - 削除 +// nvram-- NVRAM 初期化 + +void +Luna88kPROMEmuDevice::Command() +{ + std::vector buf(inputbuf.length() + 1); + std::vector 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()); } -// アドレスデコーダ -inline uint64 -Luna88kPROMEmuDevice::Decoder(uint32 addr) const +// "h" コマンド +// 引数なしなら、全体のヘルプメッセージ。 +// 引数ありなら、個別のヘルプメッセージ。 +void +Luna88kPROMEmuDevice::CommandH(const std::vector& arg) { - return addr - baseaddr; + 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 [] : show\n" +" nvram : set\n" +" nvram - : delete\n" +" nvram -- : initialize\n" + ); + return; + } else { + goto mainhelp; + } } -uint64 -Luna88kPROMEmuDevice::Read32(uint32 addr) +// "b" コマンド +// 引数なしなら NVRAM の情報 (もしくは -X オプション) に従ってプログラムを +// ロードして実行。引数ありのほうは未実装。 +void +Luna88kPROMEmuDevice::CommandB(const std::vector& arg) { - uint64 offset = Decoder(addr); + 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); + } +} - // (ロング)ワードアクセスでだけ謎の I/O 空間が見える - switch (offset) { - case ROMIO_IPL - baseaddr: - if (!ipl_done) - return IPL(); +// "nvram" コマンド +// nvram .. 全表示 +// nvram .. 表示 +// nvram .. 追加/更新 +// nvram - .. 削除 +// nvram -- .. 初期化 +void +Luna88kPROMEmuDevice::CommandNvram(const std::vector& arg) +{ + switch (arg.size()) { + case 1: + // 引数なし + CommandNvramShowAll(); break; - default: + 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& 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& 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& 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; } - return inherited::Read32(addr); + uint32 offset = FindNVRAM(key); + if (offset == 0) { + Print("** Not found\n"); + return; + } + + // キーを消せば削除になる (値のほうは消してないようだ) + nvram->WriteString(offset, ""); + WriteNVRAMCsum(); } -// 起動時の処理を行う。 -// 成功すればエントリポイント、失敗すれば 0 を返す。 +// NVRAM から key を探してそのエントリのオフセットを返す。 +// key == "" なら空きエントリを探す。 +// 見付からなければ 0 を返す (オフセット 0 はパラメータには使わないため) uint32 -Luna88kPROMEmuDevice::IPL() +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() { - uint32 entry = 0; + putmsg(1, "nvram Init"); - ipl_done = true; + prompt = "Initialize (Y/[N]):"; + prompt_idx = PROMPT_NVRAM_INIT; - // ベクタテーブルを rte で埋めておく - for (int i = 0; i < 512; i++) { - gRAM->Write32(i * 8, 0xf400fc00 /* rte */); + // 入力待ちの状態で戻る +} + +// NVRAM 初期化 (キー入力後) +void +Luna88kPROMEmuDevice::CommandNvramInit2(char asciicode) +{ + assert(prompt_idx == PROMPT_NVRAM_INIT); + + Putc('\n'); + + if (asciicode != 'y' && asciicode != 'Y') { + Print("** Not initialized\n"); + return; } - // ROM の機能テーブル - // 0x00001100 からあるようだ - // 0x0000110c GETC() - // 0x00001110 PUTC(c) - // 0x00001114 ビットマッププレーンの有効状態が入っている。 - // bit0: plane0 有効 - // bit1..7: plane1..7 有効 - gRAM->Write32(0x0000110c, 0x00001300); - gRAM->Write32(0x00001110, 0x00001400); - gRAM->Write32(0x00001114, 0x00000001); + InitNVRAM(); + Print("** Initialized\n"); +} - // ROM GETC エミュレータ - gRAM->Write32(0x00001300, 0xfc000c00); // internal inst getc - gRAM->Write32(0x00001304, 0xf400c001); // jmp r1 +// NVRAM を初期化する +bool +Luna88kPROMEmuDevice::InitNVRAM() +{ + nvram->ClearAll(); - // ROM PUTC エミュレータ - gRAM->Write32(0x00001400, 0xfc001000); // internal inst putc - gRAM->Write32(0x00001404, 0xf400c001); // jmp r1 + nvram->WriteString(0x0004, ""); + 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(); - // 初期パレット - 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); + 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(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 entry; + // 存在しないコール番号、未実装コールも含めて処理済みとする。 + return true; }