Add device memory, interrupt state, and link transport.

Signed-off-by: Weqaar Janjua <[email protected]>
---
 MAINTAINERS             |   2 +
 docs/specs/cnuasgpu.rst |  91 +++++++
 docs/specs/index.rst    |   1 +
 hw/misc/Kconfig         |   5 +
 hw/misc/cnuasgpu.c      | 557 ++++++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build     |   1 +
 6 files changed, 657 insertions(+)
 create mode 100644 docs/specs/cnuasgpu.rst
 create mode 100644 hw/misc/cnuasgpu.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 408fc58611..d11ea29ff9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2776,6 +2776,8 @@ Cnuas virtual devices
 M: Weqaar Janjua <[email protected]>
 S: Maintained
 F: docs/specs/cnuas-vnic.rst
+F: docs/specs/cnuasgpu.rst
+F: hw/misc/cnuasgpu.c
 F: hw/net/cnuas_vnic.c
 F: tests/qtest/cnuas-vnic-test.c
 
diff --git a/docs/specs/cnuasgpu.rst b/docs/specs/cnuasgpu.rst
new file mode 100644
index 0000000000..8c8241b505
--- /dev/null
+++ b/docs/specs/cnuasgpu.rst
@@ -0,0 +1,91 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+============================
+CnuasGPU virtual accelerator
+============================
+
+``cnuasgpu`` is the PCIe device model for the Cnuas virtual AI/ML
+accelerator. It emulates the guest-visible control, device-memory, interrupt,
+and peer-fabric interfaces. Compute kernels are executed by Cnuas runtime
+backends rather than by a cycle-accurate GPU pipeline inside QEMU. Its
+optional ``SOCK_SEQPACKET`` transport currently requires a Linux host.
+
+PCI interface
+-------------
+
+===================== ==================
+Vendor ID             1b36
+Device ID             0016
+Revision              1
+Class                 0b40, co-processor
+===================== ==================
+
+======  ======================================================
+BAR     Contents
+======  ======================================================
+0       64 KiB MMIO register region
+1       64-bit prefetchable device memory, 256 MiB by default
+======  ======================================================
+
+The device supports one MSI vector and INTx.
+
+Properties
+----------
+
+``gpu_id``
+  Guest-visible accelerator identifier.
+
+``sm_count``, ``lanes_per_sm``, ``tensor_size``
+  Guest-visible geometry values. They do not control a compute engine.
+
+``devmem_size``
+  BAR 1 size. It must be a power of two from 16 MiB through 1 TiB.
+
+``cnuaslink_socket``
+  Optional ``AF_UNIX`` ``SOCK_SEQPACKET`` endpoint for CnuasLink frames.
+
+``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
+======  ==================  =========================================
+0x000   VENDOR_ID           0x1b36, read only
+0x004   DEVICE_ID           0x0016, read only
+0x008   REVISION            0x01, read only
+0x00c   FW_VERSION          0x00010000, read only
+0x010   GPU_ID              ``gpu_id`` property, read only
+0x014   SM_COUNT            ``sm_count`` property, read only
+0x018   LANES_PER_SM        ``lanes_per_sm`` property, read only
+0x01c   TENSOR_SIZE         ``tensor_size`` property, read only
+0x020   DEVMEM_SIZE_LO      BAR 1 size, low 32 bits, read only
+0x024   DEVMEM_SIZE_HI      BAR 1 size, high 32 bits, read only
+0x100   IRQ_STATUS          Interrupt status; write one bits to clear
+0x104   IRQ_MASK            Interrupt enable mask
+0x200   LINK_STATUS         Bit 0 link up, bit 1 receive ready
+0x204   TX_OFFSET_LO        BAR 1 transmit offset, low 32 bits
+0x208   TX_OFFSET_HI        BAR 1 transmit offset, high 32 bits
+0x20c   TX_LEN              Transmit length from 1 through 65536 bytes
+0x210   TX_DOORBELL         Write nonzero to transmit
+0x214   RX_OFFSET_LO        BAR 1 receive offset, low 32 bits
+0x218   RX_OFFSET_HI        BAR 1 receive offset, high 32 bits
+0x21c   RX_BUF_SIZE         Maximum accepted receive length
+0x220   RX_LEN              Last receive length, read only
+0x224   RX_CONSUME          Write nonzero to release the receive buffer
+======  ==================  =========================================
+
+Interrupt bit 0 reports transmit completion and bit 1 reports an available
+receive frame.
+
+CnuasLink
+---------
+
+The optional socket transports one frame per message. A transmit reads from
+BAR 1. A receive writes to BAR 1 and pauses socket monitoring until the guest
+writes ``RX_CONSUME``. Offsets and lengths are checked against BAR 1 before
+the model accesses device memory.
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index 0fd5da2aa8..e674d76da6 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -27,6 +27,7 @@ guest hardware that is specific to QEMU.
    fsi
    vmw_pvscsi-spec
    cnuas-vnic
+   cnuasgpu
    edu
    ivshmem-spec
    pvpanic
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 46e3c03cc8..39d52674e0 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -35,6 +35,11 @@ config EDU
     default y if TEST_DEVICES
     depends on PCI && MSI_NONBROKEN
 
+config CNUAS_GPU
+    bool
+    default y if PCI_DEVICES || PCIE_DEVICES
+    depends on LINUX && PCI_EXPRESS && MSI_NONBROKEN
+
 config I2C_ECHO
     bool
     default y if TEST_DEVICES
diff --git a/hw/misc/cnuasgpu.c b/hw/misc/cnuasgpu.c
new file mode 100644
index 0000000000..38ac1bdb42
--- /dev/null
+++ b/hw/misc/cnuasgpu.c
@@ -0,0 +1,557 @@
+/*
+ * CnuasGPU virtual accelerator
+ *
+ * Copyright (c) 2026 PacketFive
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#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 "qapi/error.h"
+#include "qom/object.h"
+
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define TYPE_CNUAS_GPU "cnuasgpu"
+OBJECT_DECLARE_SIMPLE_TYPE(CnuasGpuState, CNUAS_GPU)
+
+#define CNUAS_GPU_BAR0_SIZE          (64 * KiB)
+#define CNUAS_GPU_DEVMEM_DEFAULT     (256 * MiB)
+#define CNUAS_GPU_DEVMEM_MIN         (16 * MiB)
+#define CNUAS_GPU_DEVMEM_MAX         (1024ULL * GiB)
+#define CNUAS_GPU_LINK_MAX_FRAME     (64 * KiB)
+
+#define CNUAS_GPU_REG_VENDOR_ID      0x000
+#define CNUAS_GPU_REG_DEVICE_ID      0x004
+#define CNUAS_GPU_REG_REVISION       0x008
+#define CNUAS_GPU_REG_FW_VERSION     0x00c
+#define CNUAS_GPU_REG_GPU_ID         0x010
+#define CNUAS_GPU_REG_SM_COUNT       0x014
+#define CNUAS_GPU_REG_LANES_PER_SM   0x018
+#define CNUAS_GPU_REG_TENSOR_SIZE    0x01c
+#define CNUAS_GPU_REG_DEVMEM_SIZE_LO 0x020
+#define CNUAS_GPU_REG_DEVMEM_SIZE_HI 0x024
+#define CNUAS_GPU_REG_IRQ_STATUS     0x100
+#define CNUAS_GPU_REG_IRQ_MASK       0x104
+#define CNUAS_GPU_REG_LINK_STATUS    0x200
+#define CNUAS_GPU_REG_TX_OFFSET_LO   0x204
+#define CNUAS_GPU_REG_TX_OFFSET_HI   0x208
+#define CNUAS_GPU_REG_TX_LEN         0x20c
+#define CNUAS_GPU_REG_TX_DOORBELL    0x210
+#define CNUAS_GPU_REG_RX_OFFSET_LO   0x214
+#define CNUAS_GPU_REG_RX_OFFSET_HI   0x218
+#define CNUAS_GPU_REG_RX_BUF_SIZE    0x21c
+#define CNUAS_GPU_REG_RX_LEN         0x220
+#define CNUAS_GPU_REG_RX_CONSUME     0x224
+
+#define CNUAS_GPU_FW_VERSION         0x00010000
+#define CNUAS_GPU_REVISION           1
+
+#define CNUAS_GPU_IRQ_TX_DONE        BIT(0)
+#define CNUAS_GPU_IRQ_RX_AVAILABLE   BIT(1)
+
+#define CNUAS_GPU_LINK_UP            BIT(0)
+#define CNUAS_GPU_LINK_RX_READY      BIT(1)
+
+struct CnuasGpuState {
+    PCIDevice parent_obj;
+    MemoryRegion bar0;
+    MemoryRegion bar1;
+
+    uint32_t gpu_id;
+    uint32_t sm_count;
+    uint32_t lanes_per_sm;
+    uint32_t tensor_size;
+    uint64_t devmem_size;
+    PCIExpLinkSpeed pcie_speed;
+    PCIExpLinkWidth pcie_width;
+
+    uint32_t irq_status;
+    uint32_t irq_mask;
+
+    char *link_socket;
+    int socket_fd;
+    bool link_up;
+    uint32_t tx_offset_lo;
+    uint32_t tx_offset_hi;
+    uint32_t tx_len;
+    uint32_t rx_offset_lo;
+    uint32_t rx_offset_hi;
+    uint32_t rx_buf_size;
+    uint32_t rx_len;
+    bool rx_ready;
+};
+
+static void cnuas_gpu_update_irq(CnuasGpuState *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_gpu_devmem_ptr(CnuasGpuState *s, uint64_t offset,
+                                  size_t length)
+{
+    if (offset > s->devmem_size || length > s->devmem_size - offset) {
+        return NULL;
+    }
+
+    return memory_region_get_ram_ptr(&s->bar1) + offset;
+}
+
+static void cnuas_gpu_disconnect(CnuasGpuState *s)
+{
+    if (s->socket_fd >= 0) {
+        qemu_set_fd_handler(s->socket_fd, NULL, NULL, NULL);
+        close(s->socket_fd);
+        s->socket_fd = -1;
+    }
+    s->link_up = false;
+    s->rx_ready = false;
+    s->rx_len = 0;
+}
+
+static void cnuas_gpu_receive_ready(void *opaque)
+{
+    CnuasGpuState *s = opaque;
+    uint8_t frame[CNUAS_GPU_LINK_MAX_FRAME];
+    uint64_t offset;
+    void *destination;
+    ssize_t length;
+
+    if (s->rx_ready) {
+        qemu_set_fd_handler(s->socket_fd, NULL, NULL, NULL);
+        return;
+    }
+
+    length = recv(s->socket_fd, frame, sizeof(frame), MSG_DONTWAIT);
+    if (length == 0) {
+        cnuas_gpu_disconnect(s);
+        return;
+    }
+    if (length < 0) {
+        if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "cnuasgpu: link receive failed: %s\n",
+                          strerror(errno));
+            cnuas_gpu_disconnect(s);
+        }
+        return;
+    }
+
+    if (!s->rx_buf_size || length > s->rx_buf_size) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: RX frame does not fit guest buffer\n");
+        return;
+    }
+
+    offset = ((uint64_t)s->rx_offset_hi << 32) | s->rx_offset_lo;
+    destination = cnuas_gpu_devmem_ptr(s, offset, length);
+    if (!destination) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: RX range exceeds device memory\n");
+        return;
+    }
+
+    memcpy(destination, frame, length);
+    s->rx_len = length;
+    s->rx_ready = true;
+    s->irq_status |= CNUAS_GPU_IRQ_RX_AVAILABLE;
+    cnuas_gpu_update_irq(s);
+    qemu_set_fd_handler(s->socket_fd, NULL, NULL, NULL);
+}
+
+static void cnuas_gpu_connect(CnuasGpuState *s)
+{
+    struct sockaddr_un address = { .sun_family = AF_UNIX };
+    int flags;
+    int fd;
+
+    if (!s->link_socket || !s->link_socket[0]) {
+        return;
+    }
+    if (strlen(s->link_socket) >= sizeof(address.sun_path)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: link socket path is too long\n");
+        return;
+    }
+
+    fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+    if (fd < 0) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: link socket creation failed: %s\n",
+                      strerror(errno));
+        return;
+    }
+
+    g_strlcpy(address.sun_path, s->link_socket, sizeof(address.sun_path));
+    if (connect(fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
+        qemu_log_mask(LOG_UNIMP,
+                      "cnuasgpu: cannot connect to %s: %s\n",
+                      s->link_socket, 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,
+                      "cnuasgpu: cannot make socket nonblocking: %s\n",
+                      strerror(errno));
+        close(fd);
+        return;
+    }
+
+    s->socket_fd = fd;
+    s->link_up = true;
+    qemu_set_fd_handler(fd, cnuas_gpu_receive_ready, NULL, s);
+}
+
+static void cnuas_gpu_transmit(CnuasGpuState *s)
+{
+    uint64_t offset;
+    void *source;
+    ssize_t sent;
+
+    if (!s->link_up || s->socket_fd < 0) {
+        return;
+    }
+    if (!s->tx_len || s->tx_len > CNUAS_GPU_LINK_MAX_FRAME) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: invalid TX length %u\n", s->tx_len);
+        return;
+    }
+
+    offset = ((uint64_t)s->tx_offset_hi << 32) | s->tx_offset_lo;
+    source = cnuas_gpu_devmem_ptr(s, offset, s->tx_len);
+    if (!source) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: TX range exceeds device memory\n");
+        return;
+    }
+
+    sent = send(s->socket_fd, source, s->tx_len, MSG_NOSIGNAL);
+    if (sent < 0) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: link transmit failed: %s\n",
+                      strerror(errno));
+        if (errno != EAGAIN && errno != EWOULDBLOCK) {
+            cnuas_gpu_disconnect(s);
+        }
+        return;
+    }
+    if (sent != s->tx_len) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: short TX (%zd of %u bytes)\n",
+                      sent, s->tx_len);
+        return;
+    }
+
+    s->irq_status |= CNUAS_GPU_IRQ_TX_DONE;
+    cnuas_gpu_update_irq(s);
+}
+
+static uint64_t cnuas_gpu_bar0_read(void *opaque, hwaddr addr,
+                                    unsigned size)
+{
+    CnuasGpuState *s = opaque;
+
+    switch (addr) {
+    case CNUAS_GPU_REG_VENDOR_ID:
+        return PCI_VENDOR_ID_REDHAT;
+    case CNUAS_GPU_REG_DEVICE_ID:
+        return PCI_DEVICE_ID_REDHAT_CNUASGPU;
+    case CNUAS_GPU_REG_REVISION:
+        return CNUAS_GPU_REVISION;
+    case CNUAS_GPU_REG_FW_VERSION:
+        return CNUAS_GPU_FW_VERSION;
+    case CNUAS_GPU_REG_GPU_ID:
+        return s->gpu_id;
+    case CNUAS_GPU_REG_SM_COUNT:
+        return s->sm_count;
+    case CNUAS_GPU_REG_LANES_PER_SM:
+        return s->lanes_per_sm;
+    case CNUAS_GPU_REG_TENSOR_SIZE:
+        return s->tensor_size;
+    case CNUAS_GPU_REG_DEVMEM_SIZE_LO:
+        return s->devmem_size;
+    case CNUAS_GPU_REG_DEVMEM_SIZE_HI:
+        return s->devmem_size >> 32;
+    case CNUAS_GPU_REG_IRQ_STATUS:
+        return s->irq_status;
+    case CNUAS_GPU_REG_IRQ_MASK:
+        return s->irq_mask;
+    case CNUAS_GPU_REG_LINK_STATUS:
+        return (s->link_up ? CNUAS_GPU_LINK_UP : 0) |
+               (s->rx_ready ? CNUAS_GPU_LINK_RX_READY : 0);
+    case CNUAS_GPU_REG_TX_OFFSET_LO:
+        return s->tx_offset_lo;
+    case CNUAS_GPU_REG_TX_OFFSET_HI:
+        return s->tx_offset_hi;
+    case CNUAS_GPU_REG_TX_LEN:
+        return s->tx_len;
+    case CNUAS_GPU_REG_RX_OFFSET_LO:
+        return s->rx_offset_lo;
+    case CNUAS_GPU_REG_RX_OFFSET_HI:
+        return s->rx_offset_hi;
+    case CNUAS_GPU_REG_RX_BUF_SIZE:
+        return s->rx_buf_size;
+    case CNUAS_GPU_REG_RX_LEN:
+        return s->rx_len;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: read from unknown register 0x%"
+                      HWADDR_PRIx "\n", addr);
+        return 0;
+    }
+}
+
+static void cnuas_gpu_bar0_write(void *opaque, hwaddr addr,
+                                 uint64_t value, unsigned size)
+{
+    CnuasGpuState *s = opaque;
+
+    switch (addr) {
+    case CNUAS_GPU_REG_IRQ_STATUS:
+        s->irq_status &= ~value;
+        cnuas_gpu_update_irq(s);
+        break;
+    case CNUAS_GPU_REG_IRQ_MASK:
+        s->irq_mask = value;
+        cnuas_gpu_update_irq(s);
+        break;
+    case CNUAS_GPU_REG_TX_OFFSET_LO:
+        s->tx_offset_lo = value;
+        break;
+    case CNUAS_GPU_REG_TX_OFFSET_HI:
+        s->tx_offset_hi = value;
+        break;
+    case CNUAS_GPU_REG_TX_LEN:
+        s->tx_len = value;
+        break;
+    case CNUAS_GPU_REG_TX_DOORBELL:
+        if (value) {
+            cnuas_gpu_transmit(s);
+        }
+        break;
+    case CNUAS_GPU_REG_RX_OFFSET_LO:
+        s->rx_offset_lo = value;
+        break;
+    case CNUAS_GPU_REG_RX_OFFSET_HI:
+        s->rx_offset_hi = value;
+        break;
+    case CNUAS_GPU_REG_RX_BUF_SIZE:
+        s->rx_buf_size = value;
+        break;
+    case CNUAS_GPU_REG_RX_CONSUME:
+        if (value && s->rx_ready) {
+            s->rx_ready = false;
+            s->rx_len = 0;
+            if (s->socket_fd >= 0) {
+                qemu_set_fd_handler(s->socket_fd,
+                                    cnuas_gpu_receive_ready, NULL, s);
+            }
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "cnuasgpu: write to unknown register 0x%"
+                      HWADDR_PRIx "\n", addr);
+        break;
+    }
+}
+
+static const MemoryRegionOps cnuas_gpu_bar0_ops = {
+    .read = cnuas_gpu_bar0_read,
+    .write = cnuas_gpu_bar0_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_gpu_reset_hold(Object *obj, ResetType type)
+{
+    CnuasGpuState *s = CNUAS_GPU(obj);
+
+    s->irq_status = 0;
+    s->irq_mask = 0;
+    s->tx_offset_lo = 0;
+    s->tx_offset_hi = 0;
+    s->tx_len = 0;
+    s->rx_offset_lo = 0;
+    s->rx_offset_hi = 0;
+    s->rx_buf_size = 0;
+    s->rx_len = 0;
+    s->rx_ready = false;
+    pci_set_irq(&s->parent_obj, 0);
+
+    if (s->socket_fd >= 0) {
+        qemu_set_fd_handler(s->socket_fd, cnuas_gpu_receive_ready, NULL, s);
+    }
+}
+
+static int cnuas_gpu_post_load(void *opaque, int version_id)
+{
+    CnuasGpuState *s = opaque;
+
+    if (s->socket_fd >= 0) {
+        qemu_set_fd_handler(s->socket_fd,
+                            s->rx_ready ? NULL : cnuas_gpu_receive_ready,
+                            NULL, s);
+    }
+    cnuas_gpu_update_irq(s);
+    return 0;
+}
+
+static const VMStateDescription vmstate_cnuas_gpu = {
+    .name = TYPE_CNUAS_GPU,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = cnuas_gpu_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_PCI_DEVICE(parent_obj, CnuasGpuState),
+        VMSTATE_UINT32(irq_status, CnuasGpuState),
+        VMSTATE_UINT32(irq_mask, CnuasGpuState),
+        VMSTATE_UINT32(tx_offset_lo, CnuasGpuState),
+        VMSTATE_UINT32(tx_offset_hi, CnuasGpuState),
+        VMSTATE_UINT32(tx_len, CnuasGpuState),
+        VMSTATE_UINT32(rx_offset_lo, CnuasGpuState),
+        VMSTATE_UINT32(rx_offset_hi, CnuasGpuState),
+        VMSTATE_UINT32(rx_buf_size, CnuasGpuState),
+        VMSTATE_UINT32(rx_len, CnuasGpuState),
+        VMSTATE_BOOL(rx_ready, CnuasGpuState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void cnuas_gpu_realize(PCIDevice *pdev, Error **errp)
+{
+    CnuasGpuState *s = CNUAS_GPU(pdev);
+
+    if (s->devmem_size < CNUAS_GPU_DEVMEM_MIN ||
+        s->devmem_size > CNUAS_GPU_DEVMEM_MAX ||
+        !is_power_of_2(s->devmem_size)) {
+        error_setg(errp,
+                   "cnuasgpu: devmem_size must be a power of two from "
+                   "%llu to %llu bytes",
+                   (unsigned long long)CNUAS_GPU_DEVMEM_MIN,
+                   (unsigned long long)CNUAS_GPU_DEVMEM_MAX);
+        return;
+    }
+
+    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, "cnuasgpu: 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->bar0, OBJECT(s), &cnuas_gpu_bar0_ops, s,
+                          "cnuasgpu-bar0", CNUAS_GPU_BAR0_SIZE);
+    pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
+
+    memory_region_init_ram(&s->bar1, OBJECT(s), "cnuasgpu-devmem",
+                           s->devmem_size, errp);
+    if (*errp) {
+        pcie_cap_exit(pdev);
+        msi_uninit(pdev);
+        return;
+    }
+    pci_register_bar(pdev, 1,
+                     PCI_BASE_ADDRESS_SPACE_MEMORY |
+                     PCI_BASE_ADDRESS_MEM_PREFETCH |
+                     PCI_BASE_ADDRESS_MEM_TYPE_64,
+                     &s->bar1);
+
+    s->socket_fd = -1;
+    cnuas_gpu_connect(s);
+}
+
+static void cnuas_gpu_exit(PCIDevice *pdev)
+{
+    CnuasGpuState *s = CNUAS_GPU(pdev);
+
+    cnuas_gpu_disconnect(s);
+    pcie_cap_exit(pdev);
+    msi_uninit(pdev);
+}
+
+static const Property cnuas_gpu_properties[] = {
+    DEFINE_PROP_UINT32("gpu_id", CnuasGpuState, gpu_id, 0),
+    DEFINE_PROP_UINT32("sm_count", CnuasGpuState, sm_count, 16),
+    DEFINE_PROP_UINT32("lanes_per_sm", CnuasGpuState, lanes_per_sm, 32),
+    DEFINE_PROP_UINT32("tensor_size", CnuasGpuState, tensor_size, 16),
+    DEFINE_PROP_SIZE("devmem_size", CnuasGpuState, devmem_size,
+                     CNUAS_GPU_DEVMEM_DEFAULT),
+    DEFINE_PROP_STRING("cnuaslink_socket", CnuasGpuState, link_socket),
+    DEFINE_PROP_PCIE_LINK_SPEED("x-speed", CnuasGpuState, pcie_speed,
+                                PCIE_LINK_SPEED_32),
+    DEFINE_PROP_PCIE_LINK_WIDTH("x-width", CnuasGpuState, pcie_width,
+                                PCIE_LINK_WIDTH_16),
+};
+
+static void cnuas_gpu_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_gpu_realize;
+    pc->exit = cnuas_gpu_exit;
+    pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+    pc->device_id = PCI_DEVICE_ID_REDHAT_CNUASGPU;
+    pc->class_id = PCI_CLASS_PROCESSOR_CO;
+    pc->revision = CNUAS_GPU_REVISION;
+
+    rc->phases.hold = cnuas_gpu_reset_hold;
+    dc->desc = "CnuasGPU virtual accelerator";
+    dc->vmsd = &vmstate_cnuas_gpu;
+    device_class_set_props(dc, cnuas_gpu_properties);
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo cnuas_gpu_info = {
+    .name = TYPE_CNUAS_GPU,
+    .parent = TYPE_PCI_DEVICE,
+    .instance_size = sizeof(CnuasGpuState),
+    .class_init = cnuas_gpu_class_init,
+    .interfaces = (const InterfaceInfo[]) {
+        { INTERFACE_PCIE_DEVICE },
+        { },
+    },
+};
+
+static void cnuas_gpu_register_types(void)
+{
+    type_register_static(&cnuas_gpu_info);
+}
+
+type_init(cnuas_gpu_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 9790afa104..beb3a678a5 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -1,6 +1,7 @@
 system_ss.add(when: 'CONFIG_APPLESMC', if_true: files('applesmc.c'))
 
 system_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
+system_ss.add(when: 'CONFIG_CNUAS_GPU', if_true: files('cnuasgpu.c'))
 system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
 system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
 system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
-- 
2.43.0


Reply via email to