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