File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / msxdos.cpp
Revision 1.1.1.7 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
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;
	}
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.