|
|
nono 1.7.0
//
// nono
// Copyright (C) 2023 nono project
// Licensed under nono-license.txt
//
//
// MSX-DOS コマンドラインエミュレータ
//
#include "msxdos.h"
#include "autofd.h"
#include "human68k.h"
#include "iodevstream.h"
#include "mainapp.h"
#include "memorystream.h"
#include "mpu64180.h"
#include "subram.h"
#include "xpbus.h"
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
static void msxdos_callback(void *arg);
// コンストラクタ
MSXDOSDevice::MSXDOSDevice()
: inherited()
{
}
// デストラクタ
MSXDOSDevice::~MSXDOSDevice()
{
}
// 初期化
bool
MSXDOSDevice::Init()
{
if (inherited::Init() == false) {
return false;
}
// ROM 領域を用意。
if (AllocROM(128 * 1024, 0) == false) {
// エラーメッセージは表示済み。
return false;
}
cursor_x = 0;
xp = GetMPU64180Device();
xpbus = GetXPbusDevice();
// システムコール処理のコールバックを設定
xp->SetSYSCALLCallback(msxdos_callback, this);
// 実行ファイルオープン。
// 失敗したらエラー終了したいので Init() で行うが、この時点ではまだ
// 書き込み先となる SubRAM の初期化が終わっていないので書き込めない。
if (LoadBinary(gMainApp.exec_file) == false) {
return false;
}
return true;
}
// リセット
void
MSXDOSDevice::ResetHard(bool poweron)
{
//
// ROM っぽいものを用意。
//
MemoryStreamBE ms(imagebuf.get());
ms.SetOffset(0);
ms.Write4(0x00002000); // +00: リセットベクタ SP
ms.Write4(0x41000402); // +04: リセットベクタ PC
// 全ベクタを RTE に向ける
for (int i = 8; i < 0x400; i += 4) {
ms.Write4(0x41000000);
}
ms.SetOffset(0x400); //all_handler:
ms.Write2(0x4e73); // rte
// ここからリセット時に実行する命令
ms.Write2(0x41f9); // lea.l $49000000,a0
ms.Write4(0x49000000);
ms.Write2(0x117c); // move.b #$b6,3(a0) ; SetMode
ms.Write4(0x00b60003);
ms.Write2(0x117c); // move.b #$0f,3(a0) ; XPRESET <- %1
ms.Write4(0x000f0003);
ms.Write2(0x117c); // move.b #$0e,3(a0) ; XPRESET <- %0
ms.Write4(0x000e0003);
ms.Write4(0x4e722700); //@:stop #0x2700
ms.Write2(0x60fa); // bra @b
//
// SubRAM に XP 用ファームウェアを書き込む。
// (このモードではこの後の電源オンで SubRAM を初期化しないようになってる)
//
auto subram = GetSubRAMDevice();
IODeviceStream xs(subram);
const uint16 _doscall = 0x7000;
const uint16 _term = 0x7010;
const uint16 _rominit = 0x7020;
const uint16 _msg_trap = 0x7040;
const uint16 _trap_handler = 0x7060;
// リセットベクタ
xs.SetAddr(0x0000);
xs.Write1(0xc3); // 0000: JP _rominit
xs.Write2LE(_rominit);
xs.Write2(0x0000); // 0003: 00,00
xs.Write1(0xc3); // 0005: JP _doscall
xs.Write2LE(_doscall);
// RST エントリの初期化
for (int i = 1; i < 8; i++) {
xs.SetAddr(i * 8);
xs.Write1(0x0e); // +00: LD C,`RST xxH`
xs.Write1(0xc7 | (i << 3));
xs.Write2(0xedff); // +02: SYSCALL
}
// ここから ROM。
// CP/M は 0005H の飛び先がワークの先頭らしく、ここより前を
// SP として使っているらしいので、先頭に DOSCALL ハンドラを用意。
// DOSCALL ハンドラ。
xs.SetAddr(_doscall);
xs.Write1(0x79); // 7000: LD A,C
xs.Write1(0xb7); // 7001: OR A
xs.Write1(0xca); // 7002: JP Z,_term + 2
xs.Write2LE(_term + 2);
xs.Write2(0xedff); // 7005: SYSCALL
xs.Write1(0xc9); // 7007: RET
// TERM
xs.SetAddr(_term);
xs.Write2(0x0e00); // 7010: LD C,00H
xs.Write2(0xedff); // 7012: SYSCALL
xs.Write1(0x76); // 7014: HALT
xs.Write2(0x18fd); // 7015: JR 7014H ; 念のため
// XP 側の初期化ルーチン。
// 0000H 番地をトラップハンドラに向け変えて、
// 0100H にジャンプ。戻ってきたら終了。
xs.SetAddr(_rominit);
xs.Write2(0x3e3c); // 7030: LD A,3CH
xs.Write2(0xed39); // 7032: OUT0 (36H),A
xs.Write1(0x36);
xs.Write1(0x21); // 7035: LD HL,_trap_handler
xs.Write2LE(_trap_handler);
xs.Write1(0x22); // 7038: LD (0001H),HL
xs.Write2LE(0x0001);
xs.Write1(0xcd); // 703b: CALL 0100H
xs.Write2LE(0x0100);
xs.Write1(0xc3); // 703e: JP _term
xs.Write2LE(_term);
// msg_trap
xs.SetAddr(_msg_trap);
xs.WriteString("trap occured!\x0d\x0a$");
// トラップハンドラ。
// XXX: とりあえず未定義命令をスキップしたい。
// まだ LD IXH,n のようなオペランドを持つ命令をうまくスキップできない。
xs.SetAddr(_trap_handler);
xs.Write1(0xe3); // +00: EX (SP),HL
xs.Write1(0xf5); // +01: PUSH AF
xs.Write1(0x23); // +02: INC HL
xs.Write3(0xed3834); // +03: IN0 A,(34H)
xs.Write1(0xf2); // +06: JP P,_term
xs.Write2LE(_term);
xs.Write2(0xcb77); // +09: BIT 6,A
xs.Write2(0x2802); // +0b: JR Z,@f
xs.Write1(0x23); // +0d: INC HL
xs.Write1(0x23); // +0e: INC HL
// @@:
xs.Write2(0xcbbf); // +0f: RES 7,A
xs.Write3(0xed3934); // +11: OUT0 (34H),A
xs.Write1(0xf1); // +14: POP AF
xs.Write1(0xe3); // +15: EX (SP),HL
xs.Write1(0xc9); // +16: RET
// 読んでおいたファイルをここでコピー。
xs.SetAddr(0x100);
for (auto s : filebuf) {
xs.Write1(s);
}
filebuf.clear();
}
// filename で示されるファイルを filebuf にロードする。
// 成功すれば data に格納して true を返す。
// 失敗すればエラーメッセージを表示し、false を返す。
// サポートしているのは
// o z80-asm の出力する .z80 形式
// o MSX-DOS の .COM 形式 (生バイナリ)
bool
MSXDOSDevice::LoadBinary(const char *filename)
{
struct stat st;
size_t dlsize;
char header[10];
autofd fd;
int r;
fd = open(filename, O_RDONLY);
if (fd < 0) {
warn("\"%s\" open failed", filename);
return false;
}
r = fstat(fd, &st);
if (r < 0) {
warn("\"%s\" fstat failed", filename);
return false;
}
r = read(fd, header, sizeof(header));
if (r < 0) {
warn("\"%s\" read(header) failed", filename);
return false;
}
if (r < sizeof(header)) {
warnx("\"%s\" read(header): %d: too short", filename, r);
}
if (strncmp(header, "Z80ASM\x1a\x0a", 8) == 0) {
// .z80 形式。先頭 10 バイトがヘッダ。
dlsize = st.st_size - 10;
} else {
// そうでなければ今の所 .COM 形式。ヘッダなしの生バイナリ。
dlsize = st.st_size;
if (lseek(fd, 0, SEEK_SET) < 0) {
warn("\"%s\" lseek(0) failed", filename);
return false;
}
}
// サイズ制限
if (dlsize >= 0x7000 - 0x100) {
errno = EFBIG;
warn("%s", filename);
return false;
}
filebuf.resize(dlsize);
r = read(fd, filebuf.data(), filebuf.size());
if (r < 0) {
warn("\%s\" read failed", filename);
return false;
}
if (r < dlsize) {
warn("\%s\" read: %d: too short", filename, r);
return false;
}
return true;
}
// コールバック入口のグローバル関数
void
msxdos_callback(void *arg)
{
auto *msxdosdev = reinterpret_cast<MSXDOSDevice *>(arg);
msxdosdev->Syscall();
}
// 1 文字表示
void
MSXDOSDevice::Putc(int ch)
{
putchar(ch);
cursor_x++;
if (ch == '\r' || ch == '\n') {
cursor_x = 0;
}
}
// DOS コールの処理
void
MSXDOSDevice::Syscall()
{
switch (xp->reg.c) {
case 0x00: // _TERM
if (cursor_x != 0) {
Putc('\n');
}
putmsg(1, "DOS _TERM");
Human68kDevice::RequestExit(0);
break;
case 0x02: // _PUTC
Putc(xp->reg.e);
fflush(stdout);
break;
case 0x09: // _PUTS
{
uint16 de = xp->reg.GetDE();
uint8 ch;
for (; (ch = xpbus->Read1(de)) != '$'; de++) {
Putc(ch);
}
fflush(stdout);
break;
}
case 0xc7:
case 0xcf:
case 0xd7:
case 0xdf:
case 0xe7:
case 0xef:
case 0xf7:
case 0xff:
putmsg(0, "RST %2Xh default handler, aborted.", (xp->reg.c & 0x38));
Human68kDevice::RequestExit(0);
break;
default:
putmsg(0, "Syscall: unsupported DOSCALL C=%02XH at $%04x", xp->reg.c,
xp->reg.pc);
break;
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.