// SPDX-License-Identifier: GPL-2.0 AND MIT /* * Copyright © 2022 Intel Corporation */ #include #include #include #include "tests/xe_kunit_helpers.h" #include "tests/xe_pci_test.h" #include "xe_pci.h" #include "xe_pm.h" static bool p2p_enabled(struct dma_buf_test_params *params) { return IS_ENABLED(CONFIG_PCI_P2PDMA) && params->attach_ops && params->attach_ops->allow_peer2peer; } static bool is_dynamic(struct dma_buf_test_params *params) { return IS_ENABLED(CONFIG_DMABUF_MOVE_NOTIFY) && params->attach_ops && params->attach_ops->move_notify; } static void check_residency(struct kunit *test, struct xe_bo *exported, struct xe_bo *imported, struct dma_buf *dmabuf) { struct dma_buf_test_params *params = to_dma_buf_test_params(test->priv); u32 mem_type; int ret; xe_bo_assert_held(exported); xe_bo_assert_held(imported); mem_type = XE_PL_VRAM0; if (!(params->mem_mask & XE_BO_FLAG_VRAM0)) /* No VRAM allowed */ mem_type = XE_PL_TT; else if (params->force_different_devices && !p2p_enabled(params)) /* No P2P */ mem_type = XE_PL_TT; else if (params->force_different_devices && !is_dynamic(params) && (params->mem_mask & XE_BO_FLAG_SYSTEM)) /* Pin migrated to TT */ mem_type = XE_PL_TT; if (!xe_bo_is_mem_type(exported, mem_type)) { KUNIT_FAIL(test, "Exported bo was not in expected memory type.\n"); return; } if (xe_bo_is_pinned(exported)) return; /* * Evict exporter. Note that the gem object dma_buf member isn't * set from xe_gem_prime_export(), and it's needed for the move_notify() * functionality, so hack that up here. Evicting the exported bo will * evict also the imported bo through the move_notify() functionality if * importer is on a different device. If they're on the same device, * the exporter and the importer should be the same bo. */ swap(exported->ttm.base.dma_buf, dmabuf); ret = xe_bo_evict(exported, true); swap(exported->ttm.base.dma_buf, dmabuf); if (ret) { if (ret != -EINTR && ret != -ERESTARTSYS) KUNIT_FAIL(test, "Evicting exporter failed with err=%d.\n", ret); return; } /* Verify that also importer has been evicted to SYSTEM */ if (exported != imported && !xe_bo_is_mem_type(imported, XE_PL_SYSTEM)) { KUNIT_FAIL(test, "Importer wasn't properly evicted.\n"); return; } /* Re-validate the importer. This should move also exporter in. */ ret = xe_bo_validate(imported, NULL, false); if (ret) { if (ret != -EINTR && ret != -ERESTARTSYS) KUNIT_FAIL(test, "Validating importer failed with err=%d.\n", ret); return; } /* * If on different devices, the exporter is kept in system if * possible, saving a migration step as the transfer is just * likely as fast from system memory. */ if (params->mem_mask & XE_BO_FLAG_SYSTEM) KUNIT_EXPECT_TRUE(test, xe_bo_is_mem_type(exported, XE_PL_TT)); else KUNIT_EXPECT_TRUE(test, xe_bo_is_mem_type(exported, mem_type)); if (params->force_different_devices) KUNIT_EXPECT_TRUE(test, xe_bo_is_mem_type(imported, XE_PL_TT)); else KUNIT_EXPECT_TRUE(test, exported == imported); } static void xe_test_dmabuf_import_same_driver(struct xe_device *xe) { struct kunit *test = kunit_get_current_test(); struct dma_buf_test_params *params = to_dma_buf_test_params(test->priv); struct drm_gem_object *import; struct dma_buf *dmabuf; struct xe_bo *bo; size_t size; /* No VRAM on this device? */ if (!ttm_manager_type(&xe->ttm, XE_PL_VRAM0) && (params->mem_mask & XE_BO_FLAG_VRAM0)) return; size = PAGE_SIZE; if ((params->mem_mask & XE_BO_FLAG_VRAM0) && xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K) size = SZ_64K; kunit_info(test, "running %s\n", __func__); bo = xe_bo_create_user(xe, NULL, NULL, size, DRM_XE_GEM_CPU_CACHING_WC, params->mem_mask); if (IS_ERR(bo)) { KUNIT_FAIL(test, "xe_bo_create() failed with err=%ld\n", PTR_ERR(bo)); return; } dmabuf = xe_gem_prime_export(&bo->ttm.base, 0); if (IS_ERR(dmabuf)) { KUNIT_FAIL(test, "xe_gem_prime_export() failed with err=%ld\n", PTR_ERR(dmabuf)); goto out; } import = xe_gem_prime_import(&xe->drm, dmabuf); if (!IS_ERR(import)) { struct xe_bo *import_bo = gem_to_xe_bo(import); /* * Did import succeed when it shouldn't due to lack of p2p support? */ if (params->force_different_devices && !p2p_enabled(params) && !(params->mem_mask & XE_BO_FLAG_SYSTEM)) { KUNIT_FAIL(test, "xe_gem_prime_import() succeeded when it shouldn't have\n"); } else { int err; /* Is everything where we expect it to be? */ xe_bo_lock(import_bo, false); err = xe_bo_validate(import_bo, NULL, false); /* Pinning in VRAM is not allowed. */ if (!is_dynamic(params) && params->force_different_devices && !(params->mem_mask & XE_BO_FLAG_SYSTEM)) KUNIT_EXPECT_EQ(test, err, -EINVAL); /* Otherwise only expect interrupts or success. */ else if (err && err != -EINTR && err != -ERESTARTSYS) KUNIT_EXPECT_TRUE(test, !err || err == -EINTR || err == -ERESTARTSYS); if (!err) check_residency(test, bo, import_bo, dmabuf); xe_bo_unlock(import_bo); } drm_gem_object_put(import); } else if (PTR_ERR(import) != -EOPNOTSUPP) { /* Unexpected error code. */ KUNIT_FAIL(test, "xe_gem_prime_import failed with the wrong err=%ld\n", PTR_ERR(import)); } else if (!params->force_different_devices || p2p_enabled(params) || (params->mem_mask & XE_BO_FLAG_SYSTEM)) { /* Shouldn't fail if we can reuse same bo, use p2p or use system */ KUNIT_FAIL(test, "dynamic p2p attachment failed with err=%ld\n", PTR_ERR(import)); } dma_buf_put(dmabuf); out: drm_gem_object_put(&bo->ttm.base); } static const struct dma_buf_attach_ops nop2p_attach_ops = { .allow_peer2peer = false, .move_notify = xe_dma_buf_move_notify }; /* * We test the implementation with bos of different residency and with * importers with different capabilities; some lacking p2p support and some * lacking dynamic capabilities (attach_ops == NULL). We also fake * different devices avoiding the import shortcut that just reuses the same * gem object. */ static const struct dma_buf_test_params test_params[] = { {.mem_mask = XE_BO_FLAG_VRAM0, .attach_ops = &xe_dma_buf_attach_ops}, {.mem_mask = XE_BO_FLAG_VRAM0, .attach_ops = &xe_dma_buf_attach_ops, .force_different_devices = true}, {.mem_mask = XE_BO_FLAG_VRAM0, .attach_ops = &nop2p_attach_ops}, {.mem_mask = XE_BO_FLAG_VRAM0, .attach_ops = &nop2p_attach_ops, .force_different_devices = true}, {.mem_mask = XE_BO_FLAG_VRAM0}, {.mem_mask = XE_BO_FLAG_VRAM0, .force_different_devices = true}, {.mem_mask = XE_BO_FLAG_SYSTEM, .attach_ops = &xe_dma_buf_attach_ops}, {.mem_mask = XE_BO_FLAG_SYSTEM, .attach_ops = &xe_dma_buf_attach_ops, .force_different_devices = true}, {.mem_mask = XE_BO_FLAG_SYSTEM, .attach_ops = &nop2p_attach_ops}, {.mem_mask = XE_BO_FLAG_SYSTEM, .attach_ops = &nop2p_attach_ops, .force_different_devices = true}, {.mem_mask = XE_BO_FLAG_SYSTEM}, {.mem_mask = XE_BO_FLAG_SYSTEM, .force_different_devices = true}, {.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0, .attach_ops = &xe_dma_buf_attach_ops}, {.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0, .attach_ops = &xe_dma_buf_attach_ops, .force_different_devices = true}, {.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0, .attach_ops = &nop2p_attach_ops}, {.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0, .attach_ops = &nop2p_attach_ops, .force_different_devices = true}, {.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0}, {.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0, .force_different_devices = true}, {} }; static int dma_buf_run_device(struct xe_device *xe) { const struct dma_buf_test_params *params; struct kunit *test = kunit_get_current_test(); xe_pm_runtime_get(xe); for (params = test_params; params->mem_mask; ++params) { struct dma_buf_test_params p = *params; p.base.id = XE_TEST_LIVE_DMA_BUF; test->priv = &p; xe_test_dmabuf_import_same_driver(xe); } xe_pm_runtime_put(xe); /* A non-zero return would halt iteration over driver devices */ return 0; } static void xe_dma_buf_kunit(struct kunit *test) { struct xe_device *xe = test->priv; dma_buf_run_device(xe); } static struct kunit_case xe_dma_buf_tests[] = { KUNIT_CASE_PARAM(xe_dma_buf_kunit, xe_pci_live_device_gen_param), {} }; VISIBLE_IF_KUNIT struct kunit_suite xe_dma_buf_test_suite = { .name = "xe_dma_buf", .test_cases = xe_dma_buf_tests, .init = xe_kunit_helper_xe_device_live_test_init, }; EXPORT_SYMBOL_IF_KUNIT(xe_dma_buf_test_suite);