File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / sio.cpp
Revision 1.1.1.18 (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
//

//
// SIO (uPD7201)
//

#define MPSCC_SIO_LOG_HACK
#include "sio.h"
#include "event.h"
#include "hostcom.h"
#include "keyboard.h"
#include "mainapp.h"

// コンストラクタ
SIODevice::SIODevice()
	: inherited()
{
	SetName("SIO");
	ClearAlias();
	AddAlias("SIO");
	AddAlias("MPSCC");

	mpscc.chan[0].desc = "Serial Console";
	mpscc.chan[1].desc = "Keyboard";

	// ポートアドレス。
	// デバイス自身は自分が配置されるアドレスを知らなくてよい構造になって
	// いるが、直接アクセスしたりする場合などに毎回アドレスを確認するのが
	// 面倒なので(全機種で向きと間隔が異なる)、モニタで表示されてほしい。
	if (gMainApp.IsLUNA1()) {
		mpscc.chan[0].dataaddr = 0x51000000;
		mpscc.chan[0].ctrladdr = 0x51000002;
		mpscc.chan[1].dataaddr = 0x51000004;
		mpscc.chan[1].ctrladdr = 0x51000006;
	} else {
		mpscc.chan[0].dataaddr = 0x51000000;
		mpscc.chan[0].ctrladdr = 0x51000004;
		mpscc.chan[1].dataaddr = 0x51000008;
		mpscc.chan[1].ctrladdr = 0x5100000c;
	}

	// 153.6kHz = 6.510416...usec / bit;
	// 12bit = 78.125usec
	mpscc.chan[0].xc12_tsec = 78.125_usec;
	mpscc.chan[1].xc12_tsec = 78.125_usec;

	monitor = gMonitorManager->Regist(ID_MONITOR_SIO, this);
	monitor->SetCallback(&SIODevice::MonitorScreen);
	monitor->SetSize(70, 24);
}

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

// 初期化
bool
SIODevice::Init()
{
	if (inherited::Init() == false) {
		return false;
	}

	hostcom->SetPortName("SIO Ch.A");

	keyboard = GetKeyboard();

	// LUNA では SIO と呼ぶほうが通りがいい。
	// イベントの登録は親クラスで行ってある。
	for (int ch = 0; ch < txevent.size(); ch++) {
		auto& chan = mpscc.chan[ch];
		txevent[ch]->SetName(string_format("SIO Ch%c TX", chan.name));
		rxevent[ch]->SetName(string_format("SIO Ch%c RX", chan.name));
	}

	return true;
}

// リセット
void
SIODevice::ResetHard(bool poweron)
{
	// master_int_enable は uPD7201 にはなく常に true と等価
	mpscc.master_int_enable = true;

	// CR2 b0-5,7 = %0
	mpscc.intdma_mode = 0;
	mpscc.priority_select = false;
	mpscc.int_mode = 0;
	mpscc.vectored_mode = false;
	mpscc.syncb = false;

	for (auto& chan : mpscc.chan) {
		ResetChannel(chan);
	}

	// ログ省略ステートをリセット
	sr0_logphase = 0;
}

// チャンネルリセット
void
SIODevice::ResetChannel(MPSCCChan& chan)
{
	// CR0 b0-2 = %0
	chan.ptr = 0;

	// CR1 b0,1,3,4,7 = %0
	chan.esint_enable = false;
	chan.txint_enable = false;
	chan.rxint_mode = 0;
	chan.wait_enable = false;

	// CR3 b0-7 = %0
	SetCR3(chan, 0x00);

	// CR5 b1,2,3,4,7 = %0
	uint8 val = GetCR5(chan);
	SetCR5(chan, val & 0x61);

	// SR0 b0,1 = %0、b2,6 = %1
	chan.rxlen = 0;
	chan.intpend = 0;
	chan.txlen = 0;
	chan.tx_underrun = true;

	// SR1 b4-7 = %0
	chan.sr1 &= ~(MPSCC::SR1_ENDOFFRAME |
	              MPSCC::SR1_FRAMEERROR |
	              MPSCC::SR1_RXOVERRUN  |
	              MPSCC::SR1_PARITYERROR);

	ResetChannelCommon(chan);
}

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

	switch (offset) {
	 case 0:
		data = ReadData(mpscc.chan[0]);
		break;
	 case 1:
		data = ReadCtrl(mpscc.chan[0]);
		break;
	 case 2:
		data = ReadData(mpscc.chan[1]);
		break;
	 case 3:
		data = ReadCtrl(mpscc.chan[1]);
		break;
	 default:
		VMPANIC("corrupted offset=%u", offset);
	}
	// XXX wait?
	data |= BusData::Size1;
	return data;
}

busdata
SIODevice::WritePort(uint32 offset, uint32 data)
{
	switch (offset) {
	 case 0:
		WriteData(mpscc.chan[0], data);
		break;
	 case 1:
		WriteCtrl(mpscc.chan[0], data);
		break;
	 case 2:
		WriteData(mpscc.chan[1], data);
		break;
	 case 3:
		WriteCtrl(mpscc.chan[1], data);
		break;
	 default:
		VMPANIC("corrupted offset=%u", offset);
	}
	// XXX wait?
	busdata r = BusData::Size1;
	return r;
}

busdata
SIODevice::PeekPort(uint32 offset)
{
	uint64 data;

	switch (offset) {
	 case 0:
		data = PeekData(mpscc.chan[0]);
		break;
	 case 1:
		data = PeekCtrl(mpscc.chan[0]);
		break;
	 case 2:
		data = PeekData(mpscc.chan[1]);
		break;
	 case 3:
		data = PeekCtrl(mpscc.chan[1]);
		break;
	 default:
		data = 0xff;
		break;
	}
	return data;
}

// 書き込みレジスタ名を返す
const char *
SIODevice::CRName(const MPSCCChan& chan, uint ptr_) const
{
	assert(ptr_ < 8);
	uint idx = ptr_ * 2 + chan.id;
	assert(idx < countof(crnames));
	return crnames[idx];
}

// 読み込みレジスタ名を返す
const char *
SIODevice::SRName(const MPSCCChan& chan, uint ptr_) const
{
	assert(ptr_ < 8);
	uint idx = ptr_ * 2 + chan.id;
	assert(idx < countof(srnames));
	return srnames[idx];
}

// 制御ポートの読み込み
uint8
SIODevice::ReadCtrl(MPSCCChan& chan)
{
	uint8 data;

	switch (chan.ptr) {
	 case 0:	// SR0
		data = GetSR0(chan);
		break;

	 case 1:	// SR1
		data = chan.sr1;
		break;

	 case 2:
		// SR2B はベクタの読み出し
		if (chan.id == 1) {
			data = GetSR2B();
			break;
		}

		// SR2A はない
		// XXX どうなる?
		FALLTHROUGH;

	 default:
		// XXX どうなる?
		putlog(0, "Read %s (NOT IMPLEMENTED)", SRName(chan));
		// SR0 へ戻る?
		chan.ptr = 0;
		return 0xff;
	}

	// PROM が SR0 を高速でポーリングするのでログを細工する。
	//
	//             SR0       SRn
	// loglevel<=1 非表示    非表示
	// loglevel 2  *         常に表示
	// loglevel 3  常に表示  常に表示
	//
	// *: 同じ値の読み込みログが連続したら省略。
	if (__predict_true(loglevel <= 1)) {
		// nop
	} else if (__predict_false(loglevel >= 3 || chan.ptr > 0)) {
		putlog(2, "%s -> $%02x", SRName(chan), data);
	} else {
		// ここは loglevel==2 && SR0

		switch (sr0_logphase) {
		 case 0:	// 表示する
		 first:
			sr0_logphase = 1;
			sr0_lastread = data;
		 disp:
			putlogn("%s -> $%02x", SRName(chan, 0), data);
			break;
		 case 1:	// 連続(1回目)
			if (__predict_true(data == sr0_lastread)) {
				sr0_logphase++;
				goto disp;
			} else {
				goto first;
			}
			break;
		 case 2:	// 連続(2回目)
			if (__predict_true(data == sr0_lastread)) {
				sr0_logphase++;
				putlogn("%s -> $%02x (omit the following)",
					SRName(chan, 0), data);
			} else {
				goto first;
			}
			break;
		 default:	// 連続(3回目以降)
			if (__predict_true(data == sr0_lastread)) {
				// 連続したので表示省略
			} else {
				goto first;
			}
			break;
		}
	}
	// アクセス後は SR0 へ戻す
	chan.ptr = 0;

	return data;
}

// 制御ポートの書き込み
void
SIODevice::WriteCtrl(MPSCCChan& chan, uint32 data)
{
	// CR0 はコマンドかまたは次にアクセスするレジスタを指定する。
	// CR0 以外のレジスタをアクセスすると、次のアクセスは CR0 に戻る。

	if (chan.ptr == 0) {
		WriteCR0(chan, data);
	} else {
		switch (chan.ptr) {
		 case 1:
			WriteCR1(chan, data);
			break;
		 case 2:
			WriteCR2(chan, data);
			break;
		 case 3:
			WriteCR3(chan, data);
			break;
		 case 4:
			WriteCR4(chan, data);
			break;
		 case 5:
			WriteCR5(chan, data);
			break;
		 case 6:
			WriteCR6(chan, data);
			break;
		 case 7:
			WriteCR7(chan, data);
			break;
		 default:
			VMPANIC("corrupted chan.ptr=%u", chan.ptr);
		}
		// アクセス後は CR0 へ戻す
		chan.ptr = 0;
	}
}

// 制御ポートの Peek
uint8
SIODevice::PeekCtrl(const MPSCCChan& chan) const
{
	switch (chan.ptr) {
	 case 0:	// SR0
		return GetSR0(chan);

	 case 1:	// SR1
		return chan.sr1;

	 case 2:	// SR2
		// SR2A はない。SR2B は割り込み要因込みのベクタ番号。
		if (chan.id == 1) {
			return GetSR2B();
		}
		FALLTHROUGH;

	 default:
		// 未調査
		return 0xff;
	}
}

// CR1 レジスタの内容を取得
uint8
SIODevice::GetCR1(const MPSCCChan& chan) const
{
	uint8 data = 0;

	if (chan.wait_enable)
		data |= MPSCC::CR1_WAIT_EN;
	if (chan.wait_on_rx)
		data |= MPSCC::CR1_WAIT_RX;
	// 内部フォーマットから元のビットを求める
	switch (chan.rxint_mode) {
	 case MPSCCChan::RXINT_DISABLE:
		break;
	 case MPSCCChan::RXINT_1STCHAR:
		data |= MPSCC::CR1_RXINT_1STCHAR;
		break;
	 case MPSCCChan::RXINT_ALLCHAR:
		if (chan.sp_parity) {
			data |= MPSCC::CR1_RXINT_ALLCHAR;
		} else {
			data |= MPSCC::CR1_RXINT_ALL_BUT_PE;
		}
		break;
	 default:
		assert(false);
	}
	if (chan.txint_enable)
		data |= MPSCC::CR1_TXINT_EN;
	if (chan.esint_enable)
		data |= MPSCC::CR1_ESINT_EN;

	return data;
}

// CR2A レジスタの内容を取得
uint8
SIODevice::GetCR2A() const
{
	uint8 data = 0;

	data |= mpscc.syncb ? MPSCC::CR2_SYNCB : 0;
	data |= mpscc.vectored_mode ? MPSCC::CR2_VM : 0;
	data |= mpscc.int_mode << 3;
	data |= mpscc.priority_select ? MPSCC::CR2_PRIOSEL : 0;
	data |= mpscc.intdma_mode;

	return data;
}

// CR2B レジスタの内容を取得
inline uint8
SIODevice::GetCR2B() const
{
	return mpscc.vector;
}

// SR0 レジスタの内容を取得
uint8
SIODevice::GetSR0(const MPSCCChan& chan) const
{
	uint8 data = 0;

	if (chan.break_detected)data |= MPSCC::SR0_BREAK;
	if (chan.tx_underrun)	data |= MPSCC::SR0_TXUNDER;
	if (chan.nCTS)			data |= MPSCC::SR0_nCTS;
	if (chan.nSYNC)			data |= MPSCC::SR0_nSYNC;
	if (chan.nDCD)			data |= MPSCC::SR0_nDCD;
	if (chan.txlen == 0)	data |= MPSCC::SR0_TXEMPTY;
	if (chan.intpend != 0)	data |= MPSCC::SR0_INTPEND;
	if (chan.rxlen != 0)	data |= MPSCC::SR0_RXAVAIL;

	return data;
}

// CR0 への書き込み
void
SIODevice::WriteCR0(MPSCCChan& chan, uint32 data)
{
	// XXX b7,b6 は未対応。
	// b5,b4,b3 がコマンド。
	// b2,b1,b0 が次にアクセスするレジスタ(chan->ptr)の切り替え。
	chan.crc_cmd = (data & MPSCC::CR0_CRC) >> 6;
	chan.cmd     = (data & MPSCC::CR0_CMD) >> 3;
	chan.ptr     =  data & MPSCC::CR0_PTR;

	if (chan.crc_cmd != 0) {
		putlog(0, "%s crc_cmd %u (NOT IMPLEMENTED)",
			CRName(chan, 0), chan.crc_cmd);
	}

	if (chan.cmd == 0) {
		// コマンド0 ならレジスタ切り替え
		putlog(3, "%s <- $%02x (ptr=%u)", CRName(chan, 0), data, chan.ptr);
	} else {
		// 0 以外ならコマンド発行
		putlog(2, "%s <- $%02x", CRName(chan, 0), data);
	}
	switch (chan.cmd) {
	 case 0:	// No operation
		break;
	 case 1:	// Send Abort
		SendAbort(chan);
		break;
	 case 2:	// Reset External/Status Interrupt
		ResetESIntr(chan);
		break;
	 case 3:	// Channel Reset
		putlog(1, "Channel %c Reset", chan.name);
		ResetChannel(chan);
		break;
	 case 4:	// Enable Interrupt On Next Rx Character
		EnableIntrOnNextRx(chan);
		break;
	 case 5:	// Reset Transmitter Interrupt/DMA Pending
		ResetTxIntrPending(chan);
		break;
	 case 6:	// Error Reset
		ErrorReset(chan);
		break;
	 case 7:	// End of Interrupt
		// uPD7201 ではチャンネル A のみ有効。
		// (チャンネル B でどうなるかは書いてない)
		if (chan.id == 0) {
			ResetHighestIUS(chan);
		} else {
			putlog(0, "%s: Command 'End of Interrupt' undefined",
				CRName(chan, 0));
		}
		break;
	 default:
		VMPANIC("corrupted chan.cmd=%u", chan.cmd);
	}
}

// CR1 への書き込み
void
SIODevice::WriteCR1(MPSCCChan& chan, uint32 data)
{
	putlog(2, "%s <- $%02x", CRName(chan), data);

	chan.wait_enable = (data & MPSCC::CR1_WAIT_EN);
	chan.wait_on_rx  = (data & MPSCC::CR1_WAIT_RX);
	// RXINT は内部フォーマットで覚えておく。
	// all_or_first の初期値もここでセット。
	// o RXINT_DISABLE なら (使わないけど一応) false にしとく。
	// o RXINT_1STCHAR なら初期状態が true。
	// o RXINT_ALLCHAR なら常時 true。
	switch ((data >> 3) & 3) {
	 case 0:
		chan.rxint_mode = MPSCCChan::RXINT_DISABLE;
		chan.all_or_first = false;
		break;
	 case 1:
		chan.rxint_mode = MPSCCChan::RXINT_1STCHAR;
		chan.sp_parity = false;
		chan.all_or_first = true;
		break;
	 case 2:
		chan.rxint_mode = MPSCCChan::RXINT_ALLCHAR;
		chan.sp_parity = false;
		chan.all_or_first = true;
		break;
	 case 3:
		chan.rxint_mode = MPSCCChan::RXINT_ALLCHAR;
		chan.sp_parity = true;
		chan.all_or_first = true;
		break;
	 default:
		__unreachable();
	}

	if (chan.id == 0) {
		mpscc.sav_vis = (data & MPSCC::CR1_SAV);
		// sav を変更したので vis モードを更新
		SetVIS();
	}

	chan.txint_enable = (data & MPSCC::CR1_TXINT_EN);
	chan.esint_enable = (data & MPSCC::CR1_ESINT_EN);
}

// CR2 への書き込み
void
SIODevice::WriteCR2(MPSCCChan& chan, uint32 data)
{
	if (chan.id == 0) {	// CR2A
		putlog(2, "%s <- $%02x", CRName(chan), data);
		mpscc.syncb				= (data & MPSCC::CR2_SYNCB);
		mpscc.vectored_mode		= (data & MPSCC::CR2_VM);
		mpscc.int_mode			= (data & MPSCC::CR2_INT);
		mpscc.priority_select	= (data & MPSCC::CR2_PRIOSEL);
		mpscc.intdma_mode		= (data & MPSCC::CR2_INTDMA);
		// int_mode を変更したので vis モードを更新
		SetVIS();
	} else {			// CR2B
		putlog(2, "%s <- $%02x (Set vector)", CRName(chan), data);
		mpscc.vector = data;
	}
}

// CR1 の SAV と CR2 の INT_MODE に従って VIS を更新
void
SIODevice::SetVIS()
{
	if (mpscc.sav_vis) {
		if (mpscc.int_mode == MPSCC::INTMODE_86) {
			mpscc.vis = MPSCC::VIS_210;
		} else {
			mpscc.vis = MPSCC::VIS_432;
		}
	} else {
		mpscc.vis = MPSCC::VIS_FIXED;
	}
}

// モニター
void
SIODevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	uint cr2a;
	uint cr2b;
	uint sr2b;
	int y;

	cr2a = GetCR2A();
	cr2b = mpscc.vector;
	sr2b = GetSR2B();

	screen.Clear();
	for (int i = 0; i < 2; i++) {
		int x = 0;
		y = i * 11;
		MonitorScreenCh(screen, i, x, y);
	}

	// CR2A
	y = 22;
	screen.Print(0, y, "CR2A:$%02x", cr2a);
	if ((cr2a & MPSCC::CR2_SYNCB)) {
		screen.Print(9, y, TA::On, "SYNC");
	} else {
		screen.Print(9, y, "RTSB");
	}
	screen.Print(14, y, TA::Disable, "----");
	screen.Print(19, y, TA::OnOff((cr2a & MPSCC::CR2_VM)), "Vect");
	if ((cr2a & MPSCC::CR2_INT) == 0x10) {
		screen.Print(24, y, "IM=V4-V2");
	} else {
		screen.Print(24, y, "IM=V2-V0");
	}
	screen.Print(34, y, TA::OnOff((cr2a & MPSCC::CR2_PRIOSEL)), "PRIO");
	// bit1,0 はよく分からん
	screen.Print(39, y, "INT/DMA=%u", (cr2a & MPSCC::CR2_INTDMA));
	y++;

	// CR2B
	screen.Print(0, y, "CR2B:$%02x / SR2B:$%02x", cr2b, sr2b);
}

// モニター (1チャンネル)
void
SIODevice::MonitorScreenCh(TextScreen& screen, int ch, int xbase, int ybase)
{
	const MPSCCChan& chan = mpscc.chan[ch];
	uint cr0;
	uint cr1;
	uint cr3;
	uint cr4;
	uint cr5;
	uint cr6;
	uint cr7;
	uint sr0;
	uint sr1;
	uint rxlen;
	int x;
	int y;

	cr0 = GetCR0(chan);
	cr1 = GetCR1(chan);
	cr3 = GetCR3(chan);
	cr4 = GetCR4(chan);
	cr5 = GetCR5(chan);
	cr6 = chan.cr6;
	cr7 = chan.cr7;
	sr0 = GetSR0(chan);
	sr1 = chan.sr1;
	rxlen = chan.rxlen;

	// 0         1         2         3         4         5         6
	// 0123456789012345678901234567890123456789012345678901234567890123456789
	//          0123 0123 0123 0123 0123 0123 0123 0123
	// Channel A: Serial Console                          DataAddr  $00000000
	// CR0: $xx CRC=x     CMD=x          PTR=x            CtrlAddr  $00000000
	// CR1: $xx WE   0    WR   RXInt=x   0    TXEn ESEn
	// RXBuf: 0 --- --- ---           Intr: SP Rx ES Tx

	x = xbase;
	y = ybase;
	screen.Print(x, y++, "Channel %c: %s", chan.name, chan.desc);
	screen.Print(x, y++, "CR0: $%02x", cr0);
	screen.Print(x, y++, "CR1: $%02x", cr1);
	screen.Print(x, y++, "CR3: $%02x", cr3);
	screen.Print(x, y++, "CR4: $%02x", cr4);
	screen.Print(x, y++, "CR5: $%02x", cr5);
	screen.Print(x, y++, "CR7/CR6: $%02x%02x", cr7, cr6);
	screen.Print(x, y++, "SR0: $%02x", sr0);
	screen.Print(x, y++, "SR1: $%02x", sr1);
	screen.Print(x, y,   "RXbuf: %u", rxlen);
	int i;
	for (i = 0; i < rxlen; i++) {
		screen.Print(x + 9 + i * 4, y, "$%02x", chan.rxbuf[i]);
	}
	for (; i < sizeof(chan.rxbuf); i++) {
		screen.Print(x + 9 + i * 4, y, "---");
	}
	// 内部割り込み状態
	screen.Print(x + 31, y, "Intr:");
	screen.Print(x + 37, y, TA::OnOff(chan.intpend & INTPEND_SP), "SP");
	screen.Print(x + 40, y, TA::OnOff(chan.intpend & INTPEND_RX), "Rx");
	screen.Print(x + 43, y, TA::OnOff(chan.intpend & INTPEND_ES), "ES");
	screen.Print(x + 46, y, TA::OnOff(chan.intpend & INTPEND_TX), "Tx");

	y = ybase + 1;
	x = xbase;
	// CR0
	screen.Print(x + 9, y, "CRC=%u", (cr0 >> 6));
	static const char * const cmd_str[] = {
	//   0123456789012345
		"No operation",
		"Send Abort",
		"Reset E/S Int",
		"Channel Reset",
		"Enable IntNextCh",
		"Reset TxIntPend",
		"Error Reset",
		"End of Interrupt",
	};
	uint cmd = (cr0 >> 3) & 7;
	screen.Print(x + 19, y, "CMD=%u(%s)", cmd, cmd_str[cmd]);
	screen.Print(x + 43, y, "PTR=%u", (cr0 & 7));
	y++;

	// CR1
	static const char * const cr1_str[] = {
		"Wait", "-", "OnRx", "", "", "SAV", "TxIE", "ESIE"
	};
	MonitorReg(screen, x + 9, y, cr1, cr1_str);
	// XXX All Char 割り込みのうちパリティエラーの扱いは表示省略。
	// パリティエラーの扱いが必要になったら考える。
	static const char * const rxint_str[] = {
	//   012345678
		"RxInt=Dis",
		"RI=1stChr",
		"RI=AllChr",	// Parity Error を Special Condition に含む
		"RI=AllChr",	// Parity Error を Special Condition に含まない
	};
	screen.Puts(x + 24, y, rxint_str[(cr1 >> 3) & 3]);
	// CR1 の bit2 (Modified Vector) は CR1B のみ。
	if (ch == 0) {
		screen.Puts(x + 34, y, TA::Disable, "----");
	}
	y++;

	// CR3,CR4,CR5
	y = MonitorScreenCR345(screen, x, y, cr3, cr4, cr5);

	// CR6,CR7
	y++;

	// SR0
	static const char * const sr0_str[] = {
		"BrAb", "TxUn", "!CTS", "!SYN", "!DCD", "TxEm", "IntP", "RxAv"
	};
	MonitorReg(screen, x + 9, y, sr0, sr0_str);
	// SR0 の bit1 (Interrupt Pending) は SR0A のみ。SR0B では 0。
	if (ch == 1) {
		screen.Print(39, y, TA::Disable, "----");
	}
	y++;

	// SR1
	MonitorScreenSR1(screen, x, y, sr1);
	y++;

	// 右列
	y = ybase + 1;
	x = xbase + 51;
	screen.Print(x, y++, "DataAddr: $%08x", chan.dataaddr);
	screen.Print(x, y++, "CtrlAddr: $%08x", chan.ctrladdr);
	y++;
	screen.Print(x, y++, "Param:%13s", GetParamStr(chan).c_str());
	screen.Print(x, y++, "Rx Bits/Frame:   %2u", chan.rxframebits);
	screen.Print(x, y++, "Tx Bits/Frame:   %2u", chan.txframebits);
}

// ペンディングのうち最も優先度の高い割り込み要因を返す
uint
SIODevice::GetHighestInt() const
{
	// 優先度は CR2A b2 (priority_select) で決まるが、
	// Int/DMA が %01 なら priority_select によらず 0 と同じほうになるという
	// ことのようだ。
	//
	//        CR2 b210	High -> Low
	// uPD7201	0 0 0	RxA TxA RxB TxB ESA ESB
	// uPD7201  x 0 1	RxA TxA RxB TxB ESA ESB
	// uPD7201  0 1 0	RxA TxA RxB TxB ESA ESB
	//
	// uPD7201  1 0 0	RxA RxB TxA TxB ESA ESB
	// uPD7201  1 1 0	RxA RxB TxA TxB ESA ESB

	if (mpscc.intdma_mode == 1 || mpscc.priority_select == false) {
		if ((mpscc.chan[0].intpend & INTPEND_SP)) return MPSCC::VS_SPA;
		if ((mpscc.chan[0].intpend & INTPEND_RX)) return MPSCC::VS_RxA;
		if ((mpscc.chan[0].intpend & INTPEND_TX)) return MPSCC::VS_TxA;
		if ((mpscc.chan[1].intpend & INTPEND_SP)) return MPSCC::VS_SPB;
		if ((mpscc.chan[1].intpend & INTPEND_RX)) return MPSCC::VS_RxB;
		if ((mpscc.chan[1].intpend & INTPEND_TX)) return MPSCC::VS_TxB;
		if ((mpscc.chan[0].intpend & INTPEND_ES)) return MPSCC::VS_ESA;
		if ((mpscc.chan[1].intpend & INTPEND_ES)) return MPSCC::VS_ESB;
	} else {
		if ((mpscc.chan[0].intpend & INTPEND_SP)) return MPSCC::VS_SPA;
		if ((mpscc.chan[0].intpend & INTPEND_RX)) return MPSCC::VS_RxA;
		if ((mpscc.chan[1].intpend & INTPEND_SP)) return MPSCC::VS_SPB;
		if ((mpscc.chan[1].intpend & INTPEND_RX)) return MPSCC::VS_RxB;
		if ((mpscc.chan[0].intpend & INTPEND_TX)) return MPSCC::VS_TxA;
		if ((mpscc.chan[1].intpend & INTPEND_TX)) return MPSCC::VS_TxB;
		if ((mpscc.chan[0].intpend & INTPEND_ES)) return MPSCC::VS_ESA;
		if ((mpscc.chan[1].intpend & INTPEND_ES)) return MPSCC::VS_ESB;
	}

	return MPSCC::VS_none;
}

void
SIODevice::Tx(int ch, uint32 data)
{
	switch (ch) {
	 case 0:
		hostcom->Tx(data);
		break;
	 case 1:
		keyboard->Command(data);
		break;
	 default:
		VMPANIC("corrupted ch=%u", ch);
	}
}

// ログ表示用のレジスタ名
/*static*/ const char * const
SIODevice::crnames[16] = {
	"CR0A",		"CR0B",
	"CR1A",		"CR1B",
	"CR2A",		"CR2B",
	"CR3A",		"CR3B",
	"CR4A",		"CR4B",
	"CR5A",		"CR5B",
	"CR6A",		"CR6B",
	"CR7A",		"CR7B",
};
/*static*/ const char * const
SIODevice::srnames[16] = {
	"SR0A",		"SR0B",
	"SR1A",		"SR1B",
	"SR2A?",	"SR2B",
	"SR3A?",	"SR3B?",
	"SR4A?",	"SR4B?",
	"SR5A?",	"SR5B?",
	"SR6A?",	"SR6B?",
	"SR7A?",	"SR7B?",
};

unix.superglobalmegacorp.com

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