File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / mpscc.cpp
Revision 1.1.1.12 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 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
//

//
// uPD7201(MPSC) と Z8530(SCC) の共通部分 (シリアルポート1個つき)
//

// IODevice
//    |
//    |  +-------------+
//    +--| MPSCCDevice | (uPD7201 と Z8530 の共通部分)
//       +-------------+
//          |
//          +-- SCCDevice (X68030、Z8530)
//          +-- SIODevice (LUNA、uPD7201)

#define MPSCC_SIO_LOG_HACK
#include "mpscc.h"
#include "bitops.h"
#include "event.h"
#include "hostcom.h"
#include "interrupt.h"
#include "monitor.h"
#include "scheduler.h"
#include "syncer.h"
#include <cmath>

//
// MPSCC チャンネル
//

// コンストラクタ
MPSCCChan::MPSCCChan()
{
	// これらのレジスタの値の初期値は実際は不定だがそれは実際難しいのと
	// 有効範囲内の値にしておく必要があるので、適当に初期値を固定しておく。
	rxbits = 5;
	txbits = 5;
	clkrate = 1;
}

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

// 初期化
void
MPSCCChan::Init(int id_)
{
	id = id_;
	name = 'A' + id;
}


//
// MPSCC デバイス
//

// コンストラクタ
MPSCCDevice::MPSCCDevice()
	: inherited(OBJ_MPSCC)
{
	for (int ch = 0; ch < mpscc.chan.size(); ch++) {
		auto& chan = mpscc.chan[ch];
		chan.Init(ch);
	}

	// モニタは継承先で設定する
}

// デストラクタ
MPSCCDevice::~MPSCCDevice()
{
	if ((bool)hostcom) {
		hostcom->ResetRxCallback();
	}
}

bool
MPSCCDevice::Create()
{
	// 対応するホストドライバを作成。
	// ポート名は継承先ごとに Init() で設定している。
	try {
		hostcom.reset(new HostCOMDevice(this, 0, ""));
	} catch (...) { }
	if ((bool)hostcom == false) {
		warnx("Failed to initialize HostCOMDevice at %s", __method__);
		return false;
	}
	hostcom->SetRxCallback(ToDeviceCallback(&MPSCCDevice::HostRxCallback), 0);

	return true;
}

void
MPSCCDevice::SetLogLevel(int loglevel_)
{
	inherited::SetLogLevel(loglevel_);

	if ((bool)hostcom) {
		hostcom->SetLogLevel(loglevel_);
	}
}

// 初期化
bool
MPSCCDevice::Init()
{
	interrupt = GetInterruptDevice();

	scheduler->ConnectMessage(MessageID::HOSTCOM0_RX, this,
		ToMessageCallback(&MPSCCDevice::RxMessage));

	auto evman = GetEventManager();
	for (int ch = 0; ch < mpscc.chan.size(); ch++) {
		// 名前は継承先でセットする。機種ごとに名前を使い分けているため。
		txevent[ch] = evman->Regist(this, ch,
			ToEventCallback(&MPSCCDevice::TxCallback));

		rxevent[ch] = evman->Regist(this, ch,
			ToEventCallback(&MPSCCDevice::RxCallback));
	}

	return true;
}

// チャンネルリセットの共通部
void
MPSCCDevice::ResetChannelCommon(MPSCCChan& chan)
{
	// 通信速度を反映する
	ChangeBaudrate(chan);
	SetTxPeriod(chan);
	SetRxPeriod(chan);

	// Set(Tx|Rx)Period() 呼び出し後は old_cr[345] を更新。
	chan.old_cr3 = GetCR3(chan);
	chan.old_cr4 = GetCR4(chan);
	chan.old_cr5 = GetCR5(chan);

	// リセットで送信バッファは空になるが、リセット直後に
	// Tx 割り込みを許可しても Tx 割り込みは起こさない。
	chan.txint_ready = false;

	// 割り込みをネゲートするため
	ChangeInterrupt();
}

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

// CR0/WR0 レジスタの内容を取得
// (値の取り出しだけなら共通)
/*static*/ uint8
MPSCCDevice::GetCR0(const MPSCCChan& chan)
{
	uint8 data = (chan.crc_cmd << 6) | (chan.cmd << 3) | chan.ptr;
	return data;
}

// CR3/WR3 レジスタを設定
/*static*/ void
MPSCCDevice::SetCR3(MPSCCChan& chan, uint32 data)
{
	uint cr3_rxbits = data & MPSCC::CR3_RXBITS;
	switch (cr3_rxbits) {
	 case MPSCC::RXBITS_5:
		chan.rxbits = 5;
		break;
	 case MPSCC::RXBITS_6:
		chan.rxbits = 6;
		break;
	 case MPSCC::RXBITS_7:
		chan.rxbits = 7;
		break;
	 case MPSCC::RXBITS_8:
		chan.rxbits = 8;
		break;
	 default:
		VMPANIC("corrupted cr3_rxbits=$%x", cr3_rxbits);
	}

	chan.auto_enable = (data & MPSCC::CR3_AUTO_EN);
	chan.cr3_sync    = (data & MPSCC::CR3_SYNC_MASK);
	chan.rx_enable   = (data & MPSCC::CR3_RX_EN);
}

// CR3/WR3 レジスタの内容を取得
/*static*/ uint8
MPSCCDevice::GetCR3(const MPSCCChan& chan)
{
	uint8 data;

	data = chan.cr3_sync;
	switch (chan.rxbits) {
	 case 5:
		data |= MPSCC::RXBITS_5;
		break;
	 case 6:
		data |= MPSCC::RXBITS_6;
		break;
	 case 7:
		data |= MPSCC::RXBITS_7;
		break;
	 case 8:
		data |= MPSCC::RXBITS_8;
		break;
	 default:
		VMPANIC("corrupted chan.rxbits=%u", chan.rxbits);
	}
	if (chan.auto_enable) {
		data |= MPSCC::CR3_AUTO_EN;
	}
	if (chan.rx_enable) {
		data |= MPSCC::CR3_RX_EN;
	}
	return data;
}

// CR4/WR4 レジスタを設定
/*static*/void
MPSCCDevice::SetCR4(MPSCCChan& chan, uint32 data)
{
	uint cr4_clkrate = data & MPSCC::CR4_CLKRATE;
	switch (cr4_clkrate) {
	 case MPSCC::CLKRATE_1:
		chan.clkrate = 1;
		break;
	 case MPSCC::CLKRATE_16:
		chan.clkrate = 16;
		break;
	 case MPSCC::CLKRATE_32:
		chan.clkrate = 32;
		break;
	 case MPSCC::CLKRATE_64:
		chan.clkrate = 64;
		break;
	 default:
		VMPANIC("corrupted cr4_clkrate=$%x", cr4_clkrate);
	}
	chan.syncmode = (data & MPSCC::CR4_SYNCMODE);
	chan.stopbits = (data & MPSCC::CR4_STOPBITS) >> 2;
	chan.parity_even   = (data & MPSCC::CR4_PARITY_EVEN);
	chan.parity_enable = (data & MPSCC::CR4_PARITY_EN) ? 1 : 0;
}

// CR4/WR4 レジスタの内容を取得
/*static*/ uint8
MPSCCDevice::GetCR4(const MPSCCChan& chan)
{
	uint8 data = 0;

	switch (chan.clkrate) {
	 case 1:
		data |= MPSCC::CLKRATE_1;
		break;
	 case 16:
		data |= MPSCC::CLKRATE_16;
		break;
	 case 32:
		data |= MPSCC::CLKRATE_32;
		break;
	 case 64:
		data |= MPSCC::CLKRATE_64;
		break;
	 default:
		VMPANIC("corrupted chan.clkrate=%u", chan.clkrate);
	}
	data |= chan.syncmode;
	data |= chan.stopbits << 2;
	if (chan.parity_even) {
		data |= MPSCC::CR4_PARITY_EVEN;
	}
	if (chan.parity_enable) {
		data |= MPSCC::CR4_PARITY_EN;
	}
	return data;
}

// CR5/WR5 レジスタを設定
/*static*/ void
MPSCCDevice::SetCR5(MPSCCChan& chan, uint32 data)
{
	uint cr5_txbits = data & MPSCC::CR5_TXBITS;
	switch (cr5_txbits) {
	 case MPSCC::TXBITS_5:
		chan.txbits = 5;
		break;
	 case MPSCC::TXBITS_6:
		chan.txbits = 6;
		break;
	 case MPSCC::TXBITS_7:
		chan.txbits = 7;
		break;
	 case MPSCC::TXBITS_8:
		chan.txbits = 8;
		break;
	 default:
		VMPANIC("corrupted cr5_txbits=$%x", cr5_txbits);
	}
	chan.nDTR      = (data & MPSCC::CR5_nDTR);
	chan.sendbreak = (data & MPSCC::CR5_SENDBREAK);
	chan.tx_enable = (data & MPSCC::CR5_TX_EN);
	chan.crc16     = (data & MPSCC::CR5_CRC16);
	chan.nRTS      = (data & MPSCC::CR5_nRTS);
	chan.txcrc_en  = (data & MPSCC::CR5_TXCRC_EN);
}

// CR5/WR5 レジスタの内容を取得
/*static*/ uint8
MPSCCDevice::GetCR5(const MPSCCChan& chan)
{
	uint8 data = 0;

	switch (chan.txbits) {
	 case 5:
		data |= MPSCC::TXBITS_5;
		break;
	 case 6:
		data |= MPSCC::TXBITS_6;
		break;
	 case 7:
		data |= MPSCC::TXBITS_7;
		break;
	 case 8:
		data |= MPSCC::TXBITS_8;
		break;
	 default:
		VMPANIC("corrupted chan.txbits=%u", chan.txbits);
	}
	if (chan.nDTR) {
		data |= MPSCC::CR5_nDTR;
	}
	if (chan.sendbreak) {
		data |= MPSCC::CR5_SENDBREAK;
	}
	if (chan.tx_enable) {
		data |= MPSCC::CR5_TX_EN;
	}
	if (chan.crc16) {
		data |= MPSCC::CR5_CRC16;
	}
	if (chan.nRTS) {
		data |= MPSCC::CR5_nRTS;
	}
	if (chan.txcrc_en) {
		data |= MPSCC::CR5_TXCRC_EN;
	}
	return data;
}

// SR2B/RR2B レジスタの内容を取得
uint8
MPSCCDevice::GetSR2B() const
{
	uint8 data = mpscc.vector;

	// 割り込み要因をベクタに載せない
	if (mpscc.vis == MPSCC::VIS_FIXED) {
		return data;
	}

	uint stat = GetHighestInt();

	// 8種類 (3bit) の割り込み要因をベクタに載せる。
	// 載せる場所 (と向き) はそれぞれ 2種類ずつある。
	switch (mpscc.vis) {
	 case MPSCC::VIS_432:		// uPD7201: INTMode = INT85
		// 割り込み要求がない場合は %111
		if (stat == MPSCC::VS_none)
			stat = 7;
		data &= ~0x1c;
		data |= stat << 2;
		break;

	 case MPSCC::VIS_210:		// uPD7201: INTMode = INT86
		// 割り込み要求がない場合は %111
		if (stat == MPSCC::VS_none)
			stat = 7;
		data &= ~0x07;
		data |= stat;
		break;

	 case MPSCC::VIS_321:		// Z8530: StatusHigh/StatusLow = %0
		// 割り込み要求がない場合は %011
		if (stat == MPSCC::VS_none)
			stat = 3;
		data &= ~0x0e;
		data |= stat << 1;
		break;

	 case MPSCC::VIS_456:		// Z8530: StatusHigh/StatusLow = %1
		// 割り込み要求がない場合は %011
		if (stat == MPSCC::VS_none)
			stat = 3;
		// VIS_321 とはビット順が逆
		data &= ~0x70;
		data |= bitrev8(stat << 1);
		break;
	 default:
		VMPANIC("corrupted mpscc.vis=%u", (uint)mpscc.vis);
	}

	return data;
}

// CR0/WR0 Send Abort コマンド
void
MPSCCDevice::SendAbort(MPSCCChan& chan)
{
	putlog(0, "%s: Command 'Send Abort' (NOT IMPLEMENTED)", CRName(chan, 0));
}

// CR0/WR0 Reset External/Status Interrupt コマンド
void
MPSCCDevice::ResetESIntr(MPSCCChan& chan)
{
	putlog(3, "%s: Command 'Reset E/S Interrupt'", CRName(chan, 0));
	chan.es_latched = false;
}

// CR0/WR0 Enable Interrupt on Next Rx Character コマンド
void
MPSCCDevice::EnableIntrOnNextRx(MPSCCChan& chan)
{
	putlog(1, "%s: Command 'Enable Intr on Next Rx Character'",
		CRName(chan, 0));
	chan.all_or_first = true;
}

// CR0/WR0 Reset Tx Interrupt/DMA Pending コマンド
void
MPSCCDevice::ResetTxIntrPending(MPSCCChan& chan)
{
	putlog(2, "%s: Command 'Reset Tx Intr/DMA Pending'", CRName(chan, 0));
	chan.txint_ready = false;
	ChangeInterrupt();
}

// CR0/WR0 Error Reset コマンド
void
MPSCCDevice::ErrorReset(MPSCCChan& chan)
{
	putlog(1, "%s: Command 'Error Reset'", CRName(chan, 0));
	chan.sr1 &= ~(MPSCC::SR1_ENDOFFRAME |
	              MPSCC::SR1_FRAMEERROR |
	              MPSCC::SR1_RXOVERRUN  |
	              MPSCC::SR1_PARITYERROR);
	// XXX Special Rx Condition は未実装
}

// CR0 End of Interrupt コマンド
// WR0 Reset Highest IUS コマンド
// たぶん名前が違うだけで同じ。後者のほうが分かりやすいのでこっちを使う
void
MPSCCDevice::ResetHighestIUS(MPSCCChan& chan)
{
	putlog(2, "%s: Command 'Reset Highest IUS'", CRName(chan, 0));

	// ペンディングのうち最も高い割り込み要因をクリア
	uint stat = GetHighestInt();
	switch (stat) {
	 case MPSCC::VS_TxB:
		mpscc.chan[1].intpend &= ~INTPEND_TX;
		break;
	 case MPSCC::VS_ESB:
		mpscc.chan[1].intpend &= ~INTPEND_ES;
		break;
	 case MPSCC::VS_RxB:
		mpscc.chan[1].intpend &= ~INTPEND_RX;
		break;
	 case MPSCC::VS_SPB:
		mpscc.chan[1].intpend &= ~INTPEND_SP;
		break;

	 case MPSCC::VS_TxA:
		mpscc.chan[0].intpend &= ~INTPEND_TX;
		break;
	 case MPSCC::VS_ESA:
		mpscc.chan[0].intpend &= ~INTPEND_ES;
		break;
	 case MPSCC::VS_RxA:
		mpscc.chan[0].intpend &= ~INTPEND_RX;
		break;
	 case MPSCC::VS_SPA:
		mpscc.chan[0].intpend &= ~INTPEND_SP;
		break;

	 default:
		break;
	}
	ChangeInterrupt();
}

// CR3/WR3 への書き込み
void
MPSCCDevice::WriteCR3(MPSCCChan& chan, uint32 data)
{
	SetCR3(chan, data);
	putlog(2, "%s <- $%02x", CRName(chan), data);

	SetRxPeriod(chan);

	// Set(Tx|Rx)Period() 呼び出し後は old_cr[345] を更新。
	chan.old_cr3 = GetCR3(chan);
}

// CR4/WR4 への書き込み
void
MPSCCDevice::WriteCR4(MPSCCChan& chan, uint32 data)
{
	SetCR4(chan, data);
	putlog(2, "%s <- $%02x", CRName(chan), data);

	if (chan.stopbits == MPSCC::STOPBITS_SYNC) {
		// 同期モードはサポートしない
		putlog(2, "Channel %c Synchronous mode not supported", chan.name);
	} else {
		const char *sm[] = { "?", "1", "1.5", "2" };
		putlog(2, "Channel %c Async Mode (x%u clock, stopbit %s)",
			chan.name,
			chan.clkrate,
			sm[chan.stopbits]);
		// なんとなく順序的に何倍と表示した後に速度を表示
		ChangeBaudrate(chan);
	}

	SetTxPeriod(chan);
	SetRxPeriod(chan);

	// Set(Tx|Rx)Period() 呼び出し後は old_cr[345] を更新。
	chan.old_cr4 = GetCR4(chan);
}

// CR5/WR5 への書き込み
void
MPSCCDevice::WriteCR5(MPSCCChan& chan, uint32 data)
{
	SetCR5(chan, data);
	putlog(2, "%s <- $%02x", CRName(chan), data);

	SetTxPeriod(chan);
	// Set(Tx|Rx)Period() 呼び出し後は old_cr[345] を更新。
	chan.old_cr5 = GetCR5(chan);

	TrySend(chan);
}

// CR6/WR6 への書き込み
void
MPSCCDevice::WriteCR6(MPSCCChan& chan, uint32 data)
{
	chan.cr6 = data;

	// どのみちサポートしてないけど初期化のために 0x00 を書き込むようなので
	// それ以外ならログを出す。
	if (data != 0) {
		putlog(0, "Write %s <- $%02x (NOT IMPLEMENTED)", CRName(chan), data);
	}
}

// CR7/WR7 への書き込み
void
MPSCCDevice::WriteCR7(MPSCCChan& chan, uint32 data)
{
	chan.cr7 = data;

	// どのみちサポートしてないけど初期化のために 0x00 を書き込むようなので
	// それ以外ならログを出す。
	if (data != 0) {
		putlog(0, "Write %s <- $%02x (NOT IMPLEMENTED)", CRName(chan), data);
	}
}

// データポートの読み込み
uint8
MPSCCDevice::ReadData(MPSCCChan& chan)
{
	uint8 data;

	// 受信バッファが空でも直近のデータが読めるので常に [0] でよい
	data = chan.rxbuf[0];
	if (chan.rxlen > 0) {
		putlog(3, "Ch%c Data -> $%02x", chan.name, data);
		chan.rxlen--;
		if (chan.rxlen == 0) {
			// 取り出して空になった
			ChangeInterrupt();
		} else {
			chan.rxbuf[0] = chan.rxbuf[1];
			chan.rxbuf[1] = chan.rxbuf[2];
		}
	} else {
		putlog(3, "Ch%c Data -> $%02x (Empty)", chan.name, data);
	}

	return data;
}

// データポートの書き込み
void
MPSCCDevice::WriteData(MPSCCChan& chan, uint32 data)
{
	char charbuf[4];

	if (isprint((int)data)) {
		snprintf(charbuf, sizeof(charbuf), "'%c'", data);
	} else {
		charbuf[0] = '\0';
	}
	putlog(3, "Ch%c Data <- $%02x %s", chan.name, data, charbuf);

	// XXX: EMPTY でないときにデータを書いたらどうなるかは未定義

	// 1byte の TX Buffer
	chan.txlen = 1;
	chan.txbuf = data;

	// データ書き込み後に Empty になったら割り込みを上げるため
	// ここで ready をセット
	chan.txint_ready = true;

	TrySend(chan);
}

// データポートの Peek
uint8
MPSCCDevice::PeekData(const MPSCCChan &chan) const
{
	// 受信バッファにデータがあれば先頭バイトが見える。
	// 受信バッファが空でも直近に取り出したバイトがそのまま見える。
	return chan.rxbuf[0];
}

// 送信できるか調べて送信する
void
MPSCCDevice::TrySend(MPSCCChan& chan)
{
	// 送信可で送信バッファにデータがあって送信中でなければ
	if (chan.tx_enable && chan.txlen && !txevent[chan.id]->IsRunning()) {
		// 送信する
		chan.tx_shiftreg = chan.txbuf;
		chan.txlen = 0;
		scheduler->StartEvent(txevent[chan.id]);
		putlog(2, "Ch%c send $%02x", chan.name, chan.tx_shiftreg);

		// XXX たぶん送信してから割り込みを上げるまでにいくらか遅れがあるはず
	}

	// ネゲートされることがあるので、送信しなくても割り込み状態を更新する
	ChangeInterrupt();
}

// 送信のイベントコールバック
void
MPSCCDevice::TxCallback(Event *ev)
{
	int ch = ev->code;
	MPSCCChan& chan = mpscc.chan[ch];

	// 接続デバイスの呼び出し
	Tx(ch, chan.tx_shiftreg);

	// 次の送信データが来てたら送信
	TrySend(chan);
}

// ホストスレッドからの受信通知
// (HostCOM スレッドで呼ばれる)
void
MPSCCDevice::HostRxCallback(uint32 dummy)
{
	// XXX 暫定
	// HostCom からメッセージが大量に送られると、スケジューラの
	// メッセージキューが溢れてしまうことがあったので、とりあえず
	// ここでメッセージを送るかどうかを見ているが、
	// 本当はたぶんこうではなくて、送る側が速度調節するか、
	// またはスケジューラにメッセージではないなにか別の方法で
	// 通知を出すようにする必要がある。

	const Event *ev = rxevent[COM_CH];

	if (ev->IsRunning() == false) {
		// スレッドを超えるためにメッセージを投げる
		scheduler->SendMessage(MessageID::HOSTCOM0_RX);
	}
}

// ホストシリアルからの1バイト受信メッセージ
void
MPSCCDevice::RxMessage(MessageID msgid, uint32 arg)
{
	Event *ev = rxevent[COM_CH];

	// 受信ループが止まっていれば開始
	if (ev->IsRunning() == false) {
		scheduler->StartEvent(ev);
	}
}

// 受信イベントコールバック
void
MPSCCDevice::RxCallback(Event *ev)
{
	int ch = ev->code;
	int data;

	// ホストキューにあるだけ取り出す
	data = hostcom->Rx();
	if (data >= 0) {
		Rx(ch, data);

		scheduler->StartEvent(ev);
	}
}

// 1バイト受信する。
// 受理されれば true を返す。
bool
MPSCCDevice::Rx(int ch, uint32 data)
{
	MPSCCChan& chan = mpscc.chan[ch];

	// Rx Enable でなければ受け取らない?
	if (chan.rx_enable == false) {
		return false;
	}

	// バッファが一杯なら Rx Overrun Error
	if (chan.rxlen == sizeof(chan.rxbuf)) {
		putlog(2, "Ch%c Rx Overrun", chan.name);
		chan.sr1 |= MPSCC::SR1_RXOVERRUN;
		// XXX special Rx condition interrupt occurs
		return false;
	}

	// 受信
	putlog(2, "Ch%c recv $%02x", chan.name, data);
	chan.rxbuf[chan.rxlen++] = data;

	// 割り込み状態を変更
	ChangeInterrupt();

	return true;
}

static double
tsec_to_freq(uint64 tsec)
{
	return (double)1_sec / tsec;
}

// ボーレートを再計算する。
// CR4(ClkRate) と xc12_tsec の変更で呼ぶこと。
void
MPSCCDevice::ChangeBaudrate(MPSCCChan& chan)
{
	double old_baudrate = chan.baudrate;

	// 誤差を減らすため 12bit 分の時間を基準とする。
	chan.bit12_tsec = chan.xc12_tsec * chan.clkrate;
	chan.baudrate = tsec_to_freq(chan.bit12_tsec) * 12;

	if (loglevel >= 3) {
		if (chan.baudrate != old_baudrate) {
			putlogn("Channel %c Baudrate %.1f", chan.name, chan.baudrate);
		}
	}
}

// スタートビット、ストップビット、パリティビットの総数を返す。
// enable はサポートしていないモードの時にログを出すため。
int
MPSCCDevice::AdditionalBits(MPSCCChan& chan, bool enable)
{
	int bit;

	// start bit
	bit = 1;

	// parity bit
	bit += chan.parity_enable;

	// stop bit
	bit += GetStopBits(chan, enable);

	return bit;
}

// ストップビット数を返す。
// サポートしていないモードなどの場合は enable ならログを出力する。
// enable は tx_enable か rx_enable。
int
MPSCCDevice::GetStopBits(MPSCCChan& chan, bool enable)
{
	switch (chan.stopbits) {
	 case MPSCC::STOPBITS_SYNC:
		// 非同期モードでの STOPBITS 0 は未定義 (Enable ならログ表示)
		if (enable) {
			putlog(0, "%s Undefined stopbit 0", CRName(chan, 4));
		}
		return 0;
	 case MPSCC::STOPBITS_1:
		return 1;
	 case MPSCC::STOPBITS_1_5:
		// サポートしない (Enable ならログ表示)
		if (enable) {
			putlog(0, "%s Stopbit 1.5 not supported", CRName(chan, 4));
		}
		return 0;
	 case MPSCC::STOPBITS_2:
		return 2;

	 default:
		VMPANIC("corrupted chan.stopbits=$%x", chan.stopbits);
	}
}

// 受信イベントの時間を設定する。
// CR3 または CR4 の変更で呼ぶこと。また呼び出し後は old_cr3、old_cr4 を
// 更新すること。
void
MPSCCDevice::SetRxPeriod(MPSCCChan& chan)
{
	uint bit;

	// 受信データビット数
	bit = chan.rxbits;
	// スタート/ストップ等を足す
	bit += AdditionalBits(chan, chan.rx_enable);
	chan.rxframebits = bit;

	rxevent[chan.id]->time = bit * chan.bit12_tsec / 12;

	if (loglevel >= 1) {
		bool old_enable = chan.old_cr3 & MPSCC::CR3_RX_EN;
		bool new_enable = chan.rx_enable;
		if (old_enable == false && new_enable == false) {
			// 無効→無効ならパラメータが変化しても表示しない

		} else if (old_enable == true && new_enable == false) {
			// 有効→無効なら無効にしたことだけ表示
			putlogn("Channel %c RX Disable", chan.name);
			sr0_logphase = 0;

		} else if ((old_enable == false && new_enable == true) ||
		           (chan.old_cr4 ^ GetCR4(chan)) != 0 ||
		           ((chan.old_cr3 ^ GetCR3(chan)) & MPSCC::CR3_RXBITS) != 0)
		{
			// 無効→有効なら表示。
			// そうでなければ(有効→有効なら)、パラメータが変化すれば表示。
			std::string nsstr;
			if (loglevel >= 2) {
				nsstr = string_format(" (%" PRIu64 ")", chan.bit12_tsec);
			}
			putlogn("Channel %c RX Enable, %.1f bps%s, %u bits/frame",
				chan.name,
				chan.baudrate,
				nsstr.c_str(),
				bit);
			sr0_logphase = 0;
		}
	}

	// --perf によるパフォーマンス測定用のギミック。
	// シリアルポートを特定の設定 (本体 5bit、ストップビット 2bit) にすると、
	// VM 電源オン時から現在までの実時間をログに出力する。NetBSD/OpenBSD の
	// マルチユーザブートがあらかた終わってログインプロンプトが出る手前の
	// /etc/rc.local に
	//
	// stty -f /dev/ttya cs5 cstopb; stty -f /dev/ttya cs8 -cstopb
	//
	// と書いておくことで、ここまでにかかる時間を表示できる。
	// この値はホスト性能に依存するため、同一環境下での相対比較のみ可能。
	// シリアルポートの設定を (通常ではまず使わなそうなやつに) 変えてすぐ戻す
	// だけなので、実機でも副作用はないはず。
	if (chan.id == 0 &&
	    chan.rxbits == 5 && chan.stopbits == MPSCC::STOPBITS_2)
	{
		GetSyncer()->PutlogRealtime();
	}
}

// 送信イベントの時間を設定する。
// CR4 または CR5 の変更で呼ぶこと。また呼び出し後は old_cr4、old_cr5 を
// 更新すること。
void
MPSCCDevice::SetTxPeriod(MPSCCChan& chan)
{
	uint bit;

	// 送信データビット数
	bit = chan.txbits;
	// スタート/ストップ等を足す
	bit += AdditionalBits(chan, chan.tx_enable);
	chan.txframebits = bit;

	txevent[chan.id]->time = bit * chan.bit12_tsec / 12;

	if (loglevel >= 1) {
		bool old_enable = chan.old_cr5 & MPSCC::CR5_TX_EN;
		bool new_enable = chan.tx_enable;
		if (old_enable == false && new_enable == false) {
			// 無効→無効ならパラメータが変化しても表示しない

		} else if (old_enable == true && new_enable == false) {
			// 有効→無効なら無効にしたことだけ表示
			putlogn("Channel %c TX Disable", chan.name);
			sr0_logphase = 0;

		} else if ((old_enable == false && new_enable == true) ||
		           (chan.old_cr4 ^ GetCR4(chan)) != 0 ||
		           ((chan.old_cr5 ^ GetCR5(chan)) & MPSCC::CR5_TXBITS) != 0)
		{
			// 無効→有効なら表示。
			// そうでなければ(有効→有効なら)、パラメータが変化すれば表示。
			std::string nsstr;
			if (loglevel >= 2) {
				nsstr = string_format(" (%" PRIu64 ")", chan.bit12_tsec);
			}
			putlogn("Channel %c TX Enable, %.1f bps%s, %u bits/frame",
				chan.name,
				chan.baudrate,
				nsstr.c_str(),
				bit);
			sr0_logphase = 0;
		}
	}
}

// 割り込み状態を変更
//
// uPD7201 の場合:
//
//     RxINT Disable (CR1 b4,3=%00) -->o--+ (注1)
//                                        |
//   o RxCharAvailable  ----|&            |
//                          |&---|OR      +--|&
//     All/First RxChar ----|&   |OR         |&--+
//                               |OR---------|&  |
//   o Special Rx Condition -----|OR             +---|OR
//                                                   |OR
//   o External/Status ------------------------------|OR----|&       ___
//                                                   |OR    |&-->o-- INT
//   o Tx Buffer Empty ----------|&              +---|OR  +-|&
//                               |&-----|&       |        |
//     Tx INT/DMA Enable --------|&     |&       |        |
//     (CR1 b1)                         |&-------+        |
//                                      |&                |
//     INT/DMA Mode (CR2A b1,0) --------|& (注2)          |
//                                                        |
//     Master Interrupt Enable ---------------------------+ (注3)
//     (Z8530 Only, WR9)
//
// 左端の o 印は割り込み要因(チャンネルあたり4種類)。他は制御フラグ。
//
// 注1: uPD7201/7201A Figure 3-2 では "Rx Disable (CR1:D1)" だがそこは
// Tx INT/DMA Enable ビットなので名称もビット位置も誤り。
// uPD7201 ユーザーズマニュアル p.23 の 図3-2 では "Rx Disable (CR1:D4-3=00)"
// とあるのでこれのビット位置が正しければ Rx Int Disable が正しいはず。
//
// 注2: uPD7201 のみ。Z8530 ではこの選択肢はないので常に true のはず。
//
// 注3: Z8530 のみ。uPD7201 ではこの選択肢はないので常に true にしてある。
//
void
MPSCCDevice::ChangeInterrupt()
{
	uint total_intpend = 0;
	bool int_enable[2];

	// uPD7201 の CR1 INT/DMA Mode による制御。
	// Z8530 では intdma_mode = INT_INT 固定。
	//
	// ついでにここで txint_enable も演算しておく。見た目は
	// 無駄っぽいけどコンパイラで最適化されるので気にしない。
	if (__predict_true(mpscc.intdma_mode == MPSCC::INTDMA_INT_INT)) {
		int_enable[0] = mpscc.chan[0].txint_enable && true;		// ChA INT
		int_enable[1] = mpscc.chan[1].txint_enable && true;		// ChB INT
	} else if (mpscc.intdma_mode == MPSCC::INTDMA_DMA_INT) {
		int_enable[0] = mpscc.chan[0].txint_enable && false;	// ChA DMA
		int_enable[1] = mpscc.chan[1].txint_enable && true;		// ChB INT
	} else {
		int_enable[0] = mpscc.chan[0].txint_enable && false;	// ChA DMA
		int_enable[1] = mpscc.chan[1].txint_enable && false;	// ChB DMA
	}

	for (auto& chan : mpscc.chan) {
		// Rx
		bool rx_avail = (chan.rxlen > 0) && chan.all_or_first;
		// Special Rx (XXX not yet)
		bool special = false;
		bool rx_int = (chan.rxint_mode != MPSCCChan::RXINT_DISABLE) &&
		              (rx_avail || special);

		// FirstChar モードなら1文字受信割り込み後にフラグを下ろす
		// (Rx 割り込み用の変数 rx_int が確定したここで行う)
		if (chan.rxint_mode == MPSCCChan::RXINT_1STCHAR) {
			chan.all_or_first = false;
		}

		// XXX E/S not yet
		bool es_int = false;

		// Tx
		bool tx_int = int_enable[chan.id] &&
		              chan.txint_ready && (chan.txlen == 0);

		uint intpend = 0;
		if (rx_int)
			intpend |= INTPEND_RX;
		if (tx_int)
			intpend |= INTPEND_TX;
		if (es_int)
			intpend |= INTPEND_ES;
		chan.intpend = intpend;

		total_intpend |= chan.intpend;
	}

	// Z8530 の MIE。uPD7201 では true 固定にしてある。
	if (mpscc.master_int_enable == false) {
		total_intpend = 0;
	}

	// いずれかでもあれば割り込みをアサート
	interrupt->ChangeINT(this, total_intpend);
}

// "9600,N,8,1" みたいなのを返す。最大13桁?
std::string
MPSCCDevice::GetParamStr(const MPSCCChan& chan) const
{
	std::string rv;
	char parity;
	char bits;
	char stopbits;

	int parity_idx = 0;
	if (chan.parity_enable) {
		parity_idx++;
		if (chan.parity_even) {
			parity_idx++;
		}
	}
	parity = "NOE"[parity_idx];

	// bits は RX==TX のときのみ有効出力
	// 違うときは '?' にしておく。
	if (chan.rxbits == chan.txbits) {
		bits = '0' + chan.rxbits;
	} else {
		bits = '?';
	}

	// stop bit は 1, 2 のみ有効出力。
	// SYNC とか 1.5 bit とかは '?' にしておく。
	switch (chan.stopbits) {
	 case MPSCC::STOPBITS_1:
		stopbits = '1';
		break;
	 case MPSCC::STOPBITS_2:
		stopbits = '2';
		break;
	 default:
		stopbits = '?';
		break;
	}

	// ここは、このポートに接続する時に相手方で指定する設定値を
	// 表示したいので、ボーレートをよく知られている値に丸めたい。
	// 75 * 2^n の +-5% に入っていたら採用。
	int baud;
	int e;

	// 75 * 2 ^ (e-1) を求める
	double a = (chan.baudrate / 75) * 1.5;
	std::frexp(a, &e);
	int b = 75 * (1U << (e - 1));

	if (b * 19 / 20 <= chan.baudrate && chan.baudrate <= b * 21 / 20) {
		baud = b;
	} else {
		// +-5% 範囲外なら仕方ないのでそのままの値
		baud = (int)chan.baudrate;
	}

	return string_format("%d,%c,%c,%c",
		baud,
		parity,
		bits,
		stopbits);
}

// モニタに CR3/WR3、CR4/WR4、CR5/WR5 の内容を書き出す。
// 更新後の y を返す。
int
MPSCCDevice::MonitorScreenCR345(TextScreen& screen, int x, int y,
	uint8 cr3, uint8 cr4, uint8 cr5)
{
	const std::array<char, 4> bits_per_char { '5', '7', '6', '8' };

	// CR3/WR3
	static const char * const cr3_str[] = {
		"", "", "Auto", "", "", "", "", "RxEn"
	};
	MonitorReg(screen, x + 9, y, cr3, cr3_str);
	screen.Print(x + 9, y, "RxBits=%c", bits_per_char[(cr3 >> 6) & 3]);
	// b1-4 は非同期モードでは使わないのでグレーにしておく
	screen.Puts(x + 24, y, TA::Disable, (cr3 & 0x10) ? "1" : "0");
	screen.Puts(x + 29, y, TA::Disable, (cr3 & 0x08) ? "1" : "0");
	screen.Puts(x + 34, y, TA::Disable, (cr3 & 0x04) ? "1" : "0");
	screen.Puts(x + 39, y, TA::Disable, (cr3 & 0x02) ? "1" : "0");
	y++;

	// CR4/WR4
	static const char * const cr4_str[] = {
		"", "", "", "", "", "", "PaEv", "PaEn"
	};
	MonitorReg(screen, x + 9, y, cr4, cr4_str);
	static const char * const clkrate_str[] = {
		"1", "16", "32", "64"
	};
	screen.Print(x + 9, y, "Rate=x%s", clkrate_str[cr4 >> 6]);
	static const char * const syncmode_str[] = {
		"Mono", "Bi", "HDLC", "Ext"
	};
	uint stopbit = (cr4 >> 2) & 3;
	screen.Print(x + 19, y, (stopbit == 0 ? TA::Normal : TA::Disable),
		"Sync=%s", syncmode_str[(cr4 >> 4) & 3]);
	static const char * const stopbit_str[] = {
		"SyncMode",
		"StopBit=1",
		"Stop=1.5",
		"StopBit=2"
	};
	screen.Puts(x + 29, y, stopbit_str[stopbit]);
	y++;

	// CR5/WR5
	static const char * const cr5_str[] = {
		"!DTR", "", "", "SBrk", "TxEn", "CRC", "!RTS", "TCEn"
	};
	MonitorReg(screen, x + 9, y, cr5, cr5_str);
	screen.Print(x + 14, y, "TxBits=%c", bits_per_char[(cr5 >> 5) & 3]);
	y++;

	return y;
}

// モニタに SR1/RR1 の内容を書き出す。
void
MPSCCDevice::MonitorScreenSR1(TextScreen& screen, int x, int y, uint8 sr1)
{
	static const char * const sr1_str[] = {
		"EOF", "FrEr", "RxOv", "PaEr", "", "", "", "Sent"
	};
	MonitorReg(screen, x + 9, y, sr1, sr1_str);
	screen.Print(x + 29, y, TA::Disable, "ResidueCode=%d", (sr1 >> 1) & 7);
}

// レジスタをビットごとに表示する。MonitorScreen の下請け。
void
MPSCCDevice::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]);
		}
	}
}

unix.superglobalmegacorp.com

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