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

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

unix.superglobalmegacorp.com

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