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