Cover PCI identity, register reset, DMA, and packet transport.

Signed-off-by: Weqaar Janjua <[email protected]>
---
 MAINTAINERS                   |   1 +
 tests/qtest/cnuas-vnic-test.c | 233 ++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build       |   1 +
 3 files changed, 235 insertions(+)
 create mode 100644 tests/qtest/cnuas-vnic-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 5f9e8db723..408fc58611 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2777,6 +2777,7 @@ M: Weqaar Janjua <[email protected]>
 S: Maintained
 F: docs/specs/cnuas-vnic.rst
 F: hw/net/cnuas_vnic.c
+F: tests/qtest/cnuas-vnic-test.c
 
 VMware
 M: Dmitry Fleytman <[email protected]>
diff --git a/tests/qtest/cnuas-vnic-test.c b/tests/qtest/cnuas-vnic-test.c
new file mode 100644
index 0000000000..fc27b7fa8e
--- /dev/null
+++ b/tests/qtest/cnuas-vnic-test.c
@@ -0,0 +1,233 @@
+/*
+ * QTest for the Cnuas virtual network adapter
+ *
+ * Copyright (c) 2026 PacketFive
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_regs.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+#include "libqtest.h"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define CNUAS_VNIC_DEVFN           QPCI_DEVFN(4, 0)
+#define CNUAS_VNIC_BAR_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
+
+typedef struct CnuasVnicTest {
+    QTestState *qts;
+    QPCIBus *bus;
+    QPCIDevice *dev;
+    QPCIBar bar;
+} CnuasVnicTest;
+
+static void cnuas_vnic_test_start_with_args(CnuasVnicTest *test,
+                                            const char *args)
+{
+    uint64_t bar_size;
+
+    test->qts = qtest_initf("-machine q35 "
+                            "-device cnuas-vnic,addr=04.0,"
+                            "mac=02:11:22:33:44:55%s", args);
+    test->bus = qpci_new_pc(test->qts, NULL);
+    test->dev = qpci_device_find(test->bus, CNUAS_VNIC_DEVFN);
+    g_assert_nonnull(test->dev);
+    qpci_device_enable(test->dev);
+    test->bar = qpci_iomap(test->dev, 0, &bar_size);
+    g_assert_cmpuint(bar_size, ==, CNUAS_VNIC_BAR_SIZE);
+}
+
+static void cnuas_vnic_test_start(CnuasVnicTest *test)
+{
+    cnuas_vnic_test_start_with_args(test, "");
+}
+
+static void cnuas_vnic_test_stop(CnuasVnicTest *test)
+{
+    g_free(test->dev);
+    qpci_free_pc(test->bus);
+    qtest_quit(test->qts);
+}
+
+static void cnuas_vnic_test_pci_config(void)
+{
+    CnuasVnicTest test;
+
+    cnuas_vnic_test_start(&test);
+
+    g_assert_cmphex(qpci_config_readw(test.dev, PCI_VENDOR_ID), ==,
+                    PCI_VENDOR_ID_REDHAT);
+    g_assert_cmphex(qpci_config_readw(test.dev, PCI_DEVICE_ID), ==,
+                    PCI_DEVICE_ID_REDHAT_CNUASNIC);
+    g_assert_cmphex(qpci_config_readw(test.dev, PCI_CLASS_DEVICE), ==,
+                    PCI_CLASS_NETWORK_OTHER);
+    g_assert_cmphex(qpci_config_readb(test.dev, PCI_REVISION_ID), ==, 1);
+    g_assert_cmphex(qpci_find_capability(test.dev, PCI_CAP_ID_MSI, 0), !=, 0);
+
+    cnuas_vnic_test_stop(&test);
+}
+
+static void cnuas_vnic_test_registers(void)
+{
+    CnuasVnicTest test;
+
+    cnuas_vnic_test_start(&test);
+
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_LINK_STATUS), ==, 0);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_IRQ_STATUS), ==, 0);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_MAC_LO), ==, 0x33221102);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_MAC_HI), ==, 0x5544);
+
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_TX_ADDR_LO,
+                   0x89abcdef);
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_TX_ADDR_HI,
+                   0x01234567);
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_TX_LEN, 4096);
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_IRQ_MASK, 7);
+
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_TX_ADDR_LO), ==,
+                    0x89abcdef);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_TX_ADDR_HI), ==,
+                    0x01234567);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_TX_LEN), ==, 4096);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_IRQ_MASK), ==, 7);
+
+    qtest_system_reset(test.qts);
+    qpci_device_enable(test.dev);
+
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_TX_ADDR_LO), ==, 0);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_TX_ADDR_HI), ==, 0);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_TX_LEN), ==, 0);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_RX_STATUS), ==, 0);
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_IRQ_MASK), ==, 0);
+
+    cnuas_vnic_test_stop(&test);
+}
+
+static int cnuas_vnic_test_listen(char **path)
+{
+    struct sockaddr_un address = { .sun_family = AF_UNIX };
+    int fd;
+
+    *path = g_strdup_printf("/tmp/cnuas-vnic-test-%u.sock", getpid());
+    unlink(*path);
+    g_strlcpy(address.sun_path, *path, sizeof(address.sun_path));
+
+    fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+    g_assert_cmpint(fd, >=, 0);
+    g_assert_cmpint(bind(fd, (struct sockaddr *)&address, sizeof(address)),
+                    ==, 0);
+    g_assert_cmpint(listen(fd, 1), ==, 0);
+    return fd;
+}
+
+static void cnuas_vnic_test_transport(void)
+{
+    static const uint8_t tx_frame[] = {
+        0x02, 0, 0, 0, 0, 2, 0x02, 0, 0, 0, 0, 1, 0x08, 0,
+        'C', 'n', 'u', 'a', 's',
+    };
+    static const uint8_t rx_frame[] = {
+        0x02, 0, 0, 0, 0, 1, 0x02, 0, 0, 0, 0, 2, 0x08, 0,
+        'R', 'D', 'M', 'A',
+    };
+    uint8_t result[64] = { 0 };
+    const uint64_t tx_address = 0x100000;
+    const uint64_t rx_address = 0x101000;
+    g_autofree char *path = NULL;
+    g_autofree char *args = NULL;
+    CnuasVnicTest test;
+    int connection;
+    int listener;
+    unsigned int i;
+
+    listener = cnuas_vnic_test_listen(&path);
+    args = g_strdup_printf(",socket_path=%s", path);
+    cnuas_vnic_test_start_with_args(&test, args);
+    connection = accept(listener, NULL, NULL);
+    g_assert_cmpint(connection, >=, 0);
+
+    g_assert_cmphex(qpci_io_readl(test.dev, test.bar,
+                                  CNUAS_VNIC_REG_LINK_STATUS), ==, 1);
+
+    qtest_memwrite(test.qts, tx_address, tx_frame, sizeof(tx_frame));
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_TX_ADDR_LO,
+                   tx_address);
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_TX_ADDR_HI,
+                   tx_address >> 32);
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_TX_LEN,
+                   sizeof(tx_frame));
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_TX_DOORBELL, 1);
+
+    g_assert_cmpint(recv(connection, result, sizeof(result), 0), ==,
+                    sizeof(tx_frame));
+    g_assert_cmpmem(result, sizeof(tx_frame), tx_frame, sizeof(tx_frame));
+
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_RX_ADDR_LO,
+                   rx_address);
+    qpci_io_writel(test.dev, test.bar, CNUAS_VNIC_REG_RX_ADDR_HI,
+                   rx_address >> 32);
+    g_assert_cmpint(send(connection, rx_frame, sizeof(rx_frame), 0), ==,
+                    sizeof(rx_frame));
+
+    for (i = 0; i < 1000; i++) {
+        if (qpci_io_readl(test.dev, test.bar,
+                          CNUAS_VNIC_REG_RX_STATUS) == 1) {
+            break;
+        }
+        g_usleep(1000);
+    }
+    g_assert_cmpuint(i, <, 1000);
+    g_assert_cmpuint(qpci_io_readl(test.dev, test.bar,
+                                   CNUAS_VNIC_REG_RX_LEN), ==,
+                     sizeof(rx_frame));
+
+    memset(result, 0, sizeof(result));
+    qtest_memread(test.qts, rx_address, result, sizeof(rx_frame));
+    g_assert_cmpmem(result, sizeof(rx_frame), rx_frame, sizeof(rx_frame));
+
+    close(connection);
+    close(listener);
+    unlink(path);
+    cnuas_vnic_test_stop(&test);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    qtest_add_func("/cnuas-vnic/pci-config", cnuas_vnic_test_pci_config);
+    qtest_add_func("/cnuas-vnic/registers", cnuas_vnic_test_registers);
+    qtest_add_func("/cnuas-vnic/transport", cnuas_vnic_test_transport);
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 33f58b0e95..84d2fa702a 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -59,6 +59,7 @@ qtests_filter = \
 qtests_i386 = \
   (slirp.found() ? ['pxe-test'] : []) + \
   qtests_filter + \
+  (config_all_devices.has_key('CONFIG_CNUAS_VNIC') ? ['cnuas-vnic-test'] : []) 
+            \
   (config_all_devices.has_key('CONFIG_ACPI_VMGENID') ? ['vmgenid-test'] : []) 
+             \
   (config_all_devices.has_key('CONFIG_AHCI_ICH9') and have_tools ? 
['ahci-test'] : []) +    \
   (config_all_devices.has_key('CONFIG_AHCI_ICH9') ? ['tco-test'] : []) +       
             \
-- 
2.43.0


Reply via email to