Annotation of nono/vm/virtio_net.cpp, revision 1.1.1.4

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: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.