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

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

//
// Lance (AM7990)
//

#include "lance.h"
#include "bitops.h"
#include "event.h"
#include "interrupt.h"
#include "mainbus.h"
#include "monitor.h"
#include "scheduler.h"
#include "subram.h"

// コンストラクタ
LanceDevice::LanceDevice()
	: inherited(OBJ_ETHERNET(0))
{
	SetName("Lance");
	ClearAlias();
	AddAlias("Lance");

	monitor = gMonitorManager->Regist(ID_MONITOR_LANCE, this);
	monitor->SetCallback(&LanceDevice::MonitorScreen);
	monitor->SetSize(80, 34);
}

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

// 初期化
bool
LanceDevice::Init()
{
	interrupt = GetInterruptDevice();
	subram = GetSubRAMDevice();

	hostnet->SetPortName("Lance");

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

	auto evman = GetEventManager();
	rx_event = evman->Regist(this, NULL, "Lance RX");
	tx_event = evman->Regist(this, NULL, "Lance TX");

	return true;
}

// リセット
void
LanceDevice::ResetHard(bool poweron)
{
	reg.rap = 0;
	ChipStop();
}

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

	switch (offset) {
	 case 0:	// RDP
		data = ReadData();
		break;
	 case 1:	// RAP
		data = reg.rap;
		putlog(2, "RAP -> %u", reg.rap);
		break;
	 default:
		VMPANIC("corrupted offset=%u", offset);
		data = 0xffff;
		break;
	}

	// XXX wait?
	data |= BusData::Size2;
	return data;
}

busdata
LanceDevice::WritePort(uint32 offset, uint32 data)
{
	switch (offset) {
	 case 0:	// RDP
		WriteData(data);
		break;
	 case 1:	// RAP
		reg.rap = data & 3;
		break;
	 default:
		VMPANIC("corrupted offset=%u", offset);
		break;
	}

	busdata r = BusData::Size2;
	// XXX wait?
	return r;
}

busdata
LanceDevice::PeekPort(uint32 offset)
{
	switch (offset) {
	 case 0:	// RDP
		// 今読めるはずのデータ。
		// CSR[123] は STOP 中でなければ不定だがとりあえずそれは無視。
		switch (reg.rap) {
		 case 0:
			return reg.GetCSR0();
		 case 1:
			return reg.iadr & 0xffff;
		 case 2:
			return reg.iadr >> 16;
		 case 3:
			return reg.csr3;
		 default:
			break;
		}
		break;
	 case 1:	// RAP
		return reg.rap;
	 default:
		break;
	}
	return 0xff;
}

void
LanceDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
/*
CSR0: $1111 ERR  BABL CERR MISS MERR RINT TINT IDON   IADR:$123456
                                                      CSR3:$0004 BSWP ACON BCON

MODE: $0000 PROM INTL DRTY COLL DTCR LOOP DTX  DRX
PADR: 00:11:22:33:44:55   RDRP: $12345678 (RDRA=$123456 entries=32)
LADRF:$fedcba98'76543210  TDRP:

<RMD> Current 00 OWN ERR OFLO CRC BUFF STP ENP BCNT=$123 MCNT=$123 buf=$123456
01234567890123456789012345678901234567890             00
00 OVCBSE $12345 $123/$123 00 OVCBSE $12345 $123/$123
<TMD> Current 00 OWN ERR STP ENP  BUFF UFLO BCNT=$123 buf=$123456
00 OSEBU  $12345 $123
*/

// name を表示。(var & bit) ならオン(反転)表示。
// bitpos は 0..15 で [0] が MSB にあたる。
#define MonitorBit(var, bit, name) do { \
	screen.Puts(x, y, TA::OnOff((var) & (bit)), name); \
	x += strlen(name) + 1; \
} while (0)

	int x;
	int y;
	int i;

	screen.Clear();

	y = 0;
	// CSR
	uint16 csr0 = reg.GetCSR0();
	screen.Print(0, y, "CSR0:$%04x", csr0);
	MonitorReg(screen, 11, y,     csr0 >> 8, csr0names);
	MonitorReg(screen, 11, y + 1, csr0, csr0names + 8);

	screen.Print(54, y++, "IADR:$%06x", reg.iadr);
	screen.Print(54, y, "CSR3:$%04x", reg.csr3);
	// CSR3 は下位3ビットだけなので手動で配置
	x = 65;
	MonitorBit(reg.csr3, AM7990::CSR3_BSWP, csr3names[13]);
	MonitorBit(reg.csr3, AM7990::CSR3_ACON, csr3names[14]);
	MonitorBit(reg.csr3, AM7990::CSR3_BCON, csr3names[15]);
	y++;

	screen.Puts(0, y, "<Initial Block>");
	y++;
	// MODE は bit15 と bit6-0 の8ビットだけ使われてるので
	// 途中を省略して 8ビットとして表示する
	screen.Print(0, y, "MODE:$%04x", initblock[0]);
	x = 11;
	MonitorBit(initblock[0], AM7990::IBMODE_PROM, ib_mode_names[0]);
	MonitorBit(initblock[0], AM7990::IBMODE_INTL, ib_mode_names[1]);
	MonitorBit(initblock[0], AM7990::IBMODE_DRTY, ib_mode_names[2]);
	MonitorBit(initblock[0], AM7990::IBMODE_COLL, ib_mode_names[3]);
	MonitorBit(initblock[0], AM7990::IBMODE_DTCR, ib_mode_names[4]);
	MonitorBit(initblock[0], AM7990::IBMODE_LOOP, ib_mode_names[5]);
	MonitorBit(initblock[0], AM7990::IBMODE_DTX,  ib_mode_names[6]);
	MonitorBit(initblock[0], AM7990::IBMODE_DRX,  ib_mode_names[7]);
	y++;

	screen.Print(0, y, "PADR: %02x:%02x:%02x:%02x:%02x:%02x",
		initblock[1] & 0xff, initblock[1] >> 8,
		initblock[2] & 0xff, initblock[2] >> 8,
		initblock[3] & 0xff, initblock[3] >> 8);
	screen.Print(0, y + 1, "LADRF:$%04x%04x'%04x%04x",
		initblock[7],
		initblock[6],
		initblock[5],
		initblock[4]);

	// RDRP
	uint32 rdrp = initblock[8] | (initblock[9] << 16);
	screen.Print(31, y, "RDRP:$%08x (RDRA=$%06x entries=%u)",
		rdrp, rmd_base, rmd_num);
	y++;

	// TDRP
	uint32 tdrp = initblock[10] | (initblock[11] << 16);
	screen.Print(31, y, "TDRP:$%08x (TDRA=$%06x entries=%u)",
		tdrp, tmd_base, tmd_num);
	y++;
	y++;

	// RMD
	screen.Puts(0, y, "<RMD>");

	uint16 rmd[4];
	uint32 rmd_addr;
	// 現行ディスクリプタは詳細表示
	if (rmd_num > 0) {
		rmd_addr = rmd_base + rmd_cur * 8;
		for (int j = 0; j < countof(rmd); j++) {
			rmd[j] = PeekMem(rmd_addr + j * 2);
		}
		x = 6;
		screen.Print(x, y, "Current %02x", rmd_cur);
		x += 11;
		MonitorBit(rmd[1], AM7990::RMD1_OWN,  rmd1_names[0]);
		MonitorBit(rmd[1], AM7990::RMD1_ERR,  rmd1_names[1]);
		// 発生し得ないので省略    RMD1_FRAM  rmd1_names[2]
		MonitorBit(rmd[1], AM7990::RMD1_OFLO, rmd1_names[3]);
		MonitorBit(rmd[1], AM7990::RMD1_CRC,  rmd1_names[4]);
		MonitorBit(rmd[1], AM7990::RMD1_BUFF, rmd1_names[5]);
		MonitorBit(rmd[1], AM7990::RMD1_STP,  rmd1_names[6]);
		MonitorBit(rmd[1], AM7990::RMD1_ENP,  rmd1_names[7]);
		// BCNT は負数(2の補数)でエンコードされているので正数にデコード
		screen.Print(x, y, "MCNT=$%03x BCNT=$%03x",
			rmd[3] & 0xfff,
			(-(int16)rmd[2]) & 0xfff);
		x += 20;
		screen.Print(x, y, "buf=$%06x", ((rmd[1] & 0xff) << 16) | rmd[0]);
	}
	y++;

	// 他は一覧表示
	int rs;
	if (rmd_num > 48) {
		// 画面内に収まらないので仕方ないのでカーソルのある16個ブロックが
		// 常に中央にくるようにローテートするのはどうか。
		rs = (((rmd_cur - 16 + rmd_num) % rmd_num) / 16) * 16;
	} else {
		// 画面内に収まるので最初から書いていくだけ
		rs = 0;
	}
	for (x = 0; x < 3; x++) {
		for (i = 0; i < 16 && rs < rmd_num; i++) {
			rmd_addr = rmd_base + rs * 8;
			for (int j = 0; j < countof(rmd); j++) {
				rmd[j] = PeekMem(rmd_addr + j * 2);
			}
			screen.Print(x * 27, y + i,
				(rs == rmd_cur) ? TA::Em : TA::Normal,
				"%02x %c%c%c%c%c%c $%05x $%03x/$%03x",
				rs,
				(rmd[1] & AM7990::RMD1_OWN)  ? 'O' : '-',
				(rmd[1] & AM7990::RMD1_OFLO) ? 'F' : '-',
				(rmd[1] & AM7990::RMD1_CRC)  ? 'C' : '-',
				(rmd[1] & AM7990::RMD1_BUFF) ? 'B' : '-',
				(rmd[1] & AM7990::RMD1_STP)  ? 'S' : '-',
				(rmd[1] & AM7990::RMD1_ENP)  ? 'E' : '-',
				((rmd[1] & 0xff) << 16 | rmd[0]),
				rmd[3] & 0xfff,
				(-(int16)rmd[2]) & 0xfff);

			// 画面内に収まらない場合は折り返し。
			// 収まる場合はこのまま rmd_num を超えればループを抜ける。
			rs++;
			if (rmd_num > 48 && rs >= rmd_num) {
				rs = 0;
			}
		}
	}
	y += 17;

	// TMD
	screen.Puts(0, y, "<TMD>");

	uint16 tmd[4];
	uint32 tmd_addr;
	// 現行ディスクリプタは詳細表示
	if (tmd_num > 0) {
		tmd_addr = tmd_base + tmd_cur * 8;
		for (int j = 0; j < countof(tmd); j++) {
			tmd[j] = PeekMem(tmd_addr + j * 2);
		}
		// MORE, ONE, DEF と LCOL, LCAR, RTRY, TDR は発生し得ないので省略
		x = 6;
		screen.Print(x, y, "Current %02x ", tmd_cur);
		x += 11;
		MonitorBit(tmd[1], AM7990::TMD1_OWN,  tmd1_names[0]);
		MonitorBit(tmd[1], AM7990::TMD1_ERR,  tmd1_names[1]);
		MonitorBit(tmd[1], AM7990::TMD1_STP,  tmd1_names[6]);
		MonitorBit(tmd[1], AM7990::TMD1_ENP,  tmd1_names[7]);
		x += 1;
		MonitorBit(tmd[3], AM7990::TMD3_BUFF, tmd3_names[0]);
		MonitorBit(tmd[3], AM7990::TMD3_UFLO, tmd3_names[1]);
		// BCNT は負数(2の補数)でエンコードされているので正数にデコード
		screen.Print(x, y, "BCNT=$%03x", (-(int16)tmd[2]) & 0xfff);
		x += 10;
		screen.Print(x, y, "buf=$%06x", ((tmd[1] & 0xff) << 16) | tmd[0]);
	}
	y++;

	// 他は一覧表示
	int ts;
	if (tmd_num > 24) {
		// 画面内に収まらないので仕方ないのでカーソルのある16個ブロックが
		// 常に中央にくるようにローテートするのはどうか。
		ts = (((tmd_cur - 16 + tmd_num) % tmd_num) / 16) * 16;
	} else {
		// 画面内に収まるので最初から書いていくだけ
		ts = 0;
	}
	for (x = 0; x < 3; x++) {
		for (i = 0; i < 8 && ts < tmd_num; i++) {
			tmd_addr = tmd_base + ts * 8;
			for (int j = 0; j < countof(tmd); j++) {
				tmd[j] = PeekMem(tmd_addr + j * 2);
			}
			screen.Print(x * 27, y + i,
				(ts == tmd_cur) ? TA::Em : TA::Normal,
				"%02x %c%c%c%c%c  $%05x $%03x",
				ts,
				(tmd[1] & AM7990::TMD1_OWN)  ? 'O' : '-',
				(tmd[1] & AM7990::TMD1_STP)  ? 'S' : '-',
				(tmd[1] & AM7990::TMD1_ENP)  ? 'E' : '-',
				(tmd[3] & AM7990::TMD3_BUFF) ? 'B' : '-',
				(tmd[3] & AM7990::TMD3_UFLO) ? 'U' : '-',
				((tmd[1] & 0xff) << 16 | tmd[0]),
				(-(int16)tmd[2]) & 0xfff);

			// 画面内に収まらない場合は折り返し。
			// 収まる場合はこのまま tmd_num を超えればループを抜ける。
			ts++;
			if (tmd_num > 24 && ts >= tmd_num) {
				ts = 0;
			}
		}
	}
}

// レジスタをビットごとに表示する。
// data は下位8ビットのみ使用し、上位は無視する。
void
LanceDevice::MonitorReg(TextScreen& screen,
	int x, int y, uint32 data, const char * const *names)
{
	for (int i = 0; i < 8; i++) {
		bool b = data & (1U << (7 - i));
		screen.Puts(x + i * 5, y, TA::OnOff(b), names[i]);
	}
}


// 現在選択されているレジスタを読み出す
uint32
LanceDevice::ReadData()
{
	uint32 data;

	// XXX CSR[123] は STOP 中だけ読める。そうでない時は未定義値になる。
	switch (reg.rap) {
	 case 0:
	 {
		data = reg.GetCSR0();
		const std::string name = CSR0ToString(data);
		putlog(3, "CSR0 -> $%04x<%s>", data, name.c_str());
		break;
	 }
	 case 1:
		if (reg.IsSTOP()) {
			data = reg.iadr & 0xffff;
			putlog(2, "CSR%u -> $%04x", reg.rap, data);
		} else {
			data = 0xcccc & AM7990::CSR1_MASK;
			putlog(2, "CSR%u -> $%04x (indefinate read)", reg.rap, data);
		}
		break;
	 case 2:
		if (reg.IsSTOP()) {
			data = reg.iadr >> 16;
			putlog(2, "CSR%u -> $%04x", reg.rap, data);
		} else {
			data = 0xcccc & AM7990::CSR2_MASK;
			putlog(2, "CSR%u -> $%04x (indefinate read)", reg.rap, data);
		}
		break;
	 case 3:
		if (reg.IsSTOP()) {
			data = reg.csr3;
			const std::string name = CSR3ToString(data);
			putlog(2, "CSR%u -> $%04x<%s>", reg.rap, data, name.c_str());
		} else {
			data = 0xcccc & AM7990::CSR3_MASK;
			putlog(2, "CSR%u -> $%04x (indefinate read)", reg.rap, data);
		}
		break;
	 default:
		VMPANIC("corrupted reg.rap=%u", reg.rap);
		data = 0xffff;
		break;
	}
	return data;
}

// 現在選択されてるレジスタに data を書き込む
void
LanceDevice::WriteData(uint32 data)
{
	switch (reg.rap) {
	 case 0:
	 {
		// CSR0 は STOP ビットによらず書き込み可能
		const std::string name = CSR0ToString(data);
		putlog(2, "CSR0 <- $%04x<%s>", data, name.c_str());

		// STOP,STRT,INIT 全部立てると STOP のみ有効 (AM7990.pdf, p17)。
		// STOP は実質リセットなので、これ立ってたらもうそれで終わりだろう。
		if ((data & AM7990::CSR0_STOP)) {
			ChipStop();
			break;
		}

		// INIT, STRT は立ち上がりで実行にしてみる。
		// OpenBSD が割り込みクリアするために読んだ CSR0 をそのまま書いて
		// くるようなので、毎回 INIT と STRT が走ってしまう。
		// とりあえず立ち上がりでやってみる。
		// XXX 要実機調査

		if (bit_rising(reg.csr0, data, AM7990::CSR0_INIT)) {
			ChipInit();
		}
		if (bit_rising(reg.csr0, data, AM7990::CSR0_STRT)) {
			ChipStart();
		}

		// TDMD はセットオンリー (チップがクリアする)
		if ((data & AM7990::CSR0_TDMD)) {
			reg.csr0 |= AM7990::CSR0_TDMD;
			// TX ポーリングの待ち時間ならこのあとすぐ起動
			if (tx_phase == TXPhase::IDLE) {
				ChangePhase(TXPhase::IDLE);
			}
		}

		// 書き込みがビットクリアになる人たち
		if ((data & AM7990::CSR0_IDON)) {
			reg.csr0 &= ~AM7990::CSR0_IDON;
		}
		if ((data & AM7990::CSR0_TINT)) {
			reg.csr0 &= ~AM7990::CSR0_TINT;
		}
		if ((data & AM7990::CSR0_RINT)) {
			reg.csr0 &= ~AM7990::CSR0_RINT;
		}
		if ((data & AM7990::CSR0_MERR)) {
			reg.csr0 &= ~AM7990::CSR0_MERR;
		}
		if ((data & AM7990::CSR0_MISS)) {
			reg.csr0 &= ~AM7990::CSR0_MISS;
		}
		if ((data & AM7990::CSR0_CERR)) {
			reg.csr0 &= ~AM7990::CSR0_CERR;
		}
		if ((data & AM7990::CSR0_BABL)) {
			reg.csr0 &= ~AM7990::CSR0_BABL;
		}

		// INEA は %0 も %1 も書き込める
		// INEA は STOP 中は設定できない
		if (!reg.IsSTOP()) {
			reg.csr0 &= ~AM7990::CSR0_INEA;
			reg.csr0 |= data & AM7990::CSR0_INEA;
		}

		ChangeInterrupt();
		break;
	 }

	 case 1:
		// STOP 中のみ書き込み可能。そうでない時は無視する。
		if (reg.IsSTOP()) {
			reg.iadr = (reg.iadr & 0xff0000) | (data & AM7990::IADR_L_MASK);
			putlog(2, "IDAR(L)=$%06x", reg.iadr);
		}
		break;

	 case 2:
		// STOP 中のみ書き込み可能。そうでない時は無視する。
		if (reg.IsSTOP()) {
			reg.iadr = ((data & AM7990::IADR_H_MASK) << 16)
			         | (reg.iadr & 0xffff);
			putlog(2, "IDAR(H)=$%06x", reg.iadr);
		}
		break;

	 case 3:
		// STOP 中のみ書き込み可能。そうでない時は無視する。
		if (reg.IsSTOP()) {
			reg.csr3 = data & AM7990::CSR3_MASK;
			const std::string name = CSR3ToString(reg.csr3);
			putlog(2, "CSR3 <- $%04x<%s>", reg.csr3, name.c_str());
		}
		break;

	 default:
		VMPANIC("corrupted reg.rap=%u", reg.rap);
	}
}

// メモリアクセスについて。
// LANCE(AM7990) から見たメモリは、メインメモリではなく 3ポートSRAM
// (ここでは SubRAM デバイス)。
// LANCE のメモリバスは 16bit で、DAL15..8 が SubRAM の偶数アドレス、
// DAL7..0 が SubRAM の奇数アドレスに接続されている。
//
// LANCE  | DAL15..DAL8 | DAL7..DAL0
// -------+-------------+------------
// SubRAM | +0 byte     | +1 byte
//        | +2 byte     | +3 byte
//
// メモリアクセスは常にこの構成で、CSR3.BSWP はこの1ワードを SILO(FIFO) に
// 書き込む/から読み出す順序を入れ替えるもの。

// メモリから1ワード読み込む。addr はワードアラインしていること。
uint32
LanceDevice::ReadMem(uint32 addr)
{
	assertmsg((addr & 1) == 0, "addr=$%x", addr);

	// XXX 所要クロックを考察すること
	busdata data = subram->Read2(addr);
	return data.Data();
}

// メモリに1ワード書き込む。addr はワードアラインしていること。
void
LanceDevice::WriteMem(uint32 addr, uint32 data)
{
	assertmsg((addr & 1) == 0, "addr=$%x", addr);

	// XXX 所要クロックを考察すること
	subram->Write2(addr, data);
}

// メモリ (3ポートSRAM) から1ワードピークする。バスエラーは起きない。
uint32
LanceDevice::PeekMem(uint32 addr) const
{
	busdata data = subram->Peek2(addr);
	return data.Data();
}

// INIT ビットによるチップ初期化。
void
LanceDevice::ChipInit()
{
	putlog(1, "ChipInit");

	// INIT をセットすると STOP ビットはクリア
	reg.csr0 |=  AM7990::CSR0_INIT;
	reg.csr0 &= ~AM7990::CSR0_STOP;

	// Initialization Block を読み込む
	for (int i = 0; i < countof(initblock); i++) {
		initblock[i] = ReadMem(reg.iadr + i * 2);
	}
	if (0) {
		for (int i = 0; i < countof(initblock); i++) {
			putlog(2, "initblock[%u] = $%04x", i, initblock[i]);
		}
	}

	// 送受信ディスクリプタ用の内部変数を設定
	uint32 rdrp = initblock[8] | (initblock[9] << 16);
	rmd_base = rdrp & 0xfffffc;
	int rlen = rdrp >> 29;
	rmd_num  = (1U << rlen);

	uint32 tdrp = initblock[10] | (initblock[11] << 16);
	tmd_base = tdrp & 0xfffffc;
	int tlen = tdrp >> 29;
	tmd_num  = (1U << tlen);

	padr = MacAddr(
		initblock[1] & 0xff,
		initblock[1] >> 8,
		initblock[2] & 0xff,
		initblock[2] >> 8,
		initblock[3] & 0xff,
		initblock[3] >> 8);

	// LADRF は以下のように格納されていて、そのまま LADRF(63:00) に対応する。
	// IADR+$08 LADRF(15:00)
	// IADR+$0a LADRF(31:16)
	// IADR+$0c LADRF(47:32)
	// IADR+$0e LADRF(63:48)
	ladrf = ((uint64)initblock[4] << 0)
	      | ((uint64)initblock[5] << 16)
	      | ((uint64)initblock[6] << 32)
	      | ((uint64)initblock[7] << 48);

	promisc = (initblock[0] & AM7990::IBMODE_PROM);

	// XXX 本当はいろいろ時間がかかりそうなはずだが
	// 初期化完了
	SetInterrupt(AM7990::CSR0_IDON);
}

// STRT ビットによるスタート。
void
LanceDevice::ChipStart()
{
	putlog(1, "Start");

	// STRT を書き込むと STOP はクリア
	reg.csr0 |=  AM7990::CSR0_STRT;
	reg.csr0 &= ~AM7990::CSR0_STOP;

	// MODE の DTX がクリアなら TXON をセット?
	// XXX 実際はタイミング違う気がする
	// INIT で DTX==%0 ならトランスミッタがオンになり、
	// トランスミッタがオンで STRT すると TXON がオンになる、ように読めるが
	// トランスミッタは省略してみる。
	// RX 側も同様。
	if ((initblock[0] & AM7990::IBMODE_DTX) == 0) {
		reg.csr0 |= AM7990::CSR0_TXON;
	}
	if ((initblock[0] & AM7990::IBMODE_DRX) == 0) {
		reg.csr0 |= AM7990::CSR0_RXON;
	}

	// 初回読み込みに備えて RMD1/TMD1 の先読みバッファを無効化する
	rmd1_ahead = (uint32)-1;
	tmd1_ahead = (uint32)-1;

	// バッファクリア
	tx_packet.Clear();

	ChangePhase(TXPhase::IDLE, 0_nsec);
	ChangePhase(RXPhase::IDLE, 0_nsec);

	// ホストの受信を開始する
	hostnet->EnableRx(true);
}

// STOP ビットによるチップリセット。
// ハードリセットもここに来る。
void
LanceDevice::ChipStop()
{
	putlog(1, "Reset");

	// STOPセットで CSR0 は STOP セット、他は全クリア
	reg.csr0 = AM7990::CSR0_STOP;
	ChangeInterrupt();
	// CSR1, 2 は不定
	// CSR3 はクリア
	reg.csr3 = 0;

	// 内部ポインタも戻す
	tmd_cur = 0;
	rmd_cur = 0;

	// イベントを(あれば)停止。
	ChangePhase(RXPhase::STOP);
	ChangePhase(TXPhase::STOP);

	// バッファクリア
	rx_packet.Clear();
	tx_packet.Clear();

	// ホストの受信を停止する
	hostnet->EnableRx(false);
}

// 割り込み要因を CSR0 にセットし、必要なら割り込みを上げる。
void
LanceDevice::SetInterrupt(uint32 bit)
{
	reg.csr0 |= bit;
	ChangeInterrupt();
}

// 現状に合わせて割り込み信号線の状態を変える。
void
LanceDevice::ChangeInterrupt()
{
	bool irq = (reg.IsINTR() && (reg.csr0 & AM7990::CSR0_INEA));
	interrupt->ChangeINT(this, irq);
}

// TXIDLE 送信アイドルフェーズ。
// ディスクリプタを所有できるかポーリングする。
// - 所有できれば TXCOPY フェーズへ。
// - 所有できない場合、TDMD オンなら即、
//   オフなら 1.6ms 待ってから再び TXIDLE へ。
void
LanceDevice::TXIdle(Event *ev)
{
	tx_phase = TXPhase::IDLE;

	// TDMD クリア
	reg.csr0 &= ~AM7990::CSR0_TDMD;

	// TMD1 を読み込む
	if ((int32)tmd1_ahead >= 0) {
		// すでに先読みしてあればそれを使う
		tmd1 = tmd1_ahead;
	} else {
		// 読み込む
		uint32 tmd_addr = tmd_base + tmd_cur * 8;
		tmd1 = ReadMem(tmd_addr + 2);
	}
	// どちらにしても先読みバッファは無効に戻す
	tmd1_ahead = (uint32)-1;

	// その TMD1 に OWN と STP が立っているか
	if ((tmd1 & AM7990::TMD1_OWN) && (tmd1 & AM7990::TMD1_STP)) {
		// 取り込みへ
		TXCopy();
		return;
	}

	// 立っていなければ次回は 1.6ms 後
	ChangePhase(TXPhase::IDLE, 1.6_msec);
}

// TXCOPY バッファ処理フェーズ。
// 現ディスクリプタのバッファの内容をメモリから読み込み、
// それにかかる時間相当分をウェイトする。
// - ENP ならウェイトした後 TXSEND フェーズへ。
// - 次 TMD が OWN できればウェイトした後 TXNEXT フェーズへ。
// - 次 TMD が OWN 出来なければウェイトした後 TXSEND フェーズ。
void
LanceDevice::TXCopy()
{
	tx_phase = TXPhase::COPY;

	// TMD0, TMD2 は送信することが確定してから読み出す
	uint32 tmd_addr = tmd_base + tmd_cur * 8;
	uint16 tmd0 = ReadMem(tmd_addr);
	uint16 tmd2 = ReadMem(tmd_addr + 4);

	// アドレスと送信バイト数。
	// tmd2 には 2 の補数で送信バイト数が入っているので符号反転する。
	uint32 src = ((tmd1 & 0x00ff) << 16) | tmd0;
	int32 bcnt = -(int32)(int16)tmd2;

	// ここから、SubRAM からローカルの送信バッファにコピー
	// XXX バーストコピー未対応
	uint32 srcend = src + bcnt;

	// src が奇数アドレスから始まっている(そしてコピー可能な)場合
	// (バッファを奇数アドレスから始めたりする奇特な人はいないと思うが)
	if (__predict_false((src & 1) != 0) && src < srcend) {
		uint16 data = ReadMem(src & ~1);
		src++;
		if (reg.IsBSWP() == false) {
			tx_packet.Append(data >> 8);
		} else {
			tx_packet.Append(data & 0xff);
		}
	}

	// 残り2バイト以上コピー可能な間 (ここで src はワード境界になっている)
	for (; src + 1 < srcend; ) {
		uint16 data = ReadMem(src);
		src += 2;
		if (reg.IsBSWP() == false) {
			tx_packet.Append(data & 0xff);
			tx_packet.Append(data >> 8);
		} else {
			tx_packet.Append(data >> 8);
			tx_packet.Append(data & 0xff);
		}
	}

	// src が奇数アドレスで終わっている(そしてコピー可能な)場合
	if (src < srcend) {
		uint16 data = ReadMem(src);
		if (reg.IsBSWP() == false) {
			tx_packet.Append(data & 0xff);
		} else {
			tx_packet.Append(data >> 8);
		}
	}

	// メモリからの転送にかかった時間をざっくり計算 XXX まだ適当
	// そのウェイト後に指定のフェーズへ。
	uint64 t = nsec_to_tsec(700 * bcnt / 16);

	// ここが ENP なら、送信のため TXSEND フェーズへ
	if ((tmd1 & AM7990::TMD1_ENP)) {
		ChangePhase(TXPhase::SEND, t);
	} else {
		// 次のディスクリプタを先読み
		tmd_addr = tmd_base + ((tmd_cur + 1) % tmd_num) * 8;
		tmd1_ahead = ReadMem(tmd_addr + 2);

		if ((tmd1_ahead & AM7990::TMD1_OWN)) {
			// 次ディスクリプタが所有できれば TXNEXT フェーズへ
			ChangePhase(TXPhase::NEXT, t);
		} else {
			// この時点で所有できなければアンダーフロー。
			// ここまでのバッファでパケットを送信するため TXSEND へ。
			// エラーフラグの処理は TXSEND 側で行う。
			ChangePhase(TXPhase::SEND, t);
		}
	}
}

// TXSEND フェーズ。パケットを送信。
// ただし ENP でなければアンダーフローによるパケット送信なのでフラグ処理。
// その後 TXNEXT フェーズへ。
void
LanceDevice::TXSend(Event *ev)
{
	tx_phase = TXPhase::SEND;

	if (__predict_false(tx_packet.Length() > 1518)) {
		// 本当は 1519バイト目を送ったところで BABL を立てるのだが
		// まず起きない上に面倒なので、ここで行う。
		SetInterrupt(AM7990::CSR0_BABL);

		// 1519バイト以上のパケットはホストにも投げない。
	} else {
		// 送信
		hostnet->Tx(tx_packet);
	}
	tx_packet.Clear();

	// ENP でなければアンダーフローによるものだった
	if ((tmd1 & AM7990::TMD1_ENP) == 0) {
		// TMD3 はここで書き出す
		// XXX 読まずに書くだけもいいはず?
		uint32 tmd_addr = tmd_base + tmd_cur * 8;
		uint16 tmd3 = ReadMem(tmd_addr + 6);
		tmd3 |= AM7990::TMD3_BUFF | AM7990::TMD3_UFLO;
		WriteMem(tmd_addr + 6, tmd3);

		// TMD1 は TXNEXT フェーズで書き戻す
		tmd1 |= AM7990::TMD1_ERR;
	}

	// どちらにしても送信割り込み
	SetInterrupt(AM7990::CSR0_TINT);

	ChangePhase(TXPhase::NEXT);
}

// TXNEXT フェーズ。次のディスクリプタを指して TXIDLE へ。
void
LanceDevice::TXNext(Event *ev)
{
	tx_phase = TXPhase::NEXT;

	// このディスクリプタの所有権を放棄
	tmd1 &= ~AM7990::TMD1_OWN;
	uint32 tmd_addr = tmd_base + tmd_cur * 8;
	WriteMem(tmd_addr + 2, tmd1);

	// 即、次のディスクリプタの所有を試みる
	tmd_cur = (tmd_cur + 1) % tmd_num;
	ChangePhase(TXPhase::IDLE);
}

// 指定時間後に TX フェーズを遷移する。
// (変化があれば)変更するではないことに注意。名前がアレだが IDLE フェーズは
// ポーリングしているので IDLE から IDLE への遷移も必ず起こす必要がある。
// TX か RX かは関数オーバーロードで区別している。
void
LanceDevice::ChangePhase(TXPhase new_phase, uint64 time)
{
	tx_event->time = time;
	switch (new_phase) {
	 case TXPhase::STOP:
		scheduler->StopEvent(tx_event);
		return;
	 case TXPhase::IDLE:
		tx_event->SetCallback(&LanceDevice::TXIdle);
		break;
	 case TXPhase::SEND:
		tx_event->SetCallback(&LanceDevice::TXSend);
		break;
	 case TXPhase::NEXT:
		tx_event->SetCallback(&LanceDevice::TXNext);
		break;
	 default:
		assertmsg(false, "phase %u", (uint)new_phase);
	}
	scheduler->RestartEvent(tx_event);
}

// 受信フェーズ遷移図
//
//           +--------+
//           | RXSTOP |<- - - (CSR0.STOP 書き込み / リセット)
//           +--------+
//              :
//              :        +‥‥‥+ <‥‥ RxMessage (パケット受信)
//              v        v      :
//           +-------------+    :
//   +‥‥‥>|    RXIDLE   |‥‥+ Idle
//   :       +-------------+
//   :         |     |   ^
//   :         |     |   :
//   :         v     |   :
//   :  +--------+   | +--------+
//   +‥| RXMISS |   | | RXNEXT |
//      +--------+   | +--------+
//                   |   ^   |
//                   |   :   |
//                   v   :   v
//                 +-----------+
//                 |  RXCOPY   |                   関数呼び出し ---->
//                 +-----------+             イベントによる遷移 ‥‥>

// パケット受信通知。HostNet から (EthernetDevice 経由で) 呼ばれる。
void
LanceDevice::RxMessage(MessageID msgid, uint32 arg)
{
	// RXIDLE ポーリングのウェイト中ならすぐに始める。
	// そうでないときは RXIDLE に戻ったところで処理される。
	if (rx_phase == RXPhase::IDLE) {
		ChangePhase(RXPhase::IDLE);
	}
}

// RXIDLE 受信アイドルフェーズ。
// (最初の)ディスクリプタを所有できるかポーリングする。
void
LanceDevice::RXIdle(Event *ev)
{
	rx_phase = RXPhase::IDLE;

	putlog(3, "%s[%u]", __func__, rmd_cur);

	// RMD1 を読み込む
	if ((int32)rmd1_ahead >= 0) {
		// すでにあればそれを使う
		rmd1 = rmd1_ahead;
	} else {
		// 読み込む
		uint32 rmd_addr = rmd_base + rmd_cur * 8;
		rmd1 = ReadMem(rmd_addr + 2);
	}
	// どちらにしても先読みバッファは無効に戻す
	rmd1_ahead = (uint32)-1;

	// 受信しているか
	if (hostnet->Rx(&rx_packet)) {
		// その RMD1 に OWN が立っているか
		if ((rmd1 & AM7990::RMD1_OWN)) {
			// ここが STP になる
			rmd1 |= AM7990::RMD1_STP;
			// RXCOPY へ直接移行する。
			RXCopy();
		} else {
			// バッファが無いので MISS へ直接移行する。
			// MISS までの時間は受信処理によって表現されているはず
			RXMiss();
		}
	} else {
		if ((rmd1 & AM7990::RMD1_OWN)) {
			// ここが STP になる
			rmd1 |= AM7990::RMD1_STP;
		}
		// 1.6ms 待って再度 RXIDLE へ。
		ChangePhase(RXPhase::IDLE, 1.6_msec);
	}
}

// RXCOPY バッファ処理フェーズ。
// 受信バッファからディスクリプタのバッファに書き込み、
// 書き込みにかかる時間相当分をウェイトする。
void
LanceDevice::RXCopy()
{
	rx_phase = RXPhase::COPY;

	putlog(1, "%s[%u]", __func__, rmd_cur);

	// RMD0, RMD2 は受信することが確定してから読み出す
	uint32 rmd_addr = rmd_base + rmd_cur * 8;
	uint16 rmd0 = ReadMem(rmd_addr);
	uint16 rmd2 = ReadMem(rmd_addr + 4);

	// アドレスと受信バッファのバイト数。
	uint32 dst = ((rmd1 & 0x00ff) << 16) | rmd0;
	// rmd2 は 2 の補数
	int32 bcnt = -(int32)(int16)rmd2;

	// 今回のコピーバイト数。
	uint copycnt = std::min((uint)bcnt, rx_packet.GetRemain());
	uint32 dstend = dst + copycnt;

	// ローカルの受信バッファからディスクリプタバッファにコピー。
	// XXX バーストコピー未対応

	// dst が奇数アドレスから始まっている場合。
	// 奇数バイトには不定データが出るらしいがここでは 0 にする
	if (__predict_false((dst & 1) != 0) && dst < dstend) {
		uint16 data;
		if (reg.IsBSWP() == false) {
			data = rx_packet.Read() << 8;
		} else {
			data = rx_packet.Read();
		}
		WriteMem(dst & ~1, data);
		dst += 1;
	}

	// 残り2バイト以上コピー可能な間 (ここで dst はワード境界になっている)。
	for (; dst + 1 < dstend; ) {
		uint16 data;
		if (reg.IsBSWP() == false) {
			data  = rx_packet.Read();
			data |= rx_packet.Read() << 8;
		} else {
			data  = rx_packet.Read() << 8;
			data |= rx_packet.Read();
		}
		WriteMem(dst, data);
		dst += 2;
	}

	// dst が奇数アドレスで終わっている場合。
	// 奇数バイトには不定データが出るらしいがここでは 0 にする
	if (dst < dstend) {
		uint16 data;
		if (reg.IsBSWP() == false) {
			data = rx_packet.Read();
		} else {
			data = rx_packet.Read() << 8;
		}
		WriteMem(dst, data);
	}

	// 全部書き出せたらここが ENP
	if (rx_packet.GetRemain() == 0) {
		rmd1 |= AM7990::RMD1_ENP;

		// ENP なら MCNT を更新
		WriteMem(rmd_addr + 6, copycnt & 0xfff);
	} else {
		rmd1 &= ~AM7990::RMD1_ENP;
		putlog(1, "copy remain %u", rx_packet.GetRemain());
	}

	// メモリへの転送にかかった時間をざっくり計算 XXX まだ適当
	// そのウェイト後に RXNEXT フェーズへ。
	uint64 t = nsec_to_tsec(700 * copycnt / 16);
	ChangePhase(RXPhase::NEXT, t);
}

// RXNEXT フェーズ。
// ここが ENP なら、次のディスクリプタを指して RXIDLE へ。
// ENP でなければ、
// - 次のディスクリプタが所有できれば引き続き書き出すため RXCOPY へ。
// - 次のディスクリプタが所有できなければエラーにして RXIDLE へ。
void
LanceDevice::RXNext(Event *ev)
{
	rx_phase = RXPhase::NEXT;

	putlog(1, "%s[%u]", __func__, rmd_cur);

	bool is_chained = false;

	if ((rmd1 & AM7990::RMD1_ENP)) {
		// ENP なら、RXIDLE へ。
		rx_packet.Clear();
		ChangePhase(RXPhase::IDLE);
	} else {
		// 次のディスクリプタを先読み
		uint32 rmd_addr = rmd_base + ((rmd_cur + 1) % rmd_num) * 8;
		rmd1_ahead = ReadMem(rmd_addr + 2);

		if ((rmd1_ahead & AM7990::RMD1_OWN)) {
			// 次ディスクリプタが所有できれば RXCOPY へ。
			is_chained = true;
		} else {
			// この時点で所有できなければオーバーフロー。
			// XXX BUFF と OFLO の違いがよくわからん
			rmd1 |= AM7990::RMD1_BUFF | AM7990::RMD1_OFLO;

			// 書き込み先が無いのでこのパケットは打ち切ってみる
			rx_packet.Clear();

			// 次のディスクリプタをポールするため RXIDLE へ。
			ChangePhase(RXPhase::IDLE);
		}
	}

	// このディスクリプタの所有権を放棄
	rmd1 &= ~AM7990::RMD1_OWN;
	uint32 rmd_addr = rmd_base + rmd_cur * 8;
	WriteMem(rmd_addr + 2, rmd1);

	// 次のディスクリプタへ
	rmd_cur = (rmd_cur + 1) % rmd_num;

	// どちらにしても受信割り込み
	SetInterrupt(AM7990::CSR0_RINT);

	if (is_chained) {
		// 次のディスクリプタがある場合は RXCopy に直接遷移する。
		rmd1 = rmd1_ahead;
		rmd1_ahead = -1;
		RXCopy();
	}
}

// RXMISS 受信失敗処理。
void
LanceDevice::RXMiss()
{
	rx_phase = RXPhase::MISS;

	putlog(1, "%s", __func__);

	// 受信データがあるのにディスクリプタを所有してなければエラー
	SetInterrupt(AM7990::CSR0_MISS | AM7990::CSR0_RINT);

	// 受信データを破棄。
	rx_packet.Clear();

	// そのまますぐにアイドルフェーズに戻るんだろうか
	ChangePhase(RXPhase::IDLE);
}

// この宛先アドレスを受信するかどうか。
// これは HostNet スレッドで呼ばれる。
int
LanceDevice::HWAddrFilter(const MacAddr& dstaddr) const
{
	if (promisc) {
		return HPF_PASS;
	}

	if (dstaddr.IsUnicast()) {
		if (dstaddr != padr) {
			return HPF_DROP_UNICAST;
		}
	} else {
		if (dstaddr.IsBroadcast()) {
			return HPF_PASS;
		}

		// マルチキャスト。
		//
		// AM7990 のマルチキャストフィルタ(ハッシュ) は宛先 MAC アドレスの
		// CRC (32ビット) のうち、下位6ビット *のビット順を逆順にしたもの*
		// を pos として、LADRF の下から pos ビット目が立っていれば受理する
		// というもの。
		//                   31       7   6   5   4   3   2   1   0
		//                  +---   -+---+---+---+---+---+---+---+---+
		// CRC32(dstaddr) = |   ..  | X   X | p0| p1| p2| p3| p4| p5|
		//                  +---   -+---+---+---+---+---+---+---+---+
		//
		//                                  +---+---+---+---+---+---+
		//           pos  =                 | p5| p4| p3| p2| p1| p0|
		//                                  +---+---+---+---+---+---+

		uint32 crc = EthernetDevice::CRC32(dstaddr);
		uint pos = bitrev8(crc) >> 2;
		bool mcast_pass = (ladrf & (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;
}

// 指定時間後に RX フェーズを遷移する。
// (変化があれば)変更するではないことに注意。名前がアレだが IDLE フェーズは
// ポーリングしているので IDLE から IDLE への遷移も必ず起こす必要がある。
// TX か RX かは関数オーバーロードで区別している。
void
LanceDevice::ChangePhase(RXPhase new_phase, uint64 time)
{
	rx_event->time = time;
	switch (new_phase) {
	 case RXPhase::STOP:
		scheduler->StopEvent(rx_event);
		return;
	 case RXPhase::IDLE:
		rx_event->SetCallback(&LanceDevice::RXIdle);
		break;
	 case RXPhase::NEXT:
		rx_event->SetCallback(&LanceDevice::RXNext);
		break;
	 default:
		assertmsg(false, "phase %u", (uint)new_phase);
	}
	scheduler->RestartEvent(rx_event);
}


// ビット名文字列配列は [0] が MSB 側の順で並べる

/*static*/ const char * const LanceDevice::csr0names[] = {
	"ERR",  "BABL", "CERR", "MISS", "MERR", "RINT", "TINT", "IDON",
	"INTR", "INEA", "RXON", "TXON", "TDMD", "STOP", "STRT", "INIT",
};

/*static*/ const char * const LanceDevice::csr3names[] = {
	"", "", "", "", "", "", "", "",
	"", "", "", "", "", "BSWP", "ACON", "BCON",
};

/*static*/ const char * const LanceDevice::ib_mode_names[] = {
	"PROM", "INTL", "DRTY", "COLL", "DTCR", "LOOP", "DTX",  "DRX",
};

/*static*/ const char * const LanceDevice::rmd1_names[] = {
	"OWN",  "ERR",  "FRAM", "OFLO", "CRC",  "BUFF", "STP",  "ENP",
};

/*static*/ const char * const LanceDevice::tmd1_names[] = {
	"OWN",  "ERR",  "----", "MORE", "ONE",  "DEF",  "STP",  "ENP",
};

/*static*/ const char * const LanceDevice::tmd3_names[] = {
	"BUFF", "UFLO", "----", "LCOL", "LCAR", "BTRY", "",     "",
};

// bits の立っているビット名を左から MSB->LSB の順で並べて返す
const std::string
LanceDevice::BitToString(const char * const name[], uint16 bits)
{
	std::string str;

	while (bits) {
		int clz = __builtin_clz(bits) - 16;
		if (!str.empty()) {
			str += ",";
		}
		str += name[clz];
		bits ^= 1 << (15 - clz);
	}
	return str;
}

// CSR0 のビット名を返す
const std::string
LanceDevice::CSR0ToString(uint16 bits)
{
	return BitToString(csr0names, bits);
}

// CSR3 のビット名を返す
const std::string
LanceDevice::CSR3ToString(uint16 bits)
{
	return BitToString(csr3names, bits);
}

unix.superglobalmegacorp.com

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