--- nono/vm/ethernet.cpp 2026/04/29 17:04:55 1.1.1.5 +++ nono/vm/ethernet.cpp 2026/04/29 17:05:46 1.1.1.12 @@ -4,142 +4,174 @@ // Licensed under nono-license.txt // +// +// Ethernet 基本クラス +// + +#if !defined(SELFTEST) #include "ethernet.h" #include "config.h" -#include "netdriver_selector.h" -#include - -std::unique_ptr gEthernet; +#include "hostnet.h" +#include "scheduler.h" // コンストラクタ -EthernetDevice::EthernetDevice(const std::string& objname_) - : inherited(objname_) +EthernetDevice::EthernetDevice(uint objid_) + : inherited(objid_) { } // デストラクタ EthernetDevice::~EthernetDevice() { + if ((bool)hostnet) { + hostnet->ResetRxCallback(); + } } // 動的なコンストラクション bool EthernetDevice::Create() { + // ホストドライバを作成してこのデバイスと紐付ける。 + // ポート名は継承先ごとに Init() で設定している。 + int n = GetId() - OBJ_ETHERNET0; try { - // ホストドライバをこのデバイスと紐付ける。 - gNetDriverSelector.reset(new NetDriverSelector(this)); - return true; - } catch (...) { + hostnet.reset(new HostNetDevice(this, n, "")); + } catch (...) { } + if ((bool)hostnet == false) { + warnx("Failed to initialize HostNetDevice(%u) at %s", n, __method__); + return false; } - return false; + + auto func = ToDeviceCallback(&EthernetDevice::HostRxCallback); + hostnet->SetRxCallback(func, 0); + + return true; +} + +// これは Host スレッドから呼ばれる +void +EthernetDevice::HostRxCallback(uint32 dummy) +{ + // スレッドを超えるためにメッセージを投げる + int n = GetId() - OBJ_ETHERNET0; + scheduler->SendMessage(MessageID::HOSTNET_RX(n)); } -// MAC アドレスをてきとーに生成する。 -// 02:00:01 はローカルだし割り当てもされてなさそうなので使っちゃえ。 -// 下3バイトは乱数。 -/*static*/ void -EthernetDevice::GenerateMacAddr(macaddr_t *addrp) -{ - std::random_device rnd; - uint32 x = rnd(); - - macaddr_t& addr = *addrp; - addr[0] = 0x02; - addr[1] = 0x00; - addr[2] = 0x01; - addr[3] = (x >> 16) & 0xff; - addr[4] = (x >> 8) & 0xff; - addr[5] = x & 0xff; -} - -// MAC アドレス文字列をバイナリに変換して返す。 -// "HH:HH:HH:HH:HH:HH" 形式のみ。 -// 成功すれば true を返す。 +// 設定ファイルから MAC アドレスを読み込む。 +// MAC アドレスを保持するデバイスが呼び出すこと。MAC アドレスを保持する +// デバイスがイーサネットデバイスとは限らず、例えば LANCE を採用している +// 機種ではたいてい ROM か誰かが MAC アドレスを持っている。 +// +// 引数 accept_rom が true なら、設定 ethernet-macaddr=rom を許可する。 +// これは (お手持ちの実機) ROM イメージが MAC アドレスを保持しているため、 +// 指定(や生成)不要なケースで指定できる (prom.cpp 参照)。 +// 引数 accept_rom が false で、設定値に "rom" が指定されるとエラーにする。 +// +// "rom" が指定されて許可されている場合 mac は触らず true を返す。 +// MAC アドレスが指定されているか自動生成したかで取得できれば mac に格納して +// true を返す。それ以外の場合は、エラーメッセージを表示して false を返す。 /*static*/ bool -EthernetDevice::ParseMacAddr(macaddr_t& dst, const std::string& src) +EthernetDevice::GetConfigMacAddr(uint n, MacAddr *mac, bool accept_rom) { - const char *p; - char *e; - - p = src.data(); - for (int i = 0; ; ) { - unsigned long val = strtoul(p, &e, 16); - - if (p == e) { - return false; - } - if (val > 0xff) { + std::string keyname = string_format("ethernet%u-macaddr", n); + const ConfigItem& item = gConfig->Find(keyname); + const std::string& val = item.AsString(); + + if (val == "rom") { + // ROM に書いてあるのを使う。 + if (accept_rom) { + return true; + } else { + item.Err("\"rom\" cannot be specified in this vmtype"); return false; } - dst[i++] = val; - p = e; - - // 最後だけ ':' をチェックしないので - if (i == 6) { - break; - } - - if (*p++ != ':') { - return false; + } else { + if (val == "auto") { + // 自動生成。 + // 出来れば生成したのを覚えといて、次からそれを使いたい。 + if (0) { + } else { + mac->Generate(); + } + } else { + // ユーザ指定 + if (mac->FromString(val) == false) { + item.Err("Invalid MAC address"); + return false; + } } } - if (*p != '\0') { - return false; - } - return true; -} -// パケットを送信する (VM から呼ばれる) -bool -EthernetDevice::SendPacket(const std::vector& buf) -{ - tx_pkts++; - return gNetDriver->SendPacket(buf); + return true; } -// パケット受信。 -// NetDriver スレッドから呼ばれる。 -// ロックは中で取るので、ロック解除状態で呼ぶこと。 -// XXX: 戻り値はなんだろう -bool -EthernetDevice::RecvPacket(RXPacket packet) -{ - std::lock_guard lock(mtx); +#endif // !SELFTEST - if (RecvEnabled == false) { - // 受信禁止状態 - putlog(2, "%s: receive disabled", __func__); - return true; +// CRC32 を計算する。 +// +// CRC と言ってもおそらく5つのパラメータによって定まる亜種が多数あるが、 +// Ethernet で使われる CRC32 はおそらく以下のもの。 +// 初期値: 0xffffffff +// 多項式: 0x04c11db6 +// RefIn : false +// RefOut: false (出力をビットリバースしない) +// XorOut: false (出力を XOR しない) +// +// NetBSD の src/sys/net/if_ethersubr.c にある ether_crc32_be() がこれと同じ。 +// +// その隣にある ether_crc32_le() は +// 初期値: 0xffffffff +// 多項式: 0xedb88320 +// RefIn : true +// RefOut: false (出力をビットリバースしない) +// XorOut: false (出力を XOR しない) +// というパラメータだが、RefIn と多項式のビットが反転しているだけなので +// 結果をビットリバースすれば同じものになる。 +// +// ちなみに ether_crc32_* の_le, _be はエンディアンではなく、どっち向きに +// 処理するかを示しており、名前が紛らわしい。 +/*static*/ uint32 +EthernetDevice::CRC32(const uint8 *buf, size_t buflen) +{ + static const uint32 CRC_POLY = 0x04c11db6; + uint32 crc; + uint32 cy; + + crc = 0xffffffff; + for (size_t i = 0; i < buflen; i++) { + uint32 s = buf[i]; + for (size_t j = 0; j < 8; j++) { + cy = ((crc & 0x80000000U) ? 0x01 : 0x00) ^ (s & 0x01); + crc <<= 1; + s >>= 1; + if (cy) { + crc = (crc ^ CRC_POLY) | cy; + } + } } - // イーサネットフレームより長いパケットはゲスト OS が期待していない。 - // ここでドロップしてみる。 - // これによりゲストでの dropping chained buffer が出なくなる。 - // 1518(EthernetII) にするか 1522(802.1Q) にするかは未定。 - if (packet->size() > 1518) { - // TODO: 統計とりたい - putlog(1, "%s: drop long packet(%zd)", __func__, packet->size()); - return true; - } + return crc; +} - // XXX とりあえず 16 パケットをキューする。 - // 入り切らないときは捨てる。 - if (rxq.size() > 16) { - // 前のパケットをまだ処理中なので、このパケットは捨てる - putlog(1, "%s: discard %zd bytes, pending %zd packets", __func__, - packet->size(), rxq.size()); - return true; +// MAC アドレスの CRC32 を計算する限定版。 +// 6バイトが uint64 にリトルエンディアン的に格納されていると分かっている。 +/*static*/ uint32 +EthernetDevice::CRC32(const MacAddr& mac) +{ + static const uint32 CRC_POLY = 0x04c11db6; + uint32 crc; + uint64 s = mac.Get(); + + crc = 0xffffffff; + for (size_t i = 0; i < mac.size() * 8; i++) { + uint32 cy = ((crc & 0x80000000U) ? 0x01 : 0x00) ^ (s & 0x01); + crc <<= 1; + s >>= 1; + if (cy) { + crc = (crc ^ CRC_POLY) | cy; + } } - putlog(1, "%s: enqueue %zd bytes, pending %zd packets", __func__, - packet->size(), rxq.size()); - - // キューへ投入 - rxq.push(std::move(packet)); - rx_pkts++; - // 継承クラスに通知 - NotifyRecvPacket(); - - return true; + return crc; }