Add guest DMA and packet transport while retaining the existing MIT license.

Signed-off-by: Weqaar Janjua <[email protected]>
---
 MAINTAINERS               |   6 +
 docs/specs/cnuas-vnic.rst |  78 ++++++
 docs/specs/index.rst      |   1 +
 hw/net/Kconfig            |   5 +
 hw/net/cnuas_vnic.c       | 508 ++++++++++++++++++++++++++++++++++++++
 hw/net/meson.build        |   1 +
 6 files changed, 599 insertions(+)
 create mode 100644 docs/specs/cnuas-vnic.rst
 create mode 100644 hw/net/cnuas_vnic.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6be9c5725b..5f9e8db723 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2772,6 +2772,12 @@ F: net/eth.c
 F: hw/net/net_rx_pkt*
 F: hw/net/net_tx_pkt*
 
+Cnuas virtual devices
+M: Weqaar Janjua <[email protected]>
+S: Maintained
+F: docs/specs/cnuas-vnic.rst
+F: hw/net/cnuas_vnic.c
+
 VMware
 M: Dmitry Fleytman <[email protected]>
 S: Maintained
diff --git a/docs/specs/cnuas-vnic.rst b/docs/specs/cnuas-vnic.rst
new file mode 100644
index 0000000000..a8424ad700
--- /dev/null
+++ b/docs/specs/cnuas-vnic.rst
@@ -0,0 +1,78 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+=============================
+Cnuas virtual network adapter
+=============================
+
+``cnuas-vnic`` is a PCIe network device for Cnuas RDMA and Ethernet emulation.
+Its wire side is an ``AF_UNIX`` ``SOCK_SEQPACKET`` socket,
+allowing multiple guests to connect to a separate virtual switch process.
+The socket transport currently requires a Linux host.
+
+The device intentionally uses a single transmit operation and one receive
+slot instead of descriptor rings. This keeps the programming model small
+while retaining guest DMA, interrupts, link state, and frame transport.
+
+PCI interface
+-------------
+
+===================== ==============================
+Vendor ID             1b36
+Device ID             0015
+Revision              1
+Class                 0280, other network controller
+===================== ==============================
+
+BAR 0 is a 4 KiB MMIO region. The device supports one MSI vector and INTx.
+
+Properties
+----------
+
+``socket_path``
+  Path of the switch's ``SOCK_SEQPACKET`` socket. Without this property, or
+  when the connection fails, the device still enumerates with its link down.
+
+``mac``
+  MAC address exposed through ``MAC_LO`` and ``MAC_HI``. If omitted, QEMU
+  derives a locally administered address from the PCI function number.
+
+``x-speed``, ``x-width``
+  PCIe link speed and width reported through PCIe configuration space.
+
+Register map
+------------
+
+All registers require 32-bit little-endian accesses.
+
+======  ===========  ===============================================
+Offset  Name         Meaning
+======  ===========  ===============================================
+0x00    TX_ADDR_LO   Guest physical transmit address, low 32 bits
+0x04    TX_ADDR_HI   Guest physical transmit address, high 32 bits
+0x08    TX_LEN       Frame length from 1 through 9216 bytes
+0x0c    TX_DOORBELL  Write to transmit
+0x10    RX_ADDR_LO   Guest physical receive address, low 32 bits
+0x14    RX_ADDR_HI   Guest physical receive address, high 32 bits
+0x18    RX_LEN       Length of the received frame, read only
+0x1c    RX_STATUS    Bit 0 marks a frame ready; write zero to release
+0x20    IRQ_STATUS   Interrupt status; write one bits to clear
+0x24    IRQ_MASK     Interrupt enable mask
+0x28    LINK_STATUS  Bit 0 marks the socket link up, read only
+0x2c    MAC_LO       MAC bytes 0 through 3, read only
+0x30    MAC_HI       MAC bytes 4 and 5, read only
+======  ===========  ===============================================
+
+Interrupt bits are bit 0 for receive completion, bit 1 for transmit
+completion, and bit 2 for a link-state change.
+
+Transport
+---------
+
+Writing ``TX_DOORBELL`` reads ``TX_LEN`` bytes from guest memory and sends
+one socket message. Receive messages are copied to the address in
+``RX_ADDR_LO`` and ``RX_ADDR_HI``. The device stops reading its socket while
+``RX_STATUS`` is set, preventing a later frame from overwriting the active
+receive slot.
+
+The socket transports complete Ethernet frames without an additional QEMU
+header. ``SOCK_SEQPACKET`` preserves frame boundaries.
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index 44e108db02..0fd5da2aa8 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -26,6 +26,7 @@ guest hardware that is specific to QEMU.
    fw_cfg
    fsi
    vmw_pvscsi-spec
+   cnuas-vnic
    edu
    ivshmem-spec
    pvpanic
diff --git a/hw/net/Kconfig b/hw/net/Kconfig
index b56a173eed..0721efc8b1 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -49,6 +49,11 @@ config IGB_PCI_EXPRESS
     default y if PCI_DEVICES || PCIE_DEVICES
     depends on PCI_EXPRESS && MSI_NONBROKEN
 
+config CNUAS_VNIC
+    bool
+    default y if PCI_DEVICES || PCIE_DEVICES
+    depends on LINUX && PCI_EXPRESS && MSI_NONBROKEN
+
 config RTL8139_PCI
     bool
     default y if PCI_DEVICES
diff --git a/hw/net/cnuas_vnic.c b/hw/net/cnuas_vnic.c
new file mode 100644
index 0000000000..506d917e39
--- /dev/null
+++ b/hw/net/cnuas_vnic.c
@@ -0,0 +1,508 @@
+/*
+ * Cnuas virtual RDMA network adapter
+ *
+ * Copyright (c) 2026 PacketFive
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev-properties-system.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pcie.h"
+#include "migration/vmstate.h"
+#include "net/net.h"
+#include "qapi/error.h"
+#include "qom/object.h"
+
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define TYPE_CNUAS_VNIC "cnuas-vnic"
+OBJECT_DECLARE_SIMPLE_TYPE(CnuasVnicState, CNUAS_VNIC)
+
+#define CNUAS_VNIC_MAX_FRAME 9216
+#define CNUAS_VNIC_MMIO_SIZE 4096
+
+#define CNUAS_VNIC_REG_TX_ADDR_LO   0x00
+#define CNUAS_VNIC_REG_TX_ADDR_HI   0x04
+#define CNUAS_VNIC_REG_TX_LEN       0x08
+#define CNUAS_VNIC_REG_TX_DOORBELL  0x0c
+#define CNUAS_VNIC_REG_RX_ADDR_LO   0x10
+#define CNUAS_VNIC_REG_RX_ADDR_HI   0x14
+#define CNUAS_VNIC_REG_RX_LEN       0x18
+#define CNUAS_VNIC_REG_RX_STATUS    0x1c
+#define CNUAS_VNIC_REG_IRQ_STATUS   0x20
+#define CNUAS_VNIC_REG_IRQ_MASK     0x24
+#define CNUAS_VNIC_REG_LINK_STATUS  0x28
+#define CNUAS_VNIC_REG_MAC_LO       0x2c
+#define CNUAS_VNIC_REG_MAC_HI       0x30
+
+#define CNUAS_VNIC_IRQ_RX_COMPLETE  BIT(0)
+#define CNUAS_VNIC_IRQ_TX_COMPLETE  BIT(1)
+#define CNUAS_VNIC_IRQ_LINK_CHANGE  BIT(2)
+
+#define CNUAS_VNIC_LINK_UP          BIT(0)
+#define CNUAS_VNIC_RX_READY         BIT(0)
+
+struct CnuasVnicState {
+    PCIDevice parent_obj;
+    MemoryRegion mmio;
+
+    char *socket_path;
+    int socket_fd;
+
+    MACAddr conf_mac;
+    uint8_t mac[6];
+
+    PCIExpLinkSpeed pcie_speed;
+    PCIExpLinkWidth pcie_width;
+
+    uint32_t tx_addr_lo;
+    uint32_t tx_addr_hi;
+    uint32_t tx_len;
+    uint32_t rx_addr_lo;
+    uint32_t rx_addr_hi;
+    uint32_t rx_len;
+    uint32_t rx_status;
+    uint32_t irq_status;
+    uint32_t irq_mask;
+    uint32_t link_status;
+};
+
+static void cnuas_vnic_update_irq(CnuasVnicState *s)
+{
+    bool pending = s->irq_status & s->irq_mask;
+
+    if (msi_enabled(&s->parent_obj)) {
+        if (pending) {
+            msi_notify(&s->parent_obj, 0);
+        }
+    } else {
+        pci_set_irq(&s->parent_obj, pending);
+    }
+}
+
+static void cnuas_vnic_set_link(CnuasVnicState *s, bool up)
+{
+    uint32_t new_status = up ? CNUAS_VNIC_LINK_UP : 0;
+
+    if (s->link_status == new_status) {
+        return;
+    }
+
+    s->link_status = new_status;
+    s->irq_status |= CNUAS_VNIC_IRQ_LINK_CHANGE;
+    cnuas_vnic_update_irq(s);
+}
+
+static void cnuas_vnic_disconnect(CnuasVnicState *s)
+{
+    if (s->socket_fd >= 0) {
+        qemu_set_fd_handler(s->socket_fd, NULL, NULL, NULL);
+        close(s->socket_fd);
+        s->socket_fd = -1;
+    }
+    cnuas_vnic_set_link(s, false);
+}
+
+static void cnuas_vnic_transmit(CnuasVnicState *s)
+{
+    uint8_t frame[CNUAS_VNIC_MAX_FRAME];
+    uint64_t address;
+    ssize_t sent;
+
+    if (!s->tx_len || s->tx_len > sizeof(frame)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: invalid TX length %u\n", s->tx_len);
+        return;
+    }
+
+    address = ((uint64_t)s->tx_addr_hi << 32) | s->tx_addr_lo;
+    if (pci_dma_read(&s->parent_obj, address, frame, s->tx_len) != MEMTX_OK) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: DMA read failed at 0x%" PRIx64 "\n",
+                      address);
+        return;
+    }
+
+    if (s->socket_fd >= 0 && s->link_status == CNUAS_VNIC_LINK_UP) {
+        sent = send(s->socket_fd, frame, s->tx_len, MSG_NOSIGNAL);
+        if (sent < 0) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "cnuas-vnic: TX failed: %s\n", strerror(errno));
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                cnuas_vnic_disconnect(s);
+            }
+        } else if (sent != s->tx_len) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "cnuas-vnic: short TX (%zd of %u bytes)\n",
+                          sent, s->tx_len);
+        }
+    }
+
+    s->irq_status |= CNUAS_VNIC_IRQ_TX_COMPLETE;
+    cnuas_vnic_update_irq(s);
+}
+
+static bool cnuas_vnic_receive(CnuasVnicState *s)
+{
+    uint8_t frame[CNUAS_VNIC_MAX_FRAME];
+    uint64_t address;
+    ssize_t length;
+
+    if (s->socket_fd < 0 || s->rx_status == CNUAS_VNIC_RX_READY) {
+        return false;
+    }
+
+    length = recv(s->socket_fd, frame, sizeof(frame), MSG_DONTWAIT);
+    if (length == 0) {
+        cnuas_vnic_disconnect(s);
+        return false;
+    }
+    if (length < 0) {
+        if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "cnuas-vnic: RX failed: %s\n", strerror(errno));
+            cnuas_vnic_disconnect(s);
+        }
+        return false;
+    }
+
+    address = ((uint64_t)s->rx_addr_hi << 32) | s->rx_addr_lo;
+    if (!address) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: dropped RX frame without a guest buffer\n");
+        return true;
+    }
+
+    if (pci_dma_write(&s->parent_obj, address, frame, length) != MEMTX_OK) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: DMA write failed at 0x%" PRIx64 "\n",
+                      address);
+        return true;
+    }
+
+    s->rx_len = length;
+    s->rx_status = CNUAS_VNIC_RX_READY;
+    s->irq_status |= CNUAS_VNIC_IRQ_RX_COMPLETE;
+    cnuas_vnic_update_irq(s);
+
+    /*
+     * The model has one receive slot. Stop monitoring the level-triggered
+     * socket until the guest releases that slot to avoid a busy loop.
+     */
+    qemu_set_fd_handler(s->socket_fd, NULL, NULL, NULL);
+    return true;
+}
+
+static void cnuas_vnic_receive_ready(void *opaque)
+{
+    cnuas_vnic_receive(opaque);
+}
+
+static uint64_t cnuas_vnic_mmio_read(void *opaque, hwaddr addr,
+                                     unsigned size)
+{
+    CnuasVnicState *s = opaque;
+
+    switch (addr) {
+    case CNUAS_VNIC_REG_TX_ADDR_LO:
+        return s->tx_addr_lo;
+    case CNUAS_VNIC_REG_TX_ADDR_HI:
+        return s->tx_addr_hi;
+    case CNUAS_VNIC_REG_TX_LEN:
+        return s->tx_len;
+    case CNUAS_VNIC_REG_RX_ADDR_LO:
+        return s->rx_addr_lo;
+    case CNUAS_VNIC_REG_RX_ADDR_HI:
+        return s->rx_addr_hi;
+    case CNUAS_VNIC_REG_RX_LEN:
+        return s->rx_len;
+    case CNUAS_VNIC_REG_RX_STATUS:
+        return s->rx_status;
+    case CNUAS_VNIC_REG_IRQ_STATUS:
+        return s->irq_status;
+    case CNUAS_VNIC_REG_IRQ_MASK:
+        return s->irq_mask;
+    case CNUAS_VNIC_REG_LINK_STATUS:
+        return s->link_status;
+    case CNUAS_VNIC_REG_MAC_LO:
+        return s->mac[0] | s->mac[1] << 8 | s->mac[2] << 16 |
+               s->mac[3] << 24;
+    case CNUAS_VNIC_REG_MAC_HI:
+        return s->mac[4] | s->mac[5] << 8;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: read from unknown register 0x%"
+                      HWADDR_PRIx "\n", addr);
+        return 0;
+    }
+}
+
+static void cnuas_vnic_mmio_write(void *opaque, hwaddr addr,
+                                  uint64_t value, unsigned size)
+{
+    CnuasVnicState *s = opaque;
+
+    switch (addr) {
+    case CNUAS_VNIC_REG_TX_ADDR_LO:
+        s->tx_addr_lo = value;
+        break;
+    case CNUAS_VNIC_REG_TX_ADDR_HI:
+        s->tx_addr_hi = value;
+        break;
+    case CNUAS_VNIC_REG_TX_LEN:
+        s->tx_len = value;
+        break;
+    case CNUAS_VNIC_REG_TX_DOORBELL:
+        cnuas_vnic_transmit(s);
+        break;
+    case CNUAS_VNIC_REG_RX_ADDR_LO:
+        s->rx_addr_lo = value;
+        break;
+    case CNUAS_VNIC_REG_RX_ADDR_HI:
+        s->rx_addr_hi = value;
+        break;
+    case CNUAS_VNIC_REG_RX_STATUS:
+        if (!value && s->rx_status == CNUAS_VNIC_RX_READY) {
+            s->rx_status = 0;
+            s->rx_len = 0;
+            if (s->socket_fd >= 0) {
+                qemu_set_fd_handler(s->socket_fd,
+                                    cnuas_vnic_receive_ready, NULL, s);
+                cnuas_vnic_receive(s);
+            }
+        }
+        break;
+    case CNUAS_VNIC_REG_IRQ_STATUS:
+        s->irq_status &= ~value;
+        cnuas_vnic_update_irq(s);
+        break;
+    case CNUAS_VNIC_REG_IRQ_MASK:
+        s->irq_mask = value;
+        cnuas_vnic_update_irq(s);
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: write to unknown register 0x%"
+                      HWADDR_PRIx "\n", addr);
+        break;
+    }
+}
+
+static const MemoryRegionOps cnuas_vnic_mmio_ops = {
+    .read = cnuas_vnic_mmio_read,
+    .write = cnuas_vnic_mmio_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void cnuas_vnic_connect(CnuasVnicState *s)
+{
+    struct sockaddr_un address = { .sun_family = AF_UNIX };
+    int flags;
+    int fd;
+    int buffer_size = 8 * MiB;
+
+    if (!s->socket_path || !s->socket_path[0]) {
+        return;
+    }
+    if (strlen(s->socket_path) >= sizeof(address.sun_path)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: socket path is too long\n");
+        return;
+    }
+
+    fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+    if (fd < 0) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: socket creation failed: %s\n",
+                      strerror(errno));
+        return;
+    }
+
+    g_strlcpy(address.sun_path, s->socket_path, sizeof(address.sun_path));
+    if (connect(fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
+        qemu_log_mask(LOG_UNIMP,
+                      "cnuas-vnic: cannot connect to %s: %s\n",
+                      s->socket_path, strerror(errno));
+        close(fd);
+        return;
+    }
+
+    flags = fcntl(fd, F_GETFL);
+    if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuas-vnic: cannot make socket nonblocking: %s\n",
+                      strerror(errno));
+        close(fd);
+        return;
+    }
+
+    setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
+    setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof(buffer_size));
+
+    s->socket_fd = fd;
+    cnuas_vnic_set_link(s, true);
+    qemu_set_fd_handler(fd, cnuas_vnic_receive_ready, NULL, s);
+}
+
+static void cnuas_vnic_reset_hold(Object *obj, ResetType type)
+{
+    CnuasVnicState *s = CNUAS_VNIC(obj);
+
+    s->tx_addr_lo = 0;
+    s->tx_addr_hi = 0;
+    s->tx_len = 0;
+    s->rx_addr_lo = 0;
+    s->rx_addr_hi = 0;
+    s->rx_len = 0;
+    s->rx_status = 0;
+    s->irq_status = 0;
+    s->irq_mask = 0;
+    pci_set_irq(&s->parent_obj, 0);
+
+    if (s->socket_fd >= 0) {
+        qemu_set_fd_handler(s->socket_fd, cnuas_vnic_receive_ready, NULL, s);
+    }
+}
+
+static int cnuas_vnic_post_load(void *opaque, int version_id)
+{
+    CnuasVnicState *s = opaque;
+
+    if (s->socket_fd >= 0) {
+        qemu_set_fd_handler(s->socket_fd,
+                            s->rx_status ? NULL : cnuas_vnic_receive_ready,
+                            NULL, s);
+    }
+    cnuas_vnic_update_irq(s);
+    return 0;
+}
+
+static const VMStateDescription vmstate_cnuas_vnic = {
+    .name = TYPE_CNUAS_VNIC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = cnuas_vnic_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_PCI_DEVICE(parent_obj, CnuasVnicState),
+        VMSTATE_UINT32(tx_addr_lo, CnuasVnicState),
+        VMSTATE_UINT32(tx_addr_hi, CnuasVnicState),
+        VMSTATE_UINT32(tx_len, CnuasVnicState),
+        VMSTATE_UINT32(rx_addr_lo, CnuasVnicState),
+        VMSTATE_UINT32(rx_addr_hi, CnuasVnicState),
+        VMSTATE_UINT32(rx_len, CnuasVnicState),
+        VMSTATE_UINT32(rx_status, CnuasVnicState),
+        VMSTATE_UINT32(irq_status, CnuasVnicState),
+        VMSTATE_UINT32(irq_mask, CnuasVnicState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void cnuas_vnic_realize(PCIDevice *pdev, Error **errp)
+{
+    CnuasVnicState *s = CNUAS_VNIC(pdev);
+    static const uint8_t zero_mac[6];
+
+    pci_config_set_interrupt_pin(pdev->config, 1);
+    if (msi_init(pdev, 0, 1, true, false, errp)) {
+        return;
+    }
+
+    if (pcie_endpoint_cap_init(pdev, 0x80) < 0) {
+        error_setg(errp, "cnuas-vnic: cannot initialize PCIe capability");
+        msi_uninit(pdev);
+        return;
+    }
+    pcie_cap_fill_link_ep_usp(pdev, s->pcie_width, s->pcie_speed, false);
+
+    memory_region_init_io(&s->mmio, OBJECT(s), &cnuas_vnic_mmio_ops, s,
+                          "cnuas-vnic-mmio", CNUAS_VNIC_MMIO_SIZE);
+    pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
+
+    if (!memcmp(s->conf_mac.a, zero_mac, sizeof(zero_mac))) {
+        s->mac[0] = 0x02;
+        s->mac[1] = 0x48;
+        s->mac[2] = 0x43;
+        s->mac[3] = 0x41;
+        s->mac[4] = 0x49;
+        s->mac[5] = pdev->devfn;
+    } else {
+        memcpy(s->mac, s->conf_mac.a, sizeof(s->mac));
+    }
+
+    s->socket_fd = -1;
+    cnuas_vnic_connect(s);
+}
+
+static void cnuas_vnic_exit(PCIDevice *pdev)
+{
+    CnuasVnicState *s = CNUAS_VNIC(pdev);
+
+    cnuas_vnic_disconnect(s);
+    pcie_cap_exit(pdev);
+    msi_uninit(pdev);
+}
+
+static const Property cnuas_vnic_properties[] = {
+    DEFINE_PROP_STRING("socket_path", CnuasVnicState, socket_path),
+    DEFINE_PROP_MACADDR("mac", CnuasVnicState, conf_mac),
+    DEFINE_PROP_PCIE_LINK_SPEED("x-speed", CnuasVnicState, pcie_speed,
+                                PCIE_LINK_SPEED_32),
+    DEFINE_PROP_PCIE_LINK_WIDTH("x-width", CnuasVnicState, pcie_width,
+                                PCIE_LINK_WIDTH_16),
+};
+
+static void cnuas_vnic_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    pc->realize = cnuas_vnic_realize;
+    pc->exit = cnuas_vnic_exit;
+    pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+    pc->device_id = PCI_DEVICE_ID_REDHAT_CNUASNIC;
+    pc->class_id = PCI_CLASS_NETWORK_OTHER;
+    pc->revision = 1;
+
+    rc->phases.hold = cnuas_vnic_reset_hold;
+    dc->desc = "Cnuas virtual RDMA network adapter";
+    dc->vmsd = &vmstate_cnuas_vnic;
+    device_class_set_props(dc, cnuas_vnic_properties);
+    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+}
+
+static const TypeInfo cnuas_vnic_info = {
+    .name = TYPE_CNUAS_VNIC,
+    .parent = TYPE_PCI_DEVICE,
+    .instance_size = sizeof(CnuasVnicState),
+    .class_init = cnuas_vnic_class_init,
+    .interfaces = (const InterfaceInfo[]) {
+        { INTERFACE_PCIE_DEVICE },
+        { },
+    },
+};
+
+static void cnuas_vnic_register_types(void)
+{
+    type_register_static(&cnuas_vnic_info);
+}
+
+type_init(cnuas_vnic_register_types)
diff --git a/hw/net/meson.build b/hw/net/meson.build
index 63b0e96245..1b942bd415 100644
--- a/hw/net/meson.build
+++ b/hw/net/meson.build
@@ -12,6 +12,7 @@ system_ss.add(when: 'CONFIG_E1000E_PCI_EXPRESS', if_true: 
files('net_tx_pkt.c',
 system_ss.add(when: 'CONFIG_E1000E_PCI_EXPRESS', if_true: files('e1000e.c', 
'e1000e_core.c', 'e1000x_common.c'))
 system_ss.add(when: 'CONFIG_IGB_PCI_EXPRESS', if_true: files('net_tx_pkt.c', 
'net_rx_pkt.c'))
 system_ss.add(when: 'CONFIG_IGB_PCI_EXPRESS', if_true: files('igb.c', 
'igbvf.c', 'igb_core.c'))
+system_ss.add(when: 'CONFIG_CNUAS_VNIC', if_true: files('cnuas_vnic.c'))
 system_ss.add(when: 'CONFIG_RTL8139_PCI', if_true: files('rtl8139.c'))
 system_ss.add(when: 'CONFIG_TULIP', if_true: files('tulip.c'))
 system_ss.add(when: 'CONFIG_VMXNET3_PCI', if_true: files('net_tx_pkt.c', 
'net_rx_pkt.c'))
-- 
2.43.0


Reply via email to