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

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

//
// X68030 の ROM30 (SCSI 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 のブートページ切り替え用プロキシ)

#include "romemu_x68k.h"
#include "mainbus.h"
#include "mainram.h"
#include "memorystream.h"
#include "mpu680x0.h"
#include "scsidev.h"
#include "scsidomain.h"
#include "spc.h"
#include "sram.h"

// コンストラクタ
ROM30EmuDevice::ROM30EmuDevice()
	: inherited(OBJ_IPLROM2)
{
}

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

// 初期化
bool
ROM30EmuDevice::Init()
{
	if (AllocROM(0x020000, 0xff) == false) {
		return false;
	}

	mainbus = GetMainbusDevice();
	spc = GetSPCDevice();

	MemoryStreamBE roms(imagebuf.get());
	// fc0000.L 〜 fc001c.L: SCSI ブートアドレス
	for (int i = 0; i < 8; i++) {
		roms.Write4(0x00fc0100);
	}
	// fc0020.L: SCSI 初期化アドレス
	roms.Write4(0x00fc0200);
	// fc0024.B: マジック
	roms.Write4(('S' << 24) | ('C' << 16) | ('S' << 8) | 'I');
	roms.Write2(('I' << 8) | 'N');
	roms.Write1(0);
	roms.Write1(0);

	//
	// SCSI ブート
	//
	roms.SetOffset(0x100);		//_SCSIBOOT:
	roms.Write2(0x6100);		//	bsr		$fc0200	; SCSIIOCS 初期化
	roms.Write2(0x0200 - roms.GetOffset());
	roms.Write2(0x2039);		//	move.l	(ROMIO_BOOT),d0
	roms.Write4(ROMIO_BOOT);
	roms.Write2(0x6602);		//	bne		_go		; 起動先があればGO
								//_bootfail:
	roms.Write2(0x4e75);		//	rts
								//_go:
	roms.Write2(0x2040);		//	move.l	d0,a0	; ジャンプ先を a0 にセット
	roms.Write2(0x4ed0);		//	jmp		(a0)	; で、ジャンプ

	//
	// SCSIIOCS 初期化
	//
	roms.SetOffset(0x200);		//_SCSIINIT:
	roms.Write4(0x323c01f5);	//	move.w	#$01f5,d1
	roms.Write2(0x43f9);		//	lea.l	$00fc0300,a1
	roms.Write4(0x00fc0300);
	roms.Write4(0x70804e4f);	//	IOCS	_B_INTVCS
								//					; 戻り値(旧アドレス)は破棄
	roms.Write2(0x4e75);		//	rts

	//
	// SCSIIOCS エントリ
	//
	roms.SetOffset(0x300);		//_SCSIIOCS:
	roms.Write2(0x2039);		//	move.l	(ROMIO_SCSI),d0
	roms.Write4(ROMIO_SCSI);
	roms.Write2(0x4e75);		//	rts

	return true;
}

uint64
ROM30EmuDevice::ReadROMIO(busaddr addr)
{
	// IPLROM2 領域からのロングワードアクセスでだけ謎の I/O 空間が見える。
	if ((mpu->GetPPC() & 0xfffe0000) == baseaddr && addr.GetSize() == 4) {
		switch (addr.Addr()) {
		 case ROMIO_SCSI:
			return SCSIIOCS();

		 case ROMIO_BOOT:
			return SCSIBoot();

		 default:
			break;
		}
	}

	return (uint64)-1;
}

// SRAM で指定された SCSI ID のデバイスからブートブロックを読み込む。
// 成功すればジャンプ先アドレスを、失敗すれば 0 を返す。
uint32
ROM30EmuDevice::SCSIBoot() const
{
	auto mainram = GetMainRAMDevice();
	auto sram = GetSRAMDevice();

	auto mpu680x0 = GetMPU680x0Device(mpu);
	m68kreg& reg = mpu680x0->reg;

	// SRAM の ROM 起動アドレスから SCSI ID を取得。
	uint32 romaddr = sram->GetROMAddr();
	if ((romaddr & 0xffff00) != 0xfc0000) {
		return 0;
	}
	uint id = (romaddr & 0x1f) >> 2;

	// ターゲットディスク
	SCSITarget *target = spc->GetDomain()->GetTarget(id);
	if (target == NULL) {
		putlog(1, "%s: SCSI ID %u not found", __func__, id);
		return 0;
	}
	SCSIDisk *disk = dynamic_cast<SCSIDisk*>(target);

	// Human68k は 1024 byte/sector を「セクタ」と呼んでいるが、色々紛らわしい
	// ことになるので、ここではメディアのブロックサイズのほうを「セクタ」、
	// Human68k の 1024 byte/sector のほうを「ブロック」と勝手に呼び分ける。
	//
	// セクタサイズが 512 byte/sector なら1ブロックを 1024 byte、
	// セクタサイズが 2048 byte/sector なら1ブロックを 2048 byte とするようだ。

	// セクタサイズを取得 (ただしこれが SCSI 側用語でブロックサイズ orz)
	uint32 sectsize = disk->GetBlocksize();
	uint32 blocksize;
	if (sectsize == 512) {
		blocksize = 1024;
	} else if (sectsize == 2048) {
		blocksize = sectsize;
	} else {
		putlog(1, "%s: SCSI ID %u: sectsize %u not supported", __func__,
			id, sectsize);
		return 0;
	}
	putlog(2, "%s: SCSI ID %u: sectsize=%u, blocksize=%u", __func__, id,
		sectsize, blocksize);

	// +0 ブロック目先頭8バイトのマジックを確認
	std::vector<uint8> block(blocksize);
	if (disk->Peek(&block[0], 0, 8) == false) {
		putlog(1, "%s: SCSI ID %u: Reading magic failed", __func__, id);
		return 0;
	}
	if (memcmp(&block[0], "X68SCSI1", 8) != 0) {
		putlog(1, "%s: SCSI ID %u: invalid magic", __func__, id);
		return 0;
	}

	// +1 ブロック目から1ブロックを読んで...
	if (disk->Peek(&block[0], blocksize, blocksize) == false) {
		putlog(1, "%s: SCSI ID %u: Reading boot block failed", __func__, id);
		return 0;
	}
	// 先頭バイトが 0x60 (BRA.[BW] 命令の1バイト目) であること
	if (block[0] != 0x60) {
		putlog(1, "%s: SCSI ID %u: no branch instruction", __func__, id);
		return 0;
	}
	// メモリの 0x2000 番地に置く
	mainram->WriteMem(0x2000, &block[0], blocksize);

	// D4 にはこの時の SCSI ID が置いてある(のが見えている)
	reg.D[4] = id;

	// XXX D2 は最後に _S_READ を発行した LBA を指しているようだ。
	// なので +1 ブロックの LBA になる。
	reg.D[2] = blocksize / sectsize;

	// 成功したのでジャンプ先アドレスを返す
	putlog(1, "%s succeeded", __func__);
	return 0x2000;
}

// SCSIIOCS のなんちゃってエミュレーション
uint32
ROM30EmuDevice::SCSIIOCS()
{
	auto mpu680x0 = GetMPU680x0Device(mpu);
	m68kreg& reg = mpu680x0->reg;

	uint32 scsicall = reg.D[1];
	switch (scsicall) {
	 case 0x20:	// _S_INQUIRY
		return SCSI_S_INQUIRY(scsicall);

	 case 0x21:	// _S_READ
	 case 0x26:	// _S_READEXT
		// XXX 今はどちらも Read(10) で処理している
		return SCSI_S_READEXT(scsicall);

	 case 0x24:	// _S_TESTUNIT
		return SCSI_S_TESTUNIT(scsicall);

	 case 0x25:	// _S_READCAP
		return SCSI_S_READCAP(scsicall);

	 default:
		putlog(0, "SCSIIOCS $%02x (NOT IMPLEMENTED)", scsicall);
		break;
	}

	return 0xffffffff;
}

#define PRE	\
	auto mpu680x0 = GetMPU680x0Device(mpu);	\
	m68kreg& reg = mpu680x0->reg;	\
	uint32 d4 = reg.D[4];	\
	uint32 id = d4 & 0x0000ffff;	\
	uint32 lun = (d4 >> 16) & 0x0000ffff;	\
	std::string scsiname = GetSCSICallName(scsicall);	\
	/* ターゲットディスク */	\
	SCSITarget *target = spc->GetDomain()->GetTarget(id);	\
	if (target == NULL) {	\
		putlog(1, "%s ID%u not found", scsiname.c_str(), id);	\
		return -1;	\
	}	\
	SCSIDisk *disk __unused = dynamic_cast<SCSIDisk*>(target);	\
	/* ログ表示用 */	\
	std::string loghdr;	\
	if (loglevel >= 1) {	\
		loghdr = string_format("%s ID%u", scsiname.c_str(), id);	\
		if (lun != 0) {	\
			loghdr + string_format(":LUN%u", lun);	\
		}	\
	}	\
	/*end*/

uint32
ROM30EmuDevice::SCSI_S_INQUIRY(uint32 scsicall)
{
	PRE;

	uint32 a1 = reg.A[1];
	uint32 reqlen = reg.D[3];
	putlog(1, "%s a1=$%06x", loghdr.c_str(), a1);
	std::vector<uint8> cmdseq(6);
	cmdseq[0] = SCSI::Command::Inquiry;
	cmdseq[1] = lun << 5;
	cmdseq[4] = reqlen;

	SCSICmd *cmd = disk->SelectCommand(cmdseq);
	auto phase = cmd->ExecCommand(cmdseq);
	if (phase != SCSI::XferPhase::DataIn) {
		putlog(0, "%s moved to %s (NOT IMPLEMENTED)",
			loghdr.c_str(), SCSI::GetPhaseName(phase));
		return -1;
	}

	const std::vector<uint8>& res = cmd->buf;
	// アロケーションサイズ(reqlen)と結果(cmd->buf)の小さい方まで転送
	int len = std::min((int)reqlen, (int)res.size());
	for (int i = 0; i < len; i++) {
		busdata bd = mainbus->HVWrite1(a1++, res[i]);
		if (bd.IsBusErr()) {
			return -1;
		}
	}
	return 0;
}

uint32
ROM30EmuDevice::SCSI_S_READEXT(uint32 scsicall)
{
	PRE;

	uint32 a1 = reg.A[1];
	uint32 lba = reg.D[2];
	uint32 blkcount = reg.D[3];
	uint32 blkshift = reg.D[5];

	uint32 blkbytes = 0x100 << blkshift;
	uint32 bytes = blkcount * blkbytes;

	putlog(1, "%s a1=$%06x lba=$%08x bytes=%u d5=%u",
		loghdr.c_str(), a1, lba, bytes, blkshift);
	std::vector<uint8> cmdseq(10);
	cmdseq[0] = SCSI::Command::Read10;
	cmdseq[1] = lun << 5;
	cmdseq[2] = lba >> 24;
	cmdseq[3] = lba >> 16;
	cmdseq[4] = lba >> 8;
	cmdseq[5] = lba;
	cmdseq[7] = blkcount >> 8;
	cmdseq[8] = blkcount;

	SCSICmd *cmd = disk->SelectCommand(cmdseq);
	auto phase = cmd->ExecCommand(cmdseq);
	if (phase != SCSI::XferPhase::DataIn) {
		// XXX 本当はステータスコードかメッセージコードを返す?
		putlog(0, "%s moved to %s (NOT IMPLEMENTED)",
			loghdr.c_str(), SCSI::GetPhaseName(phase));
		return -1;
	}

	const std::vector<uint8>& res = cmd->buf;
	if (res.size() != bytes) {
		putlog(1, "%s res.size=%u mismatch with bytes=%u",
			loghdr.c_str(), (uint)res.size(), bytes);
		// SCSIIOCS ブロックサイズとディスクのセクタサイズが不整合の
		// 場合、転送は DMAC か SPC のいずれかが打ち切る。
		// XXX そのときも何らかのエラーが発生しているはずだが?
		if (res.size() < bytes) {
			bytes = res.size();
		}
	}

	for (int i = 0; i < bytes; i++) {
		busdata bd = mainbus->HVWrite1(a1++, res[i]);
		if (bd.IsBusErr()) {
			return -1;
		}
	}
	return 0;
}

uint32
ROM30EmuDevice::SCSI_S_TESTUNIT(uint32 scsicall)
{
	PRE;

	putlog(1, "%s", loghdr.c_str());
	return 0;
}

uint32
ROM30EmuDevice::SCSI_S_READCAP(uint32 scsicall)
{
	PRE;

	uint32 a1 = reg.A[1];
	putlog(1, "%s a1=$%06x", loghdr.c_str(), a1);
	std::vector<uint8> cmdseq(10);
	cmdseq[0] = SCSI::Command::ReadCapacity;
	cmdseq[1] = lun << 5;

	SCSICmd *cmd = disk->SelectCommand(cmdseq);
	auto phase = cmd->ExecCommand(cmdseq);
	if (phase != SCSI::XferPhase::DataIn) {
		putlog(0, "%s moved to %s (NOT IMPLEMENTED)",
			loghdr.c_str(), SCSI::GetPhaseName(phase));
		return -1;
	}

	const std::vector<uint8>& res = cmd->buf;
	assert(res.size() == 8);
	for (auto data : res) {
		busdata bd = mainbus->HVWrite1(a1++, data);
		if (bd.IsBusErr()) {
			return -1;
		}
	}
	return 0;
}

// SCSIIOCS コール名を返す
/*static*/ std::string
ROM30EmuDevice::GetSCSICallName(uint number)
{
	if (number < countof(x68k_scsicall)) {
		const char *name = x68k_scsicall[number];
		if (name) {
			return std::string(name);
		}
	}

	return string_format("undefined SCSIIOCS $%02x", number);
}

/*static*/ const char * const
ROM30EmuDevice::x68k_scsicall[0x40] = {
	"_S_RESET",		// $00
	"_S_SELECT",	// $01
	"_S_SELECTA",	// $02
	"_S_CMDOUT",	// $03
	"_S_DATAIN",	// $04
	"_S_DATAOUT",	// $05
	"_S_STSIN",		// $06
	"_S_MSGIN",		// $07
	"_S_MSGOUT",	// $08
	"_S_PHASE",		// $09
	"_S_LEVEL",		// $0a
	"_S_DATAINI",	// $0b
	"_S_DATAOUTI",	// $0c
	"_S_MSGOUTEXT",	// $0d
	NULL,			// $0e
	NULL,			// $0f

	NULL,			// $10
	NULL,			// $11
	NULL,			// $12
	NULL,			// $13
	NULL,			// $14
	NULL,			// $15
	NULL,			// $16
	NULL,			// $17
	NULL,			// $18
	NULL,			// $19
	NULL,			// $1a
	NULL,			// $1b
	NULL,			// $1c
	NULL,			// $1d
	NULL,			// $1e
	NULL,			// $1f

	"_S_INQUIRY",	// $20
	"_S_READ",		// $21
	"_S_WRITE",		// $22
	"_S_FORMAT",	// $23
	"_S_TESTUNIT",	// $24
	"_S_READCAP",	// $25
	"_S_READEXT",	// $26
	"_S_WRITEEXT",	// $27
	"_S_VERIFYEXT",	// $28
	"_S_MODESENSE",	// $29
	"_S_MODESELECT",// $2a
	"_S_REZEROUNIT",// $2b
	"_S_REQUEST",	// $2c
	"_S_SEEK",		// $2d
	"_S_READI",		// $2e
	"_S_STARTSTOP",	// $2f

	"_S_EJECT6MO1",	// $30
	"_S_REASSIGN",	// $31
	"_S_PAMEDIUM",	// $33
	NULL,			// $34
	NULL,			// $35
	"_b_dskini",	// $36
	"_b_format",	// $37
	"_b_badfmt",	// $38
	"_b_assign",	// $39
};

unix.superglobalmegacorp.com

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