--- nono/vm/lance.cpp 2026/04/29 17:04:42 1.1.1.6 +++ nono/vm/lance.cpp 2026/04/29 17:05:20 1.1.1.14 @@ -4,138 +4,108 @@ // Licensed under nono-license.txt // -#include "lance.h" -#include "bus.h" -#include "config.h" -#include "mpu.h" -#include "mystring.h" -#include "mythread.h" -#include "zram.h" +// +// Lance (AM7990) +// -static void *lance_run(void *); +#include "lance.h" +#include "bitops.h" +#include "interrupt.h" +#include "mainbus.h" +#include "scheduler.h" +#include "subram.h" +// コンストラクタ LanceDevice::LanceDevice() + : inherited(OBJ_ETHERNET(0)) { - logname = "lance"; - devname = "Lance"; - devaddr = 0xf1000000; - - monitor_size = nnSize(80, 34); - - rx_event.dev = this; - rx_event.func = (DeviceCallback_t)&LanceDevice::Callback; - rx_event.code = 0; - rx_event.time = 0; // 都度設定する - rx_event.SetName("Lance RX"); - - tx_event.dev = this; - tx_event.func = (DeviceCallback_t)&LanceDevice::Callback; - tx_event.code = 0; - tx_event.time = 0; // 都度設定する - tx_event.SetName("Lance TX"); + SetName("Lance"); + ClearAlias(); + AddAlias("Lance"); + AddAlias("Ethernet"); + + // func, time は都度設定する + rx_event.Regist("Lance RX"); + tx_event.Regist("Lance TX"); + + monitor.func = ToMonitorCallback(&LanceDevice::MonitorUpdate); + monitor.SetSize(80, 34); + monitor.Regist(ID_MONITOR_LANCE); } +// デストラクタ LanceDevice::~LanceDevice() { - if (thread_created) { - Terminate(); - pthread_join(thread, NULL); - } } +// 初期化 bool LanceDevice::Init() { - // 親も Init() を持っている if (inherited::Init() == false) { return false; } - const ConfigItem& item = gConfig->Find("ethernet-macaddr"); - const std::string& config_macaddr = item.AsString(); - if (config_macaddr == "rom") { - // ROM に書いてあるのを使うので、ここでは何もしなくていい - } else { - if (config_macaddr == "auto") { - // 自動生成 - // すでに記録されていれば使う。なければ生成して記録したい - if (0) { - } else { - GenerateMacAddr(&macaddr); - } - } else { - // ユーザ指定 - if (ParseMacAddr(macaddr, config_macaddr) == false) { - item.Err("invalid MAC address"); - return false; - } - } - - putmsg(1, "macaddr=%s", macaddr.to_string(':').c_str()); - } + interrupt = GetInterruptDevice(); + subram = GetSubRAMDevice(); - // ワーカスレッド起動 - pthread_create(&thread, NULL, lance_run, NULL); - thread_created = true; + // ホストからの受信コールバックを登録 + scheduler->ConnectMessage(MessageID::HOSTNET_RX(0), this, + ToMessageCallback(&LanceDevice::HostRxCallback)); return true; } +// リセット void -LanceDevice::ResetHard() +LanceDevice::ResetHard(bool poweron) { - std::unique_lock lock(mtx); - reg.rap = 0; ChipStop(); } uint64 -LanceDevice::Read(uint32 addr) +LanceDevice::Read(uint32 offset) { uint32 data; - switch (addr) { + switch (offset) { case 0: // RDP data = ReadData(); return data; case 1: // RAP - { - std::unique_lock lock(mtx); data = reg.rap; putlog(2, "RAP -> %d", reg.rap); return data; - } + default: + VMPANIC("corrupted offset=%d", offset); + return 0xff; } - __unreachable(); } uint64 -LanceDevice::Write(uint32 addr, uint32 data) +LanceDevice::Write(uint32 offset, uint32 data) { - switch (addr) { + switch (offset) { case 0: // RDP WriteData(data); return 0; case 1: // RAP - { - std::unique_lock lock(mtx); reg.rap = data & 3; return 0; - } + default: + VMPANIC("corrupted offset=%d", offset); + return 0; } - __unreachable(); } uint64 -LanceDevice::Peek(uint32 addr) +LanceDevice::Peek(uint32 offset) { - switch (addr) { + switch (offset) { case 0: // RDP - { // 今読めるはずのデータ。 // CSR[123] は STOP 中でなければ不定だがとりあえずそれは無視。 - std::unique_lock lock(mtx); switch (reg.rap) { case 0: return reg.GetCSR0(); @@ -145,20 +115,20 @@ LanceDevice::Peek(uint32 addr) return reg.iadr >> 16; case 3: return reg.csr3; + default: + break; } break; - } case 1: // RAP - { - std::unique_lock lock(mtx); return reg.rap; - } + default: + break; } - __unreachable(); + return 0xff; } void -LanceDevice::MonitorUpdate(TextScreen& monitor) +LanceDevice::MonitorUpdate(Monitor *, TextScreen& screen) { /* CSR0: $1111 ERR BABL CERR MISS MERR RINT TINT IDON IADR:$123456 @@ -178,7 +148,7 @@ LADRF:$12345678_12345678 TDRP: // name を表示。(var & bit) ならオン(反転)表示。 // bitpos は 0..15 で [0] が MSB にあたる。 #define MonitorBit(var, bit, name) do { \ - monitor.Puts(x, y, TA::OnOff((var) & (bit)), name); \ + screen.Puts(x, y, TA::OnOff((var) & (bit)), name); \ x += strlen(name) + 1; \ } while (0) @@ -186,19 +156,17 @@ LADRF:$12345678_12345678 TDRP: int y; int i; - monitor.Clear(); - - // XXX lock + screen.Clear(); y = 0; // CSR uint16 csr0 = reg.GetCSR0(); - monitor.Print(0, y, "CSR0:$%04x", csr0); - MonitorReg(monitor, 11, y, csr0 >> 8, csr0names); - MonitorReg(monitor, 11, y + 1, csr0, csr0names + 8); + screen.Print(0, y, "CSR0:$%04x", csr0); + MonitorReg(screen, 11, y, csr0 >> 8, csr0names); + MonitorReg(screen, 11, y + 1, csr0, csr0names + 8); - monitor.Print(54, y++, "IADR:$%06x", reg.iadr); - monitor.Print(54, y, "CSR3:$%04x", reg.csr3); + 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]); @@ -206,11 +174,11 @@ LADRF:$12345678_12345678 TDRP: MonitorBit(reg.csr3, AM7990::CSR3_BCON, csr3names[15]); y++; - monitor.Puts(0, y, ""); + screen.Puts(0, y, ""); y++; // MODE は bit15 と bit6-0 の8ビットだけ使われてるので // 途中を省略して 8ビットとして表示する - monitor.Print(0, y, "MODE:$%04x", initblock[0]); + 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]); @@ -222,12 +190,12 @@ LADRF:$12345678_12345678 TDRP: MonitorBit(initblock[0], AM7990::IBMODE_DRX, ib_mode_names[7]); y++; - monitor.Print(0, y, "PADR: %02x:%02x:%02x:%02x:%02x:%02x", + 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); // XXX LADRF のバイト順未検証 - monitor.Print(0, y + 1, "LADRF:$%04x%04x_%04x%04x", + screen.Print(0, y + 1, "LADRF:$%04x%04x_%04x%04x", initblock[7], initblock[6], initblock[5], @@ -235,19 +203,19 @@ LADRF:$12345678_12345678 TDRP: // RDRP uint32 rdrp = initblock[8] | (initblock[9] << 16); - monitor.Print(31, y, "RDRP:$%08x (RDRA=$%06x entries=%d)", + screen.Print(31, y, "RDRP:$%08x (RDRA=$%06x entries=%d)", rdrp, rmd_base, rmd_num); y++; // TDRP uint32 tdrp = initblock[10] | (initblock[11] << 16); - monitor.Print(31, y, "TDRP:$%08x (TDRA=$%06x entries=%d)", + screen.Print(31, y, "TDRP:$%08x (TDRA=$%06x entries=%d)", tdrp, tmd_base, tmd_num); y++; y++; // RMD - monitor.Puts(0, y, ""); + screen.Puts(0, y, ""); uint16 rmd[4]; uint32 rmd_addr; @@ -258,7 +226,7 @@ LADRF:$12345678_12345678 TDRP: rmd[j] = PeekMem(rmd_addr + j * 2); } x = 6; - monitor.Print(x, y, "Current %02x", rmd_cur); x += 11; + 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] @@ -268,11 +236,11 @@ LADRF:$12345678_12345678 TDRP: MonitorBit(rmd[1], AM7990::RMD1_STP, rmd1_names[6]); MonitorBit(rmd[1], AM7990::RMD1_ENP, rmd1_names[7]); // BCNT は負数(2の補数)でエンコードされているので正数にデコード - monitor.Print(x, y, "MCNT=$%03x BCNT=$%03x", + screen.Print(x, y, "MCNT=$%03x BCNT=$%03x", rmd[3] & 0xfff, (-(int16)rmd[2]) & 0xfff); x += 20; - monitor.Print(x, y, "buf=$%06x", ((rmd[1] & 0xff) << 16) | rmd[0]); + screen.Print(x, y, "buf=$%06x", ((rmd[1] & 0xff) << 16) | rmd[0]); } y++; @@ -292,7 +260,7 @@ LADRF:$12345678_12345678 TDRP: for (int j = 0; j < countof(rmd); j++) { rmd[j] = PeekMem(rmd_addr + j * 2); } - monitor.Print(x * 27, y + i, + screen.Print(x * 27, y + i, (rs == rmd_cur) ? TA::Em : TA::Normal, "%02x %c%c%c%c%c%c $%05x $%03x/$%03x", rs, @@ -317,7 +285,7 @@ LADRF:$12345678_12345678 TDRP: y += 17; // TMD - monitor.Puts(0, y, ""); + screen.Puts(0, y, ""); uint16 tmd[4]; uint32 tmd_addr; @@ -329,7 +297,7 @@ LADRF:$12345678_12345678 TDRP: } // MORE, ONE, DEF と LCOL, LCAR, RTRY, TDR は発生し得ないので省略 x = 6; - monitor.Print(x, y, "Current %02x ", tmd_cur); x += 11; + 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]); @@ -338,9 +306,9 @@ LADRF:$12345678_12345678 TDRP: MonitorBit(tmd[3], AM7990::TMD3_BUFF, tmd3_names[0]); MonitorBit(tmd[3], AM7990::TMD3_UFLO, tmd3_names[1]); // BCNT は負数(2の補数)でエンコードされているので正数にデコード - monitor.Print(x, y, "BCNT=$%03x", (-(int16)tmd[2]) & 0xfff); + screen.Print(x, y, "BCNT=$%03x", (-(int16)tmd[2]) & 0xfff); x += 10; - monitor.Print(x, y, "buf=$%06x", ((tmd[1] & 0xff) << 16) | tmd[0]); + screen.Print(x, y, "buf=$%06x", ((tmd[1] & 0xff) << 16) | tmd[0]); } y++; @@ -360,7 +328,7 @@ LADRF:$12345678_12345678 TDRP: for (int j = 0; j < countof(tmd); j++) { tmd[j] = PeekMem(tmd_addr + j * 2); } - monitor.Print(x * 27, y + i, + screen.Print(x * 27, y + i, (ts == tmd_cur) ? TA::Em : TA::Normal, "%02x %c%c%c%c%c $%05x $%03x", ts, @@ -385,12 +353,12 @@ LADRF:$12345678_12345678 TDRP: // レジスタをビットごとに表示する。 // data は下位8ビットのみ使用し、上位は無視する。 void -LanceDevice::MonitorReg(TextScreen& monitor, +LanceDevice::MonitorReg(TextScreen& screen, int x, int y, uint32 data, const char * const *names) { for (int i = 0; i < 8; i++) { bool b = data & (1 << (7 - i)); - monitor.Puts(x + i * 5, y, TA::OnOff(b), names[i]); + screen.Puts(x + i * 5, y, TA::OnOff(b), names[i]); } } @@ -401,15 +369,13 @@ LanceDevice::ReadData() { uint32 data; - std::unique_lock lock(mtx); - // XXX CSR[123] は STOP 中だけ読める。そうでない時は未定義値になる。 switch (reg.rap) { case 0: { data = reg.GetCSR0(); const std::string name = CSR0ToString(data); - putlog(2, "CSR0 -> $%04x<%s>", data, name.c_str()); + putlog(3, "CSR0 -> $%04x<%s>", data, name.c_str()); break; } case 1: @@ -418,7 +384,7 @@ LanceDevice::ReadData() putlog(2, "CSR%d -> $%04x", reg.rap, data); } else { data = 0xcccc & AM7990::CSR1_MASK; - putlog(2, "CSR%d -> $%04x (不定読み出し)", reg.rap, data); + putlog(2, "CSR%d -> $%04x (indefinate read)", reg.rap, data); } break; case 2: @@ -427,7 +393,7 @@ LanceDevice::ReadData() putlog(2, "CSR%d -> $%04x", reg.rap, data); } else { data = 0xcccc & AM7990::CSR2_MASK; - putlog(2, "CSR%d -> $%04x (不定読み出し)", reg.rap, data); + putlog(2, "CSR%d -> $%04x (indefinate read)", reg.rap, data); } break; case 3: @@ -437,11 +403,13 @@ LanceDevice::ReadData() putlog(2, "CSR%d -> $%04x<%s>", reg.rap, data, name.c_str()); } else { data = 0xcccc & AM7990::CSR3_MASK; - putlog(2, "CSR%d -> $%04x (不定読み出し)", reg.rap, data); + putlog(2, "CSR%d -> $%04x (indefinate read)", reg.rap, data); } break; default: - __unreachable(); + VMPANIC("corrupted reg.rap=%d", reg.rap); + data = 0xff; + break; } return data; } @@ -450,8 +418,6 @@ LanceDevice::ReadData() void LanceDevice::WriteData(uint32 data) { - std::unique_lock lock(mtx); - switch (reg.rap) { case 0: { @@ -466,16 +432,26 @@ LanceDevice::WriteData(uint32 data) break; } - if ((data & AM7990::CSR0_INIT)) { + // INIT, STRT は立ち上がりで実行にしてみる。 + // OpenBSD が割り込みクリアするために読んだ CSR0 をそのまま書いて + // くるようなので、毎回 INIT と STRT が走ってしまう。 + // とりあえず立ち上がりでやってみる。 + // XXX 要実機調査 + + if (bit_rising(reg.csr0, data, AM7990::CSR0_INIT)) { ChipInit(); } - if ((data & AM7990::CSR0_STRT)) { + 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); + } } // 書き込みがビットクリアになる人たち @@ -504,16 +480,11 @@ LanceDevice::WriteData(uint32 data) // INEA は %0 も %1 も書き込める // INEA は STOP 中は設定できない if (!reg.IsSTOP()) { - uint16 old_inea = reg.csr0 & AM7990::CSR0_INEA; - uint16 new_inea = data & AM7990::CSR0_INEA; reg.csr0 &= ~AM7990::CSR0_INEA; - reg.csr0 |= new_inea; - - // INEA が 0->1 に変化して、INTR 立ってたら割り込み上げる? - if (old_inea == 0 && new_inea != 0 && reg.IsINTR()) { - Interrupt(); - } + reg.csr0 |= data & AM7990::CSR0_INEA; } + + ChangeInterrupt(); break; } @@ -522,6 +493,11 @@ LanceDevice::WriteData(uint32 data) if (reg.IsSTOP()) { reg.iadr = (reg.iadr & 0xff0000) | (data & AM7990::IADR_L_MASK); putlog(2, "IDAR(L)=$%06x", reg.iadr); + +#if 0 + // IADR 設定時にパフォーマンス測定用実時間表示 + scheduler->PutlogRealtime(); +#endif } break; @@ -544,20 +520,25 @@ LanceDevice::WriteData(uint32 data) break; default: - __unreachable(); + VMPANIC("corrupted reg.rap=%d", reg.rap); } } // メモリアクセスについて。 -// AM7990 から見たメモリは LUNA の 3ポートSRAM (ここでは ZRAM デバイス)。 +// 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 // -// AM7990 は CSR3.BSWP が %0 ならリトルエンディアンアクセス、 -// %1 ならビッグエンディアンアクセス。 -// 一方、RAMDevice.Read16() はビッグエンディアン動作になるので、 -// BSWP==%0 ならバイトスワップする。ホストのエンディアンには無関係。 +// メモリアクセスは常にこの構成で、CSR3.BSWP はこの1ワードを SILO(FIFO) に +// 書き込む/から読み出す順序を入れ替えるもの。 // メモリから1ワード読み込む。paddr はワードアラインしていること。 -// バスエラーは起きない。 uint64 LanceDevice::ReadMem(uint32 paddr) { @@ -565,14 +546,8 @@ LanceDevice::ReadMem(uint32 paddr) assertmsg((paddr & 1) == 0, "paddr=$%x", paddr); - // XXX 3ポートRAM のエンディアンとかを調べること - data = gZRAM->Read16(paddr); - if (!reg.IsBSWP()) { - data = __builtin_bswap16(data); - } - // XXX 所要クロックを考察すること - + data = subram->Read16(paddr); return data; } @@ -582,13 +557,8 @@ LanceDevice::WriteMem(uint32 paddr, uint { assertmsg((paddr & 1) == 0, "paddr=$%x", paddr); - if (!reg.IsBSWP()) { - data = __builtin_bswap16(data); - } - // XXX 所要クロックを考察すること - - gZRAM->Write16(paddr, data); + subram->Write16(paddr, data); return 0; } @@ -598,26 +568,18 @@ LanceDevice::PeekMem(uint32 paddr) { uint64 data; - paddr &= 0x1ffff; - paddr += 0x71000000; // XXX 定数にしたほうがいい + data = subram->Peek8(paddr) << 8; + data |= subram->Peek8(paddr + 1); - if (reg.IsBSWP()) { - // ビッグエンディアンアクセス - data = vm_phys_peek_8(paddr) << 8; - data |= vm_phys_peek_8(paddr + 1); - } else { - // リトルエンディアンアクセス - data = vm_phys_peek_8(paddr); - data |= vm_phys_peek_8(paddr + 1) << 8; - } return data; } // INIT ビットによるチップ初期化。 -// ロック状態で呼ばれる。 void LanceDevice::ChipInit() { + putlog(1, "ChipInit"); + // INIT をセットすると STOP ビットはクリア reg.csr0 |= AM7990::CSR0_INIT; reg.csr0 &= ~AM7990::CSR0_STOP; @@ -628,7 +590,7 @@ LanceDevice::ChipInit() } if (0) { for (int i = 0; i < countof(initblock); i++) { - putlog(3, "initblock[%d] = $%04x", i, initblock[i]); + putlog(2, "initblock[%d] = $%04x", i, initblock[i]); } } @@ -643,17 +605,27 @@ LanceDevice::ChipInit() int tlen = tdrp >> 29; tmd_num = (1 << tlen); + padr[0] = initblock[1] & 0xff; + padr[1] = initblock[1] >> 8; + padr[2] = initblock[2] & 0xff; + padr[3] = initblock[2] >> 8; + padr[4] = initblock[3] & 0xff; + padr[5] = initblock[3] >> 8; + + // ホストデバイスに設定 + hostnet->SetMyAddr(padr); + hostnet->SetPromisc((initblock[0] & AM7990::IBMODE_PROM)); + // XXX 本当はいろいろ時間がかかりそうなはずだが // 初期化完了 SetInterrupt(AM7990::CSR0_IDON); } // STRT ビットによるスタート。 -// ロック状態で呼ばれる。 void LanceDevice::ChipStart() { - putlog(1, "スタート"); + putlog(1, "Start"); // STRT を書き込むと STOP はクリア reg.csr0 |= AM7990::CSR0_STRT; @@ -670,26 +642,28 @@ LanceDevice::ChipStart() } if ((initblock[0] & AM7990::IBMODE_DRX) == 0) { reg.csr0 |= AM7990::CSR0_RXON; - // ホストからのパケット受信を許可。 - RecvEnabled = true; } // 初回読み込みに備えて RMD1/TMD1 の先読みバッファを無効化する rmd1_ahead = (uint32)-1; tmd1_ahead = (uint32)-1; - // ワーカスレッドに通知 - request |= REQ_TXIDLE | REQ_RXIDLE; - cv.notify_one(); + // バッファクリア + tx_packet.Clear(); + + ChangePhase(TXPhase::IDLE, 0_nsec); + ChangePhase(RXPhase::IDLE, 0_nsec); + + // ホストの受信を開始する + hostnet->EnableRx(true); } // STOP ビットによるチップリセット。 // ハードリセットもここに来る。 -// ロック状態で呼ばれる。 void LanceDevice::ChipStop() { - putlog(1, "リセット"); + putlog(1, "Reset"); // STOPセットで CSR0 は STOP セット、他は全クリア reg.csr0 = AM7990::CSR0_STOP; @@ -702,119 +676,49 @@ LanceDevice::ChipStop() rmd_cur = 0; // イベントを(あれば)停止。 - rx_event.Stop(); - tx_event.Stop(); + ChangePhase(RXPhase::STOP); + ChangePhase(TXPhase::STOP); - // 受信を停止 - RecvEnabled = false; + // バッファクリア + rx_packet.Clear(); + tx_packet.Clear(); + + // ホストの受信を停止する + hostnet->EnableRx(false); } -// 割り込み要因を CSR0 にセットし、許可されていれば割り込みを上げる。 +// 割り込み要因を CSR0 にセットし、必要なら割り込みを上げる。 void LanceDevice::SetInterrupt(uint32 bit) { reg.csr0 |= bit; - - Interrupt(); + ChangeInterrupt(); } -// 許可されていれば割り込みを上げる。 +// 現状に合わせて割り込み信号線の状態を変える。 void -LanceDevice::Interrupt() +LanceDevice::ChangeInterrupt() { - if ((reg.csr0 & AM7990::CSR0_INEA)) { - putlog(2, "割り込み"); - gMPU->Interrupt(this, 3); + if (reg.IsINTR() && (reg.csr0 & AM7990::CSR0_INEA)) { + interrupt->AssertINT(this); } else { - putlog(2, "割り込みはマスクされている"); + interrupt->NegateINT(this); } } -// ワーカスレッドのエントリポイント -void * -lance_run(void *dummy) -{ - PTHREAD_SETNAME("Lance worker"); - - dynamic_cast(gEthernet.get())->ThreadRun(); - return NULL; -} - -// ワーカスレッド -void -LanceDevice::ThreadRun() -{ - for (;;) { - uint32 req; - - // 何か起きるまで待つ - std::unique_lock lock(mtx); - cv.wait(lock, [&] { return (request != 0); }); - // request のほうはもうここでリセットして、コピーのほうを使う - req = request; - request = 0; - - if ((req & REQ_TERMINATE)) { - // 終了するので、他のフラグは無視 - break; - } - - // デバッグ用に TMD の OWN,STP,ENP に変更があれば表示する - if (1) { - std::string tmdstr; - for (int i = 0; i < tmd_num; i++) { - uint16 tmd1_ = PeekMem(tmd_base + i * 8 + 2); - tmdstr += string_format(" %c%c%c", - (tmd1_ & AM7990::TMD1_OWN) ? 'W' : '-', - (tmd1_ & AM7990::TMD1_STP) ? 'S' : '-', - (tmd1_ & AM7990::TMD1_ENP) ? 'E' : '-'); - } - if (tmdstr != last_tmdstr) { - putlog(3, "TMD %s", tmdstr.c_str()); - last_tmdstr = tmdstr; - } - } - -#define dispatch(flag, func) do { \ - if ((req & (flag))) { \ - req &= ~(flag); \ - (func)(); \ - } \ -} while (0) - - dispatch(REQ_TXIDLE, TXIdle); - dispatch(REQ_TXCOPY, TXCopy); - dispatch(REQ_TXSEND, TXSend); - dispatch(REQ_TXNEXT, TXNext); - - dispatch(REQ_RXIDLE, RXIdle); - dispatch(REQ_RXCOPY, RXCopy); - dispatch(REQ_RXNEXT, RXNext); - dispatch(REQ_RXMISS, RXMiss); - dispatch(REQ_RXRECV, RXRecv); - - assertmsg(req == 0, "req=$%x", req); - } -} - -// スレッドの終了を指示 -void -LanceDevice::Terminate() -{ - assert(thread_created); - - std::unique_lock lock(mtx); - request |= REQ_TERMINATE; - cv.notify_one(); -} - // TXIDLE 送信アイドルフェーズ。 // ディスクリプタを所有できるかポーリングする。 -// - 所有できれば PUSH フェーズへ。 -// - 所有できない場合、TDMD オンなら即、オフなら 1.6ms 待ってから再び POLL へ。 +// - 所有できれば TXCOPY フェーズへ。 +// - 所有できない場合、TDMD オンなら即、 +// オフなら 1.6ms 待ってから再び TXIDLE へ。 void -LanceDevice::TXIdle() +LanceDevice::TXIdle(Event& event) { + tx_phase = TXPhase::IDLE; + + // TDMD クリア + reg.csr0 &= ~AM7990::CSR0_TDMD; + // TMD1 を読み込む if ((int32)tmd1_ahead >= 0) { // すでに先読みしてあればそれを使う @@ -830,71 +734,112 @@ LanceDevice::TXIdle() // その TMD1 に OWN と STP が立っているか if ((tmd1 & AM7990::TMD1_OWN) && (tmd1 & AM7990::TMD1_STP)) { // 取り込みへ - request |= REQ_TXCOPY; + TXCopy(); return; } - // 立っていなければ - if ((reg.csr0 & AM7990::CSR0_TDMD)) { - // TDMD がオンなら即再実行。 - request |= REQ_TXIDLE; - } else { - // TDMD がオフなら 1.6ms 待って再度 POLL へ。 - tx_event.time = 1.6_msec; - tx_event.code = REQ_TXIDLE; - tx_event.Start(); - } + // 立っていなければ次回は 1.6ms 後 + ChangePhase(TXPhase::IDLE, 1.6_msec); } // TXCOPY バッファ処理フェーズ。 // 現ディスクリプタのバッファの内容をメモリから読み込み、 // それにかかる時間相当分をウェイトする。 -// - ENP ならウェイトした後 SEND フェーズへ。 -// - 次 TMD が OWN できればウェイトした後 NEXT フェーズへ。 -// - 次 TMD が OWN 出来なければウェイトした後 SEND フェーズ。 +// - 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); - // アドレスと送信バイト数。送信バイト数は2の補数。 - uint32 bufsrc = ((tmd1 & 0x00ff) << 16) | tmd0; - uint32 src = bufsrc; - int32 bcnt = (int32)(int16)tmd2; + // アドレスと送信バイト数。 + // tmd2 には 2 の補数で送信バイト数が入っているので符号反転する。 + uint32 src = ((tmd1 & 0x00ff) << 16) | tmd0; + int32 bcnt = -(int32)(int16)tmd2; + + // copycnt は bcnt のうち実際にコピーするバイト数 + int32 copycnt; + if (tx_packet.length + bcnt > tx_packet.size()) { + // コピーするとバッファが溢れてしまう計算なら、溢れないように打ち切る。 + copycnt = tx_packet.size() - tx_packet.length; + } else { + // 全部コピーできる。 + copycnt = bcnt; + } - // ローカルの送信バッファにコピー - for (; bcnt < 0; ) { - // XXX バーストコピー未対応 - // XXX BSWP==0の時のバッファ内のエンディアンとかは? + // 送信パケットキュー上の書き込み中バッファに追記する + uint8 *dst = &tx_packet[tx_packet.length]; + tx_packet.length += copycnt; + + // ここから、SubRAM からローカルの送信バッファにコピー + // XXX バーストコピー未対応 + const uint32 srcend = src + bcnt; + const uint8 *dstend = dst + copycnt; + + // src が奇数アドレスから始まっている(そしてコピー可能な)場合 + // (バッファを奇数アドレスから始めたりする奇特な人はいないと思うが) + if (__predict_false((src & 1) != 0) && dst < dstend) { + uint16 data = ReadMem(src & ~1); + if (reg.IsBSWP() == false) { + *dst++ = data >> 8; + } else { + *dst++ = data & 0xff; + } + src++; + } - if (bcnt <= -2 && (src & 1) == 0) { - // 2バイト以上残っててワード境界ならワードアクセス - uint16 data = ReadMem(src); - txq.push_back(data >> 8); - txq.push_back(data & 0xff); - src += 2; - bcnt += 2; - } else if ((src & 1) == 0) { - // 偶数アドレスから1バイト - uint16 data = ReadMem(src); - txq.push_back(data >> 8); - src += 1; - bcnt += 1; + // 残り2バイト以上コピー可能な間 (ここで src はワード境界になっている) + const uint8 *dstend_even; + dstend_even = (const uint8 *)((uintptr_t)dstend & ~(uintptr_t)1); + while (dst < dstend_even) { + uint16 data = ReadMem(src); + if (reg.IsBSWP() == false) { + *dst++ = data & 0xff; + *dst++ = data >> 8; + } else { + *dst++ = data >> 8; + *dst++ = data & 0xff; + } + src += 2; + } + + // src が奇数アドレスで終わっている(そしてコピー可能な)場合 + if (__predict_false(dst < dstend)) { + uint16 data = ReadMem(src); + if (reg.IsBSWP() == false) { + *dst++ = data & 0xff; } else { - // 奇数アドレスから1バイト - uint16 data = ReadMem(src & ~1); - txq.push_back(data & 0xff); - src += 1; - bcnt += 1; + *dst++ = data >> 8; } + src += 2; + } + + // tx_packet が一杯になったら後はディスクリプタが終わるまで SubRAM から + // 読み捨てる。tx_packet の長さに上限があるのはこっち(エミュレータ)の + // 都合であって LANCE はディスクリプタが終わるまで延々読み込んでは出力を + // 続けるようだ。ここでは同じだけ読み込むところ、それにかかる時間を求める + // ところを再現する。tx_packet がイーサネットフレーム長を超過していると + // この後 SEND フェーズで BABBLE ビットを立てるところも再現する。 + // 正確には 1519バイト目の送信で立てるのだが、通常は起きない上に面倒 + // なので省略。1519バイト以上のパケットをホストに流すこともしない。 + while (src < srcend) { + ReadMem(src); + src += 2; } + // メモリからの転送にかかった時間をざっくり計算 XXX まだ適当 + // そのウェイト後に指定のフェーズへ。 + uint64 t = 700 * bcnt / 16; + // ここが ENP なら、送信のため TXSEND フェーズへ if ((tmd1 & AM7990::TMD1_ENP)) { - tx_event.code = REQ_TXSEND; + ChangePhase(TXPhase::SEND, t); } else { // 次のディスクリプタを先読み tmd_addr = tmd_base + ((tmd_cur + 1) % tmd_num) * 8; @@ -902,33 +847,35 @@ LanceDevice::TXCopy() if ((tmd1_ahead & AM7990::TMD1_OWN)) { // 次ディスクリプタが所有できれば TXNEXT フェーズへ - tx_event.code = REQ_TXNEXT; + ChangePhase(TXPhase::NEXT, t); } else { // この時点で所有できなければアンダーフロー。 // ここまでのバッファでパケットを送信するため TXSEND へ。 // エラーフラグの処理は TXSEND 側で行う。 - tx_event.code = REQ_TXSEND; + ChangePhase(TXPhase::SEND, t); } } - - // メモリからの転送にかかった時間をざっくり計算 XXX まだ適当 - // そのウェイト後に指定のフェーズへ。 - tx_event.time = 700 * (src - bufsrc) / 16; - tx_event.Start(); } // TXSEND フェーズ。パケットを送信。 // ただし ENP でなければアンダーフローによるパケット送信なのでフラグ処理。 // その後 TXNEXT フェーズへ。 void -LanceDevice::TXSend() +LanceDevice::TXSend(Event& event) { - // 送信 - SendPacket(txq); - txq.clear(); + tx_phase = TXPhase::SEND; - // TDMD は1回送ると下ろすのかな - reg.csr0 &= ~AM7990::CSR0_TDMD; + 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) { @@ -939,59 +886,103 @@ LanceDevice::TXSend() tmd3 |= AM7990::TMD3_BUFF | AM7990::TMD3_UFLO; WriteMem(tmd_addr + 6, tmd3); - // TMD1 は NEXT フェーズで書き戻す + // TMD1 は TXNEXT フェーズで書き戻す tmd1 |= AM7990::TMD1_ERR; } // どちらにしても送信割り込み SetInterrupt(AM7990::CSR0_TINT); - request |= REQ_TXNEXT; + ChangePhase(TXPhase::NEXT); } // TXNEXT フェーズ。次のディスクリプタを指して TXIDLE へ。 void -LanceDevice::TXNext() +LanceDevice::TXNext(Event& event) { + 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; - - request |= REQ_TXIDLE; + ChangePhase(TXPhase::IDLE); } -// パケット受信。 -// これは mtx 取得状態で Nerdriver スレッドから呼ばれるので、 -// パケットを手元にコピーして VM スレッドに通知を出すだけ。 -bool -LanceDevice::RecvPacket(qvector& buf) +// 指定時間後に TX フェーズを遷移する。 +// (変化があれば)変更するではないことに注意。名前がアレだが IDLE フェーズは +// ポーリングしているので IDLE から IDLE への遷移も必ず起こす必要がある。 +// TX か RX かは関数オーバーロードで区別している。 +void +LanceDevice::ChangePhase(TXPhase new_phase, uint64 time) { - if (rxq.empty() == false) { - // 前のパケットをまだ処理中なので、このパケットは捨てる - putlog(2, "%s: discard %zd bytes", __func__, buf.size()); - return true; + tx_event.time = time; + switch (new_phase) { + case TXPhase::STOP: + scheduler->StopEvent(tx_event); + return; + case TXPhase::IDLE: + tx_event.func = ToEventCallback(&LanceDevice::TXIdle); + break; + case TXPhase::SEND: + tx_event.func = ToEventCallback(&LanceDevice::TXSend); + break; + case TXPhase::NEXT: + tx_event.func = ToEventCallback(&LanceDevice::TXNext); + break; + default: + assertmsg(false, "phase %d", (int)new_phase); } - putlog(3, "%s: %zd bytes", __func__, buf.size()); - - // コピー - rxq = buf; + scheduler->RestartEvent(tx_event); +} - // 通知 - request |= REQ_RXRECV; - cv.notify_one(); - return true; +// 受信フェーズ遷移図 +// +// +--------+ +// | RXSTOP |<- - - (CSR0.STOP 書き込み / リセット) +// +--------+ +// : +// : +‥‥‥+ <‥‥ HostRxCallback (パケット受信) +// v v : +// +-------------+ : +// +‥‥‥>| RXIDLE |‥‥+ Idle +// : +-------------+ +// : | | ^ +// : | | : +// : v | : +// : +--------+ | +--------+ +// +‥| RXMISS | | | RXNEXT | +// +--------+ | +--------+ +// | ^ | +// | : | +// v : v +// +-----------+ +// | RXCOPY | 関数呼び出し ----> +// +-----------+ イベントによる遷移 ‥‥> + +// パケット受信通知。HostNet から呼ばれる。 +void +LanceDevice::HostRxCallback(MessageID msgid, uint32 arg) +{ + // RXIDLE ポーリングのウェイト中ならすぐに始める。 + // そうでないときは RXIDLE に戻ったところで処理される。 + if (rx_phase == RXPhase::IDLE) { + ChangePhase(RXPhase::IDLE); + } } // RXIDLE 受信アイドルフェーズ。 // (最初の)ディスクリプタを所有できるかポーリングする。 void -LanceDevice::RXIdle() +LanceDevice::RXIdle(Event& event) { + rx_phase = RXPhase::IDLE; + putlog(3, "%s[%d]", __func__, rmd_cur); + // RMD1 を読み込む if ((int32)rmd1_ahead >= 0) { // すでにあればそれを使う @@ -1004,37 +995,26 @@ LanceDevice::RXIdle() // どちらにしても先読みバッファは無効に戻す rmd1_ahead = (uint32)-1; - // その RMD1 に OWN が立っているか - if ((rmd1 & AM7990::RMD1_OWN)) { - // ここが STP になる - rmd1 |= AM7990::RMD1_STP; - - // パケットを受信するまで何もすることはないのでフラグ立てずに戻る - return; - } - - // 立っていなければ - // 1.6ms 待って再度 RXIDLE へ。 - rx_event.time = 1.6_msec; - rx_event.code = REQ_RXIDLE; - rx_event.Start(); -} - -// RXRECV 先頭ディスクリプタ開始フェーズ。 -// - OWN していれば RXCOPY へ。 -// - していなければ、パケット長相当分ウェイトしてから RXMISS へ。 -void -LanceDevice::RXRecv() -{ - putlog(3, "%s[%d]", __func__, rmd_cur); - if ((rmd1 & AM7990::RMD1_OWN)) { - // 所有しているのでコピー処理へ - request |= REQ_RXCOPY; + // 受信しているか + if (hostnet->Rx(&rx_packet)) { + // その RMD1 に OWN が立っているか + if ((rmd1 & AM7990::RMD1_OWN)) { + // ここが STP になる + rmd1 |= AM7990::RMD1_STP; + // RXCOPY へ直接移行する。 + RXCopy(); + } else { + // バッファが無いので MISS へ直接移行する。 + // MISS までの時間は受信処理によって表現されているはず + RXMiss(); + } } else { - // パケットを受信してる時間に相当するウェイト XXX 適当 - rx_event.time = 700 * rxq.size() / 16; - rx_event.code = REQ_RXMISS; - rx_event.Start(); + if ((rmd1 & AM7990::RMD1_OWN)) { + // ここが STP になる + rmd1 |= AM7990::RMD1_STP; + } + // 1.6ms 待って再度 RXIDLE へ。 + ChangePhase(RXPhase::IDLE, 1.6_msec); } } @@ -1044,64 +1024,95 @@ LanceDevice::RXRecv() void LanceDevice::RXCopy() { - putlog(3, "%s[%d]", __func__, rmd_cur); + rx_phase = RXPhase::COPY; + + putlog(1, "%s[%d]", __func__, rmd_cur); + // RMD0, RMD2 は受信することが確定してから読み出す uint32 rmd_addr = rmd_base + rmd_cur * 8; uint16 rmd0 = ReadMem(rmd_addr); uint16 rmd2 = ReadMem(rmd_addr + 4); - // アドレスと受信バッファのバイト数。バッファサイズは2の補数。 + // アドレスと受信バッファのバイト数。 uint32 bufdst = ((rmd1 & 0x00ff) << 16) | rmd0; uint32 dst = bufdst; - int32 bcnt = (int32)(int16)rmd2; + // rmd2 は 2 の補数 + int32 bcnt = -(int32)(int16)rmd2; - // ローカルの受信バッファからディスクリプタバッファにコピー - for (; bcnt < 0; ) { - int remsize = rxq.size(); - if (remsize == 0) { - break; - } + if (bcnt < rx_packet.length) { + putlog(1, "will be remain: %d < %d", bcnt, rx_packet.length); + } + + uint8 *src = &rx_packet[0]; + int remsize = rx_packet.length; + if (bcnt > remsize) { + bcnt = remsize; + } + + // コピー後の残りサイズにしておく + remsize -= bcnt; + + // ローカルの受信バッファからディスクリプタバッファにコピー + for (; bcnt > 0; ) { // XXX バーストコピー未対応 - // XXX BSWP==0の時のバッファ内のエンディアンとかは? uint16 data; - if (bcnt <= -2 && remsize >= 2 && (dst & 1) == 0) { + if (bcnt >= 2 && (dst & 1) == 0) { // 2バイト以上残っててワード境界ならワードアクセス - data = rxq.pop() << 8; - data |= rxq.pop(); + if (reg.IsBSWP() == false) { + data = *src++; + data |= (*src++) << 8; + } else { + data = (*src++) << 8; + data |= *src++; + } WriteMem(dst, data); dst += 2; - bcnt += 2; + bcnt -= 2; } else if ((dst & 1) == 0) { // 偶数アドレスへ1バイト // 奇数バイトには不定データが出るらしいがここでは 0 にする - data = rxq.pop() << 8; + if (reg.IsBSWP() == false) { + data = *src++; + } else { + data = (*src++) << 8; + } WriteMem(dst, data); dst += 1; - bcnt += 1; + bcnt -= 1; } else { // 奇数アドレスへ1バイト // 偶数バイトには不定データが出るらしいがここでは 0 にする - data = rxq.pop(); + if (reg.IsBSWP() == false) { + data = (*src++) << 8; + } else { + data = *src++; + } WriteMem(dst & ~1, data); dst += 1; - bcnt += 1; + bcnt -= 1; } } // 全部書き出せたらここが ENP - if (rxq.size() == 0) { + if (remsize == 0) { rmd1 |= AM7990::RMD1_ENP; // ENP なら MCNT を更新 WriteMem(rmd_addr + 6, (dst - bufdst) & 0xfff); + } else { + rmd1 &= ~AM7990::RMD1_ENP; + putlog(1, "copy remain %d", remsize); + + // 前詰めにコピーする + memmove(&rx_packet[0], src, remsize); + rx_packet.length = remsize; } // メモリへの転送にかかった時間をざっくり計算 XXX まだ適当 // そのウェイト後に RXNEXT フェーズへ。 - rx_event.time = 700 * (dst - bufdst) / 16; - rx_event.code = REQ_RXNEXT; - rx_event.Start(); + uint64 t = 700 * (dst - bufdst) / 16; + ChangePhase(RXPhase::NEXT, t); } // RXNEXT フェーズ。 @@ -1110,18 +1121,18 @@ LanceDevice::RXCopy() // - 次のディスクリプタが所有できれば引き続き書き出すため RXCOPY へ。 // - 次のディスクリプタが所有できなければエラーにして RXIDLE へ。 void -LanceDevice::RXNext() +LanceDevice::RXNext(Event& event) { - putlog(3, "%s[%d]", __func__, rmd_cur); + rx_phase = RXPhase::NEXT; + + putlog(1, "%s[%d]", __func__, rmd_cur); - // RXCOPY フェーズで rxq が empty() になっても、(Lance スレッドの) - // メッセージループでここ(RXNEXT)に来るまでの間に、rxq に再び着信する - // 可能性はあるので、ここでは rxq を調べたり触ったりしてはいけない、 - // はず。 + bool is_chained = false; if ((rmd1 & AM7990::RMD1_ENP)) { // ENP なら、RXIDLE へ。 - request |= REQ_RXIDLE; + rx_packet.Clear(); + ChangePhase(RXPhase::IDLE); } else { // 次のディスクリプタを先読み uint32 rmd_addr = rmd_base + ((rmd_cur + 1) % rmd_num) * 8; @@ -1129,21 +1140,23 @@ LanceDevice::RXNext() if ((rmd1_ahead & AM7990::RMD1_OWN)) { // 次ディスクリプタが所有できれば RXCOPY へ。 - request |= REQ_RXCOPY; + is_chained = true; } else { // この時点で所有できなければオーバーフロー。 // XXX BUFF と OFLO の違いがよくわからん rmd1 |= AM7990::RMD1_BUFF | AM7990::RMD1_OFLO; + // 書き込み先が無いのでこのパケットは打ち切ってみる + rx_packet.Clear(); + // 次のディスクリプタをポールするため RXIDLE へ。 - request |= REQ_RXIDLE; + ChangePhase(RXPhase::IDLE); } } // このディスクリプタの所有権を放棄 rmd1 &= ~AM7990::RMD1_OWN; uint32 rmd_addr = rmd_base + rmd_cur * 8; -putlog(3, "%s[%d] addr=%x RMD1=%02x", __func__, rmd_cur, rmd_addr + 2, rmd1); WriteMem(rmd_addr + 2, rmd1); // 次のディスクリプタへ @@ -1151,35 +1164,58 @@ putlog(3, "%s[%d] addr=%x RMD1=%02x", __ // どちらにしても受信割り込み SetInterrupt(AM7990::CSR0_RINT); + + if (is_chained) { + // 次のディスクリプタがある場合は RXCopy に直接遷移する。 + rmd1 = rmd1_ahead; + rmd1_ahead = -1; + RXCopy(); + } } // RXMISS 受信失敗処理。 void LanceDevice::RXMiss() { - putlog(3, "%s", __func__); + rx_phase = RXPhase::MISS; + + putlog(1, "%s", __func__); // 受信データがあるのにディスクリプタを所有してなければエラー SetInterrupt(AM7990::CSR0_MISS | AM7990::CSR0_RINT); // 受信データを破棄。 - rxq.clear(); + rx_packet.Clear(); // そのまますぐにアイドルフェーズに戻るんだろうか - request |= REQ_RXIDLE; + ChangePhase(RXPhase::IDLE); } -// イベントハンドラ +// 指定時間後に RX フェーズを遷移する。 +// (変化があれば)変更するではないことに注意。名前がアレだが IDLE フェーズは +// ポーリングしているので IDLE から IDLE への遷移も必ず起こす必要がある。 +// TX か RX かは関数オーバーロードで区別している。 void -LanceDevice::Callback(Event& ev) +LanceDevice::ChangePhase(RXPhase new_phase, uint64 time) { - std::unique_lock lock(mtx); - - // ワーカスレッドにフェーズ遷移を伝えるだけ - request |= ev.code; - cv.notify_one(); + rx_event.time = time; + switch (new_phase) { + case RXPhase::STOP: + scheduler->StopEvent(rx_event); + return; + case RXPhase::IDLE: + rx_event.func = ToEventCallback(&LanceDevice::RXIdle); + break; + case RXPhase::NEXT: + rx_event.func = ToEventCallback(&LanceDevice::RXNext); + break; + default: + assertmsg(false, "phase %d", (int)new_phase); + } + scheduler->RestartEvent(rx_event); } + // ビット名文字列配列は [0] が MSB 側の順で並べる /*static*/ const char * const LanceDevice::csr0names[] = { @@ -1188,7 +1224,7 @@ LanceDevice::Callback(Event& ev) }; /*static*/ const char * const LanceDevice::csr3names[] = { - "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "BSWP", "ACON", "BCON", };