--- nono/vm/virtio_net.cpp 2026/04/29 17:05:24 1.1 +++ nono/vm/virtio_net.cpp 2026/04/29 17:05:46 1.1.1.6 @@ -14,14 +14,9 @@ #include "ethernet.h" #include "macaddr.h" #include "memorystream.h" +#include "monitor.h" #include "scheduler.h" -class VirtIONetReq : public VirtIOReq -{ - public: - virtio_net_hdr hdr; -}; - // デバイス構成レイアウト class VirtIONetConfigWriter { @@ -36,35 +31,31 @@ class VirtIONetConfigWriter }; // コンストラクタ -VirtIONetDevice::VirtIONetDevice(int slot_) +VirtIONetDevice::VirtIONetDevice(uint slot_) : inherited(OBJ_VIRTIO_NET, slot_) { // 短縮形 AddAlias("VNet"); AddAlias("Ethernet"); + device_id = VirtIO::DEVICE_ID_NETWORK; + vqueues.emplace_back(this, 0, "ReceiveQ1", 8); + vqueues.emplace_back(this, 1, "TransmitQ1", 16); + // 割り込み名 strlcpy(intrname, "VIONet", sizeof(intrname)); // 完了通知メッセージ msgid = MessageID::VIRTIO_NET_DONE; - device_id = VirtIO::DEVICE_ID_NET; - vqueues.emplace_back(0, "ReceiveQ1", 16); - vqueues.emplace_back(1, "TransmitQ1", 8); - - // time は都度設定する - event.func = ToEventCallback(&VirtIONetDevice::RxEvent); - event.Regist("VirtIONet RX"); - // モニタの行数。 int vqlines = 0; for (const auto& q : vqueues) { vqlines += 7 + q.num_max; } - monitor.func = ToMonitorCallback(&VirtIONetDevice::MonitorUpdate); - monitor.SetSize(MONITOR_WIDTH, 3 + vqlines); - monitor.Regist(ID_MONITOR_VIRTIO_NET); + monitor = gMonitorManager->Regist(ID_MONITOR_VIRTIO_NET, this); + monitor->func = ToMonitorCallback(&VirtIONetDevice::MonitorUpdate); + monitor->SetSize(MONITOR_WIDTH, 3 + vqlines); } // デストラクタ @@ -72,7 +63,7 @@ VirtIONetDevice::~VirtIONetDevice() { // EthernetDevice と同じ…。 if ((bool)hostnet) { - hostnet->SetRxCallback(NULL); + hostnet->ResetRxCallback(); } } @@ -85,9 +76,16 @@ VirtIONetDevice::Create() } // EthernetDevice とほぼ同じ…。 - hostnet.reset(new HostNetDevice(this, 0)); + try { + hostnet.reset(new HostNetDevice(this, 0, "VirtIO Network")); + } catch (...) { } + if ((bool)hostnet == false) { + warnx("Failed to initialize HostNetDevice at %s", __method__); + return false; + } - hostnet->SetRxCallback(ToDeviceCallback(&VirtIONetDevice::HostRxCallback)); + auto func = ToDeviceCallback(&VirtIONetDevice::HostRxCallback); + hostnet->SetRxCallback(func, 0); return true; } @@ -101,13 +99,10 @@ VirtIONetDevice::Init() } // MAC アドレスを取得。 - macaddr_t macaddr; if (EthernetDevice::GetConfigMacAddr(0, &macaddr, false) == false) { // エラーメッセージ表示済み。 return false; } - // この先設定するタイミングがないのでここで設定する? - hostnet->SetMyAddr(macaddr); // 表示用。 macaddr_str = macaddr.ToString(':'); @@ -115,7 +110,7 @@ VirtIONetDevice::Init() VirtIONetConfigWriter cfg; SetDeviceFeatures(VIRTIO_NET_F_CSUM); SetDeviceFeatures(VIRTIO_NET_F_GUEST_CSUM); - SetDeviceFeatures(VIRTIO_NET_F_MTU), cfg.mac = macaddr; + SetDeviceFeatures(VIRTIO_NET_F_MTU), macaddr.ExportTo(&cfg.mac[0]); SetDeviceFeatures(VIRTIO_NET_F_MAC), cfg.mtu = 1500; SetDeviceFeatures(VIRTIO_NET_F_MRG_RXBUF); cfg.WriteTo(&device_config[0]); @@ -124,6 +119,11 @@ VirtIONetDevice::Init() scheduler->ConnectMessage(MessageID::HOSTNET_RX(0), this, ToMessageCallback(&VirtIONetDevice::RxMessage)); + // time は都度設定する + event.func = ToEventCallback(&VirtIONetDevice::RxEvent); + event.SetName("VirtIONet RX"); + scheduler->RegistEvent(event); + return true; } @@ -149,89 +149,68 @@ VirtIONetDevice::MonitorUpdate(Monitor * } } +// QUEUE_READY が変化したら呼ばれる。 void -VirtIONetDevice::QueueReady() +VirtIONetDevice::QueueReadyChanged(VirtQueue *q) { - if (vq->idx == 0) { - // ここで受信開始だろうか。 - hostnet->EnableRx(true); + if (q->idx == 0) { + // 受信開始/停止 + if (q->GetReady() != 0) { + hostnet->EnableRx(true); + } else { + scheduler->StopEvent(event); + hostnet->EnableRx(false); + } } } // ディスクリプタを一つ処理する。 void -VirtIONetDevice::ProcessDesc(VirtQueue *q) +VirtIONetDevice::ProcessDesc(VirtIOReq& req) { - VirtIONetReq req; - VirtQDesc desc; - uint32 desc_idx; - uint32 reqlen = 0; + VirtQueue *q = req.q; + uint8 flags; + uint8 gso_type; + uint16 hdr_len; + uint16 gso_size; + uint16 csum_start; + uint16 csum_offset; + uint16 num_buffers; if (q->idx == 0) { putlog(0, "QUEUE_NOTIFY on rx queue (VQ%u)?", q->idx); return; } - // 1チャンク目は先頭 12 バイトが Read-Only なヘッダのはず。 - desc_idx = ReadLE16(q->driver + 4 + (q->last_avail_idx % q->num) * 2); - reqlen += ReadDesc(q, &desc, desc_idx); - if (__predict_false(desc.IsRead() == false)) { - putlog(0, "NetHdr segment must be device-readable"); - return; - } - // 仕様上は1バイト単位でチェインされてもいいのだが、面倒なので無視。 - if (desc.len < 12) { - putlog(0, "NetHdr segment too short (desc.len=$%x)", desc.len); - return; - } - req.idx = desc_idx; - req.hdr.flags = ReadU8(desc.addr + 0); - req.hdr.gso_type = ReadU8(desc.addr + 1); - req.hdr.hdr_len = ReadLE16(desc.addr + 2); - req.hdr.gso_size = ReadLE16(desc.addr + 4); - req.hdr.csum_start = ReadLE16(desc.addr + 6); - req.hdr.csum_offset = ReadLE16(desc.addr + 8); - req.hdr.num_buffers = ReadLE16(desc.addr + 10); + flags = ReqReadU8(req); + gso_type = ReqReadU8(req); + hdr_len = ReqReadLE16(req); + gso_size = ReqReadLE16(req); + csum_start = ReqReadLE16(req); + csum_offset = ReqReadLE16(req); + num_buffers = ReqReadLE16(req); putlog(2, "req.hdr $%08x: flags=%02x gso=%02x hdr_len=%04x gso_size=%04x " "csum_start=%04x offset=%04x num=%04x", - desc.addr, - req.hdr.flags, - req.hdr.gso_type, - req.hdr.hdr_len, - req.hdr.gso_size, - req.hdr.csum_start, - req.hdr.csum_offset, - req.hdr.num_buffers); - - // 同一セグメント内にデータ(ReadOnly) が連続していても構わない。 - const auto HDRSZ = sizeof(req.hdr); - if (desc.len > HDRSZ) { - AddDataSegment(&req, desc.addr + HDRSZ, desc.len - HDRSZ, - " (header segment)"); - } - - while (desc.IsNext()) { - desc_idx = desc.next; - reqlen += ReadDesc(q, &desc, desc_idx); - AddDataSegment(&req, desc.addr, desc.len, NULL); - } - putlog(3, "reqlen=%08x", reqlen); + req.rbuf[0].addr, + flags, + gso_type, + hdr_len, + gso_size, + csum_start, + csum_offset, + num_buffers); NetPacket tx_packet; - for (const auto seg : req.buf) { - uint32 addr = seg.addr; - for (uint32 end = addr + seg.len; addr < end; ) { - tx_packet.Append(ReadU8(addr++)); - } - } - hostnet->Tx(tx_packet); + uint32 len = std::min((uint32)tx_packet.size(), req.rremain()); + uint32 rest = ReqReadRegion(req, tx_packet.data(), len); + tx_packet.length = len - rest; - CommitDesc(q, req.idx, reqlen); + hostnet->Tx(tx_packet); } // これは Host スレッドから呼ばれる void -VirtIONetDevice::HostRxCallback() +VirtIONetDevice::HostRxCallback(uint32 dummy) { // スレッドを超えるためにメッセージを投げる scheduler->SendMessage(MessageID::HOSTNET_RX(0)); @@ -241,6 +220,12 @@ VirtIONetDevice::HostRxCallback() void VirtIONetDevice::RxMessage(MessageID msgid_, uint32 arg) { + VirtQueue *q = &vqueues[0]; + + if (__predict_false(q->GetReady() == 0)) { + return; + } + // 受信イベントが止まっていれば動かす。 // 受信イベントがすでに動いていれば、1パケット受信完了後に // ホストキューを確認するので、ここでは何もしなくてよい。 @@ -256,6 +241,10 @@ VirtIONetDevice::RxEvent(Event& ev) { VirtQueue *q = &vqueues[0]; + if (__predict_false(q->GetReady() == 0)) { + return; + } + // 空きディスクリプタがなければ、空くまでポーリングで待ち続ける。 uint16 avail_idx = ReadLE16(q->driver + 2); if (q->last_avail_idx == avail_idx) { @@ -288,13 +277,6 @@ VirtIONetDevice::RxEvent(Event& ev) void VirtIONetDevice::Rx(VirtQueue *q) { - VirtIONetReq req; - VirtQDesc desc; - uint32 desc_idx; - uint32 nseg = 0; // ゲストが指定したセグメント数 - uint32 buflen = 0; // ゲストが指定したバッファのバイト数 - uint32 datalen; // 実際に書き込んだバイト数 - assert(rx_packet.length != 0); assert(q->last_avail_idx != ReadLE16(q->driver + 2)); @@ -306,51 +288,55 @@ VirtIONetDevice::Rx(VirtQueue *q) rx_packet.length = 1514; } - desc_idx = ReadLE16(q->driver + 4 + (q->last_avail_idx % q->num) * 2); - req.idx = desc_idx; - do { - buflen += ReadDesc(q, &desc, desc_idx); - if (__predict_false(desc.IsWrite() == false)) { - putlog(0, "Segments in rxq must be device-writeable"); - return; - } - nseg++; - AddDataSegment(&req, desc.addr, desc.len, NULL); + VirtIOReq req; + req.q = q; + StartDesc(req); - desc_idx = desc.next; - } while (desc.IsNext()); - putlog(3, "buflen=%08x", buflen); - - memset(&req.hdr, 0, sizeof(req.hdr)); - req.hdr.hdr_len = htole16(sizeof(req.hdr)); - // XXX nseg は実際に使ったセグメントをカウントしないといけない - req.hdr.num_buffers = htole16(nseg); - - // 書き込む。 - const uint8 *s = (const uint8 *)&req.hdr; - for (int i = 0; i < sizeof(req.hdr); i++) { - ReqAppendByte(&req, *s++); - } - datalen = sizeof(req.hdr); - - s = rx_packet.data(); - for (int i = 0; i < rx_packet.length; i++) { - if (ReqAppendByte(&req, rx_packet[i]) == false) { - // どうする? - putlog(0, "buffer too small: buflen=%u rx_packet=%u", - buflen, rx_packet.length); + // ヘッダを用意。 + virtio_net_hdr hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.hdr_len = htole16(sizeof(hdr)); + // num_buffers は実際に使ったセグメント数らしい。 + uint nseg = 0; + uint32 len = 0; + for (; nseg < req.wbuf.size(); nseg++) { + len += req.wbuf[nseg].len; + if (len >= sizeof(hdr) + rx_packet.length) { + break; } - datalen++; } + hdr.num_buffers = htole16(nseg); + + // ヘッダを書き込む。 + ReqWriteRegion(req, (const uint8 *)&hdr, sizeof(hdr)); + + // パケット本体を書き込む。 + ReqWriteRegion(req, rx_packet.data(), rx_packet.length); - CommitDesc(q, req.idx, datalen); + CommitDesc(req); // ここは VM スレッドなのでそのまま割り込みを上げる。 Done(q); } +// この宛先アドレスを受信するかどうか。 +// これは HostNet スレッドで呼ばれる。 +int +VirtIONetDevice::HWAddrFilter(const MacAddr& dstaddr) const +{ + if (dstaddr.IsUnicast()) { + if (dstaddr != macaddr) { + return HPF_DROP_UNICAST; + } + } + // マルチキャストをホストデバイス側でフィルタする機構は + // CTRLQ 内にあるが、NetBSD のドライバは CTRLQ を実装していない。 + + return HPF_PASS; +} + const char * -VirtIONetDevice::GetFeatureName(int feature) const +VirtIONetDevice::GetFeatureName(uint feature) const { static std::pair names[] = { { VIRTIO_NET_F_CSUM, "CSUM" }, @@ -393,9 +379,9 @@ VirtIONetConfigWriter::WriteTo(uint8 *ds MemoryStreamLE mem(dst); for (auto c : mac) { - mem.Write8(c); + mem.Write1(c); } - mem.Write16(status); - mem.Write16(max_virtqueue_pairs); - mem.Write16(mtu); + mem.Write2(status); + mem.Write2(max_virtqueue_pairs); + mem.Write2(mtu); }