File:  [Isaki's NoNo m68k/m88k emulator] / nono / util / runcom / runcom.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:19 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, v024, v023, v022, v021, v020, v019, v018, HEAD
nono 0.6.0

//
// nono
// Copyright (C) 2023 nono project
// Licensed under nono-license.txt
//

// 指定のプログラムを XP 側で走らせ、送られてくる結果を表示する。
// このプログラム自身は m88k でもビルドするため C99 くらいで書くこと。

// XP 側から文字の出力は RINGADDR から 256 バイトの循環バッファを使う。
// RINGADDR としてここでは 7F00H を使う。実際にはその手前3バイトもワークと
// して使う。
// (RINGADDR - 2) からの2バイトが XP の次の書き込み位置 (下位バイトだけを
// カウンタとして使ったりもする)、(RINGADDR - 3) からの1バイトがメイン
// プロセッサ側の次の読み込み位置で、XP 側に対する (TCP の) ACK のようなもの。
//
// 1. XP 側は (RINGADDR + 0) に1文字目を書き込んで (RINGADDR - 2).b の値を
//    01H に更新。
// 2. ホスト側は (RINGADDR - 2) の値が (00H から) 変わったことをポーリングで
//    把握し、文字を読み出してから ACK として (RINGADDR - 3) を 01H に更新。
// 3. カウント 0FFH (メモリでいうと RINGADDR + 0FFH) まで書き込むと次は
//    カウント 00H (メモリでいうと RINGADDR + 0) に戻る。以降繰り返し。
// 4. 読み込み位置と書き出し位置をそれぞれ持っている循環バッファでは
//    エンプティとフルの区別がつかないためと、メインプロセッサ側の読み出し
//    速度が遅い (UNIX プロセスに処理が回ってくる頻度が低い) ため、
//    カウント 00H と 80H で同期をとる。
// 5. '^Z' 文字の出力で正常終了、とする。

#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#if defined(__m68k__) || defined(__m88k__)
#include <machine/xpio.h>
#else
// dummy
#define XPIOCDOWNLD 0
struct xp_download {
	int size;
	uint8_t *data;
};
#endif

// 共有メモリ先頭からのオフセット
#define RINGADDR (0x7f00)

// ユーザプログラムの最大サイズ
#define MAX_PROGSIZE	(0x7000 - 0x100)

extern int system_datalen;
extern unsigned char system_data[];

static struct xp_download make_world(const char *filename);
static void mainloop(volatile uint8_t *);
static struct xp_download load_binary(const char *filename);

int
main(int ac, char *av[])
{
	const char *filename;
	struct xp_download data;
	uint8_t *mem;
	int fd;
	int r;

	if (ac < 2) {
		errx(1, "usage: <z80-asm object>");
	}
	filename = av[1];

	// 転送する全体を用意。
	data = make_world(filename);

	fd = open("/dev/xp", O_RDWR);
	if (fd < 0) {
		err(1, "open: /dev/xp");
	}
	r = ioctl(fd, XPIOCDOWNLD, &data);
	if (r < 0) {
		err(1, "XPIOCDOWNLD");
	}

	mem = mmap(NULL, 65536, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (mem == MAP_FAILED) {
		err(1, "mmap /dev/xp");
	}

	mainloop((volatile uint8_t *)mem);

	munmap(mem, 65536);
	close(fd);
	return 0;
}

// 転送するプログラム全体を用意。
// 0000H〜 リセットベクタ
// 0100H〜 ユーザプログラム
// 7000H〜 OS に相当する部分
static struct xp_download
make_world(const char *filename)
{
	struct xp_download user;
	struct xp_download rv;
	int len;

	rv.size = 0x8000;
	rv.data = calloc(rv.size, 1);
	if (rv.data == NULL) {
		err(1, "malloc failed");
	}

	// プログラムをロードして 0100H からにコピー
	user = load_binary(filename);
	memcpy(rv.data + 0x100, user.data, user.size);

	// OS 領域をコピー
	memcpy(rv.data + 0x7000, system_data, system_datalen);

	// リセット時に最初に実行されるジャンプ命令を用意。
	rv.data[0x00] = 0xc3;
	rv.data[0x01] = 0x00;
	rv.data[0x02] = 0x70;

	return rv;
}

static void
mainloop(volatile uint8_t *mem)
{
	uint8_t cur;

	cur = 0x00;
	for (;;) {
		// XP 側の書き込みカーソルは (RINGADDR - 2) からリトル
		// エンディアンでワードだけど実際には1バイト変数なので、
		// バイトで読み込む。
		uint8_t newcur = mem[RINGADDR - 2];
		if (newcur == cur) {
			usleep(10);
			continue;
		}

		for (; cur != newcur;) {
			uint8_t ch = mem[RINGADDR + cur];
			if (ch == '\x1a') {
				return;
			}
			printf("%c", ch);
			fflush(stdout);

			cur++;

			// 読み込んだところまでポインタを進める(同期用)
			mem[(RINGADDR - 3)] = cur;
		}
	}
}

// filename からバイナリをロードする。サポートしているのは以下の形式。
// o MSX-DOS の .COM 形式
// o z80-asm が出力する .z80 バイナリ形式
static struct xp_download
load_binary(const char *filename)
{
	struct xp_download dl;
	struct stat st;
	char header[10];
	int fd;
	int r;

	fd = open(filename, O_RDONLY);
	if (fd < 0) {
		err(1, "open: %s", filename);
	}

	r = fstat(fd, &st);
	if (r < 0) {
		err(1, "stat: %s", filename);
	}

	r = read(fd, header, sizeof(header));
	if (r < 0) {
		err(1, "read header");
	}
	if (r < sizeof(header)) {
		errx(1, "read header: %d: too short", r);
	}

	if (strncmp(header, "Z80ASM\x1a\x0a", 8) == 0) {
		// .z80 形式。先頭 10 バイトがヘッダ。
		dl.size = st.st_size - 10;
	} else {
		// そうでなければ今の所 .COM 形式。ヘッダなしの生バイナリ。
		dl.size = st.st_size;
		if (lseek(fd, 0, SEEK_SET) < 0) {
			err(1, "lseek(0) failed");
		}
	}
	if (dl.size > MAX_PROGSIZE) {
		errno = EFBIG;
		err(1, "%s", filename);
	}

	dl.data = malloc(dl.size);
	if (dl.data == NULL) {
		err(1, "malloc failed");
	}

	r = read(fd, dl.data, dl.size);
	if (r < 0) {
		err(1, "read");
	}
	if (r < dl.size) {
		errx(1, "read: %d < %d: too short", r, dl.size);
	}

	close(fd);

	return dl;
}

unix.superglobalmegacorp.com

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