File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / romemu_virt68k.cpp
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

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

//
// virt-m68k の IPLROM 相当の何か
//

// 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_virt68k.h"
#include "autofd.h"
#include "config.h"
#include "iodevstream.h"
#include "mainapp.h"
#include "mainram.h"
#include "memorystream.h"
#include "mpu680x0.h"
#include "power.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <random>

// コンストラクタ
Virt68kROMEmuDevice::Virt68kROMEmuDevice()
	: inherited(OBJ_PROM)
{
}

// デストラクタ
Virt68kROMEmuDevice::~Virt68kROMEmuDevice()
{
}

// 初期化
bool
Virt68kROMEmuDevice::Init()
{
	// ROM 領域を用意。サイズは適当。
	if (AllocROM(2048, 0) == false) {
		// エラーメッセージは表示済み。
		return false;
	}

	mainram = GetMainRAMDevice();

	MemoryStreamBE roms(imagebuf.get());
	roms.Write4(0x00001000);	// +00: リセットベクタ SP
	roms.Write4(0xff000400);	// +04: リセットベクタ PC
	// ベクタ
	for (int i = 2; i < 256; i++) {
		roms.Write4(0);
	}

	// リセット時に実行する命令
	roms.SetOffset(0x400);
	roms.Write2(0x2039);		//	move.l	(ROMIO_INIT),d0
	roms.Write4(ROMIO_INIT);
	roms.Write2(0x0c80);		//	cmpi.l	#0xffffffff,d0
	roms.Write4(0xffffffff);
	roms.Write2(0x6704);		//	beq		@f
	roms.Write2(0x2040);		//	move.l	d0,a0	; ジャンプ先を a0 にセット
	roms.Write2(0x4ed0);		//	jmp		(a0)	; ジャンプ
								//@@:
	roms.Write4(0x4e722700);	//	stop	#0x2700
	roms.Write2(0x67fc);		//	bra		@b

	return true;
}

void
Virt68kROMEmuDevice::ResetHard(bool poweron)
{
	// 起動時に RAM にリセットベクタを書き込んでおく。(手抜き)
	mainram->WriteMem(0, &imagebuf[0], 8);
}

// ROMIO から読み出す。
// ROMIO が応答すべきでなければ (uint64)-1 を返す。
uint64
Virt68kROMEmuDevice::ReadROMIO(busaddr addr)
{
	if (addr.GetSize() == 4) {
		switch (addr.Addr()) {
		 case ROMIO_INIT:
			return ROM_Init();

		 default:
			break;
		}
	}

	return (uint64)-1;
}

uint32
Virt68kROMEmuDevice::ROM_Init()
{
	// -X file で指定されたホストファイルを読み込んでエントリポイント
	// アドレスをこの Read アクセスの読み出し値として返す。

	LoadInfo info(gMainApp.exec_file);
	// QEMU がシンボルテーブルをロードしないので、
	// カーネル側もそのように想定しているようだ。
	info.loadsym = false;

	if (BootLoader::LoadExec(mainram, &info) == false) {
		// エラーメッセージは表示済み。
		auto power = GetPowerDevice();
		power->PushPowerButton();
		return -1;
	}

	if (MakeBootInfo(&info) == false) {
		return -1;
	}

	putmsg(1, "Host program loaded.  Entry point = $%08x", info.entry);

	return info.entry;
}

// ブート情報を便利に扱うためのクラス。
class BootInfo
{
 public:
	uint16 bi_tag {};				// タグ
	std::vector<uint8> bi_data {};	// タグごと決まる可変のデータ

 public:
	BootInfo();

	void Init(uint16 tag_);
	void Init(uint16 tag_, uint32 val_);
	void AddWord(uint32 val_);
	void AddLong(uint32 val_);
	void AddString(const char *val_);

	uint32 GetWord(uint32 offset) const;
	uint32 GetLong(uint32 offset) const;
	const char *GetString() const { return (const char *)bi_data.data(); }
};

// コンストラクタ
BootInfo::BootInfo()
{
}

// タグだけ指定して初期化。
void
BootInfo::Init(uint16 tag_)
{
	bi_tag = tag_;
	bi_data.clear();
}

// タグと整数1つを指定して初期化。
void
BootInfo::Init(uint16 tag_, uint32 val_)
{
	Init(tag_);
	AddLong(val_);
}

// データに 16 ビット整数を1つ追加。
void
BootInfo::AddWord(uint32 val_)
{
	// bi_data はビッグエンディアン
	bi_data.push_back(val_ >> 8);
	bi_data.push_back(val_);
}

// データに 32 ビット整数を1つ追加。
void
BootInfo::AddLong(uint32 val_)
{
	// bi_data はビッグエンディアン
	bi_data.push_back(val_ >> 24);
	bi_data.push_back(val_ >> 16);
	bi_data.push_back(val_ >> 8);
	bi_data.push_back(val_);
}

// データに文字列を追加する。
void
BootInfo::AddString(const char *val_)
{
	for (const char *s = val_; *s; s++) {
		bi_data.push_back(*s);
	}
	bi_data.push_back('\0');
}

// offset の位置からの 16 ビット整数を取得。
uint32
BootInfo::GetWord(uint32 offset) const
{
	if (offset + 2 > bi_data.size()) {
		return -1;
	}
	uint32 data;
	data  = bi_data[offset + 0] << 8;
	data |= bi_data[offset + 1];
	return data;
}

// offset の位置からの 32 ビット整数を取得。
uint32
BootInfo::GetLong(uint32 offset) const
{
	if (offset + 4 > bi_data.size()) {
		return -1;
	}
	uint32 data;
	data  = GetWord(offset + 0) << 16;
	data |= GetWord(offset + 2);
	return data;
}


// BootInfo について。
//
// 伝統的な(?) NetBSD アーキテクチャは、ブートローダからカーネルへの
// パラメータはレジスタ渡しかスタック渡しでアーキテクチャごとに固定の
// フォーマットになっている。少なくともいくつかの m68k では。
// NetBSD/virt68k では Linux/m68k の方式を採用しているようだが、
// これは virt-m68k がそういう仕様だから。
//
// NetBSD/virt68k のブートパラメータ BootInfo は、カーネルがロードされた
// セクションの末尾 (リンカによってアドレスが求まる) 以降の次のロングワード
// 境界から連続して配置する。BootInfo はいわゆる Type-Length-Value (TLV)
// 形式のデータで、基本フォーマットは次の通り。
//
//  struct {
//    uint16 bi_tag;
//    uint16 bi_size;
//    uint8[] bi_data;
//  };
//
// bi_tag がこのパラメータの種別を表し、BI_LAST(=0) のタグで終了する。
// bi_size はタグとサイズフィールドも含めたバイト数。
// bi_data は 0 バイト以上の可変長データで、内容はタグによって決まっている。
// 各パラメータはロングワード境界に整列していること。
//
// これによって、ブートローダというかエミュレータ本体から自由度の高い
// パラメータを受け渡すことが出来る。例えば通常の m68k カーネルが自力で
// 判別している CPU 種別や FPU 種別、デバイスがどのアドレス・IRQ にあるか
// といったハードウェア構成情報もすべてブートパラメータで渡す。

// ブートパラメータを用意。
bool
Virt68kROMEmuDevice::MakeBootInfo(LoadInfo *info)
{
	IODeviceStream ds(mainram, info->end);
	BootInfo bi;
	uint32 cpu_type;
	uint32 mmu_type;
	uint32 fpu_type;

	auto mpu680x0 = GetMPU680x0Device(mpu);
	switch (mpu680x0->GetMPUType()) {
	 case m680x0MPUType::M68030:
		cpu_type = BI_CPU_68030;
		mmu_type = BI_MMU_68030;
		if (mpu680x0->HasFPU()) {
			fpu_type = BI_FPU_68881;
		} else {
			fpu_type = 0;
		}
		break;
	 case m680x0MPUType::M68040:
		cpu_type = BI_CPU_68040;
		mmu_type = BI_MMU_68040;
		fpu_type = BI_FPU_68040;
		break;
	 default:
		assert(false);
	}

	bi.Init(BI_MACHTYPE, BI_MACH_VIRT);
	WriteBootInfo(ds, bi);

	bi.Init(BI_CPUTYPE, cpu_type);
	WriteBootInfo(ds, bi);

	bi.Init(BI_MMUTYPE, mmu_type);
	WriteBootInfo(ds, bi);

	if (fpu_type != 0) {
		bi.Init(BI_FPUTYPE, fpu_type);
		WriteBootInfo(ds, bi);
	}

	uint32 ramsize = (uint32)mainram->GetSize();

	// RAMDISK は (あれば) initrd イメージ置き場で、
	// メモリセグメントとは別セグメントにする必要がある。
	// そのためここでは MainRAM の末尾に置く。
	const ConfigItem& initrd_item = gConfig->Find("exec-initrd");
	const std::string& initrd = initrd_item.AsString();
	if (initrd.empty() == false) {
		std::string path = gMainApp.NormalizePath(initrd);
		const char *cpath = path.c_str();
		autofd fd;
		struct stat st;
		uint8 *mdata;
		size_t rdsize;

		fd = open(cpath, O_RDONLY);
		if (fd == -1) {
			warn("%s \"%s\" open failed", __method__, cpath);
			return false;
		}

		if (fstat(fd, &st) == -1) {
			warn("%s \"%s\" fstat failed", __method__, cpath);
			return false;
		}

		if (S_ISDIR(st.st_mode)) {
			warnx("%s \"%s\" is a directory", __method__, cpath);
			return false;
		}
		rdsize = st.st_size;

		mdata = (uint8 *)mmap(NULL, rdsize, PROT_READ, MAP_PRIVATE, fd, 0);
		if (mdata == MAP_FAILED) {
			warnx("%s \"%s\" mmap failed", __method__, cpath);
			return false;
		}

		// ロード。
		LoadInfo rdinfo(cpath, mdata, rdsize);
		rdinfo.start = (ramsize - rdsize) & ~0xfff;
		bool rv = BootLoader::LoadData(mainram, &rdinfo);
		munmap(mdata, rdsize);
		if (rv == false) {
			warnx("** Couldn't load specified initrd.");
			return false;
		}

		bi.Init(BI_RAMDISK);
		bi.AddLong(rdinfo.start);	// bi_mem_info.addr
		bi.AddLong(rdsize);			// bi_mem_info.size
		WriteBootInfo(ds, bi);

		// ここをメモリ上限にする。
		ramsize = rdinfo.start;
	}

	// MEMCHUNK は RAM 配置情報。
	bi.Init(BI_MEMCHUNK);
	bi.AddLong(0);			// bi_mem_info.addr
	bi.AddLong(ramsize);	// bi_mem_info.size
	WriteBootInfo(ds, bi);

	// 乱数シード。QEMU は 32 バイト渡してるようだ。
	std::random_device rng;
	const uint32 rng_bytes = 32;
	bi.Init(BI_RNG_SEED);
	bi.AddWord(rng_bytes);	// bi_data.data_length
	for (int i = 0; i < rng_bytes / 4; i++) {
		bi.AddLong(rng());	// bi_data.data_bytes
	}
	WriteBootInfo(ds, bi);

	// GFPIC
	// 先頭のデバイスのアドレスとベース IRQ だけ指定する。
	// アドレスを +$1000 番地、IRQ は +1 ずつずらして計 6個あるというのは
	// カーネル側にハードコードしてあるようだ。
	//  $ff01'1000 IRQ=1 : GFPIC1
	//  $ff01'2000 IRQ=2 : GFPIC2
	//  $ff01'3000 IRQ=3 : GFPIC3
	//  $ff01'4000 IRQ=4 : GFPIC4
	//  $ff01'5000 IRQ=5 : GFPIC5
	//  $ff01'6000 IRQ=6 : GFPIC6
	bi.Init(BI_GFPIC);
	bi.AddLong(0xff011000);	// addr
	bi.AddLong(1);			// irq
	WriteBootInfo(ds, bi);

	// GFRTC
	// 先頭アドレスと IRQ だけ指定する。
	// アドレスを +$1000 番地したところに GFRTC があるというのは
	// カーネル側にハードコードしてあるようだ。
	//  $ff02'0000 IRQ=PIC6:IRQ1 : GFTimer
	//  $ff02'1000               : GFRTC
	bi.Init(BI_GFRTC);
	bi.AddLong(0xff020000);				// addr
	bi.AddLong(8 + (6 - 1) * 32 + 0);	// irq
	WriteBootInfo(ds, bi);

	// GFTTY
	// IRQ は PIC1:IRQ32
	bi.Init(BI_GFTTY);
	bi.AddLong(0xff030000);				// addr
	bi.AddLong(8 + (1 - 1) * 32 + 31);	// irq
	WriteBootInfo(ds, bi);

	// QemuSysCtlr
	bi.Init(BI_CTRL);
	bi.AddLong(0xff040000);				// addr
	bi.AddLong(0);						// irq
	WriteBootInfo(ds, bi);

	// VirtIO
	bi.Init(BI_VIRTIO);
	bi.AddLong(0xff070000);				// addr
	bi.AddLong(8 + (2 - 1) * 32 + 0);	// base irq
	WriteBootInfo(ds, bi);

	// ブートパラメータ
	const ConfigItem& bootparam_item = gConfig->Find("exec-bootparam");
	const std::string& bootparam = bootparam_item.AsString();
	if (bootparam.empty() == false) {
		bi.Init(BI_COMMANDLINE);
		bi.AddString(bootparam.c_str());
		WriteBootInfo(ds, bi);
	}

	bi.Init(BI_LAST);
	WriteBootInfo(ds, bi);

	return true;
}

#define ENTRY(x)	{ (x),	#x }
/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::tag_str[] = {
	ENTRY(BI_LAST),
	ENTRY(BI_MACHTYPE),
	ENTRY(BI_CPUTYPE),
	ENTRY(BI_FPUTYPE),
	ENTRY(BI_MMUTYPE),
	ENTRY(BI_MEMCHUNK),
	ENTRY(BI_RAMDISK),
	ENTRY(BI_COMMANDLINE),
	ENTRY(BI_RNG_SEED),

	ENTRY(BI_QEMU_VER),
	ENTRY(BI_GFPIC),
	ENTRY(BI_GFRTC),
	ENTRY(BI_GFTTY),
	ENTRY(BI_VIRTIO),
	ENTRY(BI_CTRL),
};

/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::mach_str[] = {
	ENTRY(BI_MACH_VIRT),
};

/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::cpu_str[] = {
	ENTRY(BI_CPU_68020),
	ENTRY(BI_CPU_68030),
	ENTRY(BI_CPU_68040),
	ENTRY(BI_CPU_68060),
};

/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::fpu_str[] = {
	ENTRY(BI_FPU_68881),
	ENTRY(BI_FPU_68882),
	ENTRY(BI_FPU_68040),
	ENTRY(BI_FPU_68060),
};

/*static*/ const std::pair<uint32, const char *>
Virt68kROMEmuDevice::mmu_str[] = {
	ENTRY(BI_MMU_68851),
	ENTRY(BI_MMU_68030),
	ENTRY(BI_MMU_68040),
	ENTRY(BI_MMU_68060),
};
#undef ENTRY

#define GetStr(array, v)	GetStr1(array, countof(array), v)
static std::string
GetStr1(const std::pair<uint32, const char *> *map, uint mapcount, uint32 v)
{
	for (int i = 0; i < mapcount; i++) {
		if (map[i].first == v) {
			return map[i].second;
		}
	}
	return string_format("(%u)", v);
}

// BootInfo を stream に書き出す。
void
Virt68kROMEmuDevice::WriteBootInfo(IODeviceStream& stream, BootInfo& bi)
{
	// ロングワード境界にパディング。
	uint16 size = 2 + 2 + bi.bi_data.size();
	for (; (size % 4) != 0; size++) {
		bi.bi_data.push_back(0);
	}

	// デバッグ表示。
	if (__predict_false(loglevel >= 1)) {
		std::string tagname = GetStr(tag_str, bi.bi_tag);
		std::string buf;
		switch (bi.bi_tag) {
		 case BI_LAST:
			break;
		 case BI_MACHTYPE:
			buf = GetStr(mach_str, bi.GetLong(0));
			break;
		 case BI_CPUTYPE:
			buf = GetStr(cpu_str, bi.GetLong(0));
			break;
		 case BI_FPUTYPE:
			buf = GetStr(fpu_str, bi.GetLong(0));
			break;
		 case BI_MMUTYPE:
			buf = GetStr(mmu_str, bi.GetLong(0));
			break;
		 case BI_MEMCHUNK:
		 case BI_RAMDISK:
			buf = string_format("addr=$%08x, len=$%08x",
				bi.GetLong(0), bi.GetLong(4));
			break;
		 case BI_GFPIC:
		 case BI_GFRTC:
		 case BI_GFTTY:
		 case BI_VIRTIO:
		 case BI_CTRL:
			buf = string_format("addr=$%08x, irq=$%08x",
				bi.GetLong(0), bi.GetLong(4));
			break;
		 case BI_RNG_SEED:
			buf = string_format("len=%u", bi.GetWord(0));
			break;
		 case BI_COMMANDLINE:
			buf = string_format("\"%s\"", bi.GetString());
			break;
		}
		putmsg(1, "BootInfo $%08x: %-14s: %s",
			stream.GetAddr(), tagname.c_str(), buf.c_str());
	}

	// 出力。
	stream.Write2(bi.bi_tag);
	stream.Write2(size);
	for (const auto c : bi.bi_data) {
		stream.Write1(c);
	}
}

unix.superglobalmegacorp.com

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