|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2024 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: //
8: // VirtIO ネットワーク
9: //
10:
11: #include "virtio_net.h"
12: #include "virtio_def.h"
13: #include "config.h"
14: #include "ethernet.h"
15: #include "macaddr.h"
16: #include "memorystream.h"
1.1.1.3 root 17: #include "monitor.h"
1.1 root 18: #include "scheduler.h"
1.1.1.2 root 19: #include "syncer.h"
1.1 root 20:
21: // デバイス構成レイアウト
22: class VirtIONetConfigWriter
23: {
24: public:
25: std::array<u8, 6> mac {};
26: le16 status {};
27: le16 max_virtqueue_pairs {};
28: le16 mtu {};
29:
30: public:
31: void WriteTo(uint8 *dst) const;
32: };
33:
34: // コンストラクタ
1.1.1.2 root 35: VirtIONetDevice::VirtIONetDevice(uint slot_)
1.1 root 36: : inherited(OBJ_VIRTIO_NET, slot_)
37: {
38: // 短縮形
39: AddAlias("VNet");
40: AddAlias("Ethernet");
41:
1.1.1.2 root 42: device_id = VirtIO::DEVICE_ID_NETWORK;
1.1.1.3 root 43: vqueues.emplace_back(this, 0, "ReceiveQ1", 8);
44: vqueues.emplace_back(this, 1, "TransmitQ1", 16);
1.1.1.2 root 45:
1.1 root 46: // 割り込み名
47: strlcpy(intrname, "VIONet", sizeof(intrname));
48: // 完了通知メッセージ
49: msgid = MessageID::VIRTIO_NET_DONE;
50:
51: // モニタの行数。
52: int vqlines = 0;
53: for (const auto& q : vqueues) {
54: vqlines += 7 + q.num_max;
55: }
56:
1.1.1.3 root 57: monitor = gMonitorManager->Regist(ID_MONITOR_VIRTIO_NET, this);
58: monitor->func = ToMonitorCallback(&VirtIONetDevice::MonitorUpdate);
59: monitor->SetSize(MONITOR_WIDTH, 3 + vqlines);
1.1 root 60: }
61:
62: // デストラクタ
63: VirtIONetDevice::~VirtIONetDevice()
64: {
65: // EthernetDevice と同じ…。
66: if ((bool)hostnet) {
67: hostnet->SetRxCallback(NULL);
68: }
69: }
70:
71: // 動的なコンストラクション
72: bool
73: VirtIONetDevice::Create()
74: {
75: if (inherited::Create() == false) {
76: return false;
77: }
78:
79: // EthernetDevice とほぼ同じ…。
80: hostnet.reset(new HostNetDevice(this, 0));
81:
82: hostnet->SetRxCallback(ToDeviceCallback(&VirtIONetDevice::HostRxCallback));
83:
84: return true;
85: }
86:
87: // 初期化
88: bool
89: VirtIONetDevice::Init()
90: {
91: if (inherited::Init() == false) {
92: return false;
93: }
94:
95: // MAC アドレスを取得。
96: if (EthernetDevice::GetConfigMacAddr(0, &macaddr, false) == false) {
97: // エラーメッセージ表示済み。
98: return false;
99: }
100: // 表示用。
101: macaddr_str = macaddr.ToString(':');
102:
103: // DEVICE_FEATURES と構成レイアウトを用意。
104: VirtIONetConfigWriter cfg;
105: SetDeviceFeatures(VIRTIO_NET_F_CSUM);
106: SetDeviceFeatures(VIRTIO_NET_F_GUEST_CSUM);
1.1.1.4 ! root 107: SetDeviceFeatures(VIRTIO_NET_F_MTU), macaddr.ExportTo(&cfg.mac[0]);
1.1 root 108: SetDeviceFeatures(VIRTIO_NET_F_MAC), cfg.mtu = 1500;
109: SetDeviceFeatures(VIRTIO_NET_F_MRG_RXBUF);
110: cfg.WriteTo(&device_config[0]);
111:
112: // ホストからの受信コールバックを登録
113: scheduler->ConnectMessage(MessageID::HOSTNET_RX(0), this,
114: ToMessageCallback(&VirtIONetDevice::RxMessage));
115:
1.1.1.2 root 116: // time は都度設定する
117: event.func = ToEventCallback(&VirtIONetDevice::RxEvent);
118: event.SetName("VirtIONet RX");
119: scheduler->RegistEvent(event);
120:
1.1 root 121: return true;
122: }
123:
124: void
125: VirtIONetDevice::MonitorUpdate(Monitor *, TextScreen& screen)
126: {
127: int y = 0;
128:
129: screen.Clear();
130:
131: y = MonitorUpdateDev(screen, y);
132:
133: screen.Puts(0, y, "MACAddress:");
134: screen.Puts(12, y, macaddr_str.c_str());
135: y++;
136:
137: y++;
138: for (const auto& q : vqueues) {
139: y = MonitorUpdateVirtQueue(screen, y, q);
140: y++;
141: y = MonitorUpdateVirtQDesc(screen, y, q);
142: y++;
143: }
144: }
145:
1.1.1.3 root 146: // QUEUE_READY が変化したら呼ばれる。
1.1 root 147: void
1.1.1.3 root 148: VirtIONetDevice::QueueReadyChanged(VirtQueue *q)
1.1 root 149: {
1.1.1.3 root 150: if (q->idx == 0) {
151: // 受信開始/停止
152: if (q->GetReady() != 0) {
153: hostnet->EnableRx(true);
154: } else {
155: scheduler->StopEvent(event);
156: hostnet->EnableRx(false);
157: }
1.1 root 158: }
159: }
160:
161: // ディスクリプタを一つ処理する。
162: void
1.1.1.2 root 163: VirtIONetDevice::ProcessDesc(VirtIOReq& req)
1.1 root 164: {
1.1.1.2 root 165: VirtQueue *q = req.q;
166: uint8 flags;
167: uint8 gso_type;
168: uint16 hdr_len;
169: uint16 gso_size;
170: uint16 csum_start;
171: uint16 csum_offset;
172: uint16 num_buffers;
1.1 root 173:
174: if (q->idx == 0) {
175: putlog(0, "QUEUE_NOTIFY on rx queue (VQ%u)?", q->idx);
176: return;
177: }
178:
1.1.1.2 root 179: flags = ReqReadU8(req);
180: gso_type = ReqReadU8(req);
181: hdr_len = ReqReadLE16(req);
182: gso_size = ReqReadLE16(req);
183: csum_start = ReqReadLE16(req);
184: csum_offset = ReqReadLE16(req);
185: num_buffers = ReqReadLE16(req);
1.1 root 186: putlog(2, "req.hdr $%08x: flags=%02x gso=%02x hdr_len=%04x gso_size=%04x "
187: "csum_start=%04x offset=%04x num=%04x",
1.1.1.2 root 188: req.buf[0].addr,
189: flags,
190: gso_type,
191: hdr_len,
192: gso_size,
193: csum_start,
194: csum_offset,
195: num_buffers);
1.1 root 196:
197: NetPacket tx_packet;
1.1.1.2 root 198: while (req.pos < req.len) {
199: tx_packet.Append(ReqReadU8(req));
1.1 root 200: }
201:
1.1.1.2 root 202: // パフォーマンス測定用のギミック。
203: // VirtIO-Net を特定のパケットが通過すると、
204: // VM 電源オン時から現在までの実時間をログに出力する。NetBSD の
205: // マルチユーザブートがあらかた終わってログインプロンプトが出る手前の
206: // /etc/rc.local に
207: //
208: // ping6 -X 1 -c 1 ff02::db8:db8:9090%vioif0 > /dev/null 2>&1 &
209: //
210: // と書いておくことで、ここまでにかかる時間を表示できる。
211: // この値はホスト性能に依存するため、同一環境下での相対比較のみ可能。
212: //
213: // この宛先はリンクローカルマルチキャストでドキュメント用に予約されて
214: // いるアドレスなので厳密には出すべきではないかもだが、知らずに
215: // ドキュメント通りにやってみた人のために、ちゃんとしたネットワークなら
216: // 安全に廃棄しているはずなので、事故は少ないかと。
217: // このマルチキャストアドレスに対する宛先 MAC アドレスは
218: // 33:33:0d:b8:90:90 になる。
219: static const std::array<uint8, 6> perf_dstaddr {
220: 0x33, 0x33, 0x0d, 0xb8, 0x90, 0x90
221: };
222: if (__predict_true(tx_packet.length >= 6) &&
223: __predict_false(memcmp(&tx_packet[0], &perf_dstaddr[0], 6) == 0))
224: {
225: GetSyncer()->PutlogRealtime();
226: }
227:
228: hostnet->Tx(tx_packet);
1.1 root 229: }
230:
231: // これは Host スレッドから呼ばれる
232: void
233: VirtIONetDevice::HostRxCallback()
234: {
235: // スレッドを超えるためにメッセージを投げる
236: scheduler->SendMessage(MessageID::HOSTNET_RX(0));
237: }
238:
239: // パケット受信通知
240: void
241: VirtIONetDevice::RxMessage(MessageID msgid_, uint32 arg)
242: {
1.1.1.3 root 243: VirtQueue *q = &vqueues[0];
244:
245: if (__predict_false(q->GetReady() == 0)) {
246: return;
247: }
248:
1.1 root 249: // 受信イベントが止まっていれば動かす。
250: // 受信イベントがすでに動いていれば、1パケット受信完了後に
251: // ホストキューを確認するので、ここでは何もしなくてよい。
252: if (event.IsRunning() == false) {
253: event.time = 0;
254: scheduler->StartEvent(event);
255: }
256: }
257:
258: // 受信ループイベント。
259: void
260: VirtIONetDevice::RxEvent(Event& ev)
261: {
262: VirtQueue *q = &vqueues[0];
263:
1.1.1.3 root 264: if (__predict_false(q->GetReady() == 0)) {
265: return;
266: }
267:
1.1 root 268: // 空きディスクリプタがなければ、空くまでポーリングで待ち続ける。
269: uint16 avail_idx = ReadLE16(q->driver + 2);
270: if (q->last_avail_idx == avail_idx) {
271: event.time = 500_usec;
272: putlog(2, "%s: wait %u usec", __func__, (uint)(event.time / 1_usec));
273: scheduler->RestartEvent(event);
274: return;
275: }
276:
277: // ディスクリプタに空きが出来たので、ホストキューから1パケット取り出す。
278: assert(rx_packet.length == 0);
279: if (hostnet->Rx(&rx_packet) == false) {
280: // 取り出せなくなったら、ここでイベントループを終了。
281: rx_packet.Clear();
282: putlog(2, "Rx no more rx_packet");
283: return;
284: }
285:
286: putlog(2, "%s: %u bytes from host queue", __func__, rx_packet.length);
287:
288: // rx_packet が埋まって、空きディスクリプタがあるので受信処理へ。
289: Rx(q);
290:
291: rx_packet.Clear();
292: event.time = 500_usec;
293: scheduler->RestartEvent(event);
294: }
295:
296: // 受信。(rx_packet が埋まっていてかつ空きディスクリプタがある状態で呼ぶこと)
297: void
298: VirtIONetDevice::Rx(VirtQueue *q)
299: {
300: assert(rx_packet.length != 0);
301: assert(q->last_avail_idx != ReadLE16(q->driver + 2));
302:
303: // XXX どこでやるのがいいか
304: // VirtIO は FCS を含まない 1514 バイトが上限と決まっているが、
305: // HostNet の下の受信ドライバが FCS を含めているかどうかが分からない。
306: // 仕方ないので 1514 バイトを超える時だけ取り除く…。
307: if (rx_packet.length > 1514) {
308: rx_packet.length = 1514;
309: }
310:
1.1.1.2 root 311: VirtIOReq req;
312: req.q = q;
313: StartDesc(req);
1.1 root 314:
1.1.1.2 root 315: // ヘッダを用意。
316: virtio_net_hdr hdr;
317: memset(&hdr, 0, sizeof(hdr));
318: hdr.hdr_len = htole16(sizeof(hdr));
319: // num_buffers は実際に使ったセグメント数らしい。
320: uint nseg = 0;
321: for (; nseg < req.buf.size(); nseg++) {
322: if (req.buf[nseg].pos >= sizeof(hdr) + rx_packet.length)
323: break;
324: }
325: hdr.num_buffers = htole16(nseg);
326:
327: // ヘッダを書き込む。
328: const uint8 *s = (const uint8 *)&hdr;
329: for (int i = 0; i < sizeof(hdr); i++) {
330: ReqWriteU8(req, *s++);
1.1 root 331: }
332:
1.1.1.2 root 333: // パケット本体を書き込む。
1.1 root 334: for (int i = 0; i < rx_packet.length; i++) {
1.1.1.2 root 335: if (ReqWriteU8(req, rx_packet[i]) == false) {
1.1 root 336: // どうする?
337: putlog(0, "buffer too small: buflen=%u rx_packet=%u",
1.1.1.2 root 338: req.len, rx_packet.length);
339: break;
1.1 root 340: }
341: }
342:
1.1.1.2 root 343: CommitDesc(req);
1.1 root 344:
345: // ここは VM スレッドなのでそのまま割り込みを上げる。
346: Done(q);
347: }
348:
1.1.1.4 ! root 349: // この宛先アドレスを受信するかどうか。
! 350: // これは HostNet スレッドで呼ばれる。
! 351: int
! 352: VirtIONetDevice::HWAddrFilter(const MacAddr& dstaddr) const
! 353: {
! 354: if (dstaddr.IsUnicast()) {
! 355: if (dstaddr != macaddr) {
! 356: return HPF_DROP_UNICAST;
! 357: }
! 358: }
! 359: // マルチキャストをホストデバイス側でフィルタする機構は
! 360: // CTRLQ 内にあるが、NetBSD のドライバは CTRLQ を実装していない。
! 361:
! 362: return HPF_PASS;
! 363: }
! 364:
1.1 root 365: const char *
1.1.1.2 root 366: VirtIONetDevice::GetFeatureName(uint feature) const
1.1 root 367: {
368: static std::pair<uint, const char *> names[] = {
369: { VIRTIO_NET_F_CSUM, "CSUM" },
370: { VIRTIO_NET_F_GUEST_CSUM, "G_CSUM" },
371: { VIRTIO_NET_F_CTRL_GUEST_OFFLOADS, "C_G_OFFL" },
372: { VIRTIO_NET_F_MTU, "MTU" },
373: { VIRTIO_NET_F_MAC, "MAC" },
374: { VIRTIO_NET_F_GUEST_TSO4, "G_TSO4" },
375: { VIRTIO_NET_F_GUEST_TSO6, "G_TSO6" },
376: { VIRTIO_NET_F_GUEST_ECN, "G_ECN" },
377: { VIRTIO_NET_F_GUEST_UFO, "G_UFO" },
378: { VIRTIO_NET_F_HOST_TSO4, "H_TSO4" },
379: { VIRTIO_NET_F_HOST_TSO6, "H_TSO6" },
380: { VIRTIO_NET_F_HOST_ECN, "H_ECN" },
381: { VIRTIO_NET_F_HOST_UFO, "H_UFO" },
382: { VIRTIO_NET_F_MRG_RXBUF, "MRG_RX" },
383: { VIRTIO_NET_F_STATUS, "STATUS" },
384: { VIRTIO_NET_F_CTRL_VQ, "CTL_VQ" },
385: { VIRTIO_NET_F_CTRL_RX, "CTL_RX" },
386: { VIRTIO_NET_F_CTRL_VLAN, "CTL_VLAN" },
387: { VIRTIO_NET_F_GUEST_ANNOUNCE, "G_ANN" },
388: { VIRTIO_NET_F_MQ, "MQ" },
389: { VIRTIO_NET_F_CTRL_MAC_ADDR, "CTL_MAC" },
390: { VIRTIO_NET_F_RSC_EXT, "RSC_EXT" },
391: { VIRTIO_NET_F_STANDBY, "STANDBY" },
392: };
393:
394: for (auto& p : names) {
395: if (feature == p.first) {
396: return p.second;
397: }
398: }
399: return inherited::GetFeatureName(feature);
400: }
401:
402: // デバイス構成レイアウトを書き出す。
403: void
404: VirtIONetConfigWriter::WriteTo(uint8 *dst) const
405: {
406: MemoryStreamLE mem(dst);
407:
408: for (auto c : mac) {
1.1.1.2 root 409: mem.Write1(c);
1.1 root 410: }
1.1.1.2 root 411: mem.Write2(status);
412: mem.Write2(max_virtqueue_pairs);
413: mem.Write2(mtu);
1.1 root 414: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.