/* * GSP vGPU-IO Driver Mock Test * Tests driver logic with mocked hardware interfaces */ #include #include #include #include #include #include #include #include #include #include #include #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");