|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// LUNA-I の 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 のブートページ切り替え用プロキシ)
// 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'00d4.L 'x' コマンドの引数バッファアドレス
//
// 4100'0400 リセットハンドラ
// 4100'0500 Lv5 (Sysclk) 割り込みハンドラ
// 4100'0600 Lv6 (SIO) 割り込みハンドラ
// 4100'0700 Lv7 (NMI) 割り込みハンドラ
// 4100'0800 デフォルトハンドラ
// 4100'0900 サブルーチン
// 4100'1000.B[64] 'x' コマンドの引数バッファ(ダミー)
//
// *4101'ffd8〜 "ENADDR",0,0
// *4101'ffe0〜 MACアドレス(文字列形式、英大文字、区切りなし)
// 0123456789ab c
// "0011223344AA",0
#include "romemu_luna1.h"
#include "bt45x.h"
#include "ethernet.h"
#include "iodevstream.h"
#include "mainapp.h"
#include "mainram.h"
#include "memorystream.h"
#include "mk48t02.h"
#include "mpu680x0.h"
#include "sysclk.h"
// コンストラクタ
Luna1PROMEmuDevice::Luna1PROMEmuDevice()
{
machine_name = "LUNA-I";
}
// デストラクタ
Luna1PROMEmuDevice::~Luna1PROMEmuDevice()
{
}
// 初期化
bool
Luna1PROMEmuDevice::Init()
{
if (inherited::Init() == false) {
return false;
}
// LUNA-I の ROM の特定アドレスを読み出す人がいるので、
// サイズは実 ROM と同じにしておく必要がある。
if (AllocROM(128 * 1024, 0) == false) {
return false;
}
SysClkDevice *sysclk = GetSysClkDevice();
MemoryStreamBE roms(imagebuf.get());
roms.Write4(0x00002000); // +00: リセットベクタ SP
roms.Write4(0x41000400); // +04: リセットベクタ PC
roms.Write4(0); // +08
roms.Write4(0x41000010); // +0c: メモリサイズ格納先
// +10: 値は ROM_Init() でセット
roms.SetOffset(0xb8);
roms.Write4(0x410000bc); // +b8: プレーンマスク格納先
// +bc: 値は ROM_Init() でセット
roms.SetOffset(0xd4);
roms.Write4(0x41001000); // +d4: 'x' コマンドの引数(実際はRAM)
// リセット時に実行する命令
roms.SetOffset(0x400);
roms.Write2(0x13c0); // move.b d0,#0x41000000 ; RAMに切り替え
roms.Write4(0x41000000);
roms.Write2(0x2039); // move.l (ROMIO_INIT),d0
roms.Write4(ROMIO_INIT);
roms.Write2(0x2039); // move.l (ROMIO_LOAD),d0
roms.Write4(ROMIO_LOAD);
roms.Write2(0x72ff); // moveq.l #0xffffffff,d1
roms.Write2(0x6004); // bra _entry
//_prompt:
uint32 _prompt = roms.GetOffset();
roms.Write4(0x4e722500); // stop #0x2500 ; キー入力を待つ
//_entry:
roms.Write2(0x2039); // move.l (ROMIO_ENTRY),d0
roms.Write4(ROMIO_ENTRY); // ; エントリポイント取得
roms.Write2(0xb081); // cmp.l d1,d0
roms.Write2(0x67f2); // beq _prompt ; 起動先なければループ
//_go:
roms.Write2(0x2040); // move.l d0,a0 ; ジャンプ先を a0 にセット
roms.Write2(0x2039); // move.l (ROMIO_CLOSE),d0
roms.Write4(ROMIO_CLOSE); // ; 後始末
roms.Write2(0x4ed0); // jmp (a0) ; ジャンプ
// Lv5 (Sysclk) 割り込みハンドラ。
roms.SetOffset(0x500);
roms.Write2(0x13c0); // move.b d0,#0x63000000 ; ACK
roms.Write4(0x63000000);
roms.Write2(0x52b9); // addq.l #1,(ROMIO_CLKCNT)
roms.Write4(ROMIO_CLKCNT);
roms.Write2(0x4e73); // rte
// Lv6 (SIO) 割り込みハンドラ。
// この中でコマンドなどを処理する。
roms.SetOffset(0x600);
roms.Write2(0x4ab9); // tst.l (ROMIO_SIOINTR)
roms.Write4(ROMIO_SIOINTR); // ; キー入力処理
roms.Write2(0x4e73); // rte
// NMI のハンドラ
roms.SetOffset(0x700);
roms.Write4(0x46fc2700); // move.w #0x2700,sr
roms.Write2(0x91c8); // suba.l a0,a0
roms.Write4(0x4e7b8801); // movec a0,vbr
roms.Write2(0x2e50); // movea.l (a0),sp
roms.Write2(0x4ef9); // jmp _prompt
roms.Write4(0x41000000 + _prompt);
// デフォルトのハンドラ
roms.SetOffset(0x800);
roms.Write2(0x4e73); // rte
// サブルーチン
roms.SetOffset(0x900); //_ap1:
roms.Write2(0x2039); // move.l (ROMIO_AP1),d0
roms.Write4(ROMIO_AP1); //
uint freq = sysclk->GetFreq();
roms.Write2(0x7200 + freq * 3);
// moveq.l #(CLK_FREQ*3),d1
//@@:
roms.Write4(0x4e722400); // stop #0x2400 ; クロック入力を待つ
roms.Write2(0x2039); // move.l (ROMIO_CLKCNT),d0
roms.Write4(ROMIO_CLKCNT);
roms.Write2(0xb001); // cmp.b d1,d0
roms.Write2(0x66f2); // bne @b
roms.Write2(0x2039); // move.l (ROMIO_AP2),d0
roms.Write4(ROMIO_AP2); //
roms.Write2(0x4e75); // rts
// MAC アドレス
MacAddr macaddr;
if (EthernetDevice::GetConfigMacAddr(0, &macaddr, false) == false) {
// エラーメッセージは表示済み
return false;
}
putmsg(1, "macaddr=%s", macaddr.ToString(':').c_str());
strcpy((char *)&imagebuf[0x1ffd8], "ENADDR");
memcpy(&imagebuf[0x1ffe0], macaddr.ToString().c_str(), 12);
return true;
}
// ROM 起動時の初期化 (機種依存部分) を行う。
void
Luna1PROMEmuDevice::ROM_InitMD()
{
IODeviceStream rams(mainram);
MemoryStreamBE roms(imagebuf.get());
// +0010: メモリサイズ
roms.SetOffset(0x10);
roms.Write4((uint32)mainram->GetSize());
// プレーン数(1,4,8)をプレーンマスク(0x01, 0x0f, 0xff)に
int nplane = bt45x->GetPlaneCount();
int planemask = (1U << nplane) - 1;
// プレーンマスク。実際は RAM に書き込まれるがとりあえず
roms.SetOffset(0xbc);
roms.Write4(planemask);
// RAM にベクタを書き込む
roms.SetOffset(0);
rams.SetAddr(0);
rams.Write4(roms.Read4()); // ROM のリセットベクタをコピー
rams.Write4(roms.Read4());
for (uint i = 2; i < 256; i++) { // デフォルトベクタで埋める
rams.Write4(0x41000800);
}
rams.Write4(0x74, 0x41000500); // +74: Lv5 割り込み
rams.Write4(0x78, 0x41000600); // +78: Lv6 割り込み
rams.Write4(0x7c, 0x41000700); // +7c: Lv7 割り込み = RESET
prompt_idx = 0;
prompt = default_prompt;
uint32 ap = (nvram->PeekPort(0x7fd) << 8) | nvram->PeekPort(0x7fe);
if (__predict_false(0||(ap & 0x3f1f) == 0x0104)) {
auto mpu680x0 = GetMPU680x0Device(mpu);
m68kreg& reg = mpu680x0->reg;
reg.A[7] -= 4;
rams.Write4(reg.A[7], reg.pc);
reg.pc = 0x41000900;
}
}
// ROM 処理のクローズ。
// 次段プログラムにジャンプするための処理も含む。
void
Luna1PROMEmuDevice::ROM_Close()
{
inherited::ROM_Close();
// NetBSD のブートローダがカーネルを読み込む時にはいくつかレジスタ
// 渡しのパラメータがあるので、ここで用意する。本当はターゲットが
// NetBSD カーネルの時に限定すべきのような気もするけど、とりあえず。
// ROM がブートローダを読み込む時にはそのような取り決め事は一切ない
// ので、ブートローダを読む時にも同じ値が入ってしまうが気にしない
// ことにする。
// 必要なパラメータは
// %d6 に bootdev、NetBSD/luna68k の場合は
// bootdev = $MACUPP00
// ||||++-- パーティション番号
// |||+---- SCSI ID (XXX ただしここでは 6 決め打ち)
// ||+----- unit (spc0 なら 0 のやつのはず)
// |+------ アダプタ (spc = 0、lance = 1)
// +------- magic ($a)
//
// %d7 に howto (0 でよい)
// (と %a0 にジャンプ先を入れるがこれは Init() が書き出した ROM
// アセンブラコード側で行っている)
m68kreg& reg = GetMPU680x0Device(mpu)->reg;
reg.D[6] = 0xa0060000;
reg.D[7] = 0;
}
// ファイルをロードする。
// -X が指定されていればそのホストファイル。
// そうでなければゲストファイル。この場合引数 fname があれば
// NVRAM で指定されたデバイスから読み込む際のファイル名とする。
// 空なら NVRAM で指定されたファイル名。
// ロードできればエントリポイントを返す。
// ロードできなければ errmsg にメッセージをセットし -1 を返す。
uint32
Luna1PROMEmuDevice::LoadFile(const std::string& fname)
{
uint32 entry = -1;
if (gMainApp.exec_file) {
// -X が指定されてたらホストプログラムを読み込む。
entry = LoadHostFile();
} else {
// NVRAM で指定されるゲストプログラムを読み込む。
std::string ctlr = nvram->PeekString(0x0030);
if (ctlr == "dk") { // harddisk
bootinfo_t bootinfo;
GetNVRAM(bootinfo);
// 引数で指定されたらファイル名だけ上書き
if (fname.empty() == false) {
bootinfo.dkfile = fname;
}
entry = LoadGuestFile(bootinfo);
} else if (ctlr == "et" || // netboot
ctlr == "sd" || // cassette tape
ctlr == "fl" ) // floppy
{
errmsg = ctlr + " boot not supported.";
} else {
// 起きないはず
errmsg = ctlr + ": Invalid boot controller name.";
}
}
return entry;
}
// 入力コマンド処理 (入力行が揃ったところで呼ばれる)。
void
Luna1PROMEmuDevice::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());
}
}
// "k" コマンド中ならここで先に処理する
if (prompt_idx != 0) {
CommandK(arg);
return;
}
// 空行
if (arg.size() == 0) {
return;
}
// 1ワード目がコマンド
if (arg[0] == "h") {
// ヘルプメッセージ
Print("Commands\n");
Print(
" g [<filename>] : load program\n"
" h : show this message\n"
" k : show/set boot constants\n"
" x : execute loaded program\n"
);
return;
}
if (arg[0] == "g") {
CommandG(arg);
return;
}
if (arg[0] == "k") {
CommandK(arg);
return;
}
if (arg[0] == "x") {
CommandX();
return;
}
Print("** Unknown command: %s\n", arg[0].c_str());
}
// "g" コマンド
// g なら NVRAM の情報 (もしくは -X オプション) に従ってプログラムをロード。
// g <fname> なら NVRAM の fname だけ指定の名前を使ってロード。NVRAM には
// 書き込まない。
void
Luna1PROMEmuDevice::CommandG(const std::vector<std::string>& arg)
{
std::string fname {};
switch (arg.size()) {
case 1:
break;
case 2:
fname = arg[1];
break;
default:
Print("** Invalid argument.\n");
return;
}
entrypoint = LoadFile(fname);
if (entrypoint == -1) {
Print("** %s\n", errmsg.c_str());
} else {
Print("%s. Entry point = $%08x\n",
(gMainApp.exec_file ? "Host program loaded" : "Loaded"),
entrypoint);
}
}
// k コマンドのパラメータ表
struct kinfo {
uint32 offset; // NVRAM 上のオフセット
const char * const name; // 表示名
int next; // 次インデックス
};
static const struct kinfo kinfo[] = {
{ 0 }, // 0 は通常プロンプトを指すのでここでは使わない
{ 0x0030, "controller", 0 }, // [1] BOOT (next は動的判定する)
{ 0x0090, "drive unit", 3 }, // [2] DKUNIT
{ 0x00b0, "partition ", 4 }, // [3] DKPART
{ 0x00d0, "filename ", 0 }, // [4] FLNAME
{ 0x0050, "hostname ", 6 }, // [5] HOST
{ 0x0070, "servername", 7 }, // [6] SERVER
{ 0x0190, "filename ", 8 }, // [7] ETBOOT
{ 0x01b0, "", 0 }, // [8] ETFILE (ETBOOT と1行で処理する)
};
// "k" コマンド
void
Luna1PROMEmuDevice::CommandK(const std::vector<std::string>& arg)
{
uint32 offset;
const char *name;
int next;
// 入力を処理
if (prompt_idx == 0) {
// ここは1行目表示前なので前行の入力はない。
next = 1;
} else {
std::string val;
bool rv;
// 入力があれば更新
if (arg.size() != 0) {
rv = CommandKInput(arg[0]);
} else {
// [7] の時に入力があれば CommandKInput() の中で更新されるが、
// 入力がなければそこを通らないので、こっちでやらないといけない。
// うーん。
if (prompt_idx == 7) {
prompt_idx = kinfo[prompt_idx].next;
}
rv = true;
}
// これ以降は更新後の prompt_idx を使う
// 入力エラーがあった時はこの行を再入力するため
next = prompt_idx;
if (rv == true) {
// 入力があっても空でも次インデックスへ。
if (prompt_idx == 1) {
// BOOT なら現在値によって次のインデックスが変わる
// "dk" なら [2]、"et" なら [5]。他は未対応
offset = kinfo[prompt_idx].offset;
val = nvram->PeekString(offset);
if (val == "dk") {
next = 2;
} else if (val == "et") {
next = 5;
} else {
// 起きることはないはず
Print("** Inconsistent boot parameter: %s\n",
val.c_str());
}
} else {
// それ以外はテーブルにかかれている通り
next = kinfo[prompt_idx].next;
}
}
}
// この行のプロンプト文字列を作成
prompt_idx = next;
if (prompt_idx == 0) {
// 最終行なら元に戻す
prompt = default_prompt;
} else {
// 次のプロンプトに変更
name = kinfo[prompt_idx].name;
offset = kinfo[prompt_idx].offset;
std::string val = nvram->PeekString(offset);
// [7] ETBOOT だけ1行で ETBOOT:ETFILE 形式
if (prompt_idx == 7) {
next = kinfo[prompt_idx].next; // ETFILE
offset = kinfo[next].offset;
val += ":";
val += nvram->PeekString(offset);
}
prompt = string_format("%s: %s ?", name, val.c_str());
}
}
// k コマンドの入力値を処理する部分。
// 成功すれば true を返す。
// 失敗すれば false を返す。
// 実機 ROM は入力値にエラーがあると黙って再入力を促す。
// エラーメッセージくらい表示してもいいかもだがとりあえず真似しておく。
bool
Luna1PROMEmuDevice::CommandKInput(const std::string& val)
{
int offset;
bool rv = true;
offset = kinfo[prompt_idx].offset;
switch (prompt_idx) {
case 1: // BOOT
// 正常な入力値は "dk", "et", "sd", "fl" のみ。
if (val != "dk" && val != "et" && val != "sd" && val != "fl") {
return false;
}
rv = nvram->WriteString(offset, val);
if (rv == false) {
return false;
}
break;
case 7: // ETBOOT
{
// こいつだけ ETBOOT:ETFILE 形式で2つのパラメータを同時に処理する
// ':' で前後に分ける
int sep = val.find(':');
if (sep == std::string::npos) {
return false;
}
std::string etboot = val.substr(0, sep);
std::string etfile = val.substr(sep + 1, val.size() - sep + 1);
if (etboot.empty() || etfile.empty()) {
return false;
}
// ETBOOT
rv = nvram->WriteString(offset, etboot);
if (rv == false) {
return false;
}
// 次へ
prompt_idx = kinfo[prompt_idx].next;
// ETFILE
offset = kinfo[prompt_idx].offset;
rv = nvram->WriteString(offset, etfile);
if (rv == false) {
return false;
}
break;
}
default:
rv = nvram->WriteString(offset, val);
if (rv == false) {
return false;
}
break;
}
WriteNVRAMCsum();
return rv;
}
// "x" コマンド
// 読み込まれていたらフラグを立てるだけ。
void
Luna1PROMEmuDevice::CommandX()
{
if (entrypoint == -1) {
Print("** Program is not loaded.\n");
return;
}
execute = true;
}
// NVRAM を初期化する
bool
Luna1PROMEmuDevice::InitNVRAM()
{
nvram->ClearAll();
// 初期値どうしたもんか
nvram->WriteString(0x0004, "<nv>");
nvram->WriteString(0x0020, "BOOT");
nvram->WriteString(0x0030, "dk");
nvram->WriteString(0x0040, "HOST");
nvram->WriteString(0x0050, "nono"); // 偽札的なアレ
nvram->WriteString(0x0060, "SERVER");
nvram->WriteString(0x0070, "servername");
nvram->WriteString(0x0080, "DKUNIT");
nvram->WriteString(0x0090, "0");
nvram->WriteString(0x00a0, "DKPART");
nvram->WriteString(0x00b0, "c");
nvram->WriteString(0x00c0, "DKFILE");
nvram->WriteString(0x00d0, "vmunix");
nvram->WriteString(0x00e0, "FLUNIT");
nvram->WriteString(0x00f0, "2");
nvram->WriteString(0x0100, "FLFILE");
nvram->WriteString(0x0110, "vmunix");
nvram->WriteString(0x0120, "STUNIT");
nvram->WriteString(0x0130, "0");
nvram->WriteString(0x0140, "STFLNO");
nvram->WriteString(0x0150, "1");
nvram->WriteString(0x0160, "STFILE");
nvram->WriteString(0x0170, "vmunix");
nvram->WriteString(0x0180, "ETBOOT");
nvram->WriteString(0x0190, "server");
nvram->WriteString(0x01a0, "ETFILE");
nvram->WriteString(0x01b0, "/vmunix");
nvram->WriteString(0x0560, "ENADDR");
nvram->WriteString(0x0570, ""); // どうするか
// チェックサム再計算
WriteNVRAMCsum();
return true;
}
// NVRAM のブート設定を bootinfo に取得する。
void
Luna1PROMEmuDevice::GetNVRAM(bootinfo_t& bootinfo)
{
// 起動先情報を NVRAM (mk48t02) に問い合わせる
// DKUNIT が装置番号(今はまだ SCSI ID=6 固定)。
// DKPART が 'a'..'h' でパーティション。
// DKFILE が読み込む次段プログラム (NetBSD では通常 /boot のこと)。
bootinfo.dkunit = nvram->PeekPort(0x0090) - '0';
bootinfo.dkpart = nvram->PeekPort(0x00b0) - 'a';
bootinfo.dkfile = nvram->PeekString(0x00d0);
putmsg(1, "%s DKUNIT=%u", __func__, bootinfo.dkunit);
putmsg(1, "%s DKPART=%c", __func__, bootinfo.dkpart + 'a');
putmsg(1, "%s DKFILE=|%s|", __func__, bootinfo.dkfile.c_str());
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.