Contributors: 7
Author Tokens Token Proportion Commits Commit Proportion
Matthew Brost 1114 89.69% 1 8.33%
Thomas Hellstrom 45 3.62% 3 25.00%
Lucas De Marchi 42 3.38% 4 33.33%
Mauro Carvalho Chehab 22 1.77% 1 8.33%
Rodrigo Vivi 13 1.05% 1 8.33%
Himal Prasad Ghimiray 3 0.24% 1 8.33%
Pallavi Mishra 3 0.24% 1 8.33%
Total 1242 12


// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
 * Copyright © 2022 Intel Corporation
 */

#include <drm/xe_drm.h>

#include <kunit/test.h>
#include <kunit/visibility.h>

#include "tests/xe_dma_buf_test.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 = xe_cur_kunit();
	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,
			       ttm_bo_type_device, 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 = xe_cur_kunit();

	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;
}

void xe_dma_buf_kunit(struct kunit *test)
{
	xe_call_for_each_device(dma_buf_run_device);
}
EXPORT_SYMBOL_IF_KUNIT(xe_dma_buf_kunit);