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

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"
        !            18: 
        !            19: class VirtIONetReq : public VirtIOReq
        !            20: {
        !            21:  public:
        !            22:        virtio_net_hdr hdr;
        !            23: };
        !            24: 
        !            25: // デバイス構成レイアウト
        !            26: class VirtIONetConfigWriter
        !            27: {
        !            28:  public:
        !            29:        std::array<u8, 6> mac {};
        !            30:        le16 status {};
        !            31:        le16 max_virtqueue_pairs {};
        !            32:        le16 mtu {};
        !            33: 
        !            34:  public:
        !            35:        void WriteTo(uint8 *dst) const;
        !            36: };
        !            37: 
        !            38: // コンストラクタ
        !            39: VirtIONetDevice::VirtIONetDevice(int slot_)
        !            40:        : inherited(OBJ_VIRTIO_NET, slot_)
        !            41: {
        !            42:        // 短縮形
        !            43:        AddAlias("VNet");
        !            44:        AddAlias("Ethernet");
        !            45: 
        !            46:        // 割り込み名
        !            47:        strlcpy(intrname, "VIONet", sizeof(intrname));
        !            48:        // 完了通知メッセージ
        !            49:        msgid = MessageID::VIRTIO_NET_DONE;
        !            50: 
        !            51:        device_id = VirtIO::DEVICE_ID_NET;
        !            52:        vqueues.emplace_back(0, "ReceiveQ1", 16);
        !            53:        vqueues.emplace_back(1, "TransmitQ1", 8);
        !            54: 
        !            55:        // time は都度設定する
        !            56:        event.func = ToEventCallback(&VirtIONetDevice::RxEvent);
        !            57:        event.Regist("VirtIONet RX");
        !            58: 
        !            59:        // モニタの行数。
        !            60:        int vqlines = 0;
        !            61:        for (const auto& q : vqueues) {
        !            62:                vqlines += 7 + q.num_max;
        !            63:        }
        !            64: 
        !            65:        monitor.func = ToMonitorCallback(&VirtIONetDevice::MonitorUpdate);
        !            66:        monitor.SetSize(MONITOR_WIDTH, 3 + vqlines);
        !            67:        monitor.Regist(ID_MONITOR_VIRTIO_NET);
        !            68: }
        !            69: 
        !            70: // デストラクタ
        !            71: VirtIONetDevice::~VirtIONetDevice()
        !            72: {
        !            73:        // EthernetDevice と同じ…。
        !            74:        if ((bool)hostnet) {
        !            75:                hostnet->SetRxCallback(NULL);
        !            76:        }
        !            77: }
        !            78: 
        !            79: // 動的なコンストラクション
        !            80: bool
        !            81: VirtIONetDevice::Create()
        !            82: {
        !            83:        if (inherited::Create() == false) {
        !            84:                return false;
        !            85:        }
        !            86: 
        !            87:        // EthernetDevice とほぼ同じ…。
        !            88:        hostnet.reset(new HostNetDevice(this, 0));
        !            89: 
        !            90:        hostnet->SetRxCallback(ToDeviceCallback(&VirtIONetDevice::HostRxCallback));
        !            91: 
        !            92:        return true;
        !            93: }
        !            94: 
        !            95: // 初期化
        !            96: bool
        !            97: VirtIONetDevice::Init()
        !            98: {
        !            99:        if (inherited::Init() == false) {
        !           100:                return false;
        !           101:        }
        !           102: 
        !           103:        // MAC アドレスを取得。
        !           104:        macaddr_t macaddr;
        !           105:        if (EthernetDevice::GetConfigMacAddr(0, &macaddr, false) == false) {
        !           106:                // エラーメッセージ表示済み。
        !           107:                return false;
        !           108:        }
        !           109:        // この先設定するタイミングがないのでここで設定する?
        !           110:        hostnet->SetMyAddr(macaddr);
        !           111:        // 表示用。
        !           112:        macaddr_str = macaddr.ToString(':');
        !           113: 
        !           114:        // DEVICE_FEATURES と構成レイアウトを用意。
        !           115:        VirtIONetConfigWriter cfg;
        !           116:        SetDeviceFeatures(VIRTIO_NET_F_CSUM);
        !           117:        SetDeviceFeatures(VIRTIO_NET_F_GUEST_CSUM);
        !           118:        SetDeviceFeatures(VIRTIO_NET_F_MTU), cfg.mac = macaddr;
        !           119:        SetDeviceFeatures(VIRTIO_NET_F_MAC), cfg.mtu = 1500;
        !           120:        SetDeviceFeatures(VIRTIO_NET_F_MRG_RXBUF);
        !           121:        cfg.WriteTo(&device_config[0]);
        !           122: 
        !           123:        // ホストからの受信コールバックを登録
        !           124:        scheduler->ConnectMessage(MessageID::HOSTNET_RX(0), this,
        !           125:                ToMessageCallback(&VirtIONetDevice::RxMessage));
        !           126: 
        !           127:        return true;
        !           128: }
        !           129: 
        !           130: void
        !           131: VirtIONetDevice::MonitorUpdate(Monitor *, TextScreen& screen)
        !           132: {
        !           133:        int y = 0;
        !           134: 
        !           135:        screen.Clear();
        !           136: 
        !           137:        y = MonitorUpdateDev(screen, y);
        !           138: 
        !           139:        screen.Puts(0, y, "MACAddress:");
        !           140:        screen.Puts(12, y, macaddr_str.c_str());
        !           141:        y++;
        !           142: 
        !           143:        y++;
        !           144:        for (const auto& q : vqueues) {
        !           145:                y = MonitorUpdateVirtQueue(screen, y, q);
        !           146:                y++;
        !           147:                y = MonitorUpdateVirtQDesc(screen, y, q);
        !           148:                y++;
        !           149:        }
        !           150: }
        !           151: 
        !           152: void
        !           153: VirtIONetDevice::QueueReady()
        !           154: {
        !           155:        if (vq->idx == 0) {
        !           156:                // ここで受信開始だろうか。
        !           157:                hostnet->EnableRx(true);
        !           158:        }
        !           159: }
        !           160: 
        !           161: // ディスクリプタを一つ処理する。
        !           162: void
        !           163: VirtIONetDevice::ProcessDesc(VirtQueue *q)
        !           164: {
        !           165:        VirtIONetReq req;
        !           166:        VirtQDesc desc;
        !           167:        uint32 desc_idx;
        !           168:        uint32 reqlen = 0;
        !           169: 
        !           170:        if (q->idx == 0) {
        !           171:                putlog(0, "QUEUE_NOTIFY on rx queue (VQ%u)?", q->idx);
        !           172:                return;
        !           173:        }
        !           174: 
        !           175:        // 1チャンク目は先頭 12 バイトが Read-Only なヘッダのはず。
        !           176:        desc_idx = ReadLE16(q->driver + 4 + (q->last_avail_idx % q->num) * 2);
        !           177:        reqlen += ReadDesc(q, &desc, desc_idx);
        !           178:        if (__predict_false(desc.IsRead() == false)) {
        !           179:                putlog(0, "NetHdr segment must be device-readable");
        !           180:                return;
        !           181:        }
        !           182:        // 仕様上は1バイト単位でチェインされてもいいのだが、面倒なので無視。
        !           183:        if (desc.len < 12) {
        !           184:                putlog(0, "NetHdr segment too short (desc.len=$%x)", desc.len);
        !           185:                return;
        !           186:        }
        !           187:        req.idx = desc_idx;
        !           188:        req.hdr.flags       = ReadU8(desc.addr + 0);
        !           189:        req.hdr.gso_type    = ReadU8(desc.addr + 1);
        !           190:        req.hdr.hdr_len     = ReadLE16(desc.addr + 2);
        !           191:        req.hdr.gso_size    = ReadLE16(desc.addr + 4);
        !           192:        req.hdr.csum_start  = ReadLE16(desc.addr + 6);
        !           193:        req.hdr.csum_offset = ReadLE16(desc.addr + 8);
        !           194:        req.hdr.num_buffers = ReadLE16(desc.addr + 10);
        !           195:        putlog(2, "req.hdr $%08x: flags=%02x gso=%02x hdr_len=%04x gso_size=%04x "
        !           196:                "csum_start=%04x offset=%04x num=%04x",
        !           197:                desc.addr,
        !           198:                req.hdr.flags,
        !           199:                req.hdr.gso_type,
        !           200:                req.hdr.hdr_len,
        !           201:                req.hdr.gso_size,
        !           202:                req.hdr.csum_start,
        !           203:                req.hdr.csum_offset,
        !           204:                req.hdr.num_buffers);
        !           205: 
        !           206:        // 同一セグメント内にデータ(ReadOnly) が連続していても構わない。
        !           207:        const auto HDRSZ = sizeof(req.hdr);
        !           208:        if (desc.len > HDRSZ) {
        !           209:                AddDataSegment(&req, desc.addr + HDRSZ, desc.len - HDRSZ,
        !           210:                        " (header segment)");
        !           211:        }
        !           212: 
        !           213:        while (desc.IsNext()) {
        !           214:                desc_idx = desc.next;
        !           215:                reqlen += ReadDesc(q, &desc, desc_idx);
        !           216:                AddDataSegment(&req, desc.addr, desc.len, NULL);
        !           217:        }
        !           218:        putlog(3, "reqlen=%08x", reqlen);
        !           219: 
        !           220:        NetPacket tx_packet;
        !           221:        for (const auto seg : req.buf) {
        !           222:                uint32 addr = seg.addr;
        !           223:                for (uint32 end = addr + seg.len; addr < end; ) {
        !           224:                        tx_packet.Append(ReadU8(addr++));
        !           225:                }
        !           226:        }
        !           227:        hostnet->Tx(tx_packet);
        !           228: 
        !           229:        CommitDesc(q, req.idx, reqlen);
        !           230: }
        !           231: 
        !           232: // これは Host スレッドから呼ばれる
        !           233: void
        !           234: VirtIONetDevice::HostRxCallback()
        !           235: {
        !           236:        // スレッドを超えるためにメッセージを投げる
        !           237:        scheduler->SendMessage(MessageID::HOSTNET_RX(0));
        !           238: }
        !           239: 
        !           240: // パケット受信通知
        !           241: void
        !           242: VirtIONetDevice::RxMessage(MessageID msgid_, uint32 arg)
        !           243: {
        !           244:        // 受信イベントが止まっていれば動かす。
        !           245:        // 受信イベントがすでに動いていれば、1パケット受信完了後に
        !           246:        // ホストキューを確認するので、ここでは何もしなくてよい。
        !           247:        if (event.IsRunning() == false) {
        !           248:                event.time = 0;
        !           249:                scheduler->StartEvent(event);
        !           250:        }
        !           251: }
        !           252: 
        !           253: // 受信ループイベント。
        !           254: void
        !           255: VirtIONetDevice::RxEvent(Event& ev)
        !           256: {
        !           257:        VirtQueue *q = &vqueues[0];
        !           258: 
        !           259:        // 空きディスクリプタがなければ、空くまでポーリングで待ち続ける。
        !           260:        uint16 avail_idx = ReadLE16(q->driver + 2);
        !           261:        if (q->last_avail_idx == avail_idx) {
        !           262:                event.time = 500_usec;
        !           263:                putlog(2, "%s: wait %u usec", __func__, (uint)(event.time / 1_usec));
        !           264:                scheduler->RestartEvent(event);
        !           265:                return;
        !           266:        }
        !           267: 
        !           268:        // ディスクリプタに空きが出来たので、ホストキューから1パケット取り出す。
        !           269:        assert(rx_packet.length == 0);
        !           270:        if (hostnet->Rx(&rx_packet) == false) {
        !           271:                // 取り出せなくなったら、ここでイベントループを終了。
        !           272:                rx_packet.Clear();
        !           273:                putlog(2, "Rx no more rx_packet");
        !           274:                return;
        !           275:        }
        !           276: 
        !           277:        putlog(2, "%s: %u bytes from host queue", __func__, rx_packet.length);
        !           278: 
        !           279:        // rx_packet が埋まって、空きディスクリプタがあるので受信処理へ。
        !           280:        Rx(q);
        !           281: 
        !           282:        rx_packet.Clear();
        !           283:        event.time = 500_usec;
        !           284:        scheduler->RestartEvent(event);
        !           285: }
        !           286: 
        !           287: // 受信。(rx_packet が埋まっていてかつ空きディスクリプタがある状態で呼ぶこと)
        !           288: void
        !           289: VirtIONetDevice::Rx(VirtQueue *q)
        !           290: {
        !           291:        VirtIONetReq req;
        !           292:        VirtQDesc desc;
        !           293:        uint32 desc_idx;
        !           294:        uint32 nseg = 0;        // ゲストが指定したセグメント数
        !           295:        uint32 buflen = 0;      // ゲストが指定したバッファのバイト数
        !           296:        uint32 datalen;         // 実際に書き込んだバイト数
        !           297: 
        !           298:        assert(rx_packet.length != 0);
        !           299:        assert(q->last_avail_idx != ReadLE16(q->driver + 2));
        !           300: 
        !           301:        // XXX どこでやるのがいいか
        !           302:        // VirtIO は FCS を含まない 1514 バイトが上限と決まっているが、
        !           303:        // HostNet の下の受信ドライバが FCS を含めているかどうかが分からない。
        !           304:        // 仕方ないので 1514 バイトを超える時だけ取り除く…。
        !           305:        if (rx_packet.length > 1514) {
        !           306:                rx_packet.length = 1514;
        !           307:        }
        !           308: 
        !           309:        desc_idx = ReadLE16(q->driver + 4 + (q->last_avail_idx % q->num) * 2);
        !           310:        req.idx = desc_idx;
        !           311:        do {
        !           312:                buflen += ReadDesc(q, &desc, desc_idx);
        !           313:                if (__predict_false(desc.IsWrite() == false)) {
        !           314:                        putlog(0, "Segments in rxq must be device-writeable");
        !           315:                        return;
        !           316:                }
        !           317:                nseg++;
        !           318:                AddDataSegment(&req, desc.addr, desc.len, NULL);
        !           319: 
        !           320:                desc_idx = desc.next;
        !           321:        } while (desc.IsNext());
        !           322:        putlog(3, "buflen=%08x", buflen);
        !           323: 
        !           324:        memset(&req.hdr, 0, sizeof(req.hdr));
        !           325:        req.hdr.hdr_len = htole16(sizeof(req.hdr));
        !           326:        // XXX nseg は実際に使ったセグメントをカウントしないといけない
        !           327:        req.hdr.num_buffers = htole16(nseg);
        !           328: 
        !           329:        // 書き込む。
        !           330:        const uint8 *s = (const uint8 *)&req.hdr;
        !           331:        for (int i = 0; i < sizeof(req.hdr); i++) {
        !           332:                ReqAppendByte(&req, *s++);
        !           333:        }
        !           334:        datalen = sizeof(req.hdr);
        !           335: 
        !           336:        s = rx_packet.data();
        !           337:        for (int i = 0; i < rx_packet.length; i++) {
        !           338:                if (ReqAppendByte(&req, rx_packet[i]) == false) {
        !           339:                        // どうする?
        !           340:                        putlog(0, "buffer too small: buflen=%u rx_packet=%u",
        !           341:                                buflen, rx_packet.length);
        !           342:                }
        !           343:                datalen++;
        !           344:        }
        !           345: 
        !           346:        CommitDesc(q, req.idx, datalen);
        !           347: 
        !           348:        // ここは VM スレッドなのでそのまま割り込みを上げる。
        !           349:        Done(q);
        !           350: }
        !           351: 
        !           352: const char *
        !           353: VirtIONetDevice::GetFeatureName(int feature) const
        !           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) {
        !           396:                mem.Write8(c);
        !           397:        }
        !           398:        mem.Write16(status);
        !           399:        mem.Write16(max_virtqueue_pairs);
        !           400:        mem.Write16(mtu);
        !           401: }

unix.superglobalmegacorp.com

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