File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / spc.cpp
Revision 1.1.1.21 (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) 2020 nono project
// Licensed under nono-license.txt
//

//
// SPC (MB89352)
//

#include "spc.h"
#include "config.h"
#include "event.h"
#include "mainapp.h"
#include "monitor.h"
#include "pedec.h"
#include "scheduler.h"
#include "scsidev.h"
#include "scsidomain.h"

// time 時間後に呼び出すコールバックをセットする。
// func の書式が面倒なのを省略して書きたいため。
#define CallAfter(func_, name_, time_)	do { \
	phase_event->SetCallback(&SPCDevice::func_); \
	phase_event->time = time_; \
	phase_event->SetName(name_); \
	scheduler->RestartEvent(phase_event); \
} while (0)

// コンストラクタ
SPCDevice::SPCDevice()
	: inherited(OBJ_SPC0)
{
	// 初期値は不定だが表示上困るのでとりあえず。
	myid = 7;

	monitor = gMonitorManager->Regist(ID_MONITOR_SPC, this);
	monitor->SetCallback(&SPCDevice::MonitorScreen);
	// サイズは Init で決まる
}

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

// ログレベル設定
void
SPCDevice::SetLogLevel(int loglevel_)
{
	inherited::SetLogLevel(loglevel_);

	if (host) {
		host->SetLogLevel(loglevel_);
	}
}

bool
SPCDevice::Create()
{
	// ドメイン。
	try {
		domain.reset(new SCSIDomain(this, "SPC0", "spc0"));
	} catch (...) { }
	if ((bool)domain == false) {
		warnx("Failed to initialize SCSIDomain at %s", __method__);
		return false;
	}

	// バス
	try {
		bus.reset(new SCSIBus(domain.get()));
	} catch (...) { }
	if ((bool)bus == false) {
		warnx("Failed to initialize SCSIBus at %s", __method__);
		return false;
	}

	return true;
}

// 初期化
bool
SPCDevice::Init()
{
	// 実行中に別インスタンスを頻繁に参照しないようにここで vmtype を覚えておく
	vmtype = gMainApp.GetVMType();

	host = domain->GetInitiator();
	host->SetBusCallback(
		ToBusFreeCallback(&SPCDevice::BusFreeCallback),
		ToSCSIHostCallback(&SPCDevice::SelectionAckCallback),
		ToSCSIHostCallback(&SPCDevice::TransferReqCallback));

	// ターゲットデバイスをバスに接続。
	domain->AttachTargets(bus.get());
	bus->Refresh();

	// 入力クロックは機種によって異なる。
	// X68k は 5MHz が入っている。
	// LUNA-I はおそらく 6.144MHz だと思われる。LUNA-88K は未確認。
	if (vmtype == VMType::X68030) {
		t_CLF = 200_nsec;
	} else {
		t_CLF = 163_nsec;
	}

	// アクセスウェイト
	if (vmtype == VMType::X68030) {
		// InsideOut p.135
		read_wait  = busdata::Wait(18 * 40_nsec);
		write_wait = busdata::Wait(19 * 40_nsec);
	} else {
		// 他機種は未調査だけど、判明するまでの間とりあえず
		// ノーウェイトでない適当な値を入れておく。
		read_wait  = busdata::Wait(18 * 40_nsec);
		write_wait = busdata::Wait(19 * 40_nsec);
	}

	// 割り込み信号線の接続先
	if (vmtype == VMType::X68030) {
		// X68030 は PEDEC (I/O コントローラ) にカスケード
		interrupt = GetPEDECDevice();
	} else {
		interrupt = GetInterruptDevice();
	}

	auto evman = GetEventManager();
	phase_event = evman->Regist(this, NULL, "SPC Phase");
	timer_event = evman->Regist(this, NULL, "SPC Timer");

	// モニタに表示するアドレス
	if (vmtype == VMType::X68030) {
		baseaddr = 0xe96021;
		regoffset = 2;
	} else {
		baseaddr = 0xe1000000;
		regoffset = 4;
	}
	// 「イニシエータ以外のターゲット数」はここでは確定している。
	monitor->SetSize(80, 15 + domain->GetTargetCount() + 1);

	return true;
}

// デバイスリセット
void
SPCDevice::ResetHard(bool poweron)
{
	putlog(1, "Reset");

	// 初期値 (p.61)
	spc.sctl = SPC::SCTL_RESET;
	spc.ints = 0x00;
	spc.ssts &= 0x0f;
	spc.serr = 0x00;
	// 変更しないレジスタは次の通り (p.60)
	// BDID, TMOD, TC[HML]?, SCMD, PCTL, TEMP(out)

	// XXX マニュアルには書いていないけど、
	// SDGC bit5 の転送ごとに割り込みを発生させるビットが
	// OFFになることを NetBSD が期待しているし、
	// このビットは落ちるのではないだろうか
	spc.sdgc = 0x00;

	// キューはリセットされる。
	// mb89351-scsi.pdf (日本語)
	spc.dreg.Clear();

	// 内部状態
	MakeIntsMask();
	ChangeInterrupt();
	prev_ints = (uint32)-1;
	prev_ssts = (uint32)-1;
	set_atn_in_selection = false;
	transfer_command_running = false;
	scheduler->StopEvent(phase_event);
	scheduler->StopEvent(timer_event);

	last_busfree_time = 0;
}

busdata
SPCDevice::ReadPort(uint32 offset)
{
	busdata data;

	switch (offset) {
	 case SPC::BDID:
		data = 1U << myid;
		putlog(3, "BDID -> $%02x", data.Data());
		break;

	 case SPC::SCTL:
		data = spc.sctl;
		putlog(3, "SCTL -> $%02x", data.Data());
		break;

	 case SPC::SCMD:
		data = spc.scmd;
		putlog(3, "SCMD -> $%02x", data.Data());
		break;

	 case SPC::TMOD:
		// MB89352 には存在しないレジスタ。
		// 下位2ビットは %0 らしいので意味はないだろうけど真似ておく。
		data = 0xfc;
		break;

	 case SPC::INTS:
		data = GetINTS();
		if (loglevel >= 3) {
			uint32 new_ints = data.Data();
			if (timer_event->IsRunning()) {
				// イベント中は値が変わった時だけ表示
				if (prev_ints != new_ints) {
					putlogn("INTS -> $%02x", new_ints);
					prev_ints = new_ints;
				}
			} else {
				putlogn("INTS -> $%02x", new_ints);
				prev_ints = -1U;
			}
		}
		break;

	 case SPC::PSNS:
		data = GetPSNS();
		putlog(3, "PSNS -> $%02x", data.Data());
		break;

	 case SPC::SSTS:
		data = GetSSTS();
		if (loglevel >= 3) {
			uint32 new_ssts = data.Data();
			if (timer_event->IsRunning()) {
				// イベント中は値が変わった時だけ表示
				if (prev_ssts != new_ssts) {
					putlogn("SSTS -> $%02x", new_ssts);
					prev_ssts = new_ssts;
				}
			} else {
				putlogn("SSTS -> $%02x", new_ssts);
				prev_ssts = -1U;
			}
		}
		break;

	 case SPC::SERR:
		data = GetSERR();
		putlog(3, "SERR -> $%02x", data.Data());
		break;

	 case SPC::PCTL:
		data = GetPCTL();
		putlog(3, "PCTL -> $%02x", data.Data());
		break;

	 case SPC::MBC:
		putlog(0, "Read MBC (NOT IMPLEMENTED)");
		data = 0x0f;
		break;

	 case SPC::DREG:
		data = ReadDREG();
		break;

	 case SPC::TEMP:
		data = spc.temp_in;
		putlog(3, "TEMP -> $%02x", data.Data());
		break;

	 case SPC::TCH:
		data = spc.tc >> 16;
		putlog(3, "TCH  -> $%02x", data.Data());
		break;

	 case SPC::TCM:
		data = (spc.tc >> 8) & 0xff;
		putlog(3, "TCM  -> $%02x", data.Data());
		break;

	 case SPC::TCL:
		data = spc.tc & 0xff;
		putlog(3, "TCL  -> $%02x", data.Data());
		break;

	 case SPC::EXBF:
		// MB89352 には存在しないレジスタ
		data = 0xff;
		break;

	 default:
		VMPANIC("corrupted offset=%u", offset);
	}

	data |= read_wait;
	data |= BusData::Size1;
	return data;
}

busdata
SPCDevice::WritePort(uint32 offset, uint32 data)
{
	busdata r;

	switch (offset) {
	 case SPC::BDID:
	 {
		uint cur_myid = host->GetMyID();
		putlog(3, "BDID <- $%02x", data);
		if (data == cur_myid) {
			// 同じなら何もしない
			break;
		}
		if ((int)cur_myid < 0) {
			// 初回なのでバスに参加。myid はこちらでもコピーを覚えておく。
			myid = data;
			if (domain->AttachInitiator(bus.get(), myid) == false) {
				putlog(0, "BDID %u conflicts with existing devices!", myid);
			}
			bus->Refresh();
		} else {
			// そうでなければ移動?
			putlog(0, "BDID change %u to %u (NOT IMPLEMENTED)", cur_myid, data);
		}
		break;
	 }

	 case SPC::SCTL:
		WriteSCTL(data);
		break;

	 case SPC::SCMD:
		WriteSCMD(data);
		break;

	 case SPC::TMOD:
		// MB89352 には存在しないレジスタ
		break;

	 case SPC::INTS:
		WriteINTS(data);
		break;

	 case SPC::SDGC:
		spc.sdgc = data;
		putlog(3, "SDGC <- $%02x", spc.sdgc);
		break;

	 case SPC::SSTS:
		// XXX 書き込みはどうなる?
		break;

	 case SPC::SERR:
		// XXX 書き込みはどうなる?
		break;

	 case SPC::PCTL:
		WritePCTL(data);
		break;

	 case SPC::MBC:
		// XXX 書き込みはどうなる?
		break;

	 case SPC::DREG:
		putlog(3, "DREG <- $%02x", data);
		r = WriteDREG(data);
		break;

	 case SPC::TEMP:
		spc.temp_out = data;
		putlog(3, "TEMP <- $%02x", spc.temp_out);

		// Inside (p479) によると、SPC はセレクションタイムアウト時 SEL を
		// 下げないので、代わりに TEMP に $00 を書き込むことで SEL を下げる、
		// そうなのだが、データシート PDF には記述が見当たらないし、
		// luna68k の /boot はここで SEL が下げてしまうと期待した動作に
		// ならないようだ。
		// SPC はセレクションタイムアウトしたからと言って、データバスに出した
		// ID を自動的に取り下げたりはしないので、TEMP に $00 を書き込むことで
		// データバスをクリアする、だったんでは。
		if (0 && GetPhase() == SCSI::Phase::Selection) {
			NegateSEL();
		}
		// Out 方向ならバスに出す
		if (GetIO() == false) {
			SetData(spc.temp_out);
		}
		break;

	 case SPC::TCH:
		spc.tc = (spc.tc & 0x00ffff) | (data << 16);
		putlog(3, "TCH  <- $%02x (TC=$%06x)", data, spc.tc);
		break;
	 case SPC::TCM:
		spc.tc = (spc.tc & 0xff00ff) | (data << 8);
		putlog(3, "TCM  <- $%02x (TC=$%06x)", data, spc.tc);
		break;
	 case SPC::TCL:
		spc.tc = (spc.tc & 0xffff00) | data;
		putlog(3, "TCL  <- $%02x (TC=$%06x)", data, spc.tc);
		break;

	 case SPC::EXBF:
		// MB89352 には存在しないレジスタ
		break;

	 default:
		VMPANIC("corrupted offset=%u", offset);
	}

	r |= write_wait;
	r |= BusData::Size1;
	return r;
}

busdata
SPCDevice::PeekPort(uint32 offset)
{
	switch (offset) {
	 case SPC::BDID:
		return 1U << myid;

	 case SPC::SCTL:
		return spc.sctl;

	 case SPC::SCMD:
		return spc.scmd;

	 case SPC::TMOD:
		// MB89352 には存在しないレジスタ。
		// 下位2ビットは %0 らしいので意味はないだろうけど真似ておく。
		return 0xfc;

	 case SPC::INTS:
		return GetINTS();

	 case SPC::PSNS:
		return GetPSNS();

	 case SPC::SSTS:
		return GetSSTS();

	 case SPC::SERR:
		return GetSERR();

	 case SPC::PCTL:
		return GetPCTL();

	 case SPC::MBC:
		return 0x0f;

	 case SPC::DREG:
		// X68k は SPC の DREQ 信号から DTACK 信号を作っているため、
		// DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが
		// すべてバスエラーになってしまう。
		if (vmtype == VMType::X68030) {
			if ((spc.scmd & SPC::SCMD_PROGRAM)) {
				return BusData::BusErr;
			}
		}
		return spc.dreg.Peek(0);

	 case SPC::TEMP:
		return spc.temp_in;

	 case SPC::TCH:
		return spc.tc >> 16;

	 case SPC::TCM:
		return (spc.tc >> 8) & 0xff;

	 case SPC::TCL:
		return spc.tc & 0xff;

	 case SPC::EXBF:
		// MB89352 には存在しないレジスタ
		return 0xff;

	 default:
		return 0xff;
	}
}

bool
SPCDevice::PokePort(uint32 offset, uint32 data)
{
	return false;
}

void
SPCDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	int x;
	int y;
	uint32 sctl;
	uint32 scmd;
	uint32 ints;
	uint32 psns;
	uint32 sdgc;
	uint32 ssts;
	uint32 serr;
	uint32 pctl;
	uint32 tc;

	screen.Clear();

	sctl = spc.sctl;
	scmd = spc.scmd;
	ints = GetINTS();
	psns = GetPSNS();
	sdgc = spc.sdgc;
	ssts = GetSSTS();
	serr = GetSERR();
	pctl = GetPCTL();
	tc   = spc.tc;

#define A(n)	(baseaddr + (n) * regoffset)

	y = 0;
	screen.Print(0, y++, "$%08x BDID: $%02x (ID=%u)", A(0), (1U << myid), myid);
	screen.Print(0, y++, "$%08x SCTL: $%02x", A(1), sctl);
	screen.Print(0, y++, "$%08x SCMD: $%02x", A(2), scmd);
	screen.Print(0, y++, "$%08x INTS: $%02x", A(4), ints);
	screen.Print(0, y++, "$%08x PSNS: $%02x", A(5), psns);
	screen.Print(0, y++, "$%08x SDGC: $%02x", A(5), sdgc);
	screen.Print(0, y++, "$%08x SSTS: $%02x", A(6), ssts);
	screen.Print(0, y++, "$%08x SERR: $%02x", A(7), serr);
	screen.Print(0, y++, "$%08x PCTL: $%02x", A(8), pctl);
	screen.Print(0, y++, "$%08x MBC : $XX", A(9));
	screen.Print(0, y,   "$%08x DREG: ", A(10));
	{
		uint i;
		for (i = 0; i < spc.dreg.Length(); i++) {
			screen.Print(16 + i * 4, y, "$%02x", spc.dreg.Peek(i));
		}
		for (; i < 8; i++) {
			screen.Puts(16 + i * 4, y, "---");
		}
	}
	y++;
	screen.Print(0, y++, "$%08x TEMP: $%02x(IN) $%02x(OUT)", A(11),
		spc.temp_in, spc.temp_out);
	screen.Print(0, y++, "$%08x TC  : $%06x", A(12), tc);

	x = 20;
	y = 1;

	// SCTL
	static const char * const sctl_bits[] = {
		"RST", "CTRL", "DIAG", "ARB", "PARI", "SEL", "RESL", "INTR"
	};
	MonitorReg(screen, x, y, sctl, sctl_bits);
	y++;

	// SCMD
	static const char * const scmd_bits[] = {
		"", "", "", "RST", "INTC", "PROG", "----", "TERM"
	};
	MonitorReg(screen, x, y, scmd, scmd_bits);
	static const char * const scmd_cmd[] = {
		//12345678901234
		"(Bus Release)",
		"(Select)",
		"(Reset ATN)",
		"(Set ATN)",
		"(Transfer)",
		"(XferPause)",
		"(ResetACK/REQ)",
		"(Set ACK/REQ)",
	};
	screen.Puts(x, y, scmd_cmd[scmd >> 5]);
	y++;

	// INTS
	static const char * const ints_bits[] = {
		"Sel", "Resl", "Disc", "Cmpl", "SReq", "TOut", "HErr", "Rst"
	};
	MonitorReg(screen, x, y, ints, ints_bits);
	y++;

	// PSNS
	static const char * const psns_bits[] = {
		"REQ", "ACK", "ATN", "SEL", "BSY", "MSG", "C/D", "I/O"
	};
	MonitorReg(screen, x, y, psns, psns_bits);
	y++;

	// SDGC
	static const char * const sdgc_bits[] = {
		"-", "-", "XfEn", "-", "-", "-", "-", "-"
	};
	MonitorReg(screen, x, y, sdgc, sdgc_bits);
	y++;

	// SSTS
	static const char * const ssts_bits[] = {
		"", "", "SBSY", "XInP", "RSTI", "TCZ", "Full", "Empt"
	};
	MonitorReg(screen, x, y, ssts, ssts_bits);
	static const char * const ssts_conn[] = {
		//12345678
		"(NotConn)",
		"(As Tgt)",
		"(As Init)",
		"(undef?)",
	};
	screen.Puts(x, y, ssts_conn[ssts >> 6]);
	y++;

	// SERR
	static const char * const serr_bits[] = {
		"-", "-", "XOut", "-", "PErr", "-", "Shrt", "-"
	};
	MonitorReg(screen, x, y, serr, serr_bits);
	y++;

	// PCTL
	static const char * const pctl_bits[] = {
		"BIE", "-", "-", "-", "-", "MSG", "C/D", "I/O"
	};
	MonitorReg(screen, x, y, pctl, pctl_bits);
	y++;

	// バス状態
	x = 62;
	y = 0;
	SCSI::Phase ph = GetPhase();
	uint8 rst = GetRST();
	uint8 bsy = GetBSY();
	uint8 sel = GetSEL();
	uint8 msg = GetMSG();
	uint8 cd  = GetCD();
	uint8 io  = GetIO();
	uint8 atn = GetATN();
	uint8 req = GetREQ();
	uint8 ack = GetACK();
	uint8 data = GetData();

	screen.Print(x, y++, "Phase: %s", SCSI::GetPhaseName(ph));
	screen.Puts(x, y++, TA::OnOff(rst), "RST");
	screen.Puts(x, y++, TA::OnOff(bsy), "BSY");
	screen.Puts(x, y++, TA::OnOff(sel), "SEL");
	screen.Puts(x, y++, TA::OnOff(msg), "MSG");
	screen.Puts(x, y++, TA::OnOff(cd),  "CD");
	screen.Puts(x, y++, TA::OnOff(io),  "IO");
	screen.Puts(x, y++, TA::OnOff(atn), "ATN");
	screen.Puts(x, y++, TA::OnOff(req), "REQ");
	screen.Puts(x, y++, TA::OnOff(ack), "ACK");
	screen.Print(x, y++, "DATA:$%02x", data);

	y = 2;
	screen.Puts(x + 4, y++, "(________)");
	screen.Puts(x + 4, y++, "(________)");
	// Shift-JIS は手動で置く必要がある。
	screen.Puts(x + 4, y++, "\x84\xa2");	// "┐"
	screen.Puts(x + 4, y++, "\x84\xa0");	// "│"
	screen.Puts(x + 4, y++, "\x84\xa3");	// "┘"

	// BSY, SEL の内訳。"(76543210)" の順。
	y = 2;
	for (int i = 0; i < 8; i++) {
		if ((bsy & (1U << i))) {
			screen.Print(x + 4 + 8 - i, y, "%u", i);
		} else {
			screen.Print(x + 4 + 8 - i, y, "_");
		}
	}
	y++;
	for (int i = 0; i < 8; i++) {
		if ((sel & (1U << i))) {
			screen.Print(x + 4 + 8 - i, y, "%u", i);
		} else {
			screen.Print(x + 4 + 8 - i, y, "_");
		}
	}

	// 転送フェーズ中なら現在の XferPhase
	y += 2;
	TA attrxfer = (ph == SCSI::Phase::Transfer) ? TA::Normal : TA::Disable;
	screen.Puts(x + 6, y++, attrxfer, "XferPhase:");
	screen.Puts(x + 6, y,   attrxfer, SCSI::GetXferPhaseName(GetXfer()));

	// デバイス一覧
	// 0         1
	// 01234567890123456
	// ID Device     Command
	// 0: HD(1024MB) $06 Read
	// 7: Initiator
	x = 0;
	y = 14;
	screen.Print(x, y++, "ID Device     Command");
	for (const auto d : domain->GetConnectedDevices()) {
		std::string desc = SCSI::GetDevTypeName(d->GetDevType());

		const SCSIDisk *disk = dynamic_cast<const SCSIDisk*>(d);
		if (disk && disk->IsMediumLoaded()) {
			desc += string_format("(%uMB)",
				(uint)(disk->GetSize() / 1024 / 1024));
		}

		// 本体 ID は BDID が設定される前でもエミュレータ的に知ってはいるが、
		// BDID への書き込みがされなかったりおかしかったりしたら (上の BDID
		// 欄でも分かるけど) ここも反映されるようにしておく。
		char idc;
		if ((int)d->GetMyID() >= 0) {
			idc = '0' + d->GetMyID();
		} else {
			idc = '-';
		}
		screen.Print(x, y, "%c: %s", idc, desc.c_str());
		y++;
	}

	// 実行中のコマンド
	x = 14;
	y = 15;
	const SCSIDevice *d = bus->GetSelectedTarget();
	const SCSITarget *t = dynamic_cast<const SCSITarget *>(d);
	if (t) {
		std::vector<uint8> cmds = t->GetCmdSeq();
		if (cmds.size() > 0) {
			// この ID のデバイス行に表示したい
			// XXX もうちょっとマシにしたい
			const auto connected_devices = domain->GetConnectedDevices();
			int i = 0;
			for (; i < connected_devices.size(); i++) {
				if (t == connected_devices[i]) {
					break;
				}
			}
			const char *cmdname = SCSI::GetCommandName(cmds[0]);
			screen.Print(x, y + i, "$%02x %s", cmds[0], cmdname ?: "");
		}
	}
}

// レジスタをビットごとに表示する。MonitorScreen の下請け。
void
SPCDevice::MonitorReg(TextScreen& screen,
	int x, int y, uint32 reg, const char * const *names)
{
	for (int i = 0; i < 8; i++) {
		if (names[i][0] == '-') {
			screen.Puts(x + i * 5, y, TA::Disable, "----");
		} else {
			bool b = reg & (1U << (7 - i));
			screen.Puts(x + i * 5, y, TA::OnOff(b), names[i]);
		}
	}
}

// SCTL レジスタへの書き込み
void
SPCDevice::WriteSCTL(uint32 data)
{
	// リセットの立ち上がり検出が必要
	uint32 up = ~spc.sctl & data;

	spc.sctl = data;
	putlog(3, "SCTL <- $%02x", spc.sctl);
	if ((up & SPC::SCTL_RESET)) {
		// リセット
		ResetHard(false);
	} else {
		// 通常動作
	}
	if ((spc.sctl & SPC::SCTL_CTL_RESET)) {
		putlog(2, "Control Reset");
	}
	if ((spc.sctl & SPC::SCTL_DIAG_MODE)) {
		putlog(0, "SCTL Diag Mode (NOT IMPLEMENTED)");
	}

	// 割り込みマスクを更新して信号線の状態を変える
	MakeIntsMask();
	ChangeInterrupt();
}

// SCTL_INTR_EN の状態から内部変数 ints_mask を作成する。
void
SPCDevice::MakeIntsMask()
{
	if ((spc.sctl & SPC::SCTL_INTR_EN)) {
		spc.ints_mask = SPC::INTS_ENABLE_MASK;
	} else {
		spc.ints_mask = SPC::INTS_DISABLE_MASK;
	}
}

// SCMD レジスタへの書き込み
void
SPCDevice::WriteSCMD(uint32 data)
{
	uint32 chg = spc.scmd ^ data;	// 変更点
	spc.scmd = data;
	putlog(3, "SCMD <- $%02x", spc.scmd);

	// Program, Terminate Mode の未実装判定は転送開始時のみ表示する。

	// RST Out 操作は SCTL Reset がオフの時だけ有効
	// と書いてあるけど、SCTL Reset がオンならほぼ全機能無効なのでは?
	if ((spc.sctl & SPC::SCTL_RESET) == 0 && (chg & SPC::SCMD_RST_OUT)) {
		if ((spc.scmd & SPC::SCMD_RST_OUT)) {
			// 0 -> 1
			// すでに RST が立ってたらどうするか
			if (GetRST() == false) {
				putlog(1, "SCMD RST Out");
				BusRelease();
				AssertRST();
				SetINTS(SPC::INTS_RESET_COND);
			}
		} else {
			// 1 -> 0
			// 特にすることはない?
			NegateRST();
		}
	}

	switch (spc.scmd >> 5) {
	 case 0:
		putlog(2, "SCMD Bus Release command");
		if ((spc.scmd & SPC::SCMD_INTERCEPT)) {
			// BusRelease コマンドの発行と共に Intercept ビットが立っていたら
			// Service Required 割り込みをリセットする?
			putlog(0, "SCMD Intercept with BusRelease (NOT IMPLEMENTED)");
		}
		BusFree();
		break;
	 case 1:
		SelectCommand();
		break;
	 case 2:
		putlog(1, "SCMD Reset ATN command (NOT IMPLEMENTED)");
		break;
	 case 3:
		SetATNCommand();
		break;
	 case 4:
		TransferCommand();
		break;
	 case 5:
		putlog(2, "SCMD Transfer Pause command (NOT IMPLEMENTED)");
		break;
	 case 6:
		putlog(2, "SCMD Reset ACK/REQ command");
		// イニシエータなら ACK を、ターゲットなら REQ をリセットする。
		// ターゲットモードはサポートしないので、操作するのは ACK のみ。
		NegateACK();
		break;
	 case 7:
		putlog(2, "SCMD Set ACK/REQ command");
		// イニシエータなら ACK を、ターゲットなら REQ をセットする。
		// ターゲットモードはサポートしないので、操作するのは ACK のみ。
		if ((spc.scmd & SPC::SCMD_INTERCEPT)) {
			// Intercept なら DREG を維持したまま (たぶん TEMP で) バスを
			// 直接アクセスするようだが、この DREG 維持あたりは未対応。
			// これが影響するのは DREG にデータが滞留してる時だけのはず
			// なので、その時だけログを出す。(X68030 IPLROM、NetBSD/x68k)
			if (spc.dreg.Length() != 0) {
				putlog(0, "SCMD Intercept with SetACK (NOT IMPLEMENTED)");
			}
		}
		AssertACK();
		break;
	}
}

// INTS レジスタへの書き込み
void
SPCDevice::WriteINTS(uint32 data)
{
	uint32 ack;

	// クリアすることになるビットを覚えておく
	ack = spc.ints & data;

	// '1' が書き込まれたビットをクリア
	spc.ints &= ~data;
	putlog(3, "INTS <- $%02x (INTS = $%02x)", data, GetINTS());

	// DISCONNECTED ビットが立っているときは SPC は SCSI から
	// セレクション、リセレクション要求が来ても応答しない。
	// DISCONNECTED ビットが下ろされると、応答するようになる。

	// TIMEOUT ビットをクリアした時が
	if ((ack & SPC::INTS_TIMEOUT)) {
		// セレクションフェーズなら
		if (GetPhase() == SCSI::Phase::Selection) {
			// TC がゼロならバスフリー
			// そうでなければリスタート
			if (spc.tc == 0) {
				BusFree();
			} else {
				timer_event->SetCallback(&SPCDevice::SelectionTimeout);
				timer_event->time = spc.tc * t_CLF * 2;
				scheduler->RestartEvent(timer_event);
			}
		}
	}

	// 割り込み信号線の状態を変える
	ChangeInterrupt();
}

// INTS 設定。
// 引数 data は割り込み要因(ここは内部要因なので INTS_XFER_OUT も指定可能)。
// INTS レジスタへの書き込み(割り込み要因のクリア)は WriteINTS() のほう。
void
SPCDevice::SetINTS(uint32 data)
{
	// 指定された要因を立てる
	spc.ints |= data;

	// Disconnected 割り込みが起きたら ATN を下げる
	if ((spc.ints & SPC::INTS_DISCONNECTED) && GetATN()) {
		NegateATN();
	}

	ChangeInterrupt();
}

// INTS レジスタの値を取得
uint32
SPCDevice::GetINTS() const
{
	// ints の上位には別の割り込み状態が保持されている。
	// 下位8bit は INTS レジスタのまま。
	return spc.ints & 0xff;
}

// 割り込み信号線の状態を変える
void
SPCDevice::ChangeInterrupt()
{
	// 割り込み状態は ints に集約してあり、
	// ints_mask は現在の SCTL_INTR_EN 状態に応じたマスクが用意されているので
	// 単純に AND するだけでよい。
	if ((spc.ints & spc.ints_mask) != 0) {
		putlog(3, "Interrupt (ints=$%02x)", spc.ints);
		interrupt->AssertINT(this);
	} else {
		putlog(4, "Negate Interrupt");
		interrupt->NegateINT(this);
	}
}

// PSNS レジスタ値の取得。
// ReadPort() と PeekPort() から呼ばれる。
// SCSIBus 接続前にもモニタの更新などで呼ばれることに注意。
uint32
SPCDevice::GetPSNS() const
{
	uint32 data = 0;

	if (__predict_false(bus == NULL)) {
		return 0;
	}

	if (GetREQ()) {
		data |= SPC::PSNS_REQ;
	}
	if (GetACK()) {
		data |= SPC::PSNS_ACK;
	}
	if (GetATN()) {
		data |= SPC::PSNS_ATN;
	}
	if (GetSEL()) {
		data |= SPC::PSNS_SEL;
	}
	if (GetBSY()) {
		data |= SPC::PSNS_BSY;
	}
	data |= (uint32)GetXfer();

	return data;
}

// SSTS レジスタ値の取得。
// ReadPort() と PeekPort() から呼ばれる。
// SCSIBus 接続前にもモニタの更新などで呼ばれることに注意。
uint32
SPCDevice::GetSSTS() const
{
	uint32 data;

	data = spc.ssts & 0xf0;
	if (__predict_true(bus != NULL) && GetRST()) {	// RST 信号
		data |= SPC::SSTS_RST_IN;
	}
	if (spc.tc == 0) {	// TC がゼロか
		data |= SPC::SSTS_TC_ZERO;
	}
	if (spc.dreg.IsFull()) {
		data |= SPC::SSTS_DREG_FULL;
	}
	if (spc.dreg.Empty()) {
		data |= SPC::SSTS_DREG_EMPTY;
	}

	return data;
}

// SERR レジスタ値の取得。
uint32
SPCDevice::GetSERR() const
{
	// SERR レジスタのうち XFER_OUT ビットだけ ints で保持している。
	uint32 xfer_out = (spc.ints & SPC::INTS_XFER_OUT) >> 3;
	return spc.serr | xfer_out;
}

// PCTL レジスタへの書き込み。
void
SPCDevice::WritePCTL(uint32 data)
{
	data &= SPC::PCTL_MASK;
	putlog(3, "PCTL <- $%02x", data);

	spc.busfree_intr_enable = (data & SPC::PCTL_BFINT_EN);
	spc.pctl_out = data & (SPC::PCTL_MSG | SPC::PCTL_CD | SPC::PCTL_IO);
	// この時点では pctl_out はまだバスに出さない。
	// バスに出すのは転送開始時のはず。
}

// PCTL レジスタ値の取得。
uint32
SPCDevice::GetPCTL() const
{
	return spc.busfree_intr_enable | spc.pctl_out;
}

// DREG レジスタの読み込み
// (副作用があるので Peek 系からは呼ばないこと)
busdata
SPCDevice::ReadDREG()
{
	busdata data;

	// X68k は SPC の DREQ 信号から DTACK 信号を作っているため、
	// DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが
	// すべてバスエラーになってしまう。
	if (vmtype == VMType::X68030) {
		if ((spc.scmd & SPC::SCMD_PROGRAM)) {
			data.SetBusErr();
			return data;
		} else {
			// DMA 転送で DREQ が出てないとき DTACK を出さないのを
			// Retry を返すことで表現している。
			if (spc.dreg.Length() == 0) {
				putlog(3, "DREG empty during DMA transferring");
				data.SetRetry();
				return data;
			}
		}
	}

	// XFER_OUT は DREG アクセスでリセットされる
	spc.ints &= ~SPC::INTS_XFER_OUT;
	// TransferComplete() が割り込み状態を変化させる可能性があるので
	// ChangeInterrupt() を最後に呼び出す。

	// 受信データがあれば取り出す
	if (__predict_true(spc.dreg.Length() > 0)) {
		bool q_was_full = spc.dreg.IsFull();
		data = spc.dreg.Dequeue();
		if (loglevel >= 4) {
			putlogn("DREG -> $%02x (q=%u TC=0x%x)",
				data.Data(), spc.dreg.Length(), spc.tc);
		} else {
			putlog(3, "DREG -> $%02x", data.Data());
		}
		// この読み出しが最後だったらこれで転送完了。
		if (spc.tc == 0 && spc.dreg.Length() == 0) {
			TransferComplete();
		}
		// この読み出しによってバッファに空きが出来たら転送再開。
		if (transfer_command_running && GetREQ() && q_was_full) {
			// XXX 時間は適当
			CallAfter(HardwareTransfer, "SPC ReadDREG to Transfer", t_CLF);
		}
	} else {
		// 受信データがない場合
		if ((spc.scmd & SPC::SCMD_PROGRAM)) {
			// XXX プログラム転送中ならどうなる?
			data = 0xff;
			putlog(3, "DREG -> $%02x (empty)", data.Data());
		} else {
			// DMA 転送
			// DMA 側でリトライするのでここには来ないはず
			VMPANIC("DMA転送中の DREG empty");
		}
	}

	// 割り込み状態を確定
	ChangeInterrupt();
	return data;
}

// DREG レジスタへの書き込み
busdata
SPCDevice::WriteDREG(uint32 data)
{
	busdata r;

	// X68k は SPC の DREQ 信号から DTACK 信号を作っているため、
	// DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが
	// すべてバスエラーになってしまう。
	if (vmtype == VMType::X68030) {
		if ((spc.scmd & SPC::SCMD_PROGRAM)) {
			r.SetBusErr();
			return data;
		} else {
			// DMA 転送で DREQ が出てないとき DTACK を出さないのを
			// Retry を返すことで表現している。
			if (spc.dreg.IsFull()) {
				r.SetRetry();
				return r;
			}
		}
	}

	// XFER_OUT は DREG アクセスでリセットされる
	spc.ints &= ~SPC::INTS_XFER_OUT;
	ChangeInterrupt();

	// DREG キューに入れて..
	if (spc.dreg.Enqueue(data) == false) {
		// XXX キューが一杯だったらどうなるのかは書いてないので分からない。
		// a) キューを押し出して先頭を破棄する
		// b) キューの末尾を破棄して新着データで上書きする
		// c) キューの内容は変わらず新着データを破棄する
		// どれで実装しても「この時何かしら失われるデータがある」こと自体は
		// 再現出来ているはずなので、ここではひとまず a) の動作にしておく。
		putlog(0, "DREG Queue overflow");
		spc.dreg.EnqueueForce(data);
	}
	// ハードウェア転送中で REQ が来てれば送信。
	if (transfer_command_running && GetREQ()) {
		// XXX 時間は適当
		CallAfter(HardwareTransfer, "SPC WriteDREG to Transfer", t_CLF);
	}
	return r;
}

// バスフリーフェーズに移行する
void
SPCDevice::BusFree()
{
	putlog(3, "BusFree");

	// ここは BDID 書き込み前 (まだ SCSIBus に自身をつなげる前) にも呼ばれる。
	// その場合は何もしない。バスフリーのはずでもあるし。
	if (bus == NULL) {
		return;
	}

	BusRelease();
}

// バスをリリースする。バスフリーとバスリセットから呼ばれる
void
SPCDevice::BusRelease()
{
	// 自発的にバスをリリースする場合、今のフェーズから自分が変えていい
	// BSY、SEL だけを落とす。それ以外の信号線は正しくはないけどとりあえず
	// 一括して SCSIHostDevice::BusFreeCB() が落とすことにしてある。

	switch (GetPhase()) {
	 case SCSI::Phase::BusFree:
	 case SCSI::Phase::Transfer:
		break;
	 case SCSI::Phase::Arbitration:
	 case SCSI::Phase::Selection:
	 case SCSI::Phase::Reselection:
		NegateSEL();
		NegateBSY();
		break;
	}
}

// Select コマンド実行
void
SPCDevice::SelectCommand()
{
	std::string idstr;
	uint32 data;
	uint32 tgtid;
	uint64 wait;

	// ターゲット ID を表示用に解析
	data = spc.temp_out;
	if (data == 0) {
		// ID が一切立ってない?
		idstr = "TEMP==0; No IDs?";
	} else if ((data & (1U << myid)) == 0) {
		// 自 ID が立ってない?
		idstr = string_format("TEMP=$%02x; No BDID?", data);
	} else {
		// ここで少なくとも自 ID は立っている
		data &= ~(1U << myid);

		if (data == 0) {
			// ターゲット ID がない
			idstr = string_format("#%u", myid);
		} else {
			tgtid = SCSIBus::DecodeID(data);
			idstr = string_format("#%u -> #%u", myid, tgtid);
		}
	}
	putlog(2, "Select command (%s)", idstr.c_str());

	// アービトレーションありの場合
	//
	// BSY  X\______/~~~~~~~~~~~~~~~~~~~~~~~~~~\_ .. _/^^^^^^^^^^^^^^^
	//       |t_BFBL|                          |      |t_BIDH|
	// DB*  ________<XXXXXXXXXXXXXXXXXXXXXXXXXXXX .. XXXXXXXX>________
	//              |t_ARB|t_AWSL|             |      |t_BLSH|
	// SEL  _____________________/~~~~~~~~~~~~~~~ .. ~~~~~~~~\________
	//                           |t_SIDA|t_IDBH|             |
	// ATN  ____________________________/~~~~~~~~ .. ~~~~~~~~~~~~~~~~~
	//                                                       |t_SHIR|
	// INTR _____________________________________ .. _______________/~
	//
	// t_BFBL = <Min> (6 + TCL) * t_CLF, <Max> (7 + TCL) * t_CLF + 60
	// t_ARB  = <Min> 32 * t_CLF - 60
	// t_AWSL = <Min> 0, <Max> 80
	// t_SIDA = <Min> 11 * t_CLF - 30
	// t_IDBH = <Min> 2 * t_CLF - 80
	//
	// ここではバスフリー検出から少なくとも t_BFBL 以上 + t_ARB + t_AWSL 後に
	// SEL と DB* と ATN を立てて、そこから t_SIDA + t_IDBH 後に BSY を落とす。
	//
	// アービトレーションなしの場合
	//
	// BSY  X\_______________ .. _/~~~~~~~~~~~~~~~
	//       |t_FRID|             |t_BIDH|
	// DB*  ________<XXXXXXXX .. XXXXXXXX><XXXXXXX
	//              |t_IDSL|      |t_BLSH|
	// SEL  _______________/~ .. ~~~~~~~~\________
	//                                   |t_SHIR|
	// INTR _________________ .. _______________/~
	//
	// t_FRID = <Min> (6 + TCL) * t_CLF, <Max> (7 + TCL) * t_CLF + 140
	// t_IDSL = <Min> 11 * t_CLF - 80
	// t_BIDH = <Min> t_BLSH = 2 * t_CLF
	// t_SHIR = <Max> 60
	//

	// t_FRID/t_BFBL はたぶん今からではなくバスフリーになった時点から起算。
	// t_FRID と t_BFBL は同じ式なのでここでは t_BFBL で統一。
	uint64 t_BFBL = (6 + spc.GetTCL()) * t_CLF;	// Min
	uint64 now = scheduler->GetVirtTime();
	if ((last_busfree_time + t_BFBL) > now) {
		wait = (last_busfree_time + t_BFBL) - now;
	} else {
		wait = 0;
	}

	spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG);
	spc.ssts |= SPC::SSTS_SPC_BUSY;

	// TCH,TCM によるタイマーを始動。
	// N が 0 なら無限大なのでタイマーは動かさない。
	uint32 N = (spc.tc >> 8) << 8;
	if (N != 0) {
		timer_event->SetCallback(&SPCDevice::SelectionTimeout);
		timer_event->time = (N + 15) * t_CLF * 2;
		scheduler->RestartEvent(timer_event);
	}

	if ((spc.sctl & SPC::SCTL_ARBIT_EN)) {
		// アービトレーションありの場合

		// ここでは複数のデバイスが同時にアービトレーションフェーズに
		// 入ることはないため、アービトレーションを開始したデバイスが
		// 必ずバスの使用権を得る。

		// バスフリーから規定時間後に SEL (と DB* と ATN) を上げる。
		uint64 t_ARB  = 32 * t_CLF - 60_nsec;
		uint64 t_AWSL = 40_nsec;	// 適当に中間
		CallAfter(Arbitration1, "SPC Arbitration", wait + t_ARB + t_AWSL);
	} else {
		// アービトレーションなしの場合

		// バスフリーから規定時間後に SEL (と DB*) を上げる。
		uint64 t_IDSL = 11 * t_CLF - 80_nsec;
		CallAfter(Selection1, "SPC Selection", wait + t_IDSL);
	}
}

// アービトレーションフェーズ開始後、規定時間経過したので SEL を立てるところ。
void
SPCDevice::Arbitration1(Event *ev)
{
	putlog(3, "Arbitration1");

	spc.ssts |= SPC::SSTS_SPC_BUSY;

	// 本当は DB* -> SEL -> ATN の順でタイミングも規定されてるけど省略。
	// ここでは SEL がトリガーになるので SEL のアサートは最後に行う。
	// アービトレーションありなら、セレクション/リセレクションフェーズを
	// 区別するため PCTL に設定された IO をバスに出す(p.27)。
	// MSG と CD は記述がないけどたぶん一緒に出すだろう。
	// タイミングもどこでか分からんのでとりあえずここで。
	SetData(spc.temp_out);
	SetXfer(spc.pctl_out);
	if (set_atn_in_selection) {
		AssertATN();
		set_atn_in_selection = false;
	}
	AssertSEL();

	uint64 t_SIDA = 11 * t_CLF - 30_nsec;
	uint64 t_IDBH = 2 * t_CLF - 80_nsec;
	CallAfter(Arbitration2, "SPC Arbitration2", t_SIDA + t_IDBH);
}

// アービトレーションフェーズ中、SEL を立ててから規定時間経過したので BSY を
// 下げるところ。
void
SPCDevice::Arbitration2(Event *ev)
{
	putlog(3, "Arbitration2");

	NegateBSY();
}

// アービトレーションフェーズを使わない場合のセレクションフェーズで、
// 規定時間経過したので SEL を立てるところ。
void
SPCDevice::Selection1(Event *ev)
{
	putlog(3, "Selection1");

	spc.ssts |= SPC::SSTS_SPC_BUSY;

	// 本当は DB* -> SEL の順でタイミングも規定されてるけど省略。
	// ここでは SEL がトリガーになるので SEL のアサートは最後に行う。
	// アービトレーションなしなら PCTL に関係なくセレクションフェーズ (IO=0)
	// を開始する(p.27)。MSG と CD は記述がないけどたぶん一緒に出すだろう。
	SetData(spc.temp_out);
	SetXfer(0);
	AssertSEL();
}

// セレクションコマンドがタイムアウトした時に呼ばれるコールバック。
void
SPCDevice::SelectionTimeout(Event *ev)
{
	if (loglevel >= 3) {
		int tgtid;
		uint8 data = GetData();
		data &= ~(1U << myid);
		tgtid = SCSIBus::DecodeID(data);
		if (tgtid == -1) {
			putlogn("Selection timeout");
		} else {
			putlogn("Selection timeout (#%u)", tgtid);
		}
	}

	spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_SPC_BUSY);

	// TC は実際には減算カウンタのようだが、とりあえず辻褄合わせ
	spc.tc = 0;

	SetINTS(SPC::INTS_TIMEOUT);
}

// SetATN コマンド実行
void
SPCDevice::SetATNCommand()
{
	putlog(2, "SCMD Set ATN Command");

	// Select コマンド前なら、セレクションフェーズで ATN を立てる指示。
	// 接続中(転送フェーズ中?) なら即座に ATN を立てる。
	switch (GetPhase()) {
	 case SCSI::Phase::BusFree:
		set_atn_in_selection = true;
		break;
	 case SCSI::Phase::Arbitration:
	 case SCSI::Phase::Selection:
	 case SCSI::Phase::Reselection:
		// Select Command 実行中なので実質ここで呼ばれることはないはずだが
	 case SCSI::Phase::Transfer:
		AssertATN();
		break;
	}
}

// Transfer コマンド実行
void
SPCDevice::TransferCommand()
{
	putlog(2, "SCMD Transfer Command (xfer=%s len=0x%x)",
		SCSI::GetXferPhaseName(spc.pctl_out), spc.tc);

	// SCMD レジスタ書き込みのうち Transfer コマンドに関わる未実装機能の
	// ログ表示は SCMD レジスタ書き込み時ではなくここで表示。
	if ((spc.scmd & SPC::SCMD_TERM_MODE)) {
		putlog(0, "SCMD Padding mode (NOT IMPLEMENTED)");
	}

	transfer_command_running = true;
	spc.ssts |= SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS;

	// PCTL レジスタに設定してあるフェーズをバスに出す。
	// XXX どこでかよく分からんけど、とりあえずこの辺で。
	SetXfer(spc.pctl_out);

	// 転送開始前に DREG キューを初期化する。
	// MPU 側が DREG を全部読まずにフェーズを中断とかはたぶん出来るので。
	// XXX 転送開始前なのかリセットなのか…
	spc.dreg.Clear();

	// Transfer コマンド発行時に REQ がアサートされていなければ
	// アサートされるまで待つ。この場合、ここでは何もすることはなく、
	// OnTransferReq() が呼ばれてそこから転送フェーズが動き始める。
	if (GetREQ()) {
		// コマンドから転送開始までちょっとだけかかる
		CallAfter(HardwareTransfer, "SPC SCMD to Start", 4 * t_CLF);
	}
}

// バスフリーコールバック (SCSIHostDevice から呼ばれる)。
// id はこのバスフリーを行ったデバイス (0..7)
void
SPCDevice::BusFreeCallback(uint id)
{
	putlog(4, "BusFree Callback");

	last_busfree_time = scheduler->GetVirtTime();

	transfer_command_running = false;
	spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG);
	spc.ssts &= ~(SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS);

	// マニュアルが分かりにくいんだけど、
	// o イニシエータとして動作時に、SPC が自らバスフリーにした時
	// o BusFree INT enable ビットが立っていて、ターゲットがバスフリーに
	//   した時
	// のいずれかで INTS のバスフリー検出ビットを立てる
	if (id != myid || spc.busfree_intr_enable) {
		SetINTS(SPC::INTS_DISCONNECTED);
	}
}

// ターゲットがセレクションに応答したコールバック
// (SCSIHostDevice から呼ばれる)
void
SPCDevice::SelectionAckCallback()
{
	putlog(4, "SelectionAck Callback");

	// タイムアウトイベントを停止
	scheduler->StopEvent(timer_event);

	spc.ssts &= ~SPC::SSTS_SPC_BUSY;
	spc.ssts |= SPC::SSTS_CONN_INIT;

	// Command Complete 割り込みを起こす
	SetINTS(SPC::INTS_COMPLETE);
}

// 転送フェーズでターゲットが REQ を立てたコールバック
// (SCSIHostDevice から呼ばれる)
void
SPCDevice::TransferReqCallback()
{
	// REQ が立てば Transfer Command 発行前でも Xfer In Progress は立てる
	spc.ssts |= SPC::SSTS_IN_PROGRESS;

	if (transfer_command_running) {
		// Transfer Command 実行中なら REQ 待ちだったので
		// ここでハードウェア転送を開始。
		HardwareTransfer(phase_event);
	}

	// データシートでは、マニュアル転送で IN 方向の時データバスの内容を
	// TEMP に取り込むと書いてある(p.78)。
	// HardwareTransfer によって状態が変更される可能性があるため
	// もう一回チェック。
	if (transfer_command_running == false) {
		if (GetIO()) {
			spc.temp_in = GetData();
		}
	}
}

// ハードウェア転送の1バイト分。
// イベントコールバックとして呼び出すこと。
// REQ がアサートされているのを確認してから呼び出すこと。
// (1バイト送信するとターゲットが再び REQ を上げるなどしてくるので、
// OnTransferReq() か DREG 書き込みから再びここが呼ばれる)
void
SPCDevice::HardwareTransfer(Event *ev)
{
	// SDGC_XFER_ENABLE は転送要求ごとに割り込みを上げる。
	// (SPC から見て) 入力操作時は読み込めるデータがある場合、出力操作時は
	// キューにバイトを書き込む必要がある場合に、割り込みを上げる。

	// PCTL のフェーズと SCSI バスのフェーズが一致していれば転送(開始|継続)。
	// そうでなければ Service Required 割り込み。
	if (spc.pctl_out != GetXfer()) {
		transfer_command_running = false;
		putlog(3, "Hardware Transfer terminated (phase mismatch)");
		SetINTS(SPC::INTS_SERV_REQ);
		return;
	}

	// DREG が送受信不可能ならここでは何もせずに帰るだけでよい。
	// その後 DREG アクセスによって送受信可能になればまたこれが呼ばれる。
	if (GetIO()) {
		// Input (ターゲット → イニシエータ)
		bool queued = spc.dreg.Enqueue(GetData());
		// SDGC_XFER_ENABLE なら読み込めるデータがある場合に割り込み。
		if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) {
			putlog(3, "SDGC XFER_OUT Interrupt");
			SetINTS(SPC::INTS_XFER_OUT);
		}
		if (!queued) {
			putlog(4, "Hardware Transfer (DREG full)");
			return;
		}
	} else {
		// Output (イニシエータ → ターゲット)
		if (spc.dreg.Length() != 0) {
			SetData(spc.dreg.Dequeue());
		} else {
			putlog(4, "Hardware Transfer (DREG empty)");
			// SDGC_XFER_ENABLE ならキューに書き込む必要がある場合に割り込み。
			if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) {
				putlog(3, "SDGC XFER_OUT Interrupt");
				SetINTS(SPC::INTS_XFER_OUT);
			}
			return;
		}
	}

	AssertACK();

	// 転送完了
	spc.tc--;
	putlog(4, "Hardware Transfer: %s q=%u tc=%u",
		(GetIO() ? "IN" : "OUT"), spc.dreg.Length(), spc.tc);
	if (spc.tc == 0) {
		// どちらでも転送自体はここで完了(?)
		transfer_command_running = false;

		if (GetIO()) {
			// Input 時は TC がゼロになって DREG キューを全部読み出した
			// ところで完了なので、ここでは何もしない。
		} else {
			// Output ならここで転送完了。
			TransferComplete();
		}
	}

	// メッセージアウトフェーズの最終バイト送信中(?)に ATN を下げる
	if (GetXfer() == SCSI::XferPhase::MsgOut && spc.tc == 0) {
		NegateATN();
	}

	if (GetXfer() == SCSI::XferPhase::MsgIn && spc.tc == 0) {
		// ただしメッセージインフェーズの最終バイトなら ACK を下げない。
	} else {
		NegateACK();
	}
}

// 転送完了。
// HardwareTransfer() と Read() から呼ばれる。
void
SPCDevice::TransferComplete()
{
	spc.ssts &= ~(SPC::SSTS_SPC_BUSY | SPC::SSTS_IN_PROGRESS);
	SetINTS(SPC::INTS_COMPLETE);
}

unix.superglobalmegacorp.com

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