|
|
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;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.