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

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

//
// Nereid イーサネット (RTL8019AS)
//

// ローカル側のアドレス空間は以下の配置のようだ。
//
// 0000-3fff: PROM (32 バイトでミラー)
// 4000-7fff: バッファ RAM (16KB)
// 8000-bfff: バスか何かの都合で多少再現性のある不定データが読めるようだ
// c000-ffff: バッファ RAM (16KB) のミラーのようだ

// PROM 領域(32バイト)をバイトで読み出すと以下のように読める。
// 0000: 00 00 50 50 C2 C2 10 10  YY YY ZZ ZZ FF FF FF FF
// 0010: FF FF FF FF FF FF FF FF  FF FF FF FF 57 57 57 57
//
// 同領域をワードで読み出すと以下のように読める。
// 0000: 00FF 50FF C2FF 10FF YYFF ZZFF FFFF FFFF
// 0010: FFFF FFFF FFFF FFFF FFFF FFFF 57FF 57FF
//
// MAC アドレスは 00:50:C2:10:YY:ZZ。
// $001c あたりは 'B'($42) か 'W'($57) で、バイトかワードかを示すらしい。

// メモ。電源オン時のレジスタ値ダンプ:
// (1回目)
// Page0: 21 00 FF 9F 13 00 00 EF  FF FF 50 70 16 00 00 00
// Page1: 61 00 05 02 02 10 64 F7  01 B0 91 20 01 20 60 02
// Page2: A1 80 00 FF 00 FF FF FF  FF FF FF FF D1 E0 C6 80
// Page3: E1 30 00 00 90 00 10 FF  00 FE FF FD FF FE FF FF
// (2回目)
// Page0: 21 00 FF BF 13 00 00 EF  FF FF 50 70 16 00 00 00
// Page1: 61 00 01 00 02 00 44 F7  01 B0 91 20 00 20 20 02
// Page2: A1 80 00 FF 00 FF FF FF  FF FF FF FF D1 E0 86 80
// Page3: E1 30 00 00 90 00 10 FF  00 FE FF FD FF FE FF FF

#include "rtl8019as.h"
#include "event.h"
#include "interrupt.h"
#include "monitor.h"
#include "scheduler.h"

// コンストラクタ
RTL8019ASDevice::RTL8019ASDevice(uint n, int vector_)
	: inherited(OBJ_ETHERNET(n))
{
	SetName(string_format("RTL8019AS#%u", n));
	ClearAlias();
	AddAlias(string_format("RTL8019AS%u", n));
	AddAlias(string_format("Net%u", n));

	intr_vector = vector_;

	monitor = gMonitorManager->Regist(ID_MONITOR_RTL8019AS(n), this);
	monitor->SetCallback(&RTL8019ASDevice::MonitorScreen);
	monitor->SetSize(71, 19);
}

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

// 初期化
bool
RTL8019ASDevice::Init()
{
	uint n = GetId() - OBJ_ETHERNET0;

	hostnet->SetPortName(string_format("Nereid#%u", n));

	// MAC アドレスを取得
	MacAddr macaddr;
	if (inherited::GetConfigMacAddr(n, &macaddr, false) == false) {
		// エラーメッセージ表示済み
		return false;
	}
	// PROM 領域 (実質16バイト) を作成
	memset(&prom[0], 0xff, prom.size());
	macaddr.ExportTo(&prom[0]);
	prom[0xe] = 'W';
	prom[0xf] = 'W';

	interrupt = GetInterruptDevice();

	// ホストからの受信コールバックを登録
	scheduler->ConnectMessage(MessageID::HOSTNET_RX(n), this,
		ToMessageCallback(&RTL8019ASDevice::RxMessage));

	auto evman = GetEventManager();
	tx_event = evman->Regist(this,
		ToEventCallback(&RTL8019ASDevice::TXEvent),
		string_format("RTL8019AS%u TX", n));
	rx_event = evman->Regist(this,
		ToEventCallback(&RTL8019ASDevice::RXEvent),
		string_format("RTL8019AS%u RX", n));

	txled_event = evman->Regist(this,
		ToEventCallback(&RTL8019ASDevice::TXLedEvent),
		string_format("RTL8019AS%u TX LED", n));
	rxled_event = evman->Regist(this,
		ToEventCallback(&RTL8019ASDevice::RXLedEvent),
		string_format("RTL8019AS%u RX LED", n));

	return true;
}

// リセット
void
RTL8019ASDevice::ResetHard(bool poweron)
{
	if (poweron) {
		// 電源オン時の不定値は適当。ハードリセットの影響を受けない。
		// いずれにしても極力ゼロじゃない値にしておく。
		local_addr = 0xff00;
		boundary_addr = 0xcf00;
		current_page_addr = 0xfc00;
		remote_addr = 0xffff;

		// TSR/RSR は初回送信/受信時まで不定 (DP83901A.pdf)
		tsr = 0xcc;
		rsr = 0xcc;

		// PSTART は電源オンでだいたいこれが読める?
		// ハードリセットの影響を受けない。
		pstart_addr = 0x8000;

		// RCR は電源オン時だいたい $d1 な気がする
		monitor_mode = false;
		promisc_mode = true;
		accept_mcast = false;
		accept_bcast = false;
		accept_short = false;
		accept_error = true;

		// TCR は電源オン時だいたい $e0 な気がする
		tcr = 0x00;

		// DCR は電源オン時 $86 になることが多い気がする
		dcr = RTL8019::DCR_BOS;

		// XXX カウンタがリセットでどうなるかは未調査
		for (auto& c : error_counter) {
			c = 0;
		}
	}

	// 処理中は全部破棄
	scheduler->StopEvent(tx_event);
	scheduler->StopEvent(rx_event);
	tx_in_progress = false;
	rx_packet.Clear();

	// LED は電源オンで点灯。リセットでも点灯に戻りそうな気はする。
	tx_led = true;
	rx_led = true;
	scheduler->StopEvent(txled_event);
	scheduler->StopEvent(rxled_event);

	// ハードリセットで、TPSR, RCR は変化しない。

	// CR は初期化される。
	pagesel = 0;
	dmamode = DMAMode::Complete;

	// PSTOP は 0 になるように見える。
	pstop_addr  = 0x0000;

	// TCR.LB は 0 (Normal Operation) になる。それ以外は変化しない。
	tcr &= ~RTL8019::TCR_LB_MASK;

	// DCR は LAS=1 になる。それ以外は変化しない。
	dcr |= RTL8019::DCR_LAS;

	// ISR は、電源オン後にクリアしろと書いてあるのでおそらく不定。
	// 実際 $ef が読めがち。
	intr_stat = 0xef;
	intr_mask = 0x00;
	ChangeState(State::Stop);

	// Page3 の RTL8019AS 部分はだいたいリセットされそう?
	config_write_enable = false;
	verid = 0;
	irq_enable = true;
	brom_force_disable = false;

	// XXX 初期値は?
	mediatype = RTL8019::MediaType_10BaseT_Auto;

	ChangeInterrupt();
}

// リセットポートによるソフトリセット
void
RTL8019ASDevice::Reset()
{
	// リセットといっても実質ほぼ Stop じゃないかと思う。
	// 実際ざっと試した限りレジスタの値は変化しなかった。
	ChangeState(State::Stop);
}

/*static*/ inline uint32
RTL8019ASDevice::Decoder(uint32 addr)
{
	return addr & 0x3f;
}

busdata
RTL8019ASDevice::Read(busaddr addr)
{
	busdata data;
	uint32 paddr = addr.Addr();
	uint32 reqsize = addr.GetSize();
	uint32 datasize = std::min(2 - (paddr & 1), reqsize);
	uint32 offset = Decoder(paddr) >> 1;

	if (offset < 0x10) {
		// レジスタ部は、上位にレジスタ、下位は $ff が読める。
		// ワードで読んでも上位にレジスタ、下位は $ff なので、
		// レジスタと $ff のバイト配置とも言えるかも。
		if ((paddr & 1) == 0) {
			uint32 rn = pagesel + offset;
			data = ReadReg(rn);
		} else {
			data = 0xff;
		}
		data |= BusData::Size1;
	} else if (offset < 0x18) {
		// DMA 部。
		if (datasize == 1) {
			data = ReadDMA8(paddr & 1);
			data |= BusData::Size1;
		} else {
			data = ReadDMA16();
			data |= BusData::Size2;
		}
	} else {
		// リセットポート
		putlog(1, "Reset");
		Reset();
		data = 0xffff >> ((2 - datasize) * 8);
		data |= busdata::Size(datasize);
	}

	return data;
}

busdata
RTL8019ASDevice::Write(busaddr addr, uint32 data)
{
	busdata r;
	uint32 paddr = addr.Addr();
	uint32 reqsize = addr.GetSize();
	uint32 datasize = std::min(2 - (paddr & 1), reqsize);
	uint32 offset = Decoder(paddr) >> 1;

	data >>= (reqsize - datasize) * 8;

	if (offset < 0x10) {
		// レジスタ部は、上位がレジスタ、下位は無視される(バスエラーではない)。
		// ワードで書いても、上位がレジスタ、下位は無視されるので、
		// レジスタと N.C. のバイト配置とも言えるかも。
		if ((paddr & 1) == 0) {
			uint32 rn = pagesel + offset;
			WriteReg(rn, data);
		} else {
		}
		r |= BusData::Size1;
	} else if (offset < 0x18) {
		// DMA 部。
		if (datasize == 1) {
			WriteDMA8(data);
			r |= BusData::Size1;
		} else {
			WriteDMA16(data);
			r |= BusData::Size2;
		}
	} else {
		putlog(1, "Reset");
		Reset();
		r |= busdata::Size(datasize);
	}

	return r;
}

busdata
RTL8019ASDevice::Peek1(uint32 addr)
{
	uint32 offset = Decoder(addr);

	if (offset < 0x10 * 2) {
		if (offset % 2 == 0) {
			uint32 n = pagesel + offset / 2;
			return PeekReg(n);
		} else {
			return 0xff;
		}
	} else if (offset < 0x18 * 2) {
		return PeekDMA(offset & 1);
	} else {
		// XXX ?
		return 0xff;
	}
}

void
RTL8019ASDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	uint32 val;
	const int x = 6;
	const int x2 = 59;
	int y;

	screen.Clear();
	y = 0;

	// 0         1         2         3         4         5
	// 012345678901234567890123456789012345678901234567890123456789
	// CFG0 $xx 12345 12345 12345 12345 12345 12345 12345 12345

	val = PeekCR();
	screen.Puts(0, y, "CR:");
	screen.Print(x, y, "$%02x", val);
	static const char * const cr_bits[] = {
		"", "", "", "", "", "TXP", "STA", "STP"
	};
	MonitorReg(screen, x + 4, y, val, cr_bits);
	screen.Print(x + 4, y, "PageSel=%x", val >> 6);
	uint rd = (val >> 3) & 7;
	screen.Print(x + 16, y, "RD=%x(%s)", rd, GetDMAName(rd));
	y++;

	val = PeekISR();
	screen.Puts(0, y, "ISR:");
	screen.Print(x, y, "$%02x", val);
	static const char * const isr_bits[] = {
		"RST", "RDC", "CNT", "OVW", "TXE", "RXE", "PTX", "PRX"
	};
	MonitorReg(screen, x + 4, y, val, isr_bits);
	y++;

	val = PeekIMR();
	screen.Puts(0, y, "IMR:");
	screen.Print(x, y, "$%02x", val);
	static const char * const imr_bits[] = {
		"-1", "RDCE", "CNTE", "OVWE", "TXEE", "RXEE", "PTXE", "PRXE"
	};
	MonitorReg(screen, x + 4, y, val, imr_bits);
	y++;

	val = PeekDCR();
	screen.Puts(0, y, "DCR:");
	screen.Print(x, y, "$%02x", val);
	static const char * const dcr_bits[] = {
		"-1", "FT1", "FT0", "ARM", "LS", "LAS", "(BOS)", "WTS"
	};
	MonitorReg(screen, x + 4, y, val, dcr_bits);
	y++;

	val = PeekTCR();
	screen.Puts(0, y, "TCR:");
	screen.Print(x, y, "$%02x", val);
	static const char * const tcr_bits[] = {
		"-1", "-1", "-1", "OFST", "ATD", "LB1", "LB0", "CRC"
	};
	MonitorReg(screen, x + 4, y, val, tcr_bits);
	y++;

	val = PeekTSR();
	screen.Puts(0, y, "TSR:");
	screen.Print(x, y, "$%02x", val);
	static const char * const tsr_bits[] = {
		"OWC", "CDH", "-0", "CRS", "ABT", "COL", "-1", "PTX"
	};
	MonitorReg(screen, x + 4, y, val, tsr_bits);
	y++;

	val = PeekRCR();
	screen.Puts(0, y, "RCR:");
	screen.Print(x, y, "$%02x", val);
	static const char * const rcr_bits[] = {
		"-1", "-1", "MON", "PRO", "AM", "AB", "AR", "SEP"
	};
	MonitorReg(screen, x + 4, y, val, rcr_bits);
	y++;

	val = PeekRSR();
	screen.Puts(0, y, "RSR:");
	screen.Print(x, y, "$%02x", val);
	static const char * const rsr_bits[] = {
		"DFR", "DIS", "PHY", "MPA", "-0", "FAE", "CRC", "PRX"
	};
	MonitorReg(screen, x + 4, y, val, rsr_bits);
	y++;

	// MAC アドレスは PAR0 が左側 (ビッグエンディアンぽい感じ)。
	screen.Print(0, y, "PAR0-5: %s", par.ToString(':').c_str());

	// マルチキャストフィルタは MAR0 が右側 (リトルエンディアンぽい感じ)。
	// MAR7-0: $00000000'00000000
	screen.Print(x + 23, y, "MAR7-0: $%08x'%08x",
		(uint32)(mar >> 32), (uint32)mar);
	y++;

	y++;

	val = Peek9346CR();
	screen.Puts(0, y, "9346:");
	screen.Print(x, y, "$%02x", val);
	static const char * const cr9346_bits[] = {
		"EEM1", "EEM0", "-1", "-1", "EECS", "EESK", "EEDI", "EEDO"
	};
	MonitorReg(screen, x + 4, y, val, cr9346_bits);
	uint eem = val >> 6;
	y++;

	val = PeekCONFIG0();
	screen.Puts(0, y, "CFG0:");
	screen.Print(x, y, "$%02x", val);
	static const char * const config0_bits[] = {
		"VID1", "VID0", "AUI", "PNPJP", "JP", "BNC", "-0", "-0"
	};
	MonitorReg(screen, x + 4, y, val, config0_bits);
	y++;

	val = PeekCONFIG1();
	screen.Puts(0, y, "CFG1:");
	screen.Print(x, y, "$%02x", val);
	static const char * const config1_bits[] = {
		"IRQEN", "IRQS2", "IRQS1", "IRQS0", "IOS3", "IOS2", "IOS1", "IOS0"
	};
	MonitorReg(screen, x + 4, y, val, config1_bits);
	y++;

	val = PeekCONFIG2();
	screen.Puts(0, y, "CFG2:");
	screen.Print(x, y, "$%02x", val);
	static const char * const config2_bits[] = {
		"PL1", "PL0", "BSELB", "BS4", "BS3", "BS2", "BS1", "BS0"
	};
	MonitorReg(screen, x + 4, y, val, config2_bits);
	y++;

	val = PeekCONFIG3();
	screen.Puts(0, y, "CFG3:");
	screen.Print(x, y, "$%02x", val);
	static const char * const config3_bits[] = {
		"PNP", "FUDUP", "LEDS1", "LEDS0", "-0", "SLEEP", "PWRDN", "ACTB"
	};
	MonitorReg(screen, x + 4, y, val, config3_bits);
	y++;

	val = PeekCONFIG4();
	screen.Puts(0, y, "CFG4:");
	screen.Print(x, y, "$%02x", val);
	static const char * const config4_bits[] = {
		"-1", "-1", "-1", "-1", "-1", "-1", "-1", "IOMS"
	};
	MonitorReg(screen, x + 4, y, val, config4_bits);
	y++;

	// x+        1         2         3         4
	// 01234567890123456789012345678901234567890123456789
	// EEM=0(AutoLoad) Ver=3(8019A/AS)  PL=0(10baseTAuto)
	static const char * const eem_str[] = {
		"Normal",
		"AutoLoad",
		"Program",
		"WriteEn",
	};
	screen.Print(x, y, "EEM=%u(%s)", eem, eem_str[eem]);

	static const char * const verid_str[] = {
		"8019A/AS",
		"?",
		"?",
		"8019",
	};
	screen.Print(x + 16, y, "VID=%u(%s)", verid, verid_str[verid]);

	static const char * const mediatype_str[] = {
		"10baseTAuto",
		"10baseT",
		"10base5",
		"10base2",
	};
	screen.Print(x + 33, y, "PL=%u(%s)", mediatype, mediatype_str[mediatype]);
	y++;

	// LED は、RTL8019AS の LED ピンではなく Nereid ボード上の LED で表す。
	// Nereid ボードを上 (実装面側) から見るとこのような位置関係と結線に
	// なっているため(素子は省略)、RTL8019AS の LED0,1,2 (LINK, RX, TX) と
	// Nereid 基板上の LE1,2,3 (LINK, TX, RX) は並び順が違うことに注意。
	//
	//       LINK              TX   RX
	//     (Orange)          (Red) (Green)
	//              +------+
	// +------[*]---| RJ45 |---[*]-[*]---------[USB]---+
	// |   LE1 |    +------+ LE2|   |LE3               |
	// |       |                |   |
	// |       +----------------|---|---+
	// |                        |   |   |
	// |                 +------+---+---+----+
	// |       RTL8019AS |     63  62  61    |
	// |                 |   LED2 LED1 LED0  |
	// |                 |  (TX) (RX) (LINK) |
	//
	y++;
	screen.Puts(0, y, "LED:");
	screen.Puts(x, y, TA::ReverseOrange, "LED1");
	screen.Puts(x + 4, y, "(LINK)");
	screen.Puts(x + 11, y, tx_led ? TA::ReverseRed   : TA::Off, "LED2");
	screen.Puts(x + 15, y, "(TX)");
	screen.Puts(x + 20, y, rx_led ? TA::ReverseGreen : TA::Off, "LED3");
	screen.Puts(x + 24, y, "(RX)");

	// 右カラム
	y = 0;
	screen.Puts(x2, y++, "RSAR");
	screen.Print(x2 - 1, y++, "(CRDA) =$%04x", PeekCRDA());
	screen.Print(x2, y++, "RBCR  =$%04x", remote_count);
	y++;
	//screen.Puts(x2 + 10, y, TA::Disable, "00");
	screen.Print(x2, y++, "TPSR  =$%02x", transmit_page_start_addr >> 8);
	screen.Print(x2, y++, "TBCR  =$%04x", transmit_byte_count);
	y++;
	//screen.Puts(x2 + 10, y, TA::Disable, "00");
	screen.Print(x2, y++, "PSTART=$%02x", PeekPSTART());
	//screen.Puts(x2 + 10, y, TA::Disable, "00");
	screen.Print(x2, y++, "PSTOP =$%02x", PeekPSTOP());
	//screen.Puts(x2 + 10, y, TA::Disable, "00");
	screen.Print(x2, y++, "BNRY  =$%02x", PeekBNRY());
	//screen.Puts(x2 + 10, y, TA::Disable, "00");
	screen.Print(x2, y++, "CURR  =$%02x", PeekCURR());
	y++;
	screen.Print(x2, y++, "CLDA  =$%04x", PeekCLDA());
	y++;
	screen.Print(x2, y++, "NCR   =  $%02x", PeekNCR());
	screen.Print(x2, y++, "CNTR0 =  $%02x", PeekCNTR(0));
	screen.Print(x2, y++, "CNTR1 =  $%02x", PeekCNTR(1));
	screen.Print(x2, y++, "CNTR2 =  $%02x", PeekCNTR(2));
}

// レジスタをビットごとに表示する。MonitorScreen の下請け。
// "-" で始まっていたら Disable 色にし、次の文字から表示。
void
RTL8019ASDevice::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 * 6, y, TA::Disable, names[i] + 1);
		} else {
			bool b = reg & (1U << (7 - i));
			screen.Puts(x + i * 6, y, TA::OnOff(b), names[i]);
		}
	}
}

// レジスタ読み出し (rn はページ番号込み)
uint32
RTL8019ASDevice::ReadReg(uint32 rn)
{
	uint32 data;

	switch (rn) {
	 case RTL8019::CR:		// CR は全ページで同じものが見える
	 case RTL8019::CR_1:
	 case RTL8019::CR_2:
	 case RTL8019::CR_3:
		data = PeekCR();
		break;

	 case RTL8019::CLDA0:
		data = PeekCLDA() & 0xff;
		break;
	 case RTL8019::CLDA1:
		data = PeekCLDA() >> 8;
		break;

	 case RTL8019::BNRY:
		data = PeekBNRY();
		break;

	 case RTL8019::TSR:
		data = PeekTSR();
		break;

	 case RTL8019::NCR:
		data = PeekNCR();
		break;

	 case RTL8019::FIFO:
		// それっぽい値は返すけど未実装
		data = PeekFIFO();
		putlog(0, "Read %s -> $%02x (NOT IMPLEMENTED)",
			RegNameR(rn).c_str(), data);
		return data;

	 case RTL8019::ISR:
		data = PeekISR();
		break;

	 case RTL8019::CRDA0:
		data = PeekCRDA() & 0xff;
		break;
	 case RTL8019::CRDA1:
		data = PeekCRDA() >> 8;
		break;

	 case RTL8019::ID0:
		data = Peek8019ID0();
		break;
	 case RTL8019::ID1:
		data = Peek8019ID1();
		break;

	 case RTL8019::RSR:
		data = PeekRSR();
		break;

	 case RTL8019::CNTR0:
	 case RTL8019::CNTR1:
	 case RTL8019::CNTR2:
	 {
		// 読み出しでリセットされる
		uint n = rn - RTL8019::CNTR0;
		data = PeekCNTR(n);
		error_counter[n] = 0;
		break;
	}

	 case RTL8019::PAR0
	  ... RTL8019::PAR5:
		data = PeekPAR(rn - RTL8019::PAR0);
		break;

	 case RTL8019::CURR:
		data = PeekCURR();
		break;

	 case RTL8019::MAR0
	  ... RTL8019::MAR7:
		data = PeekMAR(rn - RTL8019::MAR0);
		break;

	 case RTL8019::PSTART_2:
		data = PeekPSTART();
		break;

	 case RTL8019::PSTOP_2:
		data = PeekPSTOP();
		break;

	 case RTL8019::TPSR_2:
		data = PeekTPSR();
		break;

	 case RTL8019::RCR_2:
		data = PeekRCR();
		break;

	 case RTL8019::TCR_2:
		data = PeekTCR();
		break;

	 case RTL8019::DCR_2:
		data = PeekDCR();
		break;

	 case RTL8019::IMR_2:
		data = PeekIMR();
		break;

	 case RTL8019::CR9346:
		data = Peek9346CR();
		break;

	 case RTL8019::BPAGE:
		data = PeekBPAGE();
		break;

	 case RTL8019::CONFIG0:
		data = PeekCONFIG0();
		break;

	 case RTL8019::CONFIG1:
		data = PeekCONFIG1();
		break;

	 case RTL8019::CONFIG2:
		data = PeekCONFIG2();
		break;

	 case RTL8019::CONFIG3:
		data = PeekCONFIG3();
		break;

	 case RTL8019::CSNSAV:
		data = PeekCSNSAV();
		break;

	 case 0x39:
		data = PeekReg39();
		break;

	 case RTL8019::INTR:
		data = PeekINTR();
		break;

	 case RTL8019::CONFIG4:
		data = PeekCONFIG4();
		break;

	 case 0x23:
	 case 0x25 ... 0x2b:
	 case 0x37:
	 case 0x3a:
	 case 0x3c:
	 case 0x3e:
	 case 0x3f:
		data = 0xff;
		break;

	 default:
		putlog(0, "Read %s (NOT IMPLEMENTED)", RegNameR(rn).c_str());
		return 0xff;
	}

	putlog(2, "%s -> $%02x", RegNameR(rn).c_str(), data);
	return data;
}

// DMA ポートのバイト読み出し
uint32
RTL8019ASDevice::ReadDMA8(uint32 a0)
{
	uint32 data;

	// DMA モードが Read でない時に読み込むと、
	// 現在の CRDA が読める (がカウンタは変化しない) ような気がする。

	uint32 prev_addr = remote_addr;
	if (remote_addr < 0x4000) {
		// PROM 領域。
		// バイトアクセスだと偶数バイト、奇数バイトは同じものが見える。
		data = ReadPROM(remote_addr);
	} else if ((remote_addr & 0x4000)) {
		// RAM 領域。
		if (word_transfer || (remote_addr & 1) == 0) {
			// WTS==1 か偶数番地なら普通に読み出せる。
			// たぶんワードで読み出せてるがそのうち上位 (LE でいう下位)
			// だけが見えてる状態だと思う。
			uint16 addr = remote_addr & 0xfffe;
			data = ReadRAM(addr);
		} else {
			data = 0;
		}
	} else {
		// 未実装領域。実機ではバスの残像か何かが読めているようだが。
		data = 0xff;
	}
	PostDMA();

	if (__predict_false(loglevel >= 2)) {
		ReadDMAlog(prev_addr, data, 2);
	}

	return (uint8)data;
}

// DMA ポートのワード読み出し
uint32
RTL8019ASDevice::ReadDMA16()
{
	uint32 data;

	// DMA モードが Read でない時に読み込むと、
	// 現在の CRDA が読める (がカウンタは変化しない) ような気がする。

	// RTL8019AS は基本 LE で、Nereid 上でデータバスがクロスされてるはず
	//
	//         680x0                             RTL8019AS
	// +---------------------+       +----------------------------------+
	// レジスタ    データバス         データバス     (レジスタ)  内蔵メモリ
	//          +- D15-8: $86  <-  -  D15-8: $dd <-+             +00: $86
	//  $86dd <-+                ×                +- $dd86  <-  +01: $dd
	//          +- D7-0 : $dd  <-  -  D7-0 : $86 <-+

	uint32 prev_addr = remote_addr;
	if (remote_addr < 0x4000) {
		// PROM 領域。
		// ワードアクセスだと上位(偶数番地)にデータ、下位(奇数番地)は $ff。
		data  = ReadPROM(remote_addr) << 8;
		data |= 0xff;
	} else if ((remote_addr & 0x4000)) {
		// RAM 領域。
		// 偶数番地からのアクセスはワード値を返す。
		// WTS==1 なら、奇数番地でも A0 をマスクしたようにワード値を返す。
		// WTS==0 で奇数番地ならおかしなことになる (0000 とか ffff が読める?)
		// ようだ。
		if (word_transfer || (remote_addr & 1) == 0) {
			uint16 addr = remote_addr & 0xfffe;
			data  = ReadRAM(addr) << 8;
			data |= ReadRAM(addr + 1);
		} else {
			data = 0;
		}
	} else {
		// 未実装領域。実機ではバスの残像か何かが読めているようだが。
		data = 0xffff;
	}
	PostDMA();

	if (__predict_false(loglevel >= 2)) {
		ReadDMAlog(prev_addr, data, 4);
	}

	return (uint16)data;
}

// DMA 読み出しのログ。
// loglevel >= 3 なら、すべて表示。
// loglevel == 2 なら、受信ヘッダの読み出しと転送完了時のサマリーのみ表示。
// 受信ヘッダは、実際のバッファ上のデータが受信ヘッダかどうかではなく、
// ページ先頭から4バイトを読み出そうとしているものをヘッダの読み出しだと
// 思って表示する。ゲスト OS がヘッダだと思って読んだ(であろう)ところが
// どう読めたかが見たいので。
// この関数自体は loglevel >= 2 で呼ばれる。
void
RTL8019ASDevice::ReadDMAlog(uint32 prev_addr, uint32 data, int width)
{
	// ページ先頭から 4 バイトの転送ならヘッダ読み込みだと思ってみる
	bool header =
		((remote_start_addr & 0x00ff) == 0) && (remote_start_count == 4);

	if (loglevel >= 3 || header) {
		std::string databuf = strhex(data, width);
		if ((int32)data < 0) {
			putlogn("DMA[$%04x] -> $%s (No Data)", prev_addr, databuf.c_str());
		} else {
			putlogn("DMA[$%04x] -> $%s", prev_addr, databuf.c_str());
		}
	}

	if (__predict_false(remote_count == 0)) {
		putlogn("DMA %u bytes transferred", remote_start_count);
	}
}

// レジスタ書き込み (rn はページ番号込み)
void
RTL8019ASDevice::WriteReg(uint32 rn, uint32 data)
{
	switch (rn) {
	 case RTL8019::CR:		// CR は全ページで同じものが見える
	 case RTL8019::CR_1:
	 case RTL8019::CR_2:
	 case RTL8019::CR_3:
		WriteCR(data);
		break;

	 case RTL8019::PSTART:
		WritePSTART(data);
		break;

	 case RTL8019::PSTOP:
		WritePSTOP(data);
		break;

	 case RTL8019::BNRY:
		WriteBNRY(data);
		break;

	 case RTL8019::TPSR:
		WriteTPSR(data);
		break;

	 case RTL8019::TBCR0:
		WriteTBCR0(data);
		break;
	 case RTL8019::TBCR1:
		WriteTBCR1(data);
		break;

	 case RTL8019::ISR:
		WriteISR(data);
		break;

	 case RTL8019::RSAR0:
		WriteRSAR0(data);
		break;
	 case RTL8019::RSAR1:
		WriteRSAR1(data);
		break;

	 case RTL8019::RBCR0:
		WriteRBCR0(data);
		break;
	 case RTL8019::RBCR1:
		WriteRBCR1(data);
		break;

	 case RTL8019::RCR:
		WriteRCR(data);
		break;

	 case RTL8019::TCR:
		WriteTCR(data);
		break;

	 case RTL8019::DCR:
		WriteDCR(data);
		break;

	 case RTL8019::IMR:
		WriteIMR(data);
		break;

	 case RTL8019::PAR0
	  ... RTL8019::PAR5:
		WritePAR(rn - RTL8019::PAR0, data);
		break;

	 case RTL8019::CURR:
		WriteCURR(data);
		break;

	 case RTL8019::MAR0
	  ... RTL8019::MAR7:
		WriteMAR(rn - RTL8019::MAR0, data);
		break;

	 case RTL8019::CR9346:
		Write9346CR(data);
		break;

	 case RTL8019::CONFIG0:
		WriteCONFIG0(data);
		break;

	 case RTL8019::CONFIG1:
		WriteCONFIG1(data);
		break;

	 case RTL8019::CONFIG2:
		WriteCONFIG2(data);
		break;

	 case RTL8019::CONFIG3:
		WriteCONFIG3(data);
		break;

	 default:
		putlog(0, "%s <- $%02x (NOT IMPLEMENTED)",
			RegNameW(rn).c_str(), data);
		break;
	}
}

// CR レジスタ書き込み
void
RTL8019ASDevice::WriteCR(uint32 data)
{
	std::string msg;
	uint32 newpage;
	uint32 newmode;
	bool newstp;
	State newstate;

	// ページ
	newpage = (data >> 2) & 0x30;
	if (newpage != pagesel) {
		pagesel = newpage;
		if (loglevel >= 2) {
			msg = string_format(" Page=%x", pagesel >> 4);
		}
	}

	// DMA モード
	newmode = (data >> 3) & 7;
	if ((DMAMode)newmode != dmamode) {
		dmamode = (DMAMode)newmode;
		if (loglevel >= 2) {
			msg += " DMA=";
			msg += GetDMAName(newmode);
		}
	}

	// TXP ビット
	if (loglevel >= 2) {
		if (tx_in_progress == false && (data & RTL8019::CR_TXP)) {
			msg += " TXP";
		}
	}

	// STP ビット
	//
	// 現行		STP書き込み
	// Normal	0	: 何もしない
	// InProg	0	: どうする?
	// Stop		0	: -> Normal へ
	// Normal	1	: -> InProg へ
	// InProg	1	: 何もしない
	// Stop		1	: 何もしない
	newstp = (data & RTL8019::CR_STP);
	newstate = state;
	if (state == State::Normal) {
		if (newstp) {
			// Normal 中に STP がセットされたらストップ状態へ移行。
			newstate = State::Stop;
		}
	} else {
		if (newstp == false) {
			// Stop 中に STP がクリアされたら通常状態へ移行。
			// Progress もこっち?
			newstate = State::Normal;
		}
	}
	if (loglevel >= 2) {
		if (newstate != state) {
			msg += string_format(" STP=%u", newstp);
		}
	}

	// 表示内容が揃ったところでログ表示
	if (loglevel >= 2) {
		if (msg.empty()) {
			putlogn("CR <- $%02x", data);
		} else {
			putlogn("CR <- $%02x (%s)", data, msg.c_str() + 1);
		}
	}

	if (dmamode == DMAMode::RemoteRead || dmamode == DMAMode::RemoteWrite) {
		// ログ用に DMA 開始時の初期値を保存
		remote_start_addr  = remote_addr;
		remote_start_count = remote_count;
	} else if (__predict_false(dmamode == DMAMode::SendPacket)) {
		putlog(0, "CR: SendPacket Command (NOT IMPLEMENTED)");
	}

	// RTL8019AS では STA ビットは何もせず状態を保持してるだけ?
	cr_sta = (data & RTL8019::CR_STA);

	// XXX
	// NetBSD の sys/dev/ic/ne2000.c::detect_ne2000() は CR.STP を下げて、
	// ISR.RST が立つまで100回チェックしている。
	// RTL8019AS.pdf (DP83901A.pdf も) には STP を下げると ISR.RST もクリア
	// されると書いてあるので、このループは成立しないはず。
	// そして 100回試しても RST が立たなかったら検出失敗に goto するところは
	// (rev1.2 の時点から) if 0 されている (そりゃそれだと動かないからね…)。
	// 他の互換チップのことは分からんけど、
	// STP を下げて RST が下がるのを待つ、の間違いじゃないのかなあ。

	// XXX TXP と STP 同時にセットするとどうなる?

	// STP を立てられた時に送受信処理中でなければすぐにリセット。
	// 処理中のものがあればそれが終わり次第そっちで変更するので
	// ここでは対応不要。
	if (newstate != state) {
		ChangeState(newstate);
	}

	// 送信開始
	if ((data & RTL8019::CR_TXP)) {
		Transmit();
	}
}

// PSTART レジスタ書き込み
void
RTL8019ASDevice::WritePSTART(uint32 data)
{
	putlog(2, "PSTART <- $%02x", data);
	pstart_addr = data << 8;
}

// PSTOP レジスタ書き込み
void
RTL8019ASDevice::WritePSTOP(uint32 data)
{
	putlog(2, "PSTOP  <- $%02x", data);
	pstop_addr = data << 8;
}

// BNRY レジスタ書き込み
void
RTL8019ASDevice::WriteBNRY(uint32 data)
{
	putlog(2, "BNRY <- $%02x", data);
	boundary_addr = data << 8;
}

// TPSR レジスタ書き込み
void
RTL8019ASDevice::WriteTPSR(uint32 data)
{
	putlog(2, "TPSR <- $%02x", data);
	transmit_page_start_addr = data << 8;
}

// TBCR0 レジスタ書き込み
void
RTL8019ASDevice::WriteTBCR0(uint32 data)
{
	transmit_byte_count &= 0xff00;
	transmit_byte_count |= data;

	putlog(2, "TBCR0 <- $%02x (TBCR=$%04x)", data, transmit_byte_count);
}

// TBCR1 レジスタ書き込み
void
RTL8019ASDevice::WriteTBCR1(uint32 data)
{
	transmit_byte_count &= 0x00ff;
	transmit_byte_count |= data << 8;

	putlog(2, "TBCR1 <- $%02x (TBCR=$%04x)", data, transmit_byte_count);
}

// ISR レジスタ書き込み
void
RTL8019ASDevice::WriteISR(uint32 data)
{
	// '1' が書き込まれたビットをクリア
	intr_stat &= ~data;
	// bit7 (RST) は関係ないので落としておく
	intr_stat &= ~0x80;

	putlog(2, "ISR <- $%02x (ISR=$%02x)", data, intr_stat);

	ChangeInterrupt();
}

// RSAR0 レジスタ書き込み
void
RTL8019ASDevice::WriteRSAR0(uint32 data)
{
	// RSAR への書き込みは実質 CRDA への書き込みで、両者は別のレジスタでは
	// なく、よくあるリードとライトで名前が違うだけのように見える。
	// 実際 RSAR=$4000 から $10 バイト転送後の CRDA は $4010 になっており
	// ここから RSAR を書き込まずにもう一度 RBCR=$10 転送すると $4010 から
	// の $10 バイトが読める。

	remote_addr &= 0xff00;
	remote_addr |= data;

	putlog(2, "RSAR0 <- $%02x (CRDA=$%04x)", data, remote_addr);
}

// RSAR1 レジスタ書き込み
void
RTL8019ASDevice::WriteRSAR1(uint32 data)
{
	remote_addr &= 0x00ff;
	remote_addr |= data << 8;

	putlog(2, "RSAR1 <- $%02x (CRDA=$%04x)", data, remote_addr);
}

// RBCR0 レジスタ書き込み
void
RTL8019ASDevice::WriteRBCR0(uint32 data)
{
	remote_count &= 0xff00;
	remote_count |= data;

	putlog(2, "RBCR0 <- $%02x (RBCR=$%04x)", data, remote_count);
}

// RBCR1 レジスタ書き込み
void
RTL8019ASDevice::WriteRBCR1(uint32 data)
{
	remote_count &= 0x00ff;
	remote_count |= data << 8;

	putlog(2, "RBCR1 <- $%02x (RBCR=$%04x)", data, remote_count);
}

// RCR レジスタ書き込み
void
RTL8019ASDevice::WriteRCR(uint32 data)
{
	putlog(2, "RCR <- $%02x", data);
	monitor_mode = (data & RTL8019::RCR_MON);
	promisc_mode = (data & RTL8019::RCR_PRO);
	accept_mcast = (data & RTL8019::RCR_AM);
	accept_bcast = (data & RTL8019::RCR_AB);
	accept_short = (data & RTL8019::RCR_AR);
	accept_error = (data & RTL8019::RCR_SEP);
}

// TCR レジスタ書き込み
void
RTL8019ASDevice::WriteTCR(uint32 data)
{
	// OFST, ATD, LB1, CRC すべて未実装。
	// ループバック(LB1:0)は 0(通常), 1(内部) のみ認めておく?
	if ((data & 0x1d) != 0) {
		putlog(0, "TCR <- $%02x (NOT IMPLEMENTED)", data);
	} else {
		putlog(2, "TCR <- $%02x", data);
	}
	tcr = data;
}

// DCR レジスタ書き込み
void
RTL8019ASDevice::WriteDCR(uint32 data)
{
	// FT は NE2000 用なので不要らしい。
	// LAS (ロングワード転送?) は RTL8019AS がサポートしてないらしい。
	// BOS (バイトオーダ選択) は RTL8019AS がサポートしてない。
	// ARM, LS(==0) はエミュレータ的未サポート。
	std::string msg;
	if ((data & RTL8019::DCR_ARM) != 0) {
		msg += "ARM=1";
	}
	if ((data & RTL8019::DCR_LS) == 0) {
		msg += "LS=0";
	}
	if (msg.empty() == false) {
		putlog(0, "DCR <- $%02x (%s NOT IMPLEMENTED)", data, msg.c_str());
	} else {
		putlog(2, "DCR <- $%02x", data);
	}
	dcr = data & 0xfe;
	// XXX WTS の設定と DMA ポートをアクセスする際のバス幅が一致してない
	// ケースは未対応。
	word_transfer = (data & RTL8019::DCR_WTS);
}

// IMR レジスタ書き込み
void
RTL8019ASDevice::WriteIMR(uint32 data)
{
	putlog(2, "IMR <- $%02x", data);
	intr_mask = data & ~0x80;
	ChangeInterrupt();
}

// PAR0..5 レジスタ書き込み
void
RTL8019ASDevice::WritePAR(uint n, uint32 data)
{
	par[n] = data;

	if (__predict_false(loglevel >= 3)) {
		putlogn("PAR%u <- $%02x", n, data);
	} else {
		if (__predict_false(n == 5)) {
			putlog(2, "PAR <- %02x:%02x:%02x:%02x:%02x:%02x",
				par[0], par[1], par[2], par[3], par[4], par[5]);
		}
	}
}

// CURR レジスタ書き込み
void
RTL8019ASDevice::WriteCURR(uint32 data)
{
	putlog(2, "CURR <- $%02x", data);
	current_page_addr = data << 8;
	local_addr = current_page_addr;
}

// MAR0..7 レジスタ書き込み
void
RTL8019ASDevice::WriteMAR(uint n, uint32 data)
{
	mar &= ~(0xffULL << (n * 8));
	mar |= (uint64)data << (n * 8);

	if (__predict_false(loglevel >= 3)) {
		putlogn("MAR%u <- $%02x", n, data);
	} else {
		if (__predict_false(n == 7)) {
			putlog(2, "MAR <- $%08x'%08x", (uint32)(mar >> 32), (uint32)mar);
		}
	}
}

// 9346CR レジスタ書き込み
void
RTL8019ASDevice::Write9346CR(uint32 data)
{
	uint32 eem = (data >> 6) & 3;

	if (eem == 3) {
		// EEM = %11 で CONFIG1-3 への書き込みを許可。
		config_write_enable = true;
	} else {
		config_write_enable = false;
	}

	if (eem == 1 || eem == 2) {
		putlog(0, "9346CR <- $%02x EEM=%u (NOT IMPLEMENTED)", data, eem);
		return;
	}
	if ((data & 0x0f) != 0) {
		putlog(0, "9346CR <- $%02x (NOT IMPLEMENTED)", data);
		return;
	}
	putlog(2, "9346CR <- $%02x", data);
}

void
RTL8019ASDevice::WriteCONFIG0(uint32 data)
{
	if (config_write_enable == false) {
		putlog(1, "CONFIG0 <- $%02x: Config write is disabled", data);
		return;
	}

	// CONFIG0 は元々読み出し専用レジスタで、このうち VERID (2bit) で
	// RTL8019 のモデル情報が読み出せる構造だったっぽい。
	//  %11 なら RTL8019
	//  %00 なら RTL8019A
	// で、RTL8019AS ではこの VERID フィールドだけ R/W に変えて、
	// 設定によって RTL8019(無印) としても振る舞えるようにした(?)っぽい。
	// ほんまかいな。
	verid = data >> 6;

	if (verid != 0) {
		putlog(2, "CONFIG0 <- $%02x VERID=%x (NOT IMPLEMENTED)", data, verid);
	} else {
		putlog(2, "CONFIG0 <- $%02x", data);
	}
}

void
RTL8019ASDevice::WriteCONFIG1(uint32 data)
{
	if (config_write_enable == false) {
		putlog(1, "CONFIG1 <- $%02x: Config write is disabled", data);
		return;
	}

	putlog(2, "CONFIG1 <- $%02x", data);
	irq_enable = (data & RTL8019::CONFIG1_IRQEN);
	ChangeInterrupt();
}

void
RTL8019ASDevice::WriteCONFIG2(uint32 data)
{
	if (config_write_enable == false) {
		putlog(1, "CONFIG2 <- $%02x: Config write is disabled", data);
		return;
	}

	mediatype = (data >> 6);
	// どのみち BS4-0 で BROM Disabled なので、BSELB の影響は受けない。
	// 値だけ記憶しておく。
	brom_force_disable = (data & RTL8019::CONFIG2_BSELB);
}

void
RTL8019ASDevice::WriteCONFIG3(uint32 data)
{
	if (config_write_enable == false) {
		putlog(1, "CONFIG3 <- $%02x: Config write is disabled", data);
		return;
	}

	if ((data & 0x06) != 0) {
		// SLEEP, PWRDN はサポートしていない
		putlog(0, "CONFIG3 <- $%02x (NOT IMPLEMENTED)", data);
	} else {
		putlog(2, "CONFIG3 <- $%02x", data);
	}
	config3 = data;
}

// DMA ポートへのバイト書き込み
void
RTL8019ASDevice::WriteDMA8(uint32 data)
{
	// DMA モードが Write でない時に書き込むとどうなる?
	if (dmamode != DMAMode::RemoteWrite) {
		putlog(0, "DMA <- $%02x but not RemoteWrite (NOT IMPLEMENTED)", data);
		return;
	}

	uint16 prev_addr = remote_addr;
	bool written;
	if ((remote_addr & 0x4000)) {
		// RAM 領域。
		if (word_transfer || (remote_addr & 1) == 0) {
			// WTS==1 か偶数番地なら書き込めそう。未確認。
			uint16 addr = remote_addr & 0xfffe;
			WriteRAM(addr, data);
		} else {
			// 未確認。
		}
		written = true;
	} else {
		// 書き込み不可。たぶん何も起きない。
		written = false;
	}
	PostDMA();

	if (__predict_false(loglevel >= 1)) {
		if (written) {
			putlog(3, "DMA[$%04x] <- $%02x", prev_addr, data);
		} else {
			putlogn("DMA[$%04x] <- $%02x (Not writable)", prev_addr, data);
		}

		if (__predict_false(remote_count == 0)) {
			putlog(2, "DMA %u bytes transferred", remote_start_count);
		}
	}
}

// DMA ポートへのワード書き込み
void
RTL8019ASDevice::WriteDMA16(uint32 data)
{
	// DMA モードが Write でない時に書き込むとどうなる?
	if (dmamode != DMAMode::RemoteWrite) {
		putlog(0, "DMA <- $%04x but not RemoteWrite (NOT IMPLEMENTED)", data);
		return;
	}

	// RTL8019AS は基本 LE で、Nereid 上でデータバスがクロスされてるはず
	//
	//         680x0                             RTL8019AS
	// +---------------------+       +----------------------------------+
	// レジスタ    データバス         データバス     (レジスタ)  内蔵メモリ
	//         +-> D15-8: $86  -  ->  D15-8: $dd -+              +00: $86
	//  $86dd -+                ×                +-> $dd86  ->  +01: $dd
	//         +-> D7-0 : $dd  -  ->  D7-0 : $86 -+

	uint16 prev_addr = remote_addr;
	bool written;
	if ((remote_addr & 0x4000)) {
		// RAM 領域。
		// 書き込みは確認していないが Read 側からの推測で、
		// 偶数番地からのアクセスは書き込めるはず。
		// WTS==1 なら奇数番地でも A0 をマスクしたように振る舞うはず。
		// WTS==0 で奇数番地ならおかしなことになりそう。
		if (word_transfer || (remote_addr & 1) == 0) {
			uint16 addr = remote_addr & 0xfffe;
			WriteRAM(addr,     data >> 8);
			WriteRAM(addr + 1, data & 0xff);
		} else {
			// XXX 何が書けるかは未調査。
		}
		written = true;
	} else {
		// 書き込み不可。たぶん何も起きない。
		written = false;
	}
	PostDMA();

	if (__predict_false(loglevel >= 1)) {
		if (written) {
			putlog(3, "DMA[$%04x] <- $%04x", prev_addr, data);
		} else {
			putlogn("DMA[$%04x] <- $%04x (Not writable)", prev_addr, data);
		}

		if (__predict_false(remote_count == 0)) {
			putlog(2, "DMA %u bytes transferred", remote_start_count);
		}
	}
}

// DMA 転送後の共通処理。
// アドレスをインクリメント、カウントをデクリメント。
void
RTL8019ASDevice::PostDMA()
{
	// DCR.WTS==0(バイト転送) なら1回、
	// DCR.WTS==1(ワード転送) なら2回カウンタを進める。
	// その都度 0 チェックは行う。

	// カウンタが 0 でも、1回今の remote_addr からの読み出しは出来て、
	// ただしそれ以上は進まないので、ここにカウンタのチェックがあるっぽい。
	//
	// 実機でも、
	// 1. RSAR=$4000、RBCR=$0008 をセットして CR <- RemoteDMA
	//    -> $4000 から 8バイト読み出せて CRDA=$4008
	// 2. CR <- RemoteDMA
	//    -> $4008 から 1ワード(1回)読み出せて CRDA=$4008
	// 3. RBCR=$0008 をセットして CR <- RemoteDMA
	//    -> $4008 から 8バイト読み出せる。
	// となる。

	int n;

	if (word_transfer) {
		n = 2;
	} else {
		n = 1;
	}

	for (int i = 0; remote_count > 0 && i < n; i++) {
		remote_count--;
		remote_addr++;
		if (__predict_false(remote_addr == pstop_addr)) {
			remote_addr = pstart_addr;
		}
	}

	if (__predict_false(remote_count == 0)) {
		dmamode = DMAMode::Complete;
		intr_stat |= RTL8019::ISR_RDC;
		ChangeInterrupt();
	}
}

uint32
RTL8019ASDevice::PeekReg(uint32 rn) const
{
	switch (rn) {
	 case RTL8019::CR:		// CR は全ページで同じものが見える
	 case RTL8019::CR_1:
	 case RTL8019::CR_2:
	 case RTL8019::CR_3:
		return PeekCR();

	 case RTL8019::CLDA0:
		return PeekCLDA() & 0xff;
	 case RTL8019::CLDA1:
		return PeekCLDA() >> 8;

	 case RTL8019::BNRY:
		return PeekBNRY();

	 case RTL8019::TSR:
		return PeekTSR();

	 case RTL8019::NCR:
		return PeekNCR();

	 case RTL8019::FIFO:
		return PeekFIFO();

	 case RTL8019::ISR:
		return PeekISR();

	 case RTL8019::CRDA0:
		return PeekCRDA() & 0xff;
	 case RTL8019::CRDA1:
		return PeekCRDA() >> 8;

	 case RTL8019::ID0:
		return Peek8019ID0();
	 case RTL8019::ID1:
		return Peek8019ID1();

	 case RTL8019::RSR:
		return PeekRSR();

	 case RTL8019::CNTR0:
		return PeekCNTR(0);
	 case RTL8019::CNTR1:
		return PeekCNTR(1);
	 case RTL8019::CNTR2:
		return PeekCNTR(2);

	 case RTL8019::PAR0
	  ... RTL8019::PAR5:
		return PeekPAR(rn - RTL8019::PAR0);

	 case RTL8019::CURR:
		return PeekCURR();

	 case RTL8019::MAR0
	  ... RTL8019::MAR7:
		return PeekMAR(rn - RTL8019::MAR0);

	 case RTL8019::PSTART_2:
		return PeekPSTART();

	 case RTL8019::PSTOP_2:
		return PeekPSTOP();

	 case RTL8019::TPSR_2:
		return PeekTPSR();

	 case RTL8019::RCR_2:
		return PeekRCR();

	 case RTL8019::TCR_2:
		return PeekTCR();

	 case RTL8019::DCR_2:
		return PeekDCR();

	 case RTL8019::IMR_2:
		return PeekIMR();

	 case RTL8019::CR9346:
		return Peek9346CR();

	 case RTL8019::BPAGE:
		return PeekBPAGE();

	 case RTL8019::CONFIG0:
		return PeekCONFIG0();

	 case RTL8019::CONFIG1:
		return PeekCONFIG1();

	 case RTL8019::CONFIG2:
		return PeekCONFIG2();

	 case RTL8019::CONFIG3:
		return PeekCONFIG3();

	 case RTL8019::CSNSAV:
		return PeekCSNSAV();

	 case 0x39:
		return PeekReg39();

	 case RTL8019::INTR:
		return PeekINTR();

	 case RTL8019::CONFIG4:
		return PeekCONFIG4();
	}
	return 0xff;
}

uint32
RTL8019ASDevice::PeekCR() const
{
	uint32 data;

	// PS
	data = pagesel << 2;

	// RD
	switch (dmamode) {
	 case DMAMode::NotAllowed:
		break;
	 case DMAMode::RemoteRead:
		data |= RTL8019::CR_RD_READ;
		break;
	 case DMAMode::RemoteWrite:
		data |= RTL8019::CR_RD_WRITE;
		break;
	 case DMAMode::SendPacket:
		data |= RTL8019::CR_RD_SEND;
		break;
	 case DMAMode::Complete:
		data |= 0x20;
		break;
	}

	// TXP
	if (tx_in_progress) {
		data |= RTL8019::CR_TXP;
	}

	// STA
	data |= cr_sta;

	// STP
	if (state == State::Stop) {
		data |= RTL8019::CR_STP;
	}

	return data;
}

uint32
RTL8019ASDevice::PeekCLDA() const
{
	return local_addr;
}

uint32
RTL8019ASDevice::PeekBNRY() const
{
	return boundary_addr >> 8;
}

uint32
RTL8019ASDevice::PeekTSR() const
{
	uint32 data = tsr;

	// bit5 は %0、bit1 は %1 らしい。
	data &= ~0x20;
	data |=  0x02;

	return data;
}

uint32
RTL8019ASDevice::PeekNCR() const
{
	// コリジョンカウンタは不要なので実装していない
	return 0;
}

uint32
RTL8019ASDevice::PeekFIFO() const
{
	// 未実装だが $00 が読めているようだ
	return 0;
}

uint32
RTL8019ASDevice::PeekISR() const
{
	uint32 data = intr_stat;

	// RST は、Stop 状態とバッファフルの論理和?
	if (state == State::Stop || local_addr == boundary_addr) {
		data |= RTL8019::ISR_RST;
	}

	return data;
}

uint32
RTL8019ASDevice::PeekCRDA() const
{
	return remote_addr;
}

uint32
RTL8019ASDevice::Peek8019ID0() const
{
	return 0x50;
}

uint32
RTL8019ASDevice::Peek8019ID1() const
{
	return 0x70;
}

uint32
RTL8019ASDevice::PeekRSR() const
{
	uint32 data;

	data = rsr & ~0x48;
	if (monitor_mode) {
		data |= RTL8019::RSR_DIS;
	}

	return data;
}

uint32
RTL8019ASDevice::PeekCNTR(int n) const
{
	return error_counter[n];
}

uint32
RTL8019ASDevice::PeekPAR(int n) const
{
	return par[n];
}

uint32
RTL8019ASDevice::PeekCURR() const
{
	return current_page_addr >> 8;
}

uint32
RTL8019ASDevice::PeekMAR(int n) const
{
	return (mar >> (n * 8)) & 0xff;
}

uint32
RTL8019ASDevice::PeekPSTART() const
{
	return pstart_addr >> 8;

}

uint32
RTL8019ASDevice::PeekPSTOP() const
{
	return pstop_addr >> 8;
}

uint32
RTL8019ASDevice::PeekTPSR() const
{
	return transmit_page_start_addr >> 8;
}

uint32
RTL8019ASDevice::PeekRCR() const
{
	uint32 data = 0xc0;

	if (monitor_mode) data |= RTL8019::RCR_MON;
	if (promisc_mode) data |= RTL8019::RCR_PRO;
	if (accept_mcast) data |= RTL8019::RCR_AM;
	if (accept_bcast) data |= RTL8019::RCR_AB;
	if (accept_short) data |= RTL8019::RCR_AR;
	if (accept_error) data |= RTL8019::RCR_SEP;

	return data;
}

uint32
RTL8019ASDevice::PeekTCR() const
{
	return 0xe0 | tcr;
}

uint32
RTL8019ASDevice::PeekDCR() const
{
	uint32 data;

	data = 0x80 | dcr;
	if (word_transfer) data |= RTL8019::DCR_WTS;

	return data;
}

uint32
RTL8019ASDevice::PeekIMR() const
{
	return intr_mask | 0x80;
}

uint32
RTL8019ASDevice::Peek9346CR() const
{
	uint32 data = 0x30;

	// EEM は $0 か $3 のみサポート
	if (config_write_enable) {
		data |= 0xc0;
	}

	return data;
}

uint32
RTL8019ASDevice::PeekBPAGE() const
{
	// 使わなそうだし、サポートもしていない
	return 0;
}

uint32
RTL8019ASDevice::PeekCONFIG0() const
{
	// XXX 値確認
	return (verid << 6);
}

uint32
RTL8019ASDevice::PeekCONFIG1() const
{
	uint32 data;

	// IRQS=INT1、IOS=300H
	data = 0x10;
	if (irq_enable) data |= RTL8019::CONFIG1_IRQEN;

	return data;
}

uint32
RTL8019ASDevice::PeekCONFIG2() const
{
	uint32 data;

	data = mediatype << 6;
	if (brom_force_disable) {
		data |= RTL8019::CONFIG2_BSELB;
	}
	// BS4-0 は 0

	return data;
}

uint32
RTL8019ASDevice::PeekCONFIG3() const
{
	uint32 data;

	data = (config3 & 0x06);

	// LEDS1=0, LEDS0=1
	data |= 0x10;

	return data;
}

uint32
RTL8019ASDevice::PeekCSNSAV() const
{
	// サポートしていない
	return 0;
}

uint32
RTL8019ASDevice::PeekReg39() const
{
	// データシートには何もないと書いてあるが $fe が読めるようだ
	return 0xfe;
}

uint32
RTL8019ASDevice::PeekCONFIG4() const
{
	// IOMS=0
	return 0xfe;
}

uint32
RTL8019ASDevice::PeekINTR() const
{
	// INT1 以外は pull up?
	uint32 data = 0xfd;

	// 正論理でええんやろうか
	if (interrupt->GetINT(this)) {
		data |= 0x02;
	}

	return data;
}

uint32
RTL8019ASDevice::PeekDMA(uint32 a0) const
{
	// DMA モードが Read でない時には何を見せる?
	if (dmamode != DMAMode::RemoteRead) {
		if (a0 == 0) {
			return 0xcc;
		} else {
			return 0xdd;
		}
	}

	// このポートは実際にはバイトアクセスかワードアクセスかによって
	// (か、もしくは DCR.WTS ビットによって?)、
	// 読み出せる値が変わるが、実用上はワード転送がメインだろうと思うので、
	// 正しくワードアクセスされたと仮定した時の値を表示しておく。
	if (remote_addr < 0x4000) {
		if (a0 == 0) {
			return ReadPROM(remote_addr);
		} else {
			return 0xff;
		}
	} else if ((remote_addr & 0x4000)) {
		uint32 addr = remote_addr & ~1;
		return ReadRAM(addr + a0);
	} else {
		return 0xff;
	}
}


// PROM 領域をバイトで読み出す。
// addr は内蔵メモリ空間でのアドレス。
uint32
RTL8019ASDevice::ReadPROM(uint32 addr) const
{
	// PROM 領域は 32 バイト(16ワード) でミラー。
	uint32 offset = (addr >> 1) & 0x0f;
	assert(offset < prom.size());
	return prom[offset];
}

// 内蔵 RAM を (副作用なく) バイトで読み出す。
// addr は内蔵メモリ空間でのアドレス。
// 送信パケット用の読み出しにも使われる。
inline uint32
RTL8019ASDevice::ReadRAM(uint32 addr) const
{
	uint32 offset = addr & 0x3fff;
	assert(offset < buffer.size());
	return buffer[offset];
}

// 内蔵 RAM に (副作用なく) バイトで書き込む。
// addr は内蔵メモリ空間でのアドレス。
// 受信パケットの書き込みにも使われる。
inline void
RTL8019ASDevice::WriteRAM(uint32 addr, uint32 data)
{
	uint32 offset = addr & 0x3fff;
	assert(offset < buffer.size());
	buffer[offset] = data;
}


// 動作状態が Stop か StopInProgress なら true を返す
bool
RTL8019ASDevice::IsStopOrInProgress() const
{
	return (state != State::Normal);
}

// 動作状態を newstate に変更を試みる。
// newstate には Normal か Stop を指定すること
// (その結果 StopInProgress になるかも知れない)。
void
RTL8019ASDevice::ChangeState(State newstate)
{
	if (newstate == state) {
		return;
	}

	switch (newstate) {
	 case State::Normal:
		hostnet->EnableRx(true);
		break;

	 case State::Stop:
		if (tx_in_progress || rx_packet.Length() != 0) {
			// 処理中があれば、処理が終わるのを待つ。
			// (送信または受信処理完了時に Stop にする)
			newstate = State::StopInProgress;
		} else {
			// ホストの受信を停止する
			hostnet->EnableRx(false);
		}
		break;

	 default:
		break;
	}
	state = newstate;
}

// 送信開始。
void
RTL8019ASDevice::Transmit()
{
	// Stop なら送(受)信は不可。
	// InProgress でも新たな送信は行わない
	// (受信完了を待ってるところのかもしれないので)。
	if (IsStopOrInProgress()) {
		putlog(1, "Transmit not allowed due to CR.STP=1");
		return;
	}

	// 送信開始
	putlog(2, "Transmit TPSR=$%02x TBCR=$%04x",
		transmit_page_start_addr >> 8, transmit_byte_count);

	tx_packet.Clear();
	tx_in_progress = true;

	// TSR は転送開始時にクリア (DP83901A.pdf)
	tsr = 0;

	// 転送イベントを起こすだけ。
	tx_event->time = 0;
	scheduler->StartEvent(tx_event);

	// LED イベントは止まっていれば動かす。
	if (txled_event->IsRunning() == false) {
		txled_event->time = 0;
		scheduler->StartEvent(txled_event);
	}
}

// 転送イベント。
// TXP=1 で呼ばれて、ページごとにパケットにコピーする。
// TBCR で指定された長さを全部コピーしたらホストに送信。
void
RTL8019ASDevice::TXEvent(Event *ev)
{
	// 今回処理する分
	uint count = std::min((uint)transmit_byte_count, 256u);
	if (count == 0 && tx_packet.Length() != 0) {
		// 前回でコピーが完了していればここで送信。
		putlog(3, "TXEvent Send %u bytes", tx_packet.Length());
		hostnet->Tx(tx_packet);

		// 送信完了。
		tx_in_progress = false;
		tsr |= RTL8019::TSR_PTX;
		intr_stat |= RTL8019::ISR_PTX;
		ChangeInterrupt();

		// ストップ待ちなら、ストップ状態に移行を試みる
		if (state == State::StopInProgress) {
			ChangeState(State::Stop);
		}
		return;
	}

	// このページをパケットにコピー
	uint32 addr = transmit_page_start_addr;
	for (uint32 end = addr + count; addr < end; addr++) {
		tx_packet.Append((uint8)ReadRAM(addr));
	}
	putlog(4, "TXEvent Copy %u bytes at page $%02x",
		count, transmit_page_start_addr >> 8);

	// XXX TPSR, TBCR は更新されるのかどうか。
	// とりあえず内部で必要なので使いまわしておく。
	transmit_page_start_addr += 256;
	transmit_byte_count -= count;
	// XXX 折り返し

	// 転送にかかった時間を模しておく。
	// 時間は適当。10Mbps なので 1バイトあたり 1usec とする。
	tx_event->time = usec_to_tsec(count);
	scheduler->StartEvent(tx_event);
}

// パケット受信通知。HostNet から (EthernetDevice 経由で) 呼ばれる。
void
RTL8019ASDevice::RxMessage(MessageID msgid, uint32 arg)
{
	// 受信イベントが止まっていれば動かす。
	// 受信イベントがすでに動いていれば、1パケット受信完了後に
	// ホストキューを確認するので、ここでは何もしなくてよい。
	if (rx_event->IsRunning() == false) {
		rx_event->time = 0;
		scheduler->StartEvent(rx_event);
	}
}

// 受信イベント。
//
// rx_packet の length, remain によって状態マシンになっている。
//
// len == 0					: 受信中でない。ホストキューから読み出せる
// len != 0, remain == len	: 受信パケットのコピー (1ページ目)。ヘッダも出力
// len != 0, remain < len	: 受信パケットのコピー (2ページ目以降)
// len != 0, remain == 0	: 受信パケットのコピー完了直後。
void
RTL8019ASDevice::RXEvent(Event *ev)
{
	uint remain;

	if (rx_packet.Length() == 0) {
		// パケットが空の時に呼ばれたら、
		// ホストキューから1パケット取り出してみる。
		if (hostnet->Rx(&rx_packet) == false) {
			// 取り出せなくなったら、ここでイベントを停止。
			rx_packet.Clear();
			return;
		}

		// 取り出したらそのまま処理する。
		// ここがパケット受信処理開始なので RSR をクリア。
		rsr = 0;

		// 受信 LED イベントが止まっていれば動かす。
		// 取り出して、破棄するかもしれないより前。
		if (rxled_event->IsRunning() == false) {
			rxled_event->time = 0;
			scheduler->StartEvent(rxled_event);
		}

		// 受信無効中ならカウンタを上げてから、パケットを破棄。
		if (monitor_mode) {
			rsr |= RTL8019::RSR_MPA;
			rsr &= ~RTL8019::RSR_PRX;	// 念のため
			IncCounter(2);

			intr_stat |= RTL8019::ISR_RXE;
			ChangeInterrupt();

			goto discard;
		}
	}

	assert(rx_packet.Length() != 0);
	remain = rx_packet.GetRemain();
	if (remain != 0) {
		// 実際に書き込む前に、これから書き込むページ(local_addr) が
		// BNRY (ホストがまだ読んでない受信済みページ に追いついていたら、
		// 受信バッファオーバーフロー。
		if (local_addr == boundary_addr) {
			putlog(2, "Receive ring overflow: CURR=$%02x BNRY=$%02x",
				local_addr >> 8, boundary_addr >> 8);

			rsr |= RTL8019::RSR_MPA;
			rsr &= ~RTL8019::RSR_PRX;	// 念のため
			IncCounter(2);

			// RST は自動的にセットされる
			intr_stat |= RTL8019::ISR_OVW | RTL8019::ISR_RXE;
			ChangeInterrupt();

			goto discard;
		}

		if (remain == rx_packet.Length()) {
			// 初回処理
			RXHead();
		}
		RXCopy(ev, remain);
	} else {
		RXDone(ev);
	}
	return;

 discard:
	// パケットを破棄する。
	// この場合も、ホストキューから次のパケットを引き取る作業は必要。
	// XXX 間隔は適当
	rx_packet.Clear();
	ev->time = 1_usec;
	scheduler->RestartEvent(ev);
}

// パケット受信の初回。
// 4バイトの受信ヘッダをバッファに書き込む。
void
RTL8019ASDevice::RXHead()
{
	// 先に、このパケットの次のページ位置を求めて覚えておく。
	next_page_addr = local_addr + 4 + rx_packet.Length();
	next_page_addr = roundup(next_page_addr, 256);
	if (next_page_addr >= pstop_addr) {
		next_page_addr = pstart_addr + (next_page_addr - pstop_addr);
	}

	// 受信ヘッダは転送完了後に書き込むため、空けておく。
	// XXX ゼロクリアするかどうか
	local_addr += 4;
}

// 受信パケットをバッファにコピー。
// 1ページ埋めるかパケットをコピーし終えたら次へ。
// (初回もヘッダ書き込みに続いて呼ばれる)
void
RTL8019ASDevice::RXCopy(Event *ev, uint remain)
{
	// パケットをこのページにコピー。
	// 今回処理する分はパケットの残りとページの残りの小さいほう。
	uint count = std::min(remain, 0x100U - (local_addr & 0xff));
	putlog(4, "RXEvent Copy %u bytes at page $%02x", count, local_addr >> 8);
	for (uint32 end = local_addr + count; local_addr < end; ) {
		WriteRAM(local_addr++, rx_packet.Read());
	}

	// パケットの最後に到達したらポインタだけここで次ページへ。
	// PSTOP 折り返し処理の前にやっておくため。
	// (パケットの受信完了処理自体は次のループの先頭で行う)
	if (rx_packet.GetRemain() == 0) {
		local_addr = roundup(local_addr, 256);
	}

	// PSTOP に到達したら PSTART に折り返す。
	if (local_addr >= pstop_addr) {
		local_addr = pstart_addr;
	}

	// 転送にかかった時間を模しておく。
	// 時間は適当。10Mbps なので 1バイトあたり 1usec とする。
	ev->time = usec_to_tsec(count);
	scheduler->RestartEvent(ev);
}

// 受信パケットをバッファにコピーし終えた次のターンで呼ばれる。
// 完了処理をして、ストップ待ちならストップ状態に移行。
// ストップ待ちでなければ再び RXEvent() イベントを呼び、
// 次パケットの処理を継続する。
void
RTL8019ASDevice::RXDone(Event *ev)
{
	// 前回でコピーが完了していればここで通知。
	uint length = rx_packet.Length();
	putlog(3, "RXDone Received %u bytes", length);

	// 受信ヘッダに書き込む前に RSR を更新
	rsr |= RTL8019::RSR_PRX;
	if ((rx_packet.Peek(0) & 0x01)) {
		rsr |= RTL8019::RSR_PHY;
	}
	// 念のためエラービットをクリア (DP83901A.pdf)
	rsr &= ~(RTL8019::RSR_MPA |
	         RTL8019::RSR_FAE |
	         RTL8019::RSR_CRC);

	// XXX Receive Status には、正常受信時は RSR を書き込む。
	// エラー時はまだ見てない (DP83901A.pdf)

	// 受信ヘッダを書き込む。
	// このパケット受信中は CURR は更新しないので、これが使える。
	//
	// メモリ上のバイト順で次の通り。
	// +0: Receive Status
	// +1: 次の受信ページ
	// +2: 受信バイト長(L)
	// +3: 受信バイト長(H)
	//
	// ワード (Little Endian) 表記:
	//     D15         D8   D7          D0
	// +0: 次の受信ページ   Receive Status
	// +2: 受信バイト長(H)  受信バイト長(L)
	uint16 addr = current_page_addr;
	WriteRAM(addr++, rsr);
	WriteRAM(addr++, next_page_addr >> 8);
	WriteRAM(addr++, length & 0xff);
	WriteRAM(addr++, length >> 8);

	// CURR を次受信ページに更新。残り転送バイトが 0 になった時点で
	// local_addr は次ページ先頭に繰り上げてある。
	current_page_addr = local_addr;

	rx_packet.Clear();

	// 受信完了。
	intr_stat |= RTL8019::ISR_PRX;
	ChangeInterrupt();

	if (state == State::StopInProgress) {
		// ストップ待ちなら、ストップ状態に移行を試みる
		ChangeState(State::Stop);
	} else {
		// そうでなければ、次の受信を試す。
		// (rx_packet.Length() == 0 で RXEvent() を呼ぶとパケット受信となる)

		// どのくらいか待つ? 値は適当
		ev->time = 100_usec;
		scheduler->RestartEvent(ev);
	}
}

// この宛先アドレスを受信するかどうか。
// これは HostNet スレッドで呼ばれる。
int
RTL8019ASDevice::HWAddrFilter(const MacAddr& dstaddr) const
{
	// RTL8019AS.pdf では詳細不明だが、
	// DP83901A.pdf には、完全なプロミスキャスにするには AB, AM, PRO 全部
	// 立ててさらにマルチキャストハッシュを全部 1 にしろ、と注釈があるので、
	// - PRO は自分宛て以外のユニキャストを受け取るかどうか、
	// - AM は、ハッシュで accept になったマルチキャストを受け取るか
	//   マルチキャストを一切受け取らないか、
	// - AB はブロードキャストを受け取るかどうか、
	// の独立したビットのようだ。

	if (dstaddr.IsUnicast()) {
		if (promisc_mode == false) {
			if (dstaddr != par) {
				return HPF_DROP_UNICAST;
			}
		}
	} else {
		if (dstaddr.IsBroadcast()) {
			if (accept_bcast == false) {
				// XXX 選択肢がまだない
				return HPF_DROP_MULTICAST;
			}
		} else {
			if (accept_mcast == false) {
				return HPF_DROP_MULTICAST;
			}

			// マルチキャスト
			//
			// RTL8019AS のマルチキャストフィルタ(ハッシュ) は宛先 MAC
			// アドレスの CRC (32ビット) のうち上位6ビットを pos として、
			// MAR の下から pos ビット目が立っていれば受理する、というもの。
			//
			//                    31  30  29  28  27  26  25  24      0
			//                  +---+---+---+---+---+---+---+---+--   --+
			// CRC32(dstaddr) = | p5| p4| p3| p2| p1| p0| X   X |  ..   |
			//                  +---+---+---+---+---+---+---+---+--   --+
			//
			//                  +---+---+---+---+---+---+
			//           pos  = | p5| p4| p3| p2| p1| p0|
			//                  +---+---+---+---+---+---+

			uint32 crc = EthernetDevice::CRC32(dstaddr);
			uint pos = crc >> 26;
			bool mcast_pass = (mar & (1ULL << pos));
			putmsg(2, "mcast %02x:%02x:%02x:%02x:%02x:%02x pos=%u %s",
				dstaddr[0], dstaddr[1], dstaddr[2],
				dstaddr[3], dstaddr[4], dstaddr[5],
				pos, (mcast_pass ? "pass" : "drop"));
			if (mcast_pass == false) {
				return HPF_DROP_MULTICAST;
			}
		}
	}

	return HPF_PASS;
}

// 送信 LED イベント
void
RTL8019ASDevice::TXLedEvent(Event *ev)
{
	if (tx_led == false) {
		// 消灯中だったら、次は 6msec 点灯
		tx_led = true;
		ev->time = 6_msec;
		scheduler->RestartEvent(ev);
	} else {
		// 点灯中だったら
		// - 送信中なら次は 100msec 消灯
		// - 送信中でなければここで終わり
		if (tx_in_progress) {
			tx_led = false;
			ev->time = 100_msec;
			scheduler->RestartEvent(ev);
		}
	}
}

// 受信 LED イベント
void
RTL8019ASDevice::RXLedEvent(Event *ev)
{
	if (rx_led == false) {
		// 消灯中だったら、次は 6msec 点灯
		rx_led = true;
		ev->time = 6_msec;
		scheduler->RestartEvent(ev);
	} else {
		// 点灯中だったら
		// - 受信中なら次は 100msec 消灯
		// - 受信中でなければここで終わり
		if (rx_packet.Length() != 0) {
			rx_led = false;
			ev->time = 100_msec;
			scheduler->RestartEvent(ev);
		}
	}
}


// 実際のボードの割り込みは、SL811HS/T もいるので NereidBoard が一旦受けて
// いると思うが、それを忠実に真似すると
// RTL8019AS -> NereidBoard -> X68030Interrupt のようなカスケードになり、
// それはちょっと手間なので、ここでは NereidBoard の DIPSW で決まる割り込み
// ベクタの値だけ NereidBoard から事前に受け取っておいて、あとは
// InterruptDevice と直接やりとりする。

// 割り込み信号線の状態を変える。
// intr_stat、intr_mask、irq_enable が更新されたら呼ぶこと。
void
RTL8019ASDevice::ChangeInterrupt()
{
	if (irq_enable) {
		bool irq = ((intr_stat & intr_mask) != 0);
		interrupt->ChangeINT(this, irq);
	} else {
		// IRQEN が 0 だと出力がハイインピーダンスになる。
		interrupt->AssertINT(this);
	}
}

// 割り込みアクノリッジ
busdata
RTL8019ASDevice::InterruptAcknowledge()
{
	return intr_vector;
}

// エラーカウンタをインクリメントする。
void
RTL8019ASDevice::IncCounter(int n)
{
	error_counter[n]++;

	// XXX DP83901A はカウンタが $c0 になったらカウントとめるらしい。
	// RTL8019AS/AX88196B には記述なし。要動作確認。

	// いずれかカウンタの MSB が立ったら割り込み
	uint32 c = error_counter[0] | error_counter[1] | error_counter[2];
	if ((c & 0x80) != 0) {
		intr_stat |= RTL8019::ISR_CNT;
	} else {
		// 書いてはないけど、たぶん OR したのが繋がってるだけだとしたら
		// 最上位ビットがいずれも立たなくなったら、CNT も勝手に下がるはず?
		intr_stat &= ~RTL8019::ISR_CNT;
	}
	ChangeInterrupt();
}

// 読み込み時のレジスタ名を返す (rn はページ番号込み)
/*static*/ std::string
RTL8019ASDevice::RegNameR(int rn)
{
	if (regnames[rn]) {
		return std::string(regnames[rn]);
	} else {
		return string_format("(%02x)", rn);
	}
}

// 書き込み時のレジスタ名を返す (rn はページ番号込み)
/*static*/ std::string
RTL8019ASDevice::RegNameW(int rn)
{
	int page = rn >> 4;
	int n    = rn & 0xf;

	// Page1 は R/W 共通
	if (page == 1) {
		return RegNameR(rn);
	}

	// Page2 は ReadOnly レジスタなので名前はない。
	if (page == 2) {
		return string_format("%02x", rn);
	}

	// Page0, Page3 のレジスタ名はオフセットしたところに置いてある。
	int offset = (page == 0) ? 0x40 : 0x50;
	if (regnames[offset + n]) {
		return std::string(regnames[offset + n]);
	} else {
		return string_format("(%02x)", rn);
	}
}

/*static*/ const char * const
RTL8019ASDevice::regnames[] = {
	"CR",		// 0 Page0(Read側)
	"CLDA0",	// 1
	"CLDA1",	// 2
	"BNRY",		// 3
	"TSR",		// 4
	"NCR",		// 5
	"FIFO",		// 6
	"ISR",		// 7
	"CRDA0",	// 8
	"CRDA1",	// 9
	"8019ID0",	// a
	"8019ID1",	// b
	"RSR",		// c
	"CNTR0",	// d
	"CNTR1",	// e
	"CNTR2",	// f

	"CR",		// 0 Page1(R/W共通)
	"PAR0",		// 1
	"PAR1",		// 2
	"PAR2",		// 3
	"PAR3",		// 4
	"PAR4",		// 5
	"PAR5",		// 6
	"CURR",		// 7
	"MAR0",		// 8
	"MAR1",		// 9
	"MAR2",		// a
	"MAR3",		// b
	"MAR4",		// c
	"MAR5",		// d
	"MAR6",		// e
	"MAR7",		// f

	"CR",		// 0 Page2(Read Only)
	"PSTART",	// 1
	"PSTOP",	// 2
	NULL,		// 3
	"TPSR",		// 4
	NULL,		// 5
	NULL,		// 6
	NULL,		// 7
	NULL,		// 8
	NULL,		// 9
	NULL,		// a
	NULL,		// b
	"RCR",		// c
	"TCR",		// d
	"DCR",		// e
	"IMR",		// f

	"CR",		// 0 Page3(Read側)
	"9346CR",	// 1
	"BPAGE",	// 2
	"CONFIG0",	// 3
	"CONFIG1",	// 4
	"CONFIG2",	// 5
	"CONFIG3",	// 6
	NULL,		// 7
	"CSNSAV",	// 8
	NULL,		// 9
	NULL,		// a
	"INTR",		// b
	NULL,		// c
	"CONFIG4",	// d
	NULL,		// e
	NULL,		// f

	"CR",		// 0 Page0(Write側)
	"PSTART",	// 1
	"PSTOP",	// 2
	"BNRY",		// 3
	"TPSR",		// 4
	"TBCR0",	// 5
	"TBCR1",	// 6
	"ISR",		// 7
	"RSAR0",	// 8
	"RSAR1",	// 9
	"RBCR0",	// a
	"RBCR1",	// b
	"RCR",		// c
	"TCR",		// d
	"DCR",		// e
	"IMR",		// f

	"CR",		// 0 Page3(Write側)
	"9346CR",	// 1
	"BPAGE",	// 2
	"CONFIG0",	// 3
	"CONFIG1",	// 4
	"CONFIG2",	// 5
	"CONFIG3",	// 6
	"TEST",		// 7
	NULL,		// 8
	"HLTCLK",	// 9
	NULL,		// a
	NULL,		// b
	"FMWP",		// c
	NULL,		// d
	NULL,		// e
	NULL,		// f
};

// CR_RD (DMAMode) の名前を返す
/*static*/ const char *
RTL8019ASDevice::GetDMAName(uint32 mode)
{
	static const char * const rd_str[] = {
	//   01234567890
		"NotAllowed",
		"RemoteRead",
		"RemoteWrite",
		"SendPacket",
		"Complt/Abrt",
	};

	if (mode > 4) {
		return rd_str[4];
	} else {
		return rd_str[mode];
	}
}

unix.superglobalmegacorp.com

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