summaryrefslogtreecommitdiffstats
path: root/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c')
-rw-r--r--legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c624
1 files changed, 624 insertions, 0 deletions
diff --git a/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c
new file mode 100644
index 0000000..b9a46df
--- /dev/null
+++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c
@@ -0,0 +1,624 @@
+/*
+ * GSP vGPU-IO Driver Mock Test
+ * Tests driver logic with mocked hardware interfaces
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/idr.h>
+#include <linux/kref.h>
+#include <linux/completion.h>
+#include <linux/atomic.h>
+
+#define TEST_PASS 0
+#define TEST_FAIL -1
+
+static int test_count = 0;
+static int pass_count = 0;
+static int fail_count = 0;
+
+#define TEST_ASSERT(cond, msg) \
+ do { \
+ test_count++; \
+ if (cond) { \
+ pass_count++; \
+ pr_info("PASS: %s\n", msg); \
+ } else { \
+ fail_count++; \
+ pr_err("FAIL: %s\n", msg); \
+ } \
+ } while (0)
+
+#define TEST_ASSERT_EQ(a, b, msg) \
+ TEST_ASSERT((a) == (b), msg " (expected " #b ", got " #a ")")
+
+/* ============================================================
+ * Mock Hardware Abstraction
+ * ============================================================ */
+
+struct mock_hw {
+ u64 bar0_regs[256];
+ u64 bar1_regs[256];
+ u8 *fw_memory;
+ size_t fw_size;
+ bool doorbell_triggered;
+ u32 doorbell_count;
+ spinlock_t lock;
+};
+
+static struct mock_hw *mock_hw_create(void)
+{
+ struct mock_hw *hw = kzalloc(sizeof(*hw), GFP_KERNEL);
+ if (!hw)
+ return NULL;
+
+ hw->fw_memory = vmalloc(32 * 1024 * 1024); // 32MB
+ if (!hw->fw_memory) {
+ kfree(hw);
+ return NULL;
+ }
+ hw->fw_size = 32 * 1024 * 1024;
+ spin_lock_init(&hw->lock);
+ return hw;
+}
+
+static void mock_hw_destroy(struct mock_hw *hw)
+{
+ if (!hw)
+ return;
+ vfree(hw->fw_memory);
+ kfree(hw);
+}
+
+static void mock_hw_write_bar0(struct mock_hw *hw, u32 offset, u32 value)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&hw->lock, flags);
+ if (offset / 4 < 256)
+ hw->bar0_regs[offset / 4] = value;
+ if (offset == 0x1000) { // Doorbell
+ hw->doorbell_triggered = true;
+ hw->doorbell_count++;
+ }
+ spin_unlock_irqrestore(&hw->lock, flags);
+}
+
+static u32 mock_hw_read_bar0(struct mock_hw *hw, u32 offset)
+{
+ unsigned long flags;
+ u32 value = 0;
+ spin_lock_irqsave(&hw->lock, flags);
+ if (offset / 4 < 256)
+ value = hw->bar0_regs[offset / 4];
+ spin_unlock_irqrestore(&hw->lock, flags);
+ return value;
+}
+
+static void mock_hw_write_bar1(struct mock_hw *hw, u64 offset, u64 value)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&hw->lock, flags);
+ if (offset < hw->fw_size)
+ *(u64 *)(hw->fw_memory + offset) = value;
+ spin_unlock_irqrestore(&hw->lock, flags);
+}
+
+static u64 mock_hw_read_bar1(struct mock_hw *hw, u64 offset)
+{
+ unsigned long flags;
+ u64 value = 0;
+ spin_lock_irqsave(&hw->lock, flags);
+ if (offset < hw->fw_size)
+ value = *(u64 *)(hw->fw_memory + offset);
+ spin_unlock_irqrestore(&hw->lock, flags);
+ return value;
+}
+
+/* ============================================================
+ * Mock VF Operations
+ * ============================================================ */
+
+struct mock_vf {
+ u32 id;
+ u64 bar1_offset;
+ u64 bar1_size;
+ u64 pt_base;
+ u64 pt_size;
+ u64 cmd_base;
+ u64 cmd_size;
+ bool active;
+ struct {
+ u64 aperture_offset;
+ u64 aperture_size;
+ bool force_access;
+ u32 force_vf;
+ } bar_state;
+ struct list_head wpr_list;
+ struct mutex lock;
+};
+
+struct mock_wpr_region {
+ struct list_head list;
+ u64 base;
+ u64 size;
+ u32 flags;
+};
+
+static struct mock_vf *mock_vf_create(u32 id, u64 bar1_offset, u64 bar1_size,
+ u64 pt_base, u64 pt_size,
+ u64 cmd_base, u64 cmd_size)
+{
+ struct mock_vf *vf = kzalloc(sizeof(*vf), GFP_KERNEL);
+ if (!vf)
+ return NULL;
+
+ vf->id = id;
+ vf->bar1_offset = bar1_offset;
+ vf->bar1_size = bar1_size;
+ vf->pt_base = pt_base;
+ vf->pt_size = pt_size;
+ vf->cmd_base = cmd_base;
+ vf->cmd_size = cmd_size;
+ vf->active = true;
+ INIT_LIST_HEAD(&vf->wpr_list);
+ mutex_init(&vf->lock);
+
+ vf->bar_state.aperture_offset = bar1_offset;
+ vf->bar_state.aperture_size = bar1_size;
+ vf->bar_state.force_access = false;
+ vf->bar_state.force_vf = 0;
+
+ return vf;
+}
+
+static void mock_vf_destroy(struct mock_vf *vf)
+{
+ if (!vf)
+ return;
+
+ struct mock_wpr_region *rgn, *tmp;
+ mutex_lock(&vf->lock);
+ list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) {
+ list_del(&rgn->list);
+ kfree(rgn);
+ }
+ mutex_unlock(&vf->lock);
+
+ kfree(vf);
+}
+
+static int mock_vf_wpr_add(struct mock_vf *vf, u64 base, u64 size, u32 flags)
+{
+ struct mock_wpr_region *rgn = kzalloc(sizeof(*rgn), GFP_KERNEL);
+ if (!rgn)
+ return -ENOMEM;
+
+ rgn->base = base;
+ rgn->size = size;
+ rgn->flags = flags;
+
+ mutex_lock(&vf->lock);
+ list_add(&rgn->list, &vf->wpr_list);
+ mutex_unlock(&vf->lock);
+
+ return 0;
+}
+
+static int mock_vf_wpr_remove(struct mock_vf *vf, u64 base)
+{
+ struct mock_wpr_region *rgn, *tmp;
+ bool found = false;
+
+ mutex_lock(&vf->lock);
+ list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) {
+ if (rgn->base == base) {
+ list_del(&rgn->list);
+ kfree(rgn);
+ found = true;
+ break;
+ }
+ }
+ mutex_unlock(&vf->lock);
+
+ return found ? 0 : -ENOENT;
+}
+
+/* ============================================================
+ * Mock RPC Client
+ * ============================================================ */
+
+struct mock_rpc_client {
+ u32 seq;
+ u32 pending_seq[64];
+ u32 pending_count;
+ struct mutex lock;
+};
+
+static struct mock_rpc_client *mock_rpc_create(void)
+{
+ struct mock_rpc_client *rpc = kzalloc(sizeof(*rpc), GFP_KERNEL);
+ if (!rpc)
+ return NULL;
+ mutex_init(&rpc->lock);
+ return rpc;
+}
+
+static void mock_rpc_destroy(struct mock_rpc_client *rpc)
+{
+ if (!rpc)
+ return;
+ kfree(rpc);
+}
+
+static int mock_rpc_send(struct mock_rpc_client *rpc, u32 rpc_type,
+ void *payload, size_t payload_size,
+ void *response, size_t *response_size)
+{
+ if (!rpc)
+ return -EINVAL;
+
+ mutex_lock(&rpc->lock);
+ rpc->seq++;
+ rpc->pending_seq[rpc->pending_count++] = rpc->seq;
+ mutex_unlock(&rpc->lock);
+
+ /* Simulate successful response */
+ if (response && response_size) {
+ struct gsp_rpc_response *resp = response;
+ resp->status = 0;
+ resp->rpc_type = rpc_type;
+ resp->sequence = rpc->seq;
+ *response_size = sizeof(*resp);
+ }
+
+ return 0;
+}
+
+/* ============================================================
+ * Driver Logic Tests with Mocks
+ * ============================================================ */
+
+static int test_mock_hardware(void)
+{
+ struct mock_hw *hw = mock_hw_create();
+ TEST_ASSERT(hw != NULL, "Mock HW create");
+
+ /* Test BAR0 write/read */
+ mock_hw_write_bar0(hw, 0x1000, 0x1);
+ TEST_ASSERT(hw->doorbell_triggered, "Doorbell triggered");
+ TEST_ASSERT_EQ(hw->doorbell_count, 1, "Doorbell count");
+
+ u32 val = mock_hw_read_bar0(hw, 0x1000);
+ TEST_ASSERT_EQ(val, 0x1, "BAR0 read back");
+
+ /* Test BAR1 write/read */
+ mock_hw_write_bar1(hw, 0x1000, 0xDEADBEEFCAFEBABE);
+ u64 val64 = mock_hw_read_bar1(hw, 0x1000);
+ TEST_ASSERT_EQ(val64, 0xDEADBEEFCAFEBABE, "BAR1 read back");
+
+ mock_hw_destroy(hw);
+ return TEST_PASS;
+}
+
+static int test_mock_vf_lifecycle(void)
+{
+ struct mock_vf *vf = mock_vf_create(1, 0x10000000, 0x100000,
+ 0x20000000, 0x100000,
+ 0x30000000, 0x10000);
+ TEST_ASSERT(vf != NULL, "Mock VF create");
+
+ TEST_ASSERT(vf->active, "VF active after create");
+ TEST_ASSERT_EQ(vf->id, 1, "VF ID");
+ TEST_ASSERT_EQ(vf->bar1_offset, 0x10000000, "BAR1 offset");
+ TEST_ASSERT_EQ(vf->bar1_size, 0x100000, "BAR1 size");
+ TEST_ASSERT_EQ(vf->pt_base, 0x20000000, "Page table base");
+ TEST_ASSERT_EQ(vf->pt_size, 0x100000, "Page table size");
+
+ /* Test BAR state */
+ TEST_ASSERT_EQ(vf->bar_state.aperture_offset, 0x10000000, "Aperture offset");
+ TEST_ASSERT_EQ(vf->bar_state.aperture_size, 0x100000, "Aperture size");
+ TEST_ASSERT(!vf->bar_state.force_access, "Force access initially false");
+
+ /* Test WPR list empty */
+ TEST_ASSERT(list_empty(&vf->wpr_list), "WPR list empty initially");
+
+ mock_vf_destroy(vf);
+ return TEST_PASS;
+}
+
+static int test_mock_vf_wpr(void)
+{
+ struct mock_vf *vf = mock_vf_create(1, 0x10000000, 0x100000,
+ 0x20000000, 0x100000,
+ 0x30000000, 0x10000);
+ TEST_ASSERT(vf != NULL, "Mock VF create");
+
+ /* Add WPR region */
+ int ret = mock_vf_wpr_add(vf, 0x10000000, 0x10000, 3);
+ TEST_ASSERT_EQ(ret, 0, "WPR add");
+
+ ret = mock_vf_wpr_add(vf, 0x20000000, 0x20000, 1);
+ TEST_ASSERT_EQ(ret, 0, "WPR add second");
+
+ /* Verify list */
+ int count = 0;
+ struct mock_wpr_region *rgn;
+ mutex_lock(&vf->lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ count++;
+ }
+ mutex_unlock(&vf->lock);
+ TEST_ASSERT_EQ(count, 2, "Two WPR regions");
+
+ /* Remove one */
+ ret = mock_vf_wpr_remove(vf, 0x10000000);
+ TEST_ASSERT_EQ(ret, 0, "WPR remove");
+
+ count = 0;
+ mutex_lock(&vf->lock);
+ list_for_each_entry(rgn, &vf->wpr_list, list) {
+ count++;
+ }
+ mutex_unlock(&vf->lock);
+ TEST_ASSERT_EQ(count, 1, "One WPR region remaining");
+
+ /* Remove non-existent */
+ ret = mock_vf_wpr_remove(vf, 0x30000000);
+ TEST_ASSERT_EQ(ret, -ENOENT, "WPR remove non-existent returns ENOENT");
+
+ mock_vf_destroy(vf);
+ return TEST_PASS;
+}
+
+static int test_mock_rpc_client(void)
+{
+ struct mock_rpc_client *rpc = mock_rpc_create();
+ TEST_ASSERT(rpc != NULL, "Mock RPC create");
+
+ struct gsp_rpc_response resp;
+ size_t resp_size = sizeof(resp);
+ struct gsp_alloc_payload payload = {
+ .size = cpu_to_le64(0x100000),
+ .type = cpu_to_le32(GSP_MEM_VRAM),
+ };
+
+ int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload, sizeof(payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "RPC send");
+
+ TEST_ASSERT_EQ(resp.status, 0, "RPC response status");
+ TEST_ASSERT_EQ(resp.rpc_type, GSP_RPC_ALLOC, "RPC response type");
+ TEST_ASSERT_EQ(resp.sequence, 1, "RPC response sequence");
+
+ /* Second call */
+ ret = mock_rpc_send(rpc, GSP_RPC_PIN, &payload, sizeof(payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "RPC send second");
+ TEST_ASSERT_EQ(resp.sequence, 2, "RPC response sequence 2");
+
+ mock_rpc_destroy(rpc);
+ return TEST_PASS;
+}
+
+static int test_driver_vf_creation_logic(void)
+{
+ /* Simulate driver VF creation logic */
+ struct mock_hw *hw = mock_hw_create();
+ struct mock_rpc_client *rpc = mock_rpc_create();
+ struct mock_vf *vfs[8] = {0};
+
+ /* Create VFs 1-4 */
+ for (int i = 1; i <= 4; i++) {
+ vfs[i] = mock_vf_create(i, 0x10000000 * i, 0x100000,
+ 0x20000000 * i, 0x100000,
+ 0x30000000 * i, 0x10000);
+ TEST_ASSERT(vfs[i] != NULL, "VF creation in loop");
+ TEST_ASSERT(vfs[i]->active, "VF active in loop");
+ }
+
+ /* Initialize BAR for each VF */
+ for (int i = 1; i <= 4; i++) {
+ vfs[i]->bar_state.aperture_offset = vfs[i]->bar1_offset;
+ vfs[i]->bar_state.aperture_size = vfs[i]->bar1_size;
+ TEST_ASSERT_EQ(vfs[i]->bar_state.aperture_offset, 0x10000000 * i, "VF BAR offset");
+ TEST_ASSERT_EQ(vfs[i]->bar_state.aperture_size, 0x100000, "VF BAR size");
+ }
+
+ /* Add WPR for each VF */
+ for (int i = 1; i <= 4; i++) {
+ int ret = mock_vf_wpr_add(vfs[i], vfs[i]->pt_base, vfs[i]->pt_size, 3);
+ TEST_ASSERT_EQ(ret, 0, "VF WPR add in loop");
+ }
+
+ /* Simulate RPC calls for each VF */
+ for (int i = 1; i <= 4; i++) {
+ struct gsp_rpc_response resp;
+ size_t resp_size = sizeof(resp);
+ struct gsp_alloc_payload payload = {
+ .size = cpu_to_le64(0x100000),
+ .type = cpu_to_le32(GSP_MEM_VRAM),
+ .vf_id = cpu_to_le32(i),
+ };
+ int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload, sizeof(payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF RPC alloc");
+ TEST_ASSERT_EQ(resp.status, 0, "VF RPC response OK");
+ }
+
+ /* Cleanup */
+ for (int i = 1; i <= 4; i++) {
+ mock_vf_destroy(vfs[i]);
+ }
+ mock_rpc_destroy(rpc);
+ mock_hw_destroy(hw);
+
+ return TEST_PASS;
+}
+
+static int test_migration_progress_tracking(void)
+{
+ struct {
+ u64 pages_transferred;
+ u64 bytes_transferred;
+ u64 dirty_pages;
+ u64 *page_list;
+ u32 page_count;
+ } progress = {0};
+
+ progress.page_count = 1000;
+ progress.page_list = kcalloc(progress.page_count, sizeof(u64), GFP_KERNEL);
+ TEST_ASSERT(progress.page_list != NULL, "Progress page list alloc");
+
+ /* Simulate transfer loop */
+ for (int iter = 0; iter < 10; iter++) {
+ progress.pages_transferred += 100;
+ progress.bytes_transferred += 100 * 4096;
+ progress.dirty_pages = 50 - (iter * 5);
+
+ TEST_ASSERT(progress.pages_transferred <= 1000, "Pages not exceeded");
+ TEST_ASSERT(progress.bytes_transferred <= 1000 * 4096, "Bytes not exceeded");
+ }
+
+ TEST_ASSERT_EQ(progress.pages_transferred, 1000, "Final pages");
+ TEST_ASSERT_EQ(progress.bytes_transferred, 1000 * 4096, "Final bytes");
+ TEST_ASSERT_EQ(progress.dirty_pages, 0, "Final dirty pages");
+
+ kfree(progress.page_list);
+ return TEST_PASS;
+}
+
+static int test_bar_state_machine(void)
+{
+ struct {
+ u64 aperture_offset;
+ u64 aperture_size;
+ bool force_access;
+ u32 force_vf;
+ } bar_state = {0};
+
+ /* Initial state */
+ bar_state.aperture_offset = 0x10000000;
+ bar_state.aperture_size = 0x100000;
+ TEST_ASSERT_EQ(bar_state.aperture_offset, 0x10000000, "Initial aperture offset");
+ TEST_ASSERT(!bar_state.force_access, "Force access initially false");
+
+ /* Enable force access */
+ bar_state.force_access = true;
+ bar_state.force_vf = 1;
+ TEST_ASSERT(bar_state.force_access, "Force access enabled");
+ TEST_ASSERT_EQ(bar_state.force_vf, 1, "Force VF set");
+
+ /* Disable force access */
+ bar_state.force_access = false;
+ bar_state.force_vf = 0;
+ TEST_ASSERT(!bar_state.force_access, "Force access disabled");
+ TEST_ASSERT_EQ(bar_state.force_vf, 0, "Force VF cleared");
+
+ return TEST_PASS;
+}
+
+static int test_concurrent_vf_operations(void)
+{
+ struct mock_vf *vf1 = mock_vf_create(1, 0x10000000, 0x100000, 0x20000000, 0x100000, 0x30000000, 0x10000);
+ struct mock_vf *vf2 = mock_vf_create(2, 0x20000000, 0x100000, 0x40000000, 0x100000, 0x60000000, 0x10000);
+ struct mock_rpc_client *rpc = mock_rpc_create();
+
+ TEST_ASSERT(vf1 && vf2 && rpc, "Create two VFs and RPC");
+
+ /* Simulate concurrent operations */
+ struct gsp_rpc_response resp;
+ size_t resp_size = sizeof(resp);
+
+ /* VF1 alloc */
+ struct gsp_alloc_payload payload1 = {
+ .size = cpu_to_le64(0x100000),
+ .type = cpu_to_le32(GSP_MEM_VRAM),
+ .vf_id = cpu_to_le32(1),
+ };
+ int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload1, sizeof(payload1), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF1 alloc");
+ TEST_ASSERT_EQ(resp.status, 0, "VF1 alloc response");
+
+ /* VF2 alloc */
+ struct gsp_alloc_payload payload2 = {
+ .size = cpu_to_le64(0x200000),
+ .type = cpu_to_le32(GSP_MEM_VRAM),
+ .vf_id = cpu_to_le32(2),
+ };
+ ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload2, sizeof(payload2), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF2 alloc");
+ TEST_ASSERT_EQ(resp.status, 0, "VF2 alloc response");
+
+ /* VF1 pin */
+ struct gsp_pin_payload pin_payload = {
+ .handle = cpu_to_le64(0x1000),
+ .vf_id = cpu_to_le32(1),
+ };
+ ret = mock_rpc_send(rpc, GSP_RPC_PIN, &pin_payload, sizeof(pin_payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF1 pin");
+
+ /* VF2 BAR trap */
+ struct gsp_bar_trap_payload bar_payload = {
+ .vf_id = cpu_to_le32(2),
+ .bar_index = cpu_to_le32(1),
+ };
+ ret = mock_rpc_send(rpc, GSP_RPC_BAR_TRAP, &bar_payload, sizeof(bar_payload), &resp, &resp_size);
+ TEST_ASSERT_EQ(ret, 0, "VF2 BAR trap");
+
+ /* Verify sequence numbers */
+ TEST_ASSERT_EQ(resp.sequence, 4, "Sequence number 4");
+
+ mock_vf_destroy(vf1);
+ mock_vf_destroy(vf2);
+ mock_rpc_destroy(rpc);
+
+ return TEST_PASS;
+}
+
+/* ============================================================
+ * Module Init/Exit
+ * ============================================================ */
+
+static int __init gsp_mock_test_init(void)
+{
+ pr_info("========================================\n");
+ pr_info("GSP vGPU-IO Mock Driver Test Suite\n");
+ pr_info("========================================\n");
+
+ test_mock_hardware();
+ test_mock_vf_lifecycle();
+ test_mock_vf_wpr();
+ test_mock_rpc_client();
+ test_driver_vf_creation_logic();
+ test_migration_progress_tracking();
+ test_bar_state_machine();
+ test_concurrent_vf_operations();
+
+ pr_info("========================================\n");
+ pr_info("Mock Test Results: %d total, %d passed, %d failed\n",
+ test_count, pass_count, fail_count);
+ pr_info("========================================\n");
+
+ if (fail_count > 0) {
+ pr_err("MOCK TEST SUITE FAILED\n");
+ return -EINVAL;
+ }
+
+ pr_info("MOCK TEST SUITE PASSED\n");
+ return 0;
+}
+
+static void __exit gsp_mock_test_exit(void)
+{
+ pr_info("GSP vGPU-IO Mock Test Suite Exited\n");
+}
+
+module_init(gsp_mock_test_init);
+module_exit(gsp_mock_test_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Open-IOV Project");
+MODULE_DESCRIPTION("GSP vGPU-IO Mock Driver Test");
+MODULE_VERSION("1.0"); \ No newline at end of file