Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
# "live" kunit tests
obj-$(CONFIG_DRM_XE_KUNIT_TEST) += xe_live_test.o
xe_live_test-y = xe_live_test_mod.o
# Normal kunit tests
obj-$(CONFIG_DRM_XE_KUNIT_TEST) += xe_test.o
xe_test-y = xe_test_mod.o \
xe_args_test.o \
xe_pci_test.o \
xe_rtp_test.o \
xe_wa_test.o
@@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2024 Intel Corporation
*/
#include <kunit/test.h>
#include "xe_args.h"
static void call_args_example(struct kunit *test)
{
#define foo X, Y, Z, Q
#define bar COUNT_ARGS(foo)
#define buz CALL_ARGS(COUNT_ARGS, foo)
KUNIT_EXPECT_EQ(test, bar, 1);
KUNIT_EXPECT_EQ(test, buz, 4);
#undef foo
#undef bar
#undef buz
}
static void drop_first_arg_example(struct kunit *test)
{
#define foo X, Y, Z, Q
#define bar CALL_ARGS(COUNT_ARGS, DROP_FIRST_ARG(foo))
KUNIT_EXPECT_EQ(test, bar, 3);
#undef foo
#undef bar
}
static void first_arg_example(struct kunit *test)
{
int X = 1;
#define foo X, Y, Z, Q
#define bar FIRST_ARG(foo)
KUNIT_EXPECT_EQ(test, bar, X);
KUNIT_EXPECT_STREQ(test, __stringify(bar), "X");
#undef foo
#undef bar
}
static void last_arg_example(struct kunit *test)
{
int Q = 1;
#define foo X, Y, Z, Q
#define bar LAST_ARG(foo)
KUNIT_EXPECT_EQ(test, bar, Q);
KUNIT_EXPECT_STREQ(test, __stringify(bar), "Q");
#undef foo
#undef bar
}
static void pick_arg_example(struct kunit *test)
{
int Y = 1, Z = 2;
#define foo X, Y, Z, Q
#define bar PICK_ARG(2, foo)
#define buz PICK_ARG3(foo)
KUNIT_EXPECT_EQ(test, bar, Y);
KUNIT_EXPECT_STREQ(test, __stringify(bar), "Y");
KUNIT_EXPECT_EQ(test, buz, Z);
KUNIT_EXPECT_STREQ(test, __stringify(buz), "Z");
#undef foo
#undef bar
#undef buz
}
static void if_args_example(struct kunit *test)
{
enum { Z = 1, Q };
#define foo X, Y
#define bar IF_ARGS(Z, Q, foo)
#define buz IF_ARGS(Z, Q, DROP_FIRST_ARG(FIRST_ARG(foo)))
KUNIT_EXPECT_EQ(test, bar, Z);
KUNIT_EXPECT_EQ(test, buz, Q);
KUNIT_EXPECT_STREQ(test, __stringify(bar), "Z");
KUNIT_EXPECT_STREQ(test, __stringify(buz), "Q");
#undef foo
#undef bar
#undef buz
}
static void sep_comma_example(struct kunit *test)
{
#define foo(f) f(X) f(Y) f(Z) f(Q)
#define bar DROP_FIRST_ARG(foo(ARGS_SEP_COMMA __stringify))
#define buz CALL_ARGS(COUNT_ARGS, DROP_FIRST_ARG(foo(ARGS_SEP_COMMA)))
static const char * const a[] = { bar };
KUNIT_EXPECT_STREQ(test, a[0], "X");
KUNIT_EXPECT_STREQ(test, a[1], "Y");
KUNIT_EXPECT_STREQ(test, a[2], "Z");
KUNIT_EXPECT_STREQ(test, a[3], "Q");
KUNIT_EXPECT_EQ(test, buz, 4);
#undef foo
#undef bar
#undef buz
}
#define NO_ARGS
#define FOO_ARGS X, Y, Z, Q
#define MAX_ARGS -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12
static void count_args_test(struct kunit *test)
{
int count;
/* COUNT_ARGS() counts to 12 */
count = COUNT_ARGS();
KUNIT_EXPECT_EQ(test, count, 0);
count = COUNT_ARGS(1);
KUNIT_EXPECT_EQ(test, count, 1);
count = COUNT_ARGS(a, b, c, d, e);
KUNIT_EXPECT_EQ(test, count, 5);
count = COUNT_ARGS(a, b, c, d, e, f, g, h, i, j, k, l);
KUNIT_EXPECT_EQ(test, count, 12);
/* COUNT_ARGS() does not expand params */
count = COUNT_ARGS(NO_ARGS);
KUNIT_EXPECT_EQ(test, count, 1);
count = COUNT_ARGS(FOO_ARGS);
KUNIT_EXPECT_EQ(test, count, 1);
}
static void call_args_test(struct kunit *test)
{
int count;
count = CALL_ARGS(COUNT_ARGS, NO_ARGS);
KUNIT_EXPECT_EQ(test, count, 0);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, NO_ARGS), 0);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, FOO_ARGS), 4);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, FOO_ARGS, FOO_ARGS), 8);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, MAX_ARGS), 12);
}
static void drop_first_arg_test(struct kunit *test)
{
int Y = -2, Z = -3, Q = -4;
int a[] = { DROP_FIRST_ARG(FOO_ARGS) };
KUNIT_EXPECT_EQ(test, DROP_FIRST_ARG(0, -1), -1);
KUNIT_EXPECT_EQ(test, DROP_FIRST_ARG(DROP_FIRST_ARG(0, -1, -2)), -2);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, DROP_FIRST_ARG(FOO_ARGS)), 3);
KUNIT_EXPECT_EQ(test, DROP_FIRST_ARG(DROP_FIRST_ARG(DROP_FIRST_ARG(FOO_ARGS))), -4);
KUNIT_EXPECT_EQ(test, a[0], -2);
KUNIT_EXPECT_EQ(test, a[1], -3);
KUNIT_EXPECT_EQ(test, a[2], -4);
#define foo DROP_FIRST_ARG(FOO_ARGS)
#define bar DROP_FIRST_ARG(DROP_FIRST_ARG(FOO_ARGS))
#define buz DROP_FIRST_ARG(DROP_FIRST_ARG(DROP_FIRST_ARG(FOO_ARGS)))
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, foo), 3);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, bar), 2);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, buz), 1);
KUNIT_EXPECT_STREQ(test, __stringify(buz), "Q");
#undef foo
#undef bar
#undef buz
}
static void first_arg_test(struct kunit *test)
{
int X = -1;
int a[] = { FIRST_ARG(FOO_ARGS) };
KUNIT_EXPECT_EQ(test, FIRST_ARG(-1, -2), -1);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, FIRST_ARG(FOO_ARGS)), 1);
KUNIT_EXPECT_EQ(test, FIRST_ARG(FOO_ARGS), -1);
KUNIT_EXPECT_EQ(test, a[0], -1);
KUNIT_EXPECT_STREQ(test, __stringify(FIRST_ARG(FOO_ARGS)), "X");
}
static void last_arg_test(struct kunit *test)
{
int Q = -4;
int a[] = { LAST_ARG(FOO_ARGS) };
KUNIT_EXPECT_EQ(test, LAST_ARG(-1, -2), -2);
KUNIT_EXPECT_EQ(test, CALL_ARGS(COUNT_ARGS, LAST_ARG(FOO_ARGS)), 1);
KUNIT_EXPECT_EQ(test, LAST_ARG(FOO_ARGS), -4);
KUNIT_EXPECT_EQ(test, a[0], -4);
KUNIT_EXPECT_STREQ(test, __stringify(LAST_ARG(FOO_ARGS)), "Q");
KUNIT_EXPECT_EQ(test, LAST_ARG(MAX_ARGS), -12);
KUNIT_EXPECT_STREQ(test, __stringify(LAST_ARG(MAX_ARGS)), "-12");
}
static void if_args_test(struct kunit *test)
{
bool with_args = true;
bool no_args = false;
enum { X = 100 };
KUNIT_EXPECT_TRUE(test, IF_ARGS(true, false, FOO_ARGS));
KUNIT_EXPECT_FALSE(test, IF_ARGS(true, false, NO_ARGS));
KUNIT_EXPECT_TRUE(test, CONCATENATE(IF_ARGS(with, no, FOO_ARGS), _args));
KUNIT_EXPECT_FALSE(test, CONCATENATE(IF_ARGS(with, no, NO_ARGS), _args));
KUNIT_EXPECT_STREQ(test, __stringify(IF_ARGS(yes, no, FOO_ARGS)), "yes");
KUNIT_EXPECT_STREQ(test, __stringify(IF_ARGS(yes, no, NO_ARGS)), "no");
KUNIT_EXPECT_EQ(test, IF_ARGS(CALL_ARGS(COUNT_ARGS, FOO_ARGS), -1, FOO_ARGS), 4);
KUNIT_EXPECT_EQ(test, IF_ARGS(CALL_ARGS(COUNT_ARGS, FOO_ARGS), -1, NO_ARGS), -1);
KUNIT_EXPECT_EQ(test, IF_ARGS(CALL_ARGS(COUNT_ARGS, NO_ARGS), -1, FOO_ARGS), 0);
KUNIT_EXPECT_EQ(test, IF_ARGS(CALL_ARGS(COUNT_ARGS, NO_ARGS), -1, NO_ARGS), -1);
KUNIT_EXPECT_EQ(test,
CALL_ARGS(FIRST_ARG,
CALL_ARGS(CONCATENATE, IF_ARGS(FOO, MAX, FOO_ARGS), _ARGS)), X);
KUNIT_EXPECT_EQ(test,
CALL_ARGS(FIRST_ARG,
CALL_ARGS(CONCATENATE, IF_ARGS(FOO, MAX, NO_ARGS), _ARGS)), -1);
KUNIT_EXPECT_EQ(test,
CALL_ARGS(COUNT_ARGS,
CALL_ARGS(CONCATENATE, IF_ARGS(FOO, MAX, FOO_ARGS), _ARGS)), 4);
KUNIT_EXPECT_EQ(test,
CALL_ARGS(COUNT_ARGS,
CALL_ARGS(CONCATENATE, IF_ARGS(FOO, MAX, NO_ARGS), _ARGS)), 12);
}
static struct kunit_case args_tests[] = {
KUNIT_CASE(count_args_test),
KUNIT_CASE(call_args_example),
KUNIT_CASE(call_args_test),
KUNIT_CASE(drop_first_arg_example),
KUNIT_CASE(drop_first_arg_test),
KUNIT_CASE(first_arg_example),
KUNIT_CASE(first_arg_test),
KUNIT_CASE(last_arg_example),
KUNIT_CASE(last_arg_test),
KUNIT_CASE(pick_arg_example),
KUNIT_CASE(if_args_example),
KUNIT_CASE(if_args_test),
KUNIT_CASE(sep_comma_example),
{}
};
static struct kunit_suite args_test_suite = {
.name = "args",
.test_cases = args_tests,
};
kunit_test_suite(args_test_suite);
@@ -0,0 +1,632 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/visibility.h>
#include <linux/iosys-map.h>
#include <linux/math64.h>
#include <linux/prandom.h>
#include <linux/swap.h>
#include <uapi/linux/sysinfo.h>
#include "tests/xe_kunit_helpers.h"
#include "tests/xe_pci_test.h"
#include "tests/xe_test.h"
#include "xe_bo_evict.h"
#include "xe_gt.h"
#include "xe_pci.h"
#include "xe_pm.h"
static int ccs_test_migrate(struct xe_tile *tile, struct xe_bo *bo,
bool clear, u64 get_val, u64 assign_val,
struct kunit *test, struct drm_exec *exec)
{
struct dma_fence *fence;
struct ttm_tt *ttm;
struct page *page;
pgoff_t ccs_page;
long timeout;
u64 *cpu_map;
int ret;
u32 offset;
/* Move bo to VRAM if not already there. */
ret = xe_bo_validate(bo, NULL, false, exec);
if (ret) {
KUNIT_FAIL(test, "Failed to validate bo.\n");
return ret;
}
/* Optionally clear bo *and* CCS data in VRAM. */
if (clear) {
fence = xe_migrate_clear(tile->migrate, bo, bo->ttm.resource,
XE_MIGRATE_CLEAR_FLAG_FULL);
if (IS_ERR(fence)) {
KUNIT_FAIL(test, "Failed to submit bo clear.\n");
return PTR_ERR(fence);
}
if (dma_fence_wait_timeout(fence, false, 5 * HZ) <= 0) {
dma_fence_put(fence);
KUNIT_FAIL(test, "Timeout while clearing bo.\n");
return -ETIME;
}
dma_fence_put(fence);
}
/* Evict to system. CCS data should be copied. */
ret = xe_bo_evict(bo, exec);
if (ret) {
KUNIT_FAIL(test, "Failed to evict bo.\n");
return ret;
}
/* Sync all migration blits */
timeout = dma_resv_wait_timeout(bo->ttm.base.resv,
DMA_RESV_USAGE_KERNEL,
true,
5 * HZ);
if (timeout <= 0) {
KUNIT_FAIL(test, "Failed to sync bo eviction.\n");
return -ETIME;
}
/*
* Bo with CCS data is now in system memory. Verify backing store
* and data integrity. Then assign for the next testing round while
* we still have a CPU map.
*/
ttm = bo->ttm.ttm;
if (!ttm || !ttm_tt_is_populated(ttm)) {
KUNIT_FAIL(test, "Bo was not in expected placement.\n");
return -EINVAL;
}
ccs_page = xe_bo_ccs_pages_start(bo) >> PAGE_SHIFT;
if (ccs_page >= ttm->num_pages) {
KUNIT_FAIL(test, "No TTM CCS pages present.\n");
return -EINVAL;
}
page = ttm->pages[ccs_page];
cpu_map = kmap_local_page(page);
/* Check first CCS value */
if (cpu_map[0] != get_val) {
KUNIT_FAIL(test,
"Expected CCS readout 0x%016llx, got 0x%016llx.\n",
(unsigned long long)get_val,
(unsigned long long)cpu_map[0]);
ret = -EINVAL;
}
/* Check last CCS value, or at least last value in page. */
offset = xe_device_ccs_bytes(tile_to_xe(tile), xe_bo_size(bo));
offset = min_t(u32, offset, PAGE_SIZE) / sizeof(u64) - 1;
if (cpu_map[offset] != get_val) {
KUNIT_FAIL(test,
"Expected CCS readout 0x%016llx, got 0x%016llx.\n",
(unsigned long long)get_val,
(unsigned long long)cpu_map[offset]);
ret = -EINVAL;
}
cpu_map[0] = assign_val;
cpu_map[offset] = assign_val;
kunmap_local(cpu_map);
return ret;
}
static void ccs_test_run_tile(struct xe_device *xe, struct xe_tile *tile,
struct kunit *test)
{
struct xe_bo *bo;
int ret;
/* TODO: Sanity check */
unsigned int bo_flags = XE_BO_FLAG_VRAM_IF_DGFX(tile);
struct drm_exec *exec = XE_VALIDATION_OPT_OUT;
if (IS_DGFX(xe))
kunit_info(test, "Testing vram id %u\n", tile->id);
else
kunit_info(test, "Testing system memory\n");
bo = xe_bo_create_user(xe, NULL, SZ_1M, DRM_XE_GEM_CPU_CACHING_WC,
bo_flags, exec);
if (IS_ERR(bo)) {
KUNIT_FAIL(test, "Failed to create bo.\n");
return;
}
xe_bo_lock(bo, false);
kunit_info(test, "Verifying that CCS data is cleared on creation.\n");
ret = ccs_test_migrate(tile, bo, false, 0ULL, 0xdeadbeefdeadbeefULL,
test, exec);
if (ret)
goto out_unlock;
kunit_info(test, "Verifying that CCS data survives migration.\n");
ret = ccs_test_migrate(tile, bo, false, 0xdeadbeefdeadbeefULL,
0xdeadbeefdeadbeefULL, test, exec);
if (ret)
goto out_unlock;
kunit_info(test, "Verifying that CCS data can be properly cleared.\n");
ret = ccs_test_migrate(tile, bo, true, 0ULL, 0ULL, test, exec);
out_unlock:
xe_bo_unlock(bo);
xe_bo_put(bo);
}
static int ccs_test_run_device(struct xe_device *xe)
{
struct kunit *test = kunit_get_current_test();
struct xe_tile *tile;
int id;
if (!xe_device_has_flat_ccs(xe)) {
kunit_skip(test, "non-flat-ccs device\n");
return 0;
}
/* For xe2+ dgfx, we don't handle ccs metadata */
if (GRAPHICS_VER(xe) >= 20 && IS_DGFX(xe)) {
kunit_skip(test, "xe2+ dgfx device\n");
return 0;
}
guard(xe_pm_runtime)(xe);
for_each_tile(tile, xe, id) {
/* For igfx run only for primary tile */
if (!IS_DGFX(xe) && id > 0)
continue;
ccs_test_run_tile(xe, tile, test);
}
return 0;
}
static void xe_ccs_migrate_kunit(struct kunit *test)
{
struct xe_device *xe = test->priv;
ccs_test_run_device(xe);
}
static int evict_test_run_tile(struct xe_device *xe, struct xe_tile *tile, struct kunit *test)
{
struct xe_bo *bo, *external;
unsigned int bo_flags = XE_BO_FLAG_VRAM_IF_DGFX(tile);
struct xe_vm *vm = xe_migrate_get_vm(xe_device_get_root_tile(xe)->migrate);
struct drm_exec *exec = XE_VALIDATION_OPT_OUT;
struct xe_gt *__gt;
int err, i, id;
kunit_info(test, "Testing device %s vram id %u\n",
dev_name(xe->drm.dev), tile->id);
for (i = 0; i < 2; ++i) {
xe_vm_lock(vm, false);
bo = xe_bo_create_user(xe, vm, 0x10000,
DRM_XE_GEM_CPU_CACHING_WC,
bo_flags, exec);
xe_vm_unlock(vm);
if (IS_ERR(bo)) {
KUNIT_FAIL(test, "bo create err=%pe\n", bo);
break;
}
external = xe_bo_create_user(xe, NULL, 0x10000,
DRM_XE_GEM_CPU_CACHING_WC,
bo_flags, NULL);
if (IS_ERR(external)) {
KUNIT_FAIL(test, "external bo create err=%pe\n", external);
goto cleanup_bo;
}
xe_bo_lock(external, false);
err = xe_bo_pin_external(external, false, exec);
xe_bo_unlock(external);
if (err) {
KUNIT_FAIL(test, "external bo pin err=%pe\n",
ERR_PTR(err));
goto cleanup_external;
}
err = xe_bo_evict_all(xe);
if (err) {
KUNIT_FAIL(test, "evict err=%pe\n", ERR_PTR(err));
goto cleanup_all;
}
for_each_gt(__gt, xe, id)
xe_gt_sanitize(__gt);
err = xe_bo_restore_early(xe);
/*
* Snapshotting the CTB and copying back a potentially old
* version seems risky, depending on what might have been
* inflight. Also it seems snapshotting the ADS object and
* copying back results in serious breakage. Normally when
* calling xe_bo_restore_kernel() we always fully restart the
* GT, which re-intializes such things. We could potentially
* skip saving and restoring such objects in xe_bo_evict_all()
* however seems quite fragile not to also restart the GT. Try
* to do that here by triggering a GT reset.
*/
for_each_gt(__gt, xe, id)
xe_gt_reset(__gt);
if (err) {
KUNIT_FAIL(test, "restore kernel err=%pe\n",
ERR_PTR(err));
goto cleanup_all;
}
err = xe_bo_restore_late(xe);
if (err) {
KUNIT_FAIL(test, "restore user err=%pe\n", ERR_PTR(err));
goto cleanup_all;
}
if (!xe_bo_is_vram(external)) {
KUNIT_FAIL(test, "external bo is not vram\n");
err = -EPROTO;
goto cleanup_all;
}
if (xe_bo_is_vram(bo)) {
KUNIT_FAIL(test, "bo is vram\n");
err = -EPROTO;
goto cleanup_all;
}
if (i) {
down_read(&vm->lock);
xe_vm_lock(vm, false);
err = xe_bo_validate(bo, bo->vm, false, exec);
xe_vm_unlock(vm);
up_read(&vm->lock);
if (err) {
KUNIT_FAIL(test, "bo valid err=%pe\n",
ERR_PTR(err));
goto cleanup_all;
}
xe_bo_lock(external, false);
err = xe_bo_validate(external, NULL, false, exec);
xe_bo_unlock(external);
if (err) {
KUNIT_FAIL(test, "external bo valid err=%pe\n",
ERR_PTR(err));
goto cleanup_all;
}
}
xe_bo_lock(external, false);
xe_bo_unpin_external(external);
xe_bo_unlock(external);
xe_bo_put(external);
xe_bo_lock(bo, false);
__xe_bo_unset_bulk_move(bo);
xe_bo_unlock(bo);
xe_bo_put(bo);
continue;
cleanup_all:
xe_bo_lock(external, false);
xe_bo_unpin_external(external);
xe_bo_unlock(external);
cleanup_external:
xe_bo_put(external);
cleanup_bo:
xe_bo_lock(bo, false);
__xe_bo_unset_bulk_move(bo);
xe_bo_unlock(bo);
xe_bo_put(bo);
break;
}
xe_vm_put(vm);
return 0;
}
static int evict_test_run_device(struct xe_device *xe)
{
struct kunit *test = kunit_get_current_test();
struct xe_tile *tile;
int id;
if (!IS_DGFX(xe)) {
kunit_skip(test, "non-discrete device\n");
return 0;
}
guard(xe_pm_runtime)(xe);
for_each_tile(tile, xe, id)
evict_test_run_tile(xe, tile, test);
return 0;
}
static void xe_bo_evict_kunit(struct kunit *test)
{
struct xe_device *xe = test->priv;
evict_test_run_device(xe);
}
struct xe_bo_link {
struct list_head link;
struct xe_bo *bo;
u32 val;
};
#define XE_BO_SHRINK_SIZE ((unsigned long)SZ_64M)
static int shrink_test_fill_random(struct xe_bo *bo, struct rnd_state *state,
struct xe_bo_link *link)
{
struct iosys_map map;
int ret = ttm_bo_vmap(&bo->ttm, &map);
size_t __maybe_unused i;
if (ret)
return ret;
for (i = 0; i < bo->ttm.base.size; i += sizeof(u32)) {
u32 val = prandom_u32_state(state);
iosys_map_wr(&map, i, u32, val);
if (i == 0)
link->val = val;
}
ttm_bo_vunmap(&bo->ttm, &map);
return 0;
}
static bool shrink_test_verify(struct kunit *test, struct xe_bo *bo,
unsigned int bo_nr, struct rnd_state *state,
struct xe_bo_link *link)
{
struct iosys_map map;
int ret = ttm_bo_vmap(&bo->ttm, &map);
size_t i;
bool failed = false;
if (ret) {
KUNIT_FAIL(test, "Error mapping bo %u for content check.\n", bo_nr);
return true;
}
for (i = 0; i < bo->ttm.base.size; i += sizeof(u32)) {
u32 val = prandom_u32_state(state);
if (iosys_map_rd(&map, i, u32) != val) {
KUNIT_FAIL(test, "Content not preserved, bo %u offset 0x%016llx",
bo_nr, (unsigned long long)i);
kunit_info(test, "Failed value is 0x%08x, recorded 0x%08x\n",
(unsigned int)iosys_map_rd(&map, i, u32), val);
if (i == 0 && val != link->val)
kunit_info(test, "Looks like PRNG is out of sync.\n");
failed = true;
break;
}
}
ttm_bo_vunmap(&bo->ttm, &map);
return failed;
}
/*
* Try to create system bos corresponding to twice the amount
* of available system memory to test shrinker functionality.
* If no swap space is available to accommodate the
* memory overcommit, mark bos purgeable.
*/
static int shrink_test_run_device(struct xe_device *xe)
{
struct kunit *test = kunit_get_current_test();
LIST_HEAD(bos);
struct xe_bo_link *link, *next;
struct sysinfo si;
u64 ram, ram_and_swap, purgeable = 0, alloced, to_alloc, limit;
unsigned int interrupted = 0, successful = 0, count = 0;
struct rnd_state prng;
u64 rand_seed;
bool failed = false;
rand_seed = get_random_u64();
prandom_seed_state(&prng, rand_seed);
kunit_info(test, "Random seed is 0x%016llx.\n",
(unsigned long long)rand_seed);
/* Skip if execution time is expected to be too long. */
limit = SZ_32G;
/* IGFX with flat CCS needs to copy when swapping / shrinking */
if (!IS_DGFX(xe) && xe_device_has_flat_ccs(xe))
limit = SZ_16G;
si_meminfo(&si);
ram = (size_t)si.freeram * si.mem_unit;
if (ram > limit) {
kunit_skip(test, "Too long expected execution time.\n");
return 0;
}
to_alloc = ram * 2;
ram_and_swap = ram + get_nr_swap_pages() * PAGE_SIZE;
if (to_alloc > ram_and_swap)
purgeable = to_alloc - ram_and_swap;
purgeable += div64_u64(purgeable, 5);
kunit_info(test, "Free ram is %lu bytes. Will allocate twice of that.\n",
(unsigned long)ram);
for (alloced = 0; alloced < to_alloc; alloced += XE_BO_SHRINK_SIZE) {
struct xe_bo *bo;
unsigned int mem_type;
struct xe_ttm_tt *xe_tt;
link = kzalloc_obj(*link);
if (!link) {
KUNIT_FAIL(test, "Unexpected link allocation failure\n");
failed = true;
break;
}
INIT_LIST_HEAD(&link->link);
/* We can create bos using WC caching here. But it is slower. */
bo = xe_bo_create_user(xe, NULL, XE_BO_SHRINK_SIZE,
DRM_XE_GEM_CPU_CACHING_WB,
XE_BO_FLAG_SYSTEM, NULL);
if (IS_ERR(bo)) {
if (bo != ERR_PTR(-ENOMEM) && bo != ERR_PTR(-ENOSPC) &&
bo != ERR_PTR(-EINTR) && bo != ERR_PTR(-ERESTARTSYS))
KUNIT_FAIL(test, "Error creating bo: %pe\n", bo);
kfree(link);
failed = true;
break;
}
xe_bo_lock(bo, false);
xe_tt = container_of(bo->ttm.ttm, typeof(*xe_tt), ttm);
/*
* Allocate purgeable bos first, because if we do it the
* other way around, they may not be subject to swapping...
*/
if (alloced < purgeable) {
xe_ttm_tt_account_subtract(xe, &xe_tt->ttm);
xe_tt->purgeable = true;
xe_ttm_tt_account_add(xe, &xe_tt->ttm);
bo->ttm.priority = 0;
spin_lock(&bo->ttm.bdev->lru_lock);
ttm_bo_move_to_lru_tail(&bo->ttm);
spin_unlock(&bo->ttm.bdev->lru_lock);
} else {
int ret = shrink_test_fill_random(bo, &prng, link);
if (ret) {
xe_bo_unlock(bo);
xe_bo_put(bo);
KUNIT_FAIL(test, "Error filling bo with random data: %pe\n",
ERR_PTR(ret));
kfree(link);
failed = true;
break;
}
}
mem_type = bo->ttm.resource->mem_type;
xe_bo_unlock(bo);
link->bo = bo;
list_add_tail(&link->link, &bos);
if (mem_type != XE_PL_TT) {
KUNIT_FAIL(test, "Bo in incorrect memory type: %u\n",
bo->ttm.resource->mem_type);
failed = true;
}
cond_resched();
if (signal_pending(current))
break;
}
/*
* Read back and destroy bos. Reset the pseudo-random seed to get an
* identical pseudo-random number sequence for readback.
*/
prandom_seed_state(&prng, rand_seed);
list_for_each_entry_safe(link, next, &bos, link) {
static struct ttm_operation_ctx ctx = {.interruptible = true};
struct xe_bo *bo = link->bo;
struct xe_ttm_tt *xe_tt;
int ret;
count++;
if (!signal_pending(current) && !failed) {
bool purgeable, intr = false;
xe_bo_lock(bo, NULL);
/* xe_tt->purgeable is cleared on validate. */
xe_tt = container_of(bo->ttm.ttm, typeof(*xe_tt), ttm);
purgeable = xe_tt->purgeable;
do {
ret = ttm_bo_validate(&bo->ttm, &tt_placement, &ctx);
if (ret == -EINTR)
intr = true;
} while (ret == -EINTR && !signal_pending(current));
if (!ret && !purgeable)
failed = shrink_test_verify(test, bo, count, &prng, link);
xe_bo_unlock(bo);
if (ret) {
KUNIT_FAIL(test, "Validation failed: %pe\n",
ERR_PTR(ret));
failed = true;
} else if (intr) {
interrupted++;
} else {
successful++;
}
}
xe_bo_put(link->bo);
list_del(&link->link);
kfree(link);
}
kunit_info(test, "Readbacks interrupted: %u successful: %u\n",
interrupted, successful);
return 0;
}
static void xe_bo_shrink_kunit(struct kunit *test)
{
struct xe_device *xe = test->priv;
shrink_test_run_device(xe);
}
static struct kunit_case xe_bo_tests[] = {
KUNIT_CASE_PARAM(xe_ccs_migrate_kunit, xe_pci_live_device_gen_param),
KUNIT_CASE_PARAM(xe_bo_evict_kunit, xe_pci_live_device_gen_param),
{}
};
VISIBLE_IF_KUNIT
struct kunit_suite xe_bo_test_suite = {
.name = "xe_bo",
.test_cases = xe_bo_tests,
.init = xe_kunit_helper_xe_device_live_test_init,
};
EXPORT_SYMBOL_IF_KUNIT(xe_bo_test_suite);
static struct kunit_case xe_bo_shrink_test[] = {
KUNIT_CASE_PARAM_ATTR(xe_bo_shrink_kunit, xe_pci_live_device_gen_param,
{.speed = KUNIT_SPEED_SLOW}),
{}
};
VISIBLE_IF_KUNIT
struct kunit_suite xe_bo_shrink_test_suite = {
.name = "xe_bo_shrink",
.test_cases = xe_bo_shrink_test,
.init = xe_kunit_helper_xe_device_live_test_init,
};
EXPORT_SYMBOL_IF_KUNIT(xe_bo_shrink_test_suite);
@@ -0,0 +1,299 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include <uapi/drm/xe_drm.h>
#include <kunit/test.h>
#include <kunit/visibility.h>
#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 params->attach_ops && params->attach_ops->invalidate_mappings;
}
static void check_residency(struct kunit *test, struct xe_bo *exported,
struct xe_bo *imported, struct dma_buf *dmabuf,
struct drm_exec *exec)
{
struct dma_buf_test_params *params = to_dma_buf_test_params(test->priv);
struct dma_buf_attachment *attach;
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 on non-dynamic attachments. */
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. Evicting the exported bo will
* evict also the imported bo through the invalidate_mappings() 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.
*/
ret = xe_bo_evict(exported, exec);
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, exec);
if (ret) {
if (ret != -EINTR && ret != -ERESTARTSYS)
KUNIT_FAIL(test, "Validating importer failed with err=%d.\n",
ret);
return;
}
KUNIT_EXPECT_TRUE(test, xe_bo_is_mem_type(exported, mem_type));
/* Check that we can pin without migrating. */
attach = list_first_entry_or_null(&dmabuf->attachments, typeof(*attach), node);
if (attach) {
int err = dma_buf_pin(attach);
if (!err) {
KUNIT_EXPECT_TRUE(test, xe_bo_is_mem_type(exported, mem_type));
dma_buf_unpin(attach);
}
KUNIT_EXPECT_EQ(test, err, 0);
}
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, size, DRM_XE_GEM_CPU_CACHING_WC,
params->mem_mask, NULL);
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;
}
bo->ttm.base.dma_buf = dmabuf;
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 {
struct drm_exec *exec = XE_VALIDATION_OPT_OUT;
int err;
/* Is everything where we expect it to be? */
xe_bo_lock(import_bo, false);
err = xe_bo_validate(import_bo, NULL, false, exec);
/* Pinning in VRAM is not allowed for non-dynamic attachments */
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, exec);
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));
}
bo->ttm.base.dma_buf = NULL;
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,
.invalidate_mappings = 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 | XE_BO_FLAG_NEEDS_CPU_ACCESS,
.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 |
XE_BO_FLAG_NEEDS_CPU_ACCESS,
.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();
guard(xe_pm_runtime)(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);
}
/* 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);
@@ -0,0 +1,319 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2025 Intel Corporation
*/
#include <kunit/static_stub.h>
#include <kunit/test.h>
#include <kunit/test-bug.h>
#include "xe_kunit_helpers.h"
#include "xe_pci_test.h"
#define TEST_MAX_VFS 63
#define TEST_VRAM 0x7a800000ull /* random size that works on 32-bit */
static bool xe_device_is_admin_only_stub_enable(const struct xe_device *xe)
{
return true;
}
static bool xe_device_is_admin_only_stub_disable(const struct xe_device *xe)
{
return false;
}
static void pf_set_admin_mode(struct xe_device *xe, bool enable)
{
typeof(xe_device_is_admin_only) *stub = enable ?
xe_device_is_admin_only_stub_enable :
xe_device_is_admin_only_stub_disable;
kunit_activate_static_stub(kunit_get_current_test(),
xe_device_is_admin_only,
*stub);
KUNIT_EXPECT_EQ(kunit_get_current_test(), enable, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_EQ(kunit_get_current_test(), enable, xe_device_is_admin_only(xe));
}
static void pf_set_usable_vram(struct xe_device *xe, u64 usable)
{
struct xe_tile *tile = xe_device_get_root_tile(xe);
struct kunit *test = kunit_get_current_test();
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tile);
xe->mem.vram->usable_size = usable;
tile->mem.vram->usable_size = usable;
KUNIT_ASSERT_EQ(test, usable, xe_vram_region_usable_size(tile->mem.vram));
}
static const void *num_vfs_gen_param(struct kunit *test, const void *prev, char *desc)
{
unsigned long next = 1 + (unsigned long)prev;
if (next > TEST_MAX_VFS)
return NULL;
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%lu VF%s",
next, str_plural(next));
return (void *)next;
}
static int pf_gt_config_test_init(struct kunit *test)
{
struct xe_pci_fake_data fake = {
.sriov_mode = XE_SRIOV_MODE_PF,
.platform = XE_BATTLEMAGE, /* any random DGFX platform with SR-IOV */
.subplatform = XE_SUBPLATFORM_NONE,
.graphics_verx100 = 2001,
};
struct xe_vram_region *vram;
struct xe_device *xe;
struct xe_gt *gt;
test->priv = &fake;
xe_kunit_helper_xe_device_test_init(test);
xe = test->priv;
KUNIT_ASSERT_TRUE(test, IS_SRIOV_PF(xe));
gt = xe_root_mmio_gt(xe);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gt);
test->priv = gt;
/* pretend it has some VRAM */
KUNIT_ASSERT_TRUE(test, IS_DGFX(xe));
vram = kunit_kzalloc(test, sizeof(*vram), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vram);
vram->usable_size = TEST_VRAM;
xe->mem.vram = vram;
xe->tiles[0].mem.vram = vram;
/* pretend we have a valid LMTT */
KUNIT_ASSERT_TRUE(test, xe_device_has_lmtt(xe));
KUNIT_ASSERT_GE(test, GRAPHICS_VERx100(xe), 1260);
xe->tiles[0].sriov.pf.lmtt.ops = &lmtt_ml_ops;
/* pretend it can support up to 63 VFs */
xe->sriov.pf.device_total_vfs = TEST_MAX_VFS;
xe->sriov.pf.driver_max_vfs = TEST_MAX_VFS;
KUNIT_ASSERT_EQ(test, xe_sriov_pf_get_totalvfs(xe), 63);
pf_set_admin_mode(xe, false);
KUNIT_ASSERT_EQ(test, xe_sriov_init(xe), 0);
/* more sanity checks */
KUNIT_EXPECT_EQ(test, GUC_ID_MAX + 1, SZ_64K);
KUNIT_EXPECT_EQ(test, GUC_NUM_DOORBELLS, SZ_256);
return 0;
}
static void fair_contexts_1vf(struct kunit *test)
{
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
pf_set_admin_mode(xe, false);
KUNIT_ASSERT_FALSE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_EQ(test, SZ_32K, pf_profile_fair_ctxs(gt, 1));
pf_set_admin_mode(xe, true);
KUNIT_ASSERT_TRUE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_EQ(test, SZ_64K - SZ_1K, pf_profile_fair_ctxs(gt, 1));
}
static void fair_contexts(struct kunit *test)
{
unsigned int num_vfs = (unsigned long)test->param_value;
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
pf_set_admin_mode(xe, false);
KUNIT_ASSERT_FALSE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_TRUE(test, is_power_of_2(pf_profile_fair_ctxs(gt, num_vfs)));
KUNIT_EXPECT_GT(test, GUC_ID_MAX, num_vfs * pf_profile_fair_ctxs(gt, num_vfs));
if (num_vfs > 31)
KUNIT_ASSERT_EQ(test, SZ_1K, pf_profile_fair_ctxs(gt, num_vfs));
else if (num_vfs > 15)
KUNIT_ASSERT_EQ(test, SZ_2K, pf_profile_fair_ctxs(gt, num_vfs));
else if (num_vfs > 7)
KUNIT_ASSERT_EQ(test, SZ_4K, pf_profile_fair_ctxs(gt, num_vfs));
else if (num_vfs > 3)
KUNIT_ASSERT_EQ(test, SZ_8K, pf_profile_fair_ctxs(gt, num_vfs));
else if (num_vfs > 1)
KUNIT_ASSERT_EQ(test, SZ_16K, pf_profile_fair_ctxs(gt, num_vfs));
else
KUNIT_ASSERT_EQ(test, SZ_32K, pf_profile_fair_ctxs(gt, num_vfs));
}
static void fair_doorbells_1vf(struct kunit *test)
{
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
pf_set_admin_mode(xe, false);
KUNIT_ASSERT_FALSE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_EQ(test, 128, pf_profile_fair_dbs(gt, 1));
pf_set_admin_mode(xe, true);
KUNIT_ASSERT_TRUE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_EQ(test, 240, pf_profile_fair_dbs(gt, 1));
}
static void fair_doorbells(struct kunit *test)
{
unsigned int num_vfs = (unsigned long)test->param_value;
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
pf_set_admin_mode(xe, false);
KUNIT_ASSERT_FALSE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_TRUE(test, is_power_of_2(pf_profile_fair_dbs(gt, num_vfs)));
KUNIT_EXPECT_GE(test, GUC_NUM_DOORBELLS, (num_vfs + 1) * pf_profile_fair_dbs(gt, num_vfs));
if (num_vfs > 31)
KUNIT_ASSERT_EQ(test, SZ_4, pf_profile_fair_dbs(gt, num_vfs));
else if (num_vfs > 15)
KUNIT_ASSERT_EQ(test, SZ_8, pf_profile_fair_dbs(gt, num_vfs));
else if (num_vfs > 7)
KUNIT_ASSERT_EQ(test, SZ_16, pf_profile_fair_dbs(gt, num_vfs));
else if (num_vfs > 3)
KUNIT_ASSERT_EQ(test, SZ_32, pf_profile_fair_dbs(gt, num_vfs));
else if (num_vfs > 1)
KUNIT_ASSERT_EQ(test, SZ_64, pf_profile_fair_dbs(gt, num_vfs));
else
KUNIT_ASSERT_EQ(test, SZ_128, pf_profile_fair_dbs(gt, num_vfs));
}
static void fair_ggtt_1vf(struct kunit *test)
{
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
pf_set_admin_mode(xe, false);
KUNIT_ASSERT_FALSE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_EQ(test, SZ_2G, pf_profile_fair_ggtt(gt, 1));
pf_set_admin_mode(xe, true);
KUNIT_ASSERT_TRUE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_EQ(test, SZ_2G + SZ_1G + SZ_512M, pf_profile_fair_ggtt(gt, 1));
}
static void fair_ggtt(struct kunit *test)
{
unsigned int num_vfs = (unsigned long)test->param_value;
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
u64 alignment = pf_get_ggtt_alignment(gt);
u64 shareable = SZ_2G + SZ_1G + SZ_512M;
pf_set_admin_mode(xe, false);
KUNIT_ASSERT_FALSE(test, xe_sriov_pf_admin_only(xe));
KUNIT_EXPECT_TRUE(test, IS_ALIGNED(pf_profile_fair_ggtt(gt, num_vfs), alignment));
KUNIT_EXPECT_GE(test, shareable, num_vfs * pf_profile_fair_ggtt(gt, num_vfs));
if (num_vfs > 56)
KUNIT_ASSERT_EQ(test, SZ_64M - SZ_8M, pf_profile_fair_ggtt(gt, num_vfs));
else if (num_vfs > 28)
KUNIT_ASSERT_EQ(test, SZ_64M, pf_profile_fair_ggtt(gt, num_vfs));
else if (num_vfs > 14)
KUNIT_ASSERT_EQ(test, SZ_128M, pf_profile_fair_ggtt(gt, num_vfs));
else if (num_vfs > 7)
KUNIT_ASSERT_EQ(test, SZ_256M, pf_profile_fair_ggtt(gt, num_vfs));
else if (num_vfs > 3)
KUNIT_ASSERT_EQ(test, SZ_512M, pf_profile_fair_ggtt(gt, num_vfs));
else if (num_vfs > 1)
KUNIT_ASSERT_EQ(test, SZ_1G, pf_profile_fair_ggtt(gt, num_vfs));
else
KUNIT_ASSERT_EQ(test, SZ_2G, pf_profile_fair_ggtt(gt, num_vfs));
}
static const u64 vram_sizes[] = {
SZ_4G - SZ_512M,
SZ_8G + SZ_4G - SZ_512M,
SZ_16G - SZ_512M,
SZ_32G - SZ_512M,
SZ_64G - SZ_512M,
TEST_VRAM,
};
static void u64_param_get_desc(const u64 *p, char *desc)
{
string_get_size(*p, 1, STRING_UNITS_2, desc, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(vram_size, vram_sizes, u64_param_get_desc);
static void fair_vram_1vf(struct kunit *test)
{
const u64 usable = *(const u64 *)test->param_value;
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
pf_set_admin_mode(xe, false);
pf_set_usable_vram(xe, usable);
KUNIT_EXPECT_NE(test, 0, pf_profile_fair_lmem(gt, 1));
KUNIT_EXPECT_GE(test, usable, pf_profile_fair_lmem(gt, 1));
KUNIT_EXPECT_TRUE(test, is_power_of_2(pf_profile_fair_lmem(gt, 1)));
KUNIT_EXPECT_GE(test, usable - pf_profile_fair_lmem(gt, 1), pf_profile_fair_lmem(gt, 1));
}
static void fair_vram_1vf_admin_only(struct kunit *test)
{
const u64 usable = *(const u64 *)test->param_value;
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
pf_set_admin_mode(xe, true);
pf_set_usable_vram(xe, usable);
KUNIT_EXPECT_NE(test, 0, pf_profile_fair_lmem(gt, 1));
KUNIT_EXPECT_GE(test, usable, pf_profile_fair_lmem(gt, 1));
KUNIT_EXPECT_LT(test, usable - pf_profile_fair_lmem(gt, 1), pf_profile_fair_lmem(gt, 1));
KUNIT_EXPECT_TRUE(test, IS_ALIGNED(pf_profile_fair_lmem(gt, 1), SZ_1G));
}
static void fair_vram(struct kunit *test)
{
unsigned int num_vfs = (unsigned long)test->param_value;
struct xe_gt *gt = test->priv;
struct xe_device *xe = gt_to_xe(gt);
u64 alignment = pf_get_lmem_alignment(gt);
char size[10];
pf_set_admin_mode(xe, false);
string_get_size(pf_profile_fair_lmem(gt, num_vfs), 1, STRING_UNITS_2, size, sizeof(size));
kunit_info(test, "fair %s %llx\n", size, pf_profile_fair_lmem(gt, num_vfs));
KUNIT_EXPECT_TRUE(test, is_power_of_2(pf_profile_fair_lmem(gt, num_vfs)));
KUNIT_EXPECT_TRUE(test, IS_ALIGNED(pf_profile_fair_lmem(gt, num_vfs), alignment));
KUNIT_EXPECT_GE(test, TEST_VRAM, num_vfs * pf_profile_fair_lmem(gt, num_vfs));
}
static struct kunit_case pf_gt_config_test_cases[] = {
KUNIT_CASE(fair_contexts_1vf),
KUNIT_CASE(fair_doorbells_1vf),
KUNIT_CASE(fair_ggtt_1vf),
KUNIT_CASE_PARAM(fair_vram_1vf, vram_size_gen_params),
KUNIT_CASE_PARAM(fair_vram_1vf_admin_only, vram_size_gen_params),
KUNIT_CASE_PARAM(fair_contexts, num_vfs_gen_param),
KUNIT_CASE_PARAM(fair_doorbells, num_vfs_gen_param),
KUNIT_CASE_PARAM(fair_ggtt, num_vfs_gen_param),
KUNIT_CASE_PARAM(fair_vram, num_vfs_gen_param),
{}
};
static struct kunit_suite pf_gt_config_suite = {
.name = "pf_gt_config",
.test_cases = pf_gt_config_test_cases,
.init = pf_gt_config_test_init,
};
kunit_test_suite(pf_gt_config_suite);
@@ -0,0 +1,329 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2024 Intel Corporation
*/
#include <kunit/static_stub.h>
#include <kunit/test.h>
#include <kunit/test-bug.h>
#include "xe_device.h"
#include "xe_ggtt.h"
#include "xe_guc_ct.h"
#include "xe_kunit_helpers.h"
#include "xe_pci_test.h"
#define DUT_GGTT_START SZ_1M
#define DUT_GGTT_SIZE SZ_2M
static struct xe_bo *replacement_xe_managed_bo_create_pin_map(struct xe_device *xe,
struct xe_tile *tile,
size_t size, u32 flags)
{
struct kunit *test = kunit_get_current_test();
struct xe_bo *bo;
void *buf;
bo = drmm_kzalloc(&xe->drm, sizeof(*bo), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bo);
buf = drmm_kzalloc(&xe->drm, size, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
bo->tile = tile;
bo->ttm.bdev = &xe->ttm;
bo->ttm.base.size = size;
iosys_map_set_vaddr(&bo->vmap, buf);
if (flags & XE_BO_FLAG_GGTT) {
struct xe_ggtt *ggtt = tile->mem.ggtt;
bo->ggtt_node[tile->id] = xe_ggtt_insert_node(ggtt, xe_bo_size(bo), SZ_4K);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bo->ggtt_node[tile->id]);
}
return bo;
}
static int guc_buf_test_init(struct kunit *test)
{
struct xe_pci_fake_data fake = {
.sriov_mode = XE_SRIOV_MODE_PF,
.platform = XE_TIGERLAKE, /* some random platform */
.subplatform = XE_SUBPLATFORM_NONE,
};
struct xe_ggtt *ggtt;
struct xe_guc *guc;
test->priv = &fake;
xe_kunit_helper_xe_device_test_init(test);
ggtt = xe_device_get_root_tile(test->priv)->mem.ggtt;
guc = &xe_device_get_gt(test->priv, 0)->uc.guc;
KUNIT_ASSERT_EQ(test, 0,
xe_ggtt_init_kunit(ggtt, DUT_GGTT_START,
DUT_GGTT_SIZE));
kunit_activate_static_stub(test, xe_managed_bo_create_pin_map,
replacement_xe_managed_bo_create_pin_map);
KUNIT_ASSERT_EQ(test, 0, xe_guc_buf_cache_init(&guc->buf));
test->priv = &guc->buf;
return 0;
}
static void test_smallest(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf buf;
buf = xe_guc_buf_reserve(cache, 1);
KUNIT_ASSERT_TRUE(test, xe_guc_buf_is_valid(buf));
KUNIT_EXPECT_NOT_NULL(test, xe_guc_buf_cpu_ptr(buf));
KUNIT_EXPECT_NE(test, 0, xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_LE(test, DUT_GGTT_START, xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_GT(test, DUT_GGTT_START + DUT_GGTT_SIZE, xe_guc_buf_gpu_addr(buf));
xe_guc_buf_release(buf);
}
static void test_largest(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf buf;
buf = xe_guc_buf_reserve(cache, xe_guc_buf_cache_dwords(cache));
KUNIT_ASSERT_TRUE(test, xe_guc_buf_is_valid(buf));
KUNIT_EXPECT_NOT_NULL(test, xe_guc_buf_cpu_ptr(buf));
KUNIT_EXPECT_NE(test, 0, xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_LE(test, DUT_GGTT_START, xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_GT(test, DUT_GGTT_START + DUT_GGTT_SIZE, xe_guc_buf_gpu_addr(buf));
xe_guc_buf_release(buf);
}
static void test_granular(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf *bufs;
int n, dwords;
dwords = xe_guc_buf_cache_dwords(cache);
bufs = kunit_kcalloc(test, dwords, sizeof(*bufs), GFP_KERNEL);
KUNIT_EXPECT_NOT_NULL(test, bufs);
for (n = 0; n < dwords; n++)
bufs[n] = xe_guc_buf_reserve(cache, 1);
for (n = 0; n < dwords; n++)
KUNIT_EXPECT_TRUE_MSG(test, xe_guc_buf_is_valid(bufs[n]), "n=%d", n);
for (n = 0; n < dwords; n++)
xe_guc_buf_release(bufs[n]);
}
static void test_unique(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf *bufs;
int n, m, dwords;
dwords = xe_guc_buf_cache_dwords(cache);
bufs = kunit_kcalloc(test, dwords, sizeof(*bufs), GFP_KERNEL);
KUNIT_EXPECT_NOT_NULL(test, bufs);
for (n = 0; n < dwords; n++)
bufs[n] = xe_guc_buf_reserve(cache, 1);
for (n = 0; n < dwords; n++) {
for (m = n + 1; m < dwords; m++) {
KUNIT_EXPECT_PTR_NE_MSG(test, xe_guc_buf_cpu_ptr(bufs[n]),
xe_guc_buf_cpu_ptr(bufs[m]), "n=%d, m=%d", n, m);
KUNIT_ASSERT_NE_MSG(test, xe_guc_buf_gpu_addr(bufs[n]),
xe_guc_buf_gpu_addr(bufs[m]), "n=%d, m=%d", n, m);
}
}
for (n = 0; n < dwords; n++)
xe_guc_buf_release(bufs[n]);
}
static void test_overlap(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf b1, b2;
u32 dwords = xe_guc_buf_cache_dwords(cache) / 2;
u32 bytes = dwords * sizeof(u32);
void *p1, *p2;
u64 a1, a2;
b1 = xe_guc_buf_reserve(cache, dwords);
b2 = xe_guc_buf_reserve(cache, dwords);
p1 = xe_guc_buf_cpu_ptr(b1);
p2 = xe_guc_buf_cpu_ptr(b2);
a1 = xe_guc_buf_gpu_addr(b1);
a2 = xe_guc_buf_gpu_addr(b2);
KUNIT_EXPECT_PTR_NE(test, p1, p2);
if (p1 < p2)
KUNIT_EXPECT_LT(test, (uintptr_t)(p1 + bytes - 1), (uintptr_t)p2);
else
KUNIT_EXPECT_LT(test, (uintptr_t)(p2 + bytes - 1), (uintptr_t)p1);
KUNIT_EXPECT_NE(test, a1, a2);
if (a1 < a2)
KUNIT_EXPECT_LT(test, a1 + bytes - 1, a2);
else
KUNIT_EXPECT_LT(test, a2 + bytes - 1, a1);
xe_guc_buf_release(b1);
xe_guc_buf_release(b2);
}
static void test_reusable(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf b1, b2;
void *p1;
u64 a1;
b1 = xe_guc_buf_reserve(cache, xe_guc_buf_cache_dwords(cache));
KUNIT_ASSERT_TRUE(test, xe_guc_buf_is_valid(b1));
KUNIT_EXPECT_NOT_NULL(test, p1 = xe_guc_buf_cpu_ptr(b1));
KUNIT_EXPECT_NE(test, 0, a1 = xe_guc_buf_gpu_addr(b1));
xe_guc_buf_release(b1);
b2 = xe_guc_buf_reserve(cache, xe_guc_buf_cache_dwords(cache));
KUNIT_EXPECT_PTR_EQ(test, p1, xe_guc_buf_cpu_ptr(b2));
KUNIT_EXPECT_EQ(test, a1, xe_guc_buf_gpu_addr(b2));
xe_guc_buf_release(b2);
}
static void test_too_big(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf buf;
buf = xe_guc_buf_reserve(cache, xe_guc_buf_cache_dwords(cache) + 1);
KUNIT_EXPECT_FALSE(test, xe_guc_buf_is_valid(buf));
xe_guc_buf_release(buf); /* shouldn't crash */
}
static void test_flush(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf buf;
const u32 dwords = xe_guc_buf_cache_dwords(cache);
const u32 bytes = dwords * sizeof(u32);
u32 *s, *p, *d;
int n;
KUNIT_ASSERT_NOT_NULL(test, s = kunit_kcalloc(test, dwords, sizeof(u32), GFP_KERNEL));
KUNIT_ASSERT_NOT_NULL(test, d = kunit_kcalloc(test, dwords, sizeof(u32), GFP_KERNEL));
for (n = 0; n < dwords; n++)
s[n] = n;
buf = xe_guc_buf_reserve(cache, dwords);
KUNIT_ASSERT_TRUE(test, xe_guc_buf_is_valid(buf));
KUNIT_ASSERT_NOT_NULL(test, p = xe_guc_buf_cpu_ptr(buf));
KUNIT_EXPECT_PTR_NE(test, p, s);
KUNIT_EXPECT_PTR_NE(test, p, d);
memcpy(p, s, bytes);
KUNIT_EXPECT_NE(test, 0, xe_guc_buf_flush(buf));
iosys_map_memcpy_from(d, &cache->sam->bo->vmap, 0, bytes);
KUNIT_EXPECT_MEMEQ(test, s, d, bytes);
xe_guc_buf_release(buf);
}
static void test_lookup(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf buf;
u32 dwords;
u64 addr;
u32 *p;
int n;
dwords = xe_guc_buf_cache_dwords(cache);
buf = xe_guc_buf_reserve(cache, dwords);
KUNIT_ASSERT_TRUE(test, xe_guc_buf_is_valid(buf));
KUNIT_ASSERT_NOT_NULL(test, p = xe_guc_buf_cpu_ptr(buf));
KUNIT_ASSERT_NE(test, 0, addr = xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_EQ(test, 0, xe_guc_cache_gpu_addr_from_ptr(cache, p - 1, sizeof(u32)));
KUNIT_EXPECT_EQ(test, 0, xe_guc_cache_gpu_addr_from_ptr(cache, p + dwords, sizeof(u32)));
for (n = 0; n < dwords; n++)
KUNIT_EXPECT_EQ_MSG(test, xe_guc_cache_gpu_addr_from_ptr(cache, p + n, sizeof(u32)),
addr + n * sizeof(u32), "n=%d", n);
xe_guc_buf_release(buf);
}
static void test_data(struct kunit *test)
{
static const u32 data[] = { 1, 2, 3, 4, 5, 6 };
struct xe_guc_buf_cache *cache = test->priv;
struct xe_guc_buf buf;
void *p;
buf = xe_guc_buf_from_data(cache, data, sizeof(data));
KUNIT_ASSERT_TRUE(test, xe_guc_buf_is_valid(buf));
KUNIT_ASSERT_NOT_NULL(test, p = xe_guc_buf_cpu_ptr(buf));
KUNIT_EXPECT_MEMEQ(test, p, data, sizeof(data));
xe_guc_buf_release(buf);
}
static void test_class(struct kunit *test)
{
struct xe_guc_buf_cache *cache = test->priv;
u32 dwords = xe_guc_buf_cache_dwords(cache);
{
CLASS(xe_guc_buf, buf)(cache, dwords);
KUNIT_ASSERT_TRUE(test, xe_guc_buf_is_valid(buf));
KUNIT_EXPECT_NOT_NULL(test, xe_guc_buf_cpu_ptr(buf));
KUNIT_EXPECT_NE(test, 0, xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_LE(test, DUT_GGTT_START, xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_GT(test, DUT_GGTT_START + DUT_GGTT_SIZE, xe_guc_buf_gpu_addr(buf));
}
{
CLASS(xe_guc_buf, buf)(cache, dwords);
KUNIT_ASSERT_TRUE(test, xe_guc_buf_is_valid(buf));
KUNIT_EXPECT_NOT_NULL(test, xe_guc_buf_cpu_ptr(buf));
KUNIT_EXPECT_NE(test, 0, xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_LE(test, DUT_GGTT_START, xe_guc_buf_gpu_addr(buf));
KUNIT_EXPECT_GT(test, DUT_GGTT_START + DUT_GGTT_SIZE, xe_guc_buf_gpu_addr(buf));
}
}
static struct kunit_case guc_buf_test_cases[] = {
KUNIT_CASE(test_smallest),
KUNIT_CASE(test_largest),
KUNIT_CASE(test_granular),
KUNIT_CASE(test_unique),
KUNIT_CASE(test_overlap),
KUNIT_CASE(test_reusable),
KUNIT_CASE(test_too_big),
KUNIT_CASE(test_flush),
KUNIT_CASE(test_lookup),
KUNIT_CASE(test_data),
KUNIT_CASE(test_class),
{}
};
static struct kunit_suite guc_buf_suite = {
.name = "guc_buf",
.test_cases = guc_buf_test_cases,
.init = guc_buf_test_init,
};
kunit_test_suites(&guc_buf_suite);
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <kunit/test.h>
#include "xe_device.h"
#include "xe_kunit_helpers.h"
static int guc_dbm_test_init(struct kunit *test)
{
struct xe_guc_db_mgr *dbm;
xe_kunit_helper_xe_device_test_init(test);
dbm = &xe_device_get_gt(test->priv, 0)->uc.guc.dbm;
mutex_init(dbm_mutex(dbm));
test->priv = dbm;
return 0;
}
static void test_empty(struct kunit *test)
{
struct xe_guc_db_mgr *dbm = test->priv;
KUNIT_ASSERT_EQ(test, xe_guc_db_mgr_init(dbm, 0), 0);
KUNIT_ASSERT_EQ(test, dbm->count, 0);
mutex_lock(dbm_mutex(dbm));
KUNIT_EXPECT_LT(test, xe_guc_db_mgr_reserve_id_locked(dbm), 0);
mutex_unlock(dbm_mutex(dbm));
KUNIT_EXPECT_LT(test, xe_guc_db_mgr_reserve_range(dbm, 1, 0), 0);
}
static void test_default(struct kunit *test)
{
struct xe_guc_db_mgr *dbm = test->priv;
KUNIT_ASSERT_EQ(test, xe_guc_db_mgr_init(dbm, ~0), 0);
KUNIT_ASSERT_EQ(test, dbm->count, GUC_NUM_DOORBELLS);
}
static const unsigned int guc_dbm_params[] = {
GUC_NUM_DOORBELLS / 64,
GUC_NUM_DOORBELLS / 32,
GUC_NUM_DOORBELLS / 8,
GUC_NUM_DOORBELLS,
};
static void uint_param_get_desc(const unsigned int *p, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%u", *p);
}
KUNIT_ARRAY_PARAM(guc_dbm, guc_dbm_params, uint_param_get_desc);
static void test_size(struct kunit *test)
{
const unsigned int *p = test->param_value;
struct xe_guc_db_mgr *dbm = test->priv;
unsigned int n;
int id;
KUNIT_ASSERT_EQ(test, xe_guc_db_mgr_init(dbm, *p), 0);
KUNIT_ASSERT_EQ(test, dbm->count, *p);
mutex_lock(dbm_mutex(dbm));
for (n = 0; n < *p; n++) {
KUNIT_EXPECT_GE(test, id = xe_guc_db_mgr_reserve_id_locked(dbm), 0);
KUNIT_EXPECT_LT(test, id, dbm->count);
}
KUNIT_EXPECT_LT(test, xe_guc_db_mgr_reserve_id_locked(dbm), 0);
mutex_unlock(dbm_mutex(dbm));
mutex_lock(dbm_mutex(dbm));
for (n = 0; n < *p; n++)
xe_guc_db_mgr_release_id_locked(dbm, n);
mutex_unlock(dbm_mutex(dbm));
}
static void test_reuse(struct kunit *test)
{
const unsigned int *p = test->param_value;
struct xe_guc_db_mgr *dbm = test->priv;
unsigned int n;
KUNIT_ASSERT_EQ(test, xe_guc_db_mgr_init(dbm, *p), 0);
mutex_lock(dbm_mutex(dbm));
for (n = 0; n < *p; n++)
KUNIT_EXPECT_GE(test, xe_guc_db_mgr_reserve_id_locked(dbm), 0);
KUNIT_EXPECT_LT(test, xe_guc_db_mgr_reserve_id_locked(dbm), 0);
mutex_unlock(dbm_mutex(dbm));
mutex_lock(dbm_mutex(dbm));
for (n = 0; n < *p; n++) {
xe_guc_db_mgr_release_id_locked(dbm, n);
KUNIT_EXPECT_EQ(test, xe_guc_db_mgr_reserve_id_locked(dbm), n);
}
KUNIT_EXPECT_LT(test, xe_guc_db_mgr_reserve_id_locked(dbm), 0);
mutex_unlock(dbm_mutex(dbm));
mutex_lock(dbm_mutex(dbm));
for (n = 0; n < *p; n++)
xe_guc_db_mgr_release_id_locked(dbm, n);
mutex_unlock(dbm_mutex(dbm));
}
static void test_range_overlap(struct kunit *test)
{
const unsigned int *p = test->param_value;
struct xe_guc_db_mgr *dbm = test->priv;
int id1, id2, id3;
unsigned int n;
KUNIT_ASSERT_EQ(test, xe_guc_db_mgr_init(dbm, ~0), 0);
KUNIT_ASSERT_LE(test, *p, dbm->count);
KUNIT_ASSERT_GE(test, id1 = xe_guc_db_mgr_reserve_range(dbm, *p, 0), 0);
for (n = 0; n < dbm->count - *p; n++) {
KUNIT_ASSERT_GE(test, id2 = xe_guc_db_mgr_reserve_range(dbm, 1, 0), 0);
KUNIT_ASSERT_NE(test, id2, id1);
KUNIT_ASSERT_NE_MSG(test, id2 < id1, id2 > id1 + *p - 1,
"id1=%d id2=%d", id1, id2);
}
KUNIT_ASSERT_LT(test, xe_guc_db_mgr_reserve_range(dbm, 1, 0), 0);
xe_guc_db_mgr_release_range(dbm, 0, dbm->count);
if (*p >= 1) {
KUNIT_ASSERT_GE(test, id1 = xe_guc_db_mgr_reserve_range(dbm, 1, 0), 0);
KUNIT_ASSERT_GE(test, id2 = xe_guc_db_mgr_reserve_range(dbm, *p - 1, 0), 0);
KUNIT_ASSERT_NE(test, id2, id1);
KUNIT_ASSERT_NE_MSG(test, id1 < id2, id1 > id2 + *p - 2,
"id1=%d id2=%d", id1, id2);
for (n = 0; n < dbm->count - *p; n++) {
KUNIT_ASSERT_GE(test, id3 = xe_guc_db_mgr_reserve_range(dbm, 1, 0), 0);
KUNIT_ASSERT_NE(test, id3, id1);
KUNIT_ASSERT_NE(test, id3, id2);
KUNIT_ASSERT_NE_MSG(test, id3 < id2, id3 > id2 + *p - 2,
"id3=%d id2=%d", id3, id2);
}
KUNIT_ASSERT_LT(test, xe_guc_db_mgr_reserve_range(dbm, 1, 0), 0);
xe_guc_db_mgr_release_range(dbm, 0, dbm->count);
}
}
static void test_range_compact(struct kunit *test)
{
const unsigned int *p = test->param_value;
struct xe_guc_db_mgr *dbm = test->priv;
unsigned int n;
KUNIT_ASSERT_EQ(test, xe_guc_db_mgr_init(dbm, ~0), 0);
KUNIT_ASSERT_NE(test, *p, 0);
KUNIT_ASSERT_LE(test, *p, dbm->count);
if (dbm->count % *p)
kunit_skip(test, "must be divisible");
KUNIT_ASSERT_GE(test, xe_guc_db_mgr_reserve_range(dbm, *p, 0), 0);
for (n = 1; n < dbm->count / *p; n++)
KUNIT_ASSERT_GE(test, xe_guc_db_mgr_reserve_range(dbm, *p, 0), 0);
KUNIT_ASSERT_LT(test, xe_guc_db_mgr_reserve_range(dbm, 1, 0), 0);
xe_guc_db_mgr_release_range(dbm, 0, dbm->count);
}
static void test_range_spare(struct kunit *test)
{
const unsigned int *p = test->param_value;
struct xe_guc_db_mgr *dbm = test->priv;
int id;
KUNIT_ASSERT_EQ(test, xe_guc_db_mgr_init(dbm, ~0), 0);
KUNIT_ASSERT_LE(test, *p, dbm->count);
KUNIT_ASSERT_LT(test, xe_guc_db_mgr_reserve_range(dbm, *p, dbm->count), 0);
KUNIT_ASSERT_LT(test, xe_guc_db_mgr_reserve_range(dbm, *p, dbm->count - *p + 1), 0);
KUNIT_ASSERT_EQ(test, id = xe_guc_db_mgr_reserve_range(dbm, *p, dbm->count - *p), 0);
KUNIT_ASSERT_LT(test, xe_guc_db_mgr_reserve_range(dbm, 1, dbm->count - *p), 0);
xe_guc_db_mgr_release_range(dbm, id, *p);
}
static struct kunit_case guc_dbm_test_cases[] = {
KUNIT_CASE(test_empty),
KUNIT_CASE(test_default),
KUNIT_CASE_PARAM(test_size, guc_dbm_gen_params),
KUNIT_CASE_PARAM(test_reuse, guc_dbm_gen_params),
KUNIT_CASE_PARAM(test_range_overlap, guc_dbm_gen_params),
KUNIT_CASE_PARAM(test_range_compact, guc_dbm_gen_params),
KUNIT_CASE_PARAM(test_range_spare, guc_dbm_gen_params),
{}
};
static struct kunit_suite guc_dbm_suite = {
.name = "guc_dbm",
.test_cases = guc_dbm_test_cases,
.init = guc_dbm_test_init,
};
kunit_test_suites(&guc_dbm_suite);
@@ -0,0 +1,833 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2025 Intel Corporation
*/
#include <linux/delay.h>
#include <kunit/test.h>
#include <kunit/visibility.h>
#include "tests/xe_kunit_helpers.h"
#include "tests/xe_pci_test.h"
#include "tests/xe_test.h"
#include "xe_bo.h"
#include "xe_device.h"
#include "xe_pm.h"
/*
* There are different ways to allocate the G2G buffers. The plan for this test
* is to make sure that all the possible options work. The particular option
* chosen by the driver may vary from one platform to another, it may also change
* with time. So to ensure consistency of testing, the relevant driver code is
* replicated here to guarantee it won't change without the test being updated
* to keep testing the other options.
*
* In order to test the actual code being used by the driver, there is also the
* 'default' scheme. That will use the official driver routines to test whatever
* method the driver is using on the current platform at the current time.
*/
enum {
/* Driver defined allocation scheme */
G2G_CTB_TYPE_DEFAULT,
/* Single buffer in host memory */
G2G_CTB_TYPE_HOST,
/* Single buffer in a specific tile, loops across all tiles */
G2G_CTB_TYPE_TILE,
};
/*
* Payload is opaque to GuC. So KMD can define any structure or size it wants.
*/
struct g2g_test_payload {
u32 tx_dev;
u32 tx_tile;
u32 rx_dev;
u32 rx_tile;
u32 seqno;
};
static int slot_index_from_gts(struct xe_gt *tx_gt, struct xe_gt *rx_gt)
{
struct xe_device *xe = gt_to_xe(tx_gt);
int idx = 0, found = 0, id, tx_idx, rx_idx;
struct xe_gt *gt;
struct kunit *test = kunit_get_current_test();
for (id = 0; id < xe->info.tile_count * xe->info.max_gt_per_tile; id++) {
gt = xe_device_get_gt(xe, id);
if (!gt)
continue;
if (gt == tx_gt) {
tx_idx = idx;
found++;
}
if (gt == rx_gt) {
rx_idx = idx;
found++;
}
if (found == 2)
break;
idx++;
}
if (found != 2)
KUNIT_FAIL(test, "GT index not found");
return (tx_idx * xe->info.gt_count) + rx_idx;
}
static void g2g_test_send(struct kunit *test, struct xe_guc *guc,
u32 far_tile, u32 far_dev,
struct g2g_test_payload *payload)
{
struct xe_device *xe = guc_to_xe(guc);
struct xe_gt *gt = guc_to_gt(guc);
u32 *action, total;
size_t payload_len;
int ret;
static_assert(IS_ALIGNED(sizeof(*payload), sizeof(u32)));
payload_len = sizeof(*payload) / sizeof(u32);
total = 4 + payload_len;
action = kunit_kmalloc_array(test, total, sizeof(*action), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, action);
action[0] = XE_GUC_ACTION_TEST_G2G_SEND;
action[1] = far_tile;
action[2] = far_dev;
action[3] = payload_len;
memcpy(action + 4, payload, payload_len * sizeof(u32));
atomic_inc(&xe->g2g_test_count);
/*
* Should specify the expected response notification here. Problem is that
* the response will be coming from a different GuC. By the end, it should
* all add up as long as an equal number of messages are sent from each GuC
* and to each GuC. However, in the middle negative reservation space errors
* and such like can occur. Rather than add intrusive changes to the CT layer
* it is simpler to just not bother counting it at all. The system should be
* idle when running the selftest, and the selftest's notification total size
* is well within the G2H allocation size. So there should be no issues with
* needing to block for space, which is all the tracking code is really for.
*/
ret = xe_guc_ct_send(&guc->ct, action, total, 0, 0);
kunit_kfree(test, action);
KUNIT_ASSERT_EQ_MSG(test, 0, ret, "G2G send failed: %d [%d:%d -> %d:%d]\n", ret,
gt_to_tile(gt)->id, G2G_DEV(gt), far_tile, far_dev);
}
/*
* NB: Can't use KUNIT_ASSERT and friends in here as this is called asynchronously
* from the G2H notification handler. Need that to actually complete rather than
* thread-abort in order to keep the rest of the driver alive!
*/
int xe_guc_g2g_test_notification(struct xe_guc *guc, u32 *msg, u32 len)
{
struct xe_device *xe = guc_to_xe(guc);
struct xe_gt *rx_gt = guc_to_gt(guc), *test_gt, *tx_gt = NULL;
u32 tx_tile, tx_dev, rx_tile, rx_dev, idx, got_len;
struct g2g_test_payload *payload;
size_t payload_len;
int ret = 0, i;
payload_len = sizeof(*payload) / sizeof(u32);
if (unlikely(len != (G2H_LEN_DW_G2G_NOTIFY_MIN + payload_len))) {
xe_gt_err(rx_gt, "G2G test notification invalid length %u", len);
ret = -EPROTO;
goto done;
}
tx_tile = msg[0];
tx_dev = msg[1];
got_len = msg[2];
payload = (struct g2g_test_payload *)(msg + 3);
rx_tile = gt_to_tile(rx_gt)->id;
rx_dev = G2G_DEV(rx_gt);
if (got_len != payload_len) {
xe_gt_err(rx_gt, "G2G: Invalid payload length: %u vs %zu\n", got_len, payload_len);
ret = -EPROTO;
goto done;
}
if (payload->tx_dev != tx_dev || payload->tx_tile != tx_tile ||
payload->rx_dev != rx_dev || payload->rx_tile != rx_tile) {
xe_gt_err(rx_gt, "G2G: Invalid payload: %d:%d -> %d:%d vs %d:%d -> %d:%d! [%d]\n",
payload->tx_tile, payload->tx_dev, payload->rx_tile, payload->rx_dev,
tx_tile, tx_dev, rx_tile, rx_dev, payload->seqno);
ret = -EPROTO;
goto done;
}
if (!xe->g2g_test_array) {
xe_gt_err(rx_gt, "G2G: Missing test array!\n");
ret = -ENOMEM;
goto done;
}
for_each_gt(test_gt, xe, i) {
if (gt_to_tile(test_gt)->id != tx_tile)
continue;
if (G2G_DEV(test_gt) != tx_dev)
continue;
if (tx_gt) {
xe_gt_err(rx_gt, "G2G: Got duplicate TX GTs: %d vs %d for %d:%d!\n",
tx_gt->info.id, test_gt->info.id, tx_tile, tx_dev);
ret = -EINVAL;
goto done;
}
tx_gt = test_gt;
}
if (!tx_gt) {
xe_gt_err(rx_gt, "G2G: Failed to find a TX GT for %d:%d!\n", tx_tile, tx_dev);
ret = -EINVAL;
goto done;
}
idx = slot_index_from_gts(tx_gt, rx_gt);
if (xe->g2g_test_array[idx] != payload->seqno - 1) {
xe_gt_err(rx_gt, "G2G: Seqno mismatch %d vs %d for %d:%d -> %d:%d!\n",
xe->g2g_test_array[idx], payload->seqno - 1,
tx_tile, tx_dev, rx_tile, rx_dev);
ret = -EINVAL;
goto done;
}
xe->g2g_test_array[idx] = payload->seqno;
done:
atomic_dec(&xe->g2g_test_count);
return ret;
}
#define G2G_WAIT_TIMEOUT_MS 100
#define G2G_WAIT_POLL_MS 1
/*
* Send the given seqno from all GuCs to all other GuCs in tile/GT order
*/
static void g2g_test_in_order(struct kunit *test, struct xe_device *xe, u32 seqno)
{
struct xe_gt *near_gt, *far_gt;
int i, j, waited;
u32 idx;
for_each_gt(near_gt, xe, i) {
u32 near_tile = gt_to_tile(near_gt)->id;
u32 near_dev = G2G_DEV(near_gt);
for_each_gt(far_gt, xe, j) {
u32 far_tile = gt_to_tile(far_gt)->id;
u32 far_dev = G2G_DEV(far_gt);
struct g2g_test_payload payload;
if (far_gt->info.id == near_gt->info.id)
continue;
payload.tx_dev = near_dev;
payload.tx_tile = near_tile;
payload.rx_dev = far_dev;
payload.rx_tile = far_tile;
payload.seqno = seqno;
/* Calculate idx for event-based wait */
idx = slot_index_from_gts(near_gt, far_gt);
waited = 0;
/*
* Wait for previous seqno to be acknowledged before sending,
* to avoid queuing too many back-to-back messages and
* causing a test timeout. Actual correctness of message
* will be checked later in xe_guc_g2g_test_notification()
*/
while (xe->g2g_test_array[idx] != (seqno - 1)) {
msleep(G2G_WAIT_POLL_MS);
waited += G2G_WAIT_POLL_MS;
if (waited >= G2G_WAIT_TIMEOUT_MS) {
kunit_info(test, "Timeout waiting! tx gt: %d, rx gt: %d\n",
near_gt->info.id, far_gt->info.id);
break;
}
}
g2g_test_send(test, &near_gt->uc.guc, far_tile, far_dev, &payload);
}
}
}
#define WAIT_TIME_MS 100
#define WAIT_COUNT (1000 / WAIT_TIME_MS)
static void g2g_wait_for_complete(void *_xe)
{
struct xe_device *xe = (struct xe_device *)_xe;
struct kunit *test = kunit_get_current_test();
int wait = 0;
/* Wait for all G2H messages to be received */
while (atomic_read(&xe->g2g_test_count)) {
if (++wait > WAIT_COUNT)
break;
msleep(WAIT_TIME_MS);
}
KUNIT_ASSERT_EQ_MSG(test, 0, atomic_read(&xe->g2g_test_count),
"Timed out waiting for notifications\n");
kunit_info(test, "Got all notifications back\n");
}
#undef WAIT_TIME_MS
#undef WAIT_COUNT
static void g2g_clean_array(void *_xe)
{
struct xe_device *xe = (struct xe_device *)_xe;
xe->g2g_test_array = NULL;
}
#define NUM_LOOPS 16
static void g2g_run_test(struct kunit *test, struct xe_device *xe)
{
u32 seqno, max_array;
int ret, i, j;
max_array = xe->info.gt_count * xe->info.gt_count;
xe->g2g_test_array = kunit_kcalloc(test, max_array, sizeof(u32), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe->g2g_test_array);
ret = kunit_add_action_or_reset(test, g2g_clean_array, xe);
KUNIT_ASSERT_EQ_MSG(test, 0, ret, "Failed to register clean up action\n");
/*
* Send incrementing seqnos from all GuCs to all other GuCs in tile/GT order.
* Tile/GT order doesn't really mean anything to the hardware but it is going
* to be a fixed sequence every time.
*
* Verify that each one comes back having taken the correct route.
*/
ret = kunit_add_action(test, g2g_wait_for_complete, xe);
KUNIT_ASSERT_EQ_MSG(test, 0, ret, "Failed to register clean up action\n");
for (seqno = 1; seqno < NUM_LOOPS; seqno++)
g2g_test_in_order(test, xe, seqno);
seqno--;
kunit_release_action(test, &g2g_wait_for_complete, xe);
/* Check for the final seqno in each slot */
for (i = 0; i < xe->info.gt_count; i++) {
for (j = 0; j < xe->info.gt_count; j++) {
u32 idx = (j * xe->info.gt_count) + i;
if (i == j)
KUNIT_ASSERT_EQ_MSG(test, 0, xe->g2g_test_array[idx],
"identity seqno modified: %d for %dx%d!\n",
xe->g2g_test_array[idx], i, j);
else
KUNIT_ASSERT_EQ_MSG(test, seqno, xe->g2g_test_array[idx],
"invalid seqno: %d vs %d for %dx%d!\n",
xe->g2g_test_array[idx], seqno, i, j);
}
}
kunit_kfree(test, xe->g2g_test_array);
kunit_release_action(test, &g2g_clean_array, xe);
kunit_info(test, "Test passed\n");
}
#undef NUM_LOOPS
static void g2g_ct_stop(struct xe_guc *guc)
{
struct xe_gt *remote_gt, *gt = guc_to_gt(guc);
struct xe_device *xe = gt_to_xe(gt);
int i, t;
for_each_gt(remote_gt, xe, i) {
u32 tile, dev;
if (remote_gt->info.id == gt->info.id)
continue;
tile = gt_to_tile(remote_gt)->id;
dev = G2G_DEV(remote_gt);
for (t = 0; t < XE_G2G_TYPE_LIMIT; t++)
guc_g2g_deregister(guc, tile, dev, t);
}
}
/* Size of a single allocation that contains all G2G CTBs across all GTs */
static u32 g2g_ctb_size(struct kunit *test, struct xe_device *xe)
{
unsigned int count = xe->info.gt_count;
u32 num_channels = (count * (count - 1)) / 2;
kunit_info(test, "Size: (%d * %d / 2) * %d * 0x%08X + 0x%08X => 0x%08X [%d]\n",
count, count - 1, XE_G2G_TYPE_LIMIT, G2G_BUFFER_SIZE, G2G_DESC_AREA_SIZE,
num_channels * XE_G2G_TYPE_LIMIT * G2G_BUFFER_SIZE + G2G_DESC_AREA_SIZE,
num_channels * XE_G2G_TYPE_LIMIT);
return num_channels * XE_G2G_TYPE_LIMIT * G2G_BUFFER_SIZE + G2G_DESC_AREA_SIZE;
}
/*
* Use the driver's regular CTB allocation scheme.
*/
static void g2g_alloc_default(struct kunit *test, struct xe_device *xe)
{
struct xe_gt *gt;
int i;
kunit_info(test, "Default [tiles = %d, GTs = %d]\n",
xe->info.tile_count, xe->info.gt_count);
for_each_gt(gt, xe, i) {
struct xe_guc *guc = &gt->uc.guc;
int ret;
ret = guc_g2g_alloc(guc);
KUNIT_ASSERT_EQ_MSG(test, 0, ret, "G2G alloc failed: %pe", ERR_PTR(ret));
continue;
}
}
static void g2g_distribute(struct kunit *test, struct xe_device *xe, struct xe_bo *bo)
{
struct xe_gt *root_gt, *gt;
int i;
root_gt = xe_device_get_gt(xe, 0);
root_gt->uc.guc.g2g.bo = bo;
root_gt->uc.guc.g2g.owned = true;
kunit_info(test, "[%d.%d] Assigned 0x%p\n", gt_to_tile(root_gt)->id, root_gt->info.id, bo);
for_each_gt(gt, xe, i) {
if (gt->info.id != 0) {
gt->uc.guc.g2g.owned = false;
gt->uc.guc.g2g.bo = xe_bo_get(bo);
kunit_info(test, "[%d.%d] Pinned 0x%p\n",
gt_to_tile(gt)->id, gt->info.id, gt->uc.guc.g2g.bo);
}
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gt->uc.guc.g2g.bo);
}
}
/*
* Allocate a single blob on the host and split between all G2G CTBs.
*/
static void g2g_alloc_host(struct kunit *test, struct xe_device *xe)
{
struct xe_bo *bo;
u32 g2g_size;
kunit_info(test, "Host [tiles = %d, GTs = %d]\n", xe->info.tile_count, xe->info.gt_count);
g2g_size = g2g_ctb_size(test, xe);
bo = xe_managed_bo_create_pin_map(xe, xe_device_get_root_tile(xe), g2g_size,
XE_BO_FLAG_SYSTEM |
XE_BO_FLAG_GGTT |
XE_BO_FLAG_GGTT_ALL |
XE_BO_FLAG_GGTT_INVALIDATE);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bo);
kunit_info(test, "[HST] G2G buffer create: 0x%p\n", bo);
xe_map_memset(xe, &bo->vmap, 0, 0, g2g_size);
g2g_distribute(test, xe, bo);
}
/*
* Allocate a single blob on the given tile and split between all G2G CTBs.
*/
static void g2g_alloc_tile(struct kunit *test, struct xe_device *xe, struct xe_tile *tile)
{
struct xe_bo *bo;
u32 g2g_size;
KUNIT_ASSERT_TRUE(test, IS_DGFX(xe));
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tile);
kunit_info(test, "Tile %d [tiles = %d, GTs = %d]\n",
tile->id, xe->info.tile_count, xe->info.gt_count);
g2g_size = g2g_ctb_size(test, xe);
bo = xe_managed_bo_create_pin_map(xe, tile, g2g_size,
XE_BO_FLAG_VRAM_IF_DGFX(tile) |
XE_BO_FLAG_GGTT |
XE_BO_FLAG_GGTT_ALL |
XE_BO_FLAG_GGTT_INVALIDATE);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bo);
kunit_info(test, "[%d.*] G2G buffer create: 0x%p\n", tile->id, bo);
xe_map_memset(xe, &bo->vmap, 0, 0, g2g_size);
g2g_distribute(test, xe, bo);
}
static void g2g_free(struct kunit *test, struct xe_device *xe)
{
struct xe_gt *gt;
struct xe_bo *bo;
int i;
for_each_gt(gt, xe, i) {
bo = gt->uc.guc.g2g.bo;
if (!bo)
continue;
if (gt->uc.guc.g2g.owned) {
xe_managed_bo_unpin_map_no_vm(bo);
kunit_info(test, "[%d.%d] Unmapped 0x%p\n",
gt_to_tile(gt)->id, gt->info.id, bo);
} else {
xe_bo_put(bo);
kunit_info(test, "[%d.%d] Unpinned 0x%p\n",
gt_to_tile(gt)->id, gt->info.id, bo);
}
gt->uc.guc.g2g.bo = NULL;
}
}
static void g2g_stop(struct kunit *test, struct xe_device *xe)
{
struct xe_gt *gt;
int i;
for_each_gt(gt, xe, i) {
struct xe_guc *guc = &gt->uc.guc;
if (!guc->g2g.bo)
continue;
g2g_ct_stop(guc);
}
g2g_free(test, xe);
}
/*
* Generate a unique id for each bi-directional CTB for each pair of
* near and far tiles/devices. The id can then be used as an index into
* a single allocation that is sub-divided into multiple CTBs.
*
* For example, with two devices per tile and two tiles, the table should
* look like:
* Far <tile>.<dev>
* 0.0 0.1 1.0 1.1
* N 0.0 --/-- 00/01 02/03 04/05
* e 0.1 01/00 --/-- 06/07 08/09
* a 1.0 03/02 07/06 --/-- 10/11
* r 1.1 05/04 09/08 11/10 --/--
*
* Where each entry is Rx/Tx channel id.
*
* So GuC #3 (tile 1, dev 1) talking to GuC #2 (tile 1, dev 0) would
* be reading from channel #11 and writing to channel #10. Whereas,
* GuC #2 talking to GuC #3 would be read on #10 and write to #11.
*/
static int g2g_slot_flat(u32 near_tile, u32 near_dev, u32 far_tile, u32 far_dev,
u32 type, u32 max_inst, bool have_dev)
{
u32 near = near_tile, far = far_tile;
u32 idx = 0, x, y, direction;
int i;
if (have_dev) {
near = (near << 1) | near_dev;
far = (far << 1) | far_dev;
}
/* No need to send to one's self */
if (far == near)
return -1;
if (far > near) {
/* Top right table half */
x = far;
y = near;
/* T/R is 'forwards' direction */
direction = type;
} else {
/* Bottom left table half */
x = near;
y = far;
/* B/L is 'backwards' direction */
direction = (1 - type);
}
/* Count the rows prior to the target */
for (i = y; i > 0; i--)
idx += max_inst - i;
/* Count this row up to the target */
idx += (x - 1 - y);
/* Slots are in Rx/Tx pairs */
idx *= 2;
/* Pick Rx/Tx direction */
idx += direction;
return idx;
}
static int g2g_register_flat(struct xe_guc *guc, u32 far_tile, u32 far_dev, u32 type, bool have_dev)
{
struct xe_gt *gt = guc_to_gt(guc);
struct xe_device *xe = gt_to_xe(gt);
u32 near_tile = gt_to_tile(gt)->id;
u32 near_dev = G2G_DEV(gt);
u32 max = xe->info.gt_count;
int idx;
u32 base, desc, buf;
if (!guc->g2g.bo)
return -ENODEV;
idx = g2g_slot_flat(near_tile, near_dev, far_tile, far_dev, type, max, have_dev);
xe_assert(xe, idx >= 0);
base = guc_bo_ggtt_addr(guc, guc->g2g.bo);
desc = base + idx * G2G_DESC_SIZE;
buf = base + idx * G2G_BUFFER_SIZE + G2G_DESC_AREA_SIZE;
xe_assert(xe, (desc - base + G2G_DESC_SIZE) <= G2G_DESC_AREA_SIZE);
xe_assert(xe, (buf - base + G2G_BUFFER_SIZE) <= xe_bo_size(guc->g2g.bo));
return guc_action_register_g2g_buffer(guc, type, far_tile, far_dev,
desc, buf, G2G_BUFFER_SIZE);
}
static void g2g_start(struct kunit *test, struct xe_guc *guc)
{
struct xe_gt *remote_gt, *gt = guc_to_gt(guc);
struct xe_device *xe = gt_to_xe(gt);
unsigned int i;
int t, ret;
bool have_dev;
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, guc->g2g.bo);
/* GuC interface will need extending if more GT device types are ever created. */
KUNIT_ASSERT_TRUE(test,
(gt->info.type == XE_GT_TYPE_MAIN) ||
(gt->info.type == XE_GT_TYPE_MEDIA));
/* Channel numbering depends on whether there are multiple GTs per tile */
have_dev = xe->info.gt_count > xe->info.tile_count;
for_each_gt(remote_gt, xe, i) {
u32 tile, dev;
if (remote_gt->info.id == gt->info.id)
continue;
tile = gt_to_tile(remote_gt)->id;
dev = G2G_DEV(remote_gt);
for (t = 0; t < XE_G2G_TYPE_LIMIT; t++) {
ret = g2g_register_flat(guc, tile, dev, t, have_dev);
KUNIT_ASSERT_EQ_MSG(test, 0, ret, "G2G register failed: %pe", ERR_PTR(ret));
}
}
}
static void g2g_reinit(struct kunit *test, struct xe_device *xe, int ctb_type, struct xe_tile *tile)
{
struct xe_gt *gt;
int i, found = 0;
g2g_stop(test, xe);
for_each_gt(gt, xe, i) {
struct xe_guc *guc = &gt->uc.guc;
KUNIT_ASSERT_NULL(test, guc->g2g.bo);
}
switch (ctb_type) {
case G2G_CTB_TYPE_DEFAULT:
g2g_alloc_default(test, xe);
break;
case G2G_CTB_TYPE_HOST:
g2g_alloc_host(test, xe);
break;
case G2G_CTB_TYPE_TILE:
g2g_alloc_tile(test, xe, tile);
break;
default:
KUNIT_ASSERT_TRUE(test, false);
}
for_each_gt(gt, xe, i) {
struct xe_guc *guc = &gt->uc.guc;
if (!guc->g2g.bo)
continue;
if (ctb_type == G2G_CTB_TYPE_DEFAULT)
guc_g2g_start(guc);
else
g2g_start(test, guc);
found++;
}
KUNIT_ASSERT_GT_MSG(test, found, 1, "insufficient G2G channels running: %d", found);
kunit_info(test, "Testing across %d GTs\n", found);
}
static void g2g_recreate_ctb(void *_xe)
{
struct xe_device *xe = (struct xe_device *)_xe;
struct kunit *test = kunit_get_current_test();
g2g_stop(test, xe);
if (xe_guc_g2g_wanted(xe))
g2g_reinit(test, xe, G2G_CTB_TYPE_DEFAULT, NULL);
}
static void g2g_pm_runtime_put(void *_xe)
{
struct xe_device *xe = (struct xe_device *)_xe;
xe_pm_runtime_put(xe);
}
static void g2g_pm_runtime_get(struct kunit *test)
{
struct xe_device *xe = test->priv;
int ret;
xe_pm_runtime_get(xe);
ret = kunit_add_action_or_reset(test, g2g_pm_runtime_put, xe);
KUNIT_ASSERT_EQ_MSG(test, 0, ret, "Failed to register runtime PM action\n");
}
static void g2g_check_skip(struct kunit *test)
{
struct xe_device *xe = test->priv;
struct xe_gt *gt;
int i;
if (IS_SRIOV_VF(xe))
kunit_skip(test, "not supported from a VF");
if (xe->info.gt_count <= 1)
kunit_skip(test, "not enough GTs");
for_each_gt(gt, xe, i) {
struct xe_guc *guc = &gt->uc.guc;
if (guc->fw.build_type == CSS_UKERNEL_INFO_BUILDTYPE_PROD)
kunit_skip(test,
"G2G test interface not available in production firmware builds\n");
}
}
/*
* Simple test that does not try to recreate the CTBs.
* Requires that the platform already enables G2G comms
* but has no risk of leaving the system in a broken state
* afterwards.
*/
static void xe_live_guc_g2g_kunit_default(struct kunit *test)
{
struct xe_device *xe = test->priv;
if (!xe_guc_g2g_wanted(xe))
kunit_skip(test, "G2G not enabled");
g2g_check_skip(test);
g2g_pm_runtime_get(test);
kunit_info(test, "Testing default CTBs\n");
g2g_run_test(test, xe);
kunit_release_action(test, &g2g_pm_runtime_put, xe);
}
/*
* More complex test that re-creates the CTBs in various location to
* test access to each location from each GuC. Can be run even on
* systems that do not enable G2G by default. On the other hand,
* because it recreates the CTBs, if something goes wrong it could
* leave the system with broken G2G comms.
*/
static void xe_live_guc_g2g_kunit_allmem(struct kunit *test)
{
struct xe_device *xe = test->priv;
int ret;
g2g_check_skip(test);
g2g_pm_runtime_get(test);
/* Make sure to leave the system as we found it */
ret = kunit_add_action_or_reset(test, g2g_recreate_ctb, xe);
KUNIT_ASSERT_EQ_MSG(test, 0, ret, "Failed to register CTB re-creation action\n");
kunit_info(test, "Testing CTB type 'default'...\n");
g2g_reinit(test, xe, G2G_CTB_TYPE_DEFAULT, NULL);
g2g_run_test(test, xe);
kunit_info(test, "Testing CTB type 'host'...\n");
g2g_reinit(test, xe, G2G_CTB_TYPE_HOST, NULL);
g2g_run_test(test, xe);
if (IS_DGFX(xe)) {
struct xe_tile *tile;
int id;
for_each_tile(tile, xe, id) {
kunit_info(test, "Testing CTB type 'tile: #%d'...\n", id);
g2g_reinit(test, xe, G2G_CTB_TYPE_TILE, tile);
g2g_run_test(test, xe);
}
} else {
kunit_info(test, "Skipping local memory on integrated platform\n");
}
kunit_release_action(test, g2g_recreate_ctb, xe);
kunit_release_action(test, g2g_pm_runtime_put, xe);
}
static struct kunit_case xe_guc_g2g_tests[] = {
KUNIT_CASE_PARAM(xe_live_guc_g2g_kunit_default, xe_pci_live_device_gen_param),
KUNIT_CASE_PARAM(xe_live_guc_g2g_kunit_allmem, xe_pci_live_device_gen_param),
{}
};
VISIBLE_IF_KUNIT
struct kunit_suite xe_guc_g2g_test_suite = {
.name = "xe_guc_g2g",
.test_cases = xe_guc_g2g_tests,
.init = xe_kunit_helper_xe_device_live_test_init,
};
EXPORT_SYMBOL_IF_KUNIT(xe_guc_g2g_test_suite);
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2024 Intel Corporation
*/
#include <kunit/test.h>
#include "xe_device.h"
#include "xe_kunit_helpers.h"
static int guc_id_mgr_test_init(struct kunit *test)
{
struct xe_guc_id_mgr *idm;
xe_kunit_helper_xe_device_test_init(test);
idm = &xe_device_get_gt(test->priv, 0)->uc.guc.submission_state.idm;
mutex_init(idm_mutex(idm));
test->priv = idm;
return 0;
}
static void bad_init(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
KUNIT_EXPECT_EQ(test, -EINVAL, xe_guc_id_mgr_init(idm, 0));
KUNIT_EXPECT_EQ(test, -ERANGE, xe_guc_id_mgr_init(idm, GUC_ID_MAX + 1));
}
static void no_init(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
mutex_lock(idm_mutex(idm));
KUNIT_EXPECT_EQ(test, -ENODATA, xe_guc_id_mgr_reserve_locked(idm, 0));
mutex_unlock(idm_mutex(idm));
KUNIT_EXPECT_EQ(test, -ENODATA, xe_guc_id_mgr_reserve(idm, 1, 1));
}
static void init_fini(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
KUNIT_ASSERT_EQ(test, 0, xe_guc_id_mgr_init(idm, -1));
KUNIT_EXPECT_NOT_NULL(test, idm->bitmap);
KUNIT_EXPECT_EQ(test, idm->total, GUC_ID_MAX);
__fini_idm(NULL, idm);
KUNIT_EXPECT_NULL(test, idm->bitmap);
KUNIT_EXPECT_EQ(test, idm->total, 0);
}
static void check_used(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
unsigned int n;
KUNIT_ASSERT_EQ(test, 0, xe_guc_id_mgr_init(idm, 2));
mutex_lock(idm_mutex(idm));
for (n = 0; n < idm->total; n++) {
kunit_info(test, "n=%u", n);
KUNIT_EXPECT_EQ(test, idm->used, n);
KUNIT_EXPECT_GE(test, idm_reserve_chunk_locked(idm, 1, 0), 0);
KUNIT_EXPECT_EQ(test, idm->used, n + 1);
}
KUNIT_EXPECT_EQ(test, idm->used, idm->total);
idm_release_chunk_locked(idm, 0, idm->used);
KUNIT_EXPECT_EQ(test, idm->used, 0);
mutex_unlock(idm_mutex(idm));
}
static void check_quota(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
unsigned int n;
KUNIT_ASSERT_EQ(test, 0, xe_guc_id_mgr_init(idm, 2));
mutex_lock(idm_mutex(idm));
for (n = 0; n < idm->total - 1; n++) {
kunit_info(test, "n=%u", n);
KUNIT_EXPECT_EQ(test, idm_reserve_chunk_locked(idm, 1, idm->total), -EDQUOT);
KUNIT_EXPECT_EQ(test, idm_reserve_chunk_locked(idm, 1, idm->total - n), -EDQUOT);
KUNIT_EXPECT_EQ(test, idm_reserve_chunk_locked(idm, idm->total - n, 1), -EDQUOT);
KUNIT_EXPECT_GE(test, idm_reserve_chunk_locked(idm, 1, 1), 0);
}
KUNIT_EXPECT_LE(test, 0, idm_reserve_chunk_locked(idm, 1, 0));
KUNIT_EXPECT_EQ(test, idm->used, idm->total);
idm_release_chunk_locked(idm, 0, idm->total);
KUNIT_EXPECT_EQ(test, idm->used, 0);
mutex_unlock(idm_mutex(idm));
}
static void check_all(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
unsigned int n;
KUNIT_ASSERT_EQ(test, 0, xe_guc_id_mgr_init(idm, -1));
mutex_lock(idm_mutex(idm));
for (n = 0; n < idm->total; n++)
KUNIT_EXPECT_LE(test, 0, idm_reserve_chunk_locked(idm, 1, 0));
KUNIT_EXPECT_EQ(test, idm->used, idm->total);
for (n = 0; n < idm->total; n++)
idm_release_chunk_locked(idm, n, 1);
mutex_unlock(idm_mutex(idm));
}
static struct kunit_case guc_id_mgr_test_cases[] = {
KUNIT_CASE(bad_init),
KUNIT_CASE(no_init),
KUNIT_CASE(init_fini),
KUNIT_CASE(check_used),
KUNIT_CASE(check_quota),
KUNIT_CASE_SLOW(check_all),
{}
};
static struct kunit_suite guc_id_mgr_suite = {
.name = "guc_idm",
.test_cases = guc_id_mgr_test_cases,
.init = guc_id_mgr_test_init,
.exit = NULL,
};
kunit_test_suites(&guc_id_mgr_suite);
@@ -0,0 +1,522 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <kunit/static_stub.h>
#include <kunit/test.h>
#include <kunit/test-bug.h>
#include "xe_device.h"
#include "xe_kunit_helpers.h"
#include "xe_pci_test.h"
#define TEST_RID 1234
#define TEST_VFID 5
#define TEST_LEN 6
#define TEST_ACTION 0xa
#define TEST_DATA(n) (0xd0 + (n))
static int replacement_relay_get_totalvfs(struct xe_guc_relay *relay)
{
return TEST_VFID;
}
static int relay_test_init(struct kunit *test)
{
struct xe_pci_fake_data fake = {
.sriov_mode = XE_SRIOV_MODE_PF,
.platform = XE_TIGERLAKE, /* some random platform */
.subplatform = XE_SUBPLATFORM_NONE,
};
struct xe_guc_relay *relay;
struct xe_device *xe;
test->priv = &fake;
xe_kunit_helper_xe_device_test_init(test);
xe = test->priv;
KUNIT_ASSERT_EQ(test, xe_sriov_init(xe), 0);
relay = &xe_device_get_gt(xe, 0)->uc.guc.relay;
kunit_activate_static_stub(test, relay_get_totalvfs,
replacement_relay_get_totalvfs);
KUNIT_ASSERT_EQ(test, xe_guc_relay_init(relay), 0);
KUNIT_EXPECT_TRUE(test, relay_is_ready(relay));
relay->last_rid = TEST_RID - 1;
test->priv = relay;
return 0;
}
static const u32 TEST_MSG[TEST_LEN] = {
FIELD_PREP_CONST(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP_CONST(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_EVENT) |
FIELD_PREP_CONST(GUC_HXG_EVENT_MSG_0_ACTION, TEST_ACTION) |
FIELD_PREP_CONST(GUC_HXG_EVENT_MSG_0_DATA0, TEST_DATA(0)),
TEST_DATA(1), TEST_DATA(2), TEST_DATA(3), TEST_DATA(4),
};
static int replacement_xe_guc_ct_send_recv_always_fails(struct xe_guc_ct *ct,
const u32 *msg, u32 len,
u32 *response_buffer)
{
struct kunit *test = kunit_get_current_test();
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ct);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, msg);
KUNIT_ASSERT_GE(test, len, GUC_HXG_MSG_MIN_LEN);
return -ECOMM;
}
static int replacement_xe_guc_ct_send_recv_expects_pf2guc_relay(struct xe_guc_ct *ct,
const u32 *msg, u32 len,
u32 *response_buffer)
{
struct kunit *test = kunit_get_current_test();
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ct);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, msg);
KUNIT_ASSERT_GE(test, len, PF2GUC_RELAY_TO_VF_REQUEST_MSG_MIN_LEN);
KUNIT_ASSERT_EQ(test, len, PF2GUC_RELAY_TO_VF_REQUEST_MSG_MIN_LEN + TEST_LEN);
KUNIT_EXPECT_EQ(test, GUC_HXG_ORIGIN_HOST, FIELD_GET(GUC_HXG_MSG_0_ORIGIN, msg[0]));
KUNIT_EXPECT_EQ(test, GUC_HXG_TYPE_REQUEST, FIELD_GET(GUC_HXG_MSG_0_TYPE, msg[0]));
KUNIT_EXPECT_EQ(test, XE_GUC_ACTION_PF2GUC_RELAY_TO_VF,
FIELD_GET(GUC_HXG_REQUEST_MSG_0_ACTION, msg[0]));
KUNIT_EXPECT_EQ(test, TEST_VFID,
FIELD_GET(PF2GUC_RELAY_TO_VF_REQUEST_MSG_1_VFID, msg[1]));
KUNIT_EXPECT_EQ(test, TEST_RID,
FIELD_GET(PF2GUC_RELAY_TO_VF_REQUEST_MSG_2_RELAY_ID, msg[2]));
KUNIT_EXPECT_MEMEQ(test, TEST_MSG, msg + PF2GUC_RELAY_TO_VF_REQUEST_MSG_MIN_LEN,
sizeof(u32) * TEST_LEN);
return 0;
}
static const u32 test_guc2pf[GUC2PF_RELAY_FROM_VF_EVENT_MSG_MAX_LEN] = {
/* transport */
FIELD_PREP_CONST(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_GUC) |
FIELD_PREP_CONST(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_EVENT) |
FIELD_PREP_CONST(GUC_HXG_EVENT_MSG_0_ACTION, XE_GUC_ACTION_GUC2PF_RELAY_FROM_VF),
FIELD_PREP_CONST(GUC2PF_RELAY_FROM_VF_EVENT_MSG_1_VFID, TEST_VFID),
FIELD_PREP_CONST(GUC2PF_RELAY_FROM_VF_EVENT_MSG_2_RELAY_ID, TEST_RID),
/* payload */
FIELD_PREP_CONST(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP_CONST(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_RESPONSE_SUCCESS),
};
static const u32 test_guc2vf[GUC2VF_RELAY_FROM_PF_EVENT_MSG_MAX_LEN] = {
/* transport */
FIELD_PREP_CONST(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_GUC) |
FIELD_PREP_CONST(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_EVENT) |
FIELD_PREP_CONST(GUC_HXG_EVENT_MSG_0_ACTION, XE_GUC_ACTION_GUC2VF_RELAY_FROM_PF),
FIELD_PREP_CONST(GUC2VF_RELAY_FROM_PF_EVENT_MSG_1_RELAY_ID, TEST_RID),
/* payload */
FIELD_PREP_CONST(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP_CONST(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_RESPONSE_SUCCESS),
};
static void pf_rejects_guc2pf_too_short(struct kunit *test)
{
const u32 len = GUC2PF_RELAY_FROM_VF_EVENT_MSG_MIN_LEN - 1;
struct xe_guc_relay *relay = test->priv;
const u32 *msg = test_guc2pf;
KUNIT_ASSERT_EQ(test, -EPROTO, xe_guc_relay_process_guc2pf(relay, msg, len));
}
static void pf_rejects_guc2pf_too_long(struct kunit *test)
{
const u32 len = GUC2PF_RELAY_FROM_VF_EVENT_MSG_MAX_LEN + 1;
struct xe_guc_relay *relay = test->priv;
const u32 *msg = test_guc2pf;
KUNIT_ASSERT_EQ(test, -EMSGSIZE, xe_guc_relay_process_guc2pf(relay, msg, len));
}
static void pf_rejects_guc2pf_no_payload(struct kunit *test)
{
const u32 len = GUC2PF_RELAY_FROM_VF_EVENT_MSG_MIN_LEN;
struct xe_guc_relay *relay = test->priv;
const u32 *msg = test_guc2pf;
KUNIT_ASSERT_EQ(test, -EPROTO, xe_guc_relay_process_guc2pf(relay, msg, len));
}
static void pf_fails_no_payload(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
const u32 msg = 0;
KUNIT_ASSERT_EQ(test, -EPROTO, relay_process_msg(relay, TEST_VFID, TEST_RID, &msg, 0));
}
static void pf_fails_bad_origin(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
static const u32 msg[] = {
FIELD_PREP_CONST(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_GUC) |
FIELD_PREP_CONST(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_RESPONSE_SUCCESS),
};
u32 len = ARRAY_SIZE(msg);
KUNIT_ASSERT_EQ(test, -EPROTO, relay_process_msg(relay, TEST_VFID, TEST_RID, msg, len));
}
static void pf_fails_bad_type(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
const u32 msg[] = {
FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP(GUC_HXG_MSG_0_TYPE, 4), /* only 4 is undefined */
};
u32 len = ARRAY_SIZE(msg);
KUNIT_ASSERT_EQ(test, -EBADRQC, relay_process_msg(relay, TEST_VFID, TEST_RID, msg, len));
}
static void pf_txn_reports_error(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
struct relay_transaction *txn;
txn = __relay_get_transaction(relay, false, TEST_VFID, TEST_RID,
TEST_MSG, TEST_LEN, NULL, 0);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, txn);
kunit_activate_static_stub(test, xe_guc_ct_send_recv,
replacement_xe_guc_ct_send_recv_always_fails);
KUNIT_EXPECT_EQ(test, -ECOMM, relay_send_transaction(relay, txn));
relay_release_transaction(relay, txn);
}
static void pf_txn_sends_pf2guc(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
struct relay_transaction *txn;
txn = __relay_get_transaction(relay, false, TEST_VFID, TEST_RID,
TEST_MSG, TEST_LEN, NULL, 0);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, txn);
kunit_activate_static_stub(test, xe_guc_ct_send_recv,
replacement_xe_guc_ct_send_recv_expects_pf2guc_relay);
KUNIT_ASSERT_EQ(test, 0, relay_send_transaction(relay, txn));
relay_release_transaction(relay, txn);
}
static void pf_sends_pf2guc(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
kunit_activate_static_stub(test, xe_guc_ct_send_recv,
replacement_xe_guc_ct_send_recv_expects_pf2guc_relay);
KUNIT_ASSERT_EQ(test, 0,
xe_guc_relay_send_to_vf(relay, TEST_VFID,
TEST_MSG, TEST_LEN, NULL, 0));
}
static int replacement_xe_guc_ct_send_recv_loopback_relay(struct xe_guc_ct *ct,
const u32 *msg, u32 len,
u32 *response_buffer)
{
struct kunit *test = kunit_get_current_test();
struct xe_guc_relay *relay = test->priv;
u32 *reply = kunit_kzalloc(test, len * sizeof(u32), GFP_KERNEL);
int (*guc2relay)(struct xe_guc_relay *, const u32 *, u32);
u32 action;
int err;
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ct);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, msg);
KUNIT_ASSERT_GE(test, len, GUC_HXG_MSG_MIN_LEN);
KUNIT_ASSERT_EQ(test, GUC_HXG_TYPE_REQUEST,
FIELD_GET(GUC_HXG_MSG_0_TYPE, msg[0]));
KUNIT_ASSERT_GE(test, len, GUC_HXG_REQUEST_MSG_MIN_LEN);
KUNIT_ASSERT_NOT_NULL(test, reply);
switch (FIELD_GET(GUC_HXG_REQUEST_MSG_0_ACTION, msg[0])) {
case XE_GUC_ACTION_PF2GUC_RELAY_TO_VF:
KUNIT_ASSERT_GE(test, len, PF2GUC_RELAY_TO_VF_REQUEST_MSG_MIN_LEN);
action = XE_GUC_ACTION_GUC2PF_RELAY_FROM_VF;
guc2relay = xe_guc_relay_process_guc2pf;
break;
case XE_GUC_ACTION_VF2GUC_RELAY_TO_PF:
KUNIT_ASSERT_GE(test, len, VF2GUC_RELAY_TO_PF_REQUEST_MSG_MIN_LEN);
action = XE_GUC_ACTION_GUC2VF_RELAY_FROM_PF;
guc2relay = xe_guc_relay_process_guc2vf;
break;
default:
KUNIT_FAIL(test, "bad RELAY action %#x", msg[0]);
return -EINVAL;
}
reply[0] = FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_GUC) |
FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_EVENT) |
FIELD_PREP(GUC_HXG_EVENT_MSG_0_ACTION, action);
memcpy(reply + 1, msg + 1, sizeof(u32) * (len - 1));
err = guc2relay(relay, reply, len);
KUNIT_EXPECT_EQ(test, err, 0);
return err;
}
static void test_requires_relay_testloop(struct kunit *test)
{
/*
* The debug relay action GUC_RELAY_ACTION_VFXPF_TESTLOOP is available
* only on builds with CONFIG_DRM_XE_DEBUG_SRIOV enabled.
* See "kunit.py --kconfig_add" option if it's missing.
*/
if (!IS_ENABLED(CONFIG_DRM_XE_DEBUG_SRIOV))
kunit_skip(test, "requires %s\n", __stringify(CONFIG_DRM_XE_DEBUG_SRIOV));
}
static void pf_loopback_nop(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
u32 request[] = {
FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_RELAY_ACTION_VFXPF_TESTLOOP) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_DATA0, VFXPF_TESTLOOP_OPCODE_NOP),
};
u32 response[GUC_HXG_RESPONSE_MSG_MIN_LEN];
int ret;
test_requires_relay_testloop(test);
kunit_activate_static_stub(test, relay_kick_worker, relay_process_incoming_action);
kunit_activate_static_stub(test, xe_guc_ct_send_recv,
replacement_xe_guc_ct_send_recv_loopback_relay);
ret = xe_guc_relay_send_to_vf(relay, TEST_VFID,
request, ARRAY_SIZE(request),
response, ARRAY_SIZE(response));
KUNIT_ASSERT_EQ(test, ret, GUC_HXG_RESPONSE_MSG_MIN_LEN);
KUNIT_EXPECT_EQ(test, FIELD_GET(GUC_HXG_MSG_0_ORIGIN, response[0]),
GUC_HXG_ORIGIN_HOST);
KUNIT_EXPECT_EQ(test, FIELD_GET(GUC_HXG_MSG_0_TYPE, response[0]),
GUC_HXG_TYPE_RESPONSE_SUCCESS);
KUNIT_EXPECT_EQ(test, FIELD_GET(GUC_HXG_RESPONSE_MSG_0_DATA0, response[0]), 0);
}
static void pf_loopback_echo(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
u32 request[] = {
FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_RELAY_ACTION_VFXPF_TESTLOOP) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_DATA0, VFXPF_TESTLOOP_OPCODE_ECHO),
TEST_DATA(1), TEST_DATA(2), TEST_DATA(3), TEST_DATA(4),
};
u32 response[ARRAY_SIZE(request)];
unsigned int n;
int ret;
test_requires_relay_testloop(test);
kunit_activate_static_stub(test, relay_kick_worker, relay_process_incoming_action);
kunit_activate_static_stub(test, xe_guc_ct_send_recv,
replacement_xe_guc_ct_send_recv_loopback_relay);
ret = xe_guc_relay_send_to_vf(relay, TEST_VFID,
request, ARRAY_SIZE(request),
response, ARRAY_SIZE(response));
KUNIT_ASSERT_EQ(test, ret, ARRAY_SIZE(response));
KUNIT_EXPECT_EQ(test, FIELD_GET(GUC_HXG_MSG_0_ORIGIN, response[0]),
GUC_HXG_ORIGIN_HOST);
KUNIT_EXPECT_EQ(test, FIELD_GET(GUC_HXG_MSG_0_TYPE, response[0]),
GUC_HXG_TYPE_RESPONSE_SUCCESS);
KUNIT_EXPECT_EQ(test, FIELD_GET(GUC_HXG_RESPONSE_MSG_0_DATA0, response[0]),
ARRAY_SIZE(response));
for (n = GUC_HXG_RESPONSE_MSG_MIN_LEN; n < ret; n++)
KUNIT_EXPECT_EQ(test, request[n], response[n]);
}
static void pf_loopback_fail(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
u32 request[] = {
FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_RELAY_ACTION_VFXPF_TESTLOOP) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_DATA0, VFXPF_TESTLOOP_OPCODE_FAIL),
};
u32 response[GUC_HXG_RESPONSE_MSG_MIN_LEN];
int ret;
test_requires_relay_testloop(test);
kunit_activate_static_stub(test, relay_kick_worker, relay_process_incoming_action);
kunit_activate_static_stub(test, xe_guc_ct_send_recv,
replacement_xe_guc_ct_send_recv_loopback_relay);
ret = xe_guc_relay_send_to_vf(relay, TEST_VFID,
request, ARRAY_SIZE(request),
response, ARRAY_SIZE(response));
KUNIT_ASSERT_EQ(test, ret, -EREMOTEIO);
}
static void pf_loopback_busy(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
u32 request[] = {
FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_RELAY_ACTION_VFXPF_TESTLOOP) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_DATA0, VFXPF_TESTLOOP_OPCODE_BUSY),
TEST_DATA(0xb),
};
u32 response[GUC_HXG_RESPONSE_MSG_MIN_LEN];
int ret;
test_requires_relay_testloop(test);
kunit_activate_static_stub(test, relay_testonly_nop, relay_process_incoming_action);
kunit_activate_static_stub(test, relay_kick_worker, relay_process_incoming_action);
kunit_activate_static_stub(test, xe_guc_ct_send_recv,
replacement_xe_guc_ct_send_recv_loopback_relay);
ret = xe_guc_relay_send_to_vf(relay, TEST_VFID,
request, ARRAY_SIZE(request),
response, ARRAY_SIZE(response));
KUNIT_ASSERT_EQ(test, ret, GUC_HXG_RESPONSE_MSG_MIN_LEN);
}
static void pf_loopback_retry(struct kunit *test)
{
struct xe_guc_relay *relay = test->priv;
u32 request[] = {
FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) |
FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_RELAY_ACTION_VFXPF_TESTLOOP) |
FIELD_PREP(GUC_HXG_REQUEST_MSG_0_DATA0, VFXPF_TESTLOOP_OPCODE_RETRY),
TEST_DATA(0xd), TEST_DATA(0xd),
};
u32 response[GUC_HXG_RESPONSE_MSG_MIN_LEN];
int ret;
test_requires_relay_testloop(test);
kunit_activate_static_stub(test, relay_kick_worker, relay_process_incoming_action);
kunit_activate_static_stub(test, xe_guc_ct_send_recv,
replacement_xe_guc_ct_send_recv_loopback_relay);
ret = xe_guc_relay_send_to_vf(relay, TEST_VFID,
request, ARRAY_SIZE(request),
response, ARRAY_SIZE(response));
KUNIT_ASSERT_EQ(test, ret, GUC_HXG_RESPONSE_MSG_MIN_LEN);
}
static struct kunit_case pf_relay_test_cases[] = {
KUNIT_CASE(pf_rejects_guc2pf_too_short),
KUNIT_CASE(pf_rejects_guc2pf_too_long),
KUNIT_CASE(pf_rejects_guc2pf_no_payload),
KUNIT_CASE(pf_fails_no_payload),
KUNIT_CASE(pf_fails_bad_origin),
KUNIT_CASE(pf_fails_bad_type),
KUNIT_CASE(pf_txn_reports_error),
KUNIT_CASE(pf_txn_sends_pf2guc),
KUNIT_CASE(pf_sends_pf2guc),
KUNIT_CASE(pf_loopback_nop),
KUNIT_CASE(pf_loopback_echo),
KUNIT_CASE(pf_loopback_fail),
KUNIT_CASE_SLOW(pf_loopback_busy),
KUNIT_CASE_SLOW(pf_loopback_retry),
{}
};
static struct kunit_suite pf_relay_suite = {
.name = "pf_relay",
.test_cases = pf_relay_test_cases,
.init = relay_test_init,
};
static void vf_rejects_guc2vf_too_short(struct kunit *test)
{
const u32 len = GUC2VF_RELAY_FROM_PF_EVENT_MSG_MIN_LEN - 1;
struct xe_guc_relay *relay = test->priv;
const u32 *msg = test_guc2vf;
KUNIT_ASSERT_EQ(test, -EPROTO, xe_guc_relay_process_guc2vf(relay, msg, len));
}
static void vf_rejects_guc2vf_too_long(struct kunit *test)
{
const u32 len = GUC2VF_RELAY_FROM_PF_EVENT_MSG_MAX_LEN + 1;
struct xe_guc_relay *relay = test->priv;
const u32 *msg = test_guc2vf;
KUNIT_ASSERT_EQ(test, -EMSGSIZE, xe_guc_relay_process_guc2vf(relay, msg, len));
}
static void vf_rejects_guc2vf_no_payload(struct kunit *test)
{
const u32 len = GUC2VF_RELAY_FROM_PF_EVENT_MSG_MIN_LEN;
struct xe_guc_relay *relay = test->priv;
const u32 *msg = test_guc2vf;
KUNIT_ASSERT_EQ(test, -EPROTO, xe_guc_relay_process_guc2vf(relay, msg, len));
}
static struct kunit_case vf_relay_test_cases[] = {
KUNIT_CASE(vf_rejects_guc2vf_too_short),
KUNIT_CASE(vf_rejects_guc2vf_too_long),
KUNIT_CASE(vf_rejects_guc2vf_no_payload),
{}
};
static struct kunit_suite vf_relay_suite = {
.name = "vf_relay",
.test_cases = vf_relay_test_cases,
.init = relay_test_init,
};
static void xe_drops_guc2pf_if_not_ready(struct kunit *test)
{
struct xe_device *xe = test->priv;
struct xe_guc_relay *relay = &xe_device_get_gt(xe, 0)->uc.guc.relay;
const u32 *msg = test_guc2pf;
u32 len = GUC2PF_RELAY_FROM_VF_EVENT_MSG_MIN_LEN + GUC_RELAY_MSG_MIN_LEN;
KUNIT_ASSERT_EQ(test, -ENODEV, xe_guc_relay_process_guc2pf(relay, msg, len));
}
static void xe_drops_guc2vf_if_not_ready(struct kunit *test)
{
struct xe_device *xe = test->priv;
struct xe_guc_relay *relay = &xe_device_get_gt(xe, 0)->uc.guc.relay;
const u32 *msg = test_guc2vf;
u32 len = GUC2VF_RELAY_FROM_PF_EVENT_MSG_MIN_LEN + GUC_RELAY_MSG_MIN_LEN;
KUNIT_ASSERT_EQ(test, -ENODEV, xe_guc_relay_process_guc2vf(relay, msg, len));
}
static void xe_rejects_send_if_not_ready(struct kunit *test)
{
struct xe_device *xe = test->priv;
struct xe_guc_relay *relay = &xe_device_get_gt(xe, 0)->uc.guc.relay;
u32 msg[GUC_RELAY_MSG_MIN_LEN];
u32 len = ARRAY_SIZE(msg);
KUNIT_ASSERT_EQ(test, -ENODEV, xe_guc_relay_send_to_pf(relay, msg, len, NULL, 0));
KUNIT_ASSERT_EQ(test, -ENODEV, relay_send_to(relay, TEST_VFID, msg, len, NULL, 0));
}
static struct kunit_case no_relay_test_cases[] = {
KUNIT_CASE(xe_drops_guc2pf_if_not_ready),
KUNIT_CASE(xe_drops_guc2vf_if_not_ready),
KUNIT_CASE(xe_rejects_send_if_not_ready),
{}
};
static struct kunit_suite no_relay_suite = {
.name = "no_relay",
.test_cases = no_relay_test_cases,
.init = xe_kunit_helper_xe_device_test_init,
};
kunit_test_suites(&no_relay_suite,
&pf_relay_suite,
&vf_relay_suite);
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/static_stub.h>
#include <kunit/visibility.h>
#include <drm/drm_drv.h>
#include <drm/drm_kunit_helpers.h>
#include "tests/xe_kunit_helpers.h"
#include "tests/xe_pci_test.h"
#include "xe_device.h"
#include "xe_device_types.h"
#include "xe_pm.h"
/**
* xe_kunit_helper_alloc_xe_device - Allocate a &xe_device for a KUnit test.
* @test: the &kunit where this &xe_device will be used
* @dev: The parent device object
*
* This function allocates xe_device using drm_kunit_helper_alloc_device().
* The xe_device allocation is managed by the test.
*
* @dev should be allocated using drm_kunit_helper_alloc_device().
*
* This function uses KUNIT_ASSERT to detect any allocation failures.
*
* Return: A pointer to the new &xe_device.
*/
struct xe_device *xe_kunit_helper_alloc_xe_device(struct kunit *test,
struct device *dev)
{
struct xe_device *xe;
xe = drm_kunit_helper_alloc_drm_device(test, dev,
struct xe_device,
drm, DRIVER_GEM);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe);
return xe;
}
EXPORT_SYMBOL_IF_KUNIT(xe_kunit_helper_alloc_xe_device);
static void kunit_action_restore_priv(void *priv)
{
struct kunit *test = kunit_get_current_test();
test->priv = priv;
}
/**
* xe_kunit_helper_xe_device_test_init - Prepare a &xe_device for a KUnit test.
* @test: the &kunit where this fake &xe_device will be used
*
* This function allocates and initializes a fake &xe_device and stores its
* pointer as &kunit.priv to allow the test code to access it.
*
* This function can be directly used as custom implementation of
* &kunit_suite.init.
*
* It is possible to prepare specific variant of the fake &xe_device by passing
* in &kunit.priv pointer to the struct xe_pci_fake_data supplemented with
* desired parameters prior to calling this function.
*
* This function uses KUNIT_ASSERT to detect any failures.
*
* Return: Always 0.
*/
int xe_kunit_helper_xe_device_test_init(struct kunit *test)
{
struct xe_device *xe;
struct device *dev;
int err;
dev = drm_kunit_helper_alloc_device(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
xe = xe_kunit_helper_alloc_xe_device(test, dev);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe);
err = xe_pci_fake_device_init(xe);
KUNIT_ASSERT_EQ(test, err, 0);
err = kunit_add_action_or_reset(test, kunit_action_restore_priv, test->priv);
KUNIT_ASSERT_EQ(test, err, 0);
test->priv = xe;
return 0;
}
EXPORT_SYMBOL_IF_KUNIT(xe_kunit_helper_xe_device_test_init);
KUNIT_DEFINE_ACTION_WRAPPER(put_xe_pm_runtime, xe_pm_runtime_put, struct xe_device *);
/**
* xe_kunit_helper_xe_device_live_test_init - Prepare a &xe_device for
* use in a live KUnit test.
* @test: the &kunit where live &xe_device will be used
*
* This function expects pointer to the &xe_device in the &test.param_value,
* like it is prepared by the &xe_pci_live_device_gen_param and stores that
* pointer as &kunit.priv to allow the test code to access it.
*
* This function makes sure that device is not wedged and then resumes it
* to avoid waking up the device inside the test. It uses deferred cleanup
* action to release a runtime_pm reference.
*
* This function can be used as custom implementation of &kunit_suite.init.
*
* This function uses KUNIT_ASSERT to detect any failures.
*
* Return: Always 0.
*/
int xe_kunit_helper_xe_device_live_test_init(struct kunit *test)
{
struct xe_device *xe = xe_device_const_cast(test->param_value);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe);
kunit_info(test, "running on %s device\n", xe->info.platform_name);
KUNIT_ASSERT_FALSE(test, xe_device_wedged(xe));
xe_pm_runtime_get(xe);
KUNIT_ASSERT_EQ(test, 0, kunit_add_action_or_reset(test, put_xe_pm_runtime, xe));
test->priv = xe;
return 0;
}
EXPORT_SYMBOL_IF_KUNIT(xe_kunit_helper_xe_device_live_test_init);
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 AND MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef _XE_KUNIT_HELPERS_H_
#define _XE_KUNIT_HELPERS_H_
struct device;
struct kunit;
struct xe_device;
struct xe_device *xe_kunit_helper_alloc_xe_device(struct kunit *test,
struct device *dev);
int xe_kunit_helper_xe_device_test_init(struct kunit *test);
int xe_kunit_helper_xe_device_live_test_init(struct kunit *test);
#endif
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/module.h>
#include <kunit/test.h>
extern struct kunit_suite xe_bo_test_suite;
extern struct kunit_suite xe_bo_shrink_test_suite;
extern struct kunit_suite xe_dma_buf_test_suite;
extern struct kunit_suite xe_migrate_test_suite;
extern struct kunit_suite xe_mocs_test_suite;
extern struct kunit_suite xe_guc_g2g_test_suite;
kunit_test_suite(xe_bo_test_suite);
kunit_test_suite(xe_bo_shrink_test_suite);
kunit_test_suite(xe_dma_buf_test_suite);
kunit_test_suite(xe_migrate_test_suite);
kunit_test_suite(xe_mocs_test_suite);
kunit_test_suite(xe_guc_g2g_test_suite);
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("xe live kunit tests");
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <kunit/test.h>
static const struct lmtt_ops_param {
const char *desc;
const struct xe_lmtt_ops *ops;
} lmtt_ops_params[] = {
{ "2-level", &lmtt_2l_ops, },
{ "multi-level", &lmtt_ml_ops, },
};
static void lmtt_ops_param_get_desc(const struct lmtt_ops_param *p, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->desc);
}
KUNIT_ARRAY_PARAM(lmtt_ops, lmtt_ops_params, lmtt_ops_param_get_desc);
static void test_ops(struct kunit *test)
{
const struct lmtt_ops_param *p = test->param_value;
const struct xe_lmtt_ops *ops = p->ops;
unsigned int n;
KUNIT_ASSERT_NOT_NULL(test, ops->lmtt_root_pd_level);
KUNIT_ASSERT_NOT_NULL(test, ops->lmtt_pte_num);
KUNIT_ASSERT_NOT_NULL(test, ops->lmtt_pte_size);
KUNIT_ASSERT_NOT_NULL(test, ops->lmtt_pte_shift);
KUNIT_ASSERT_NOT_NULL(test, ops->lmtt_pte_index);
KUNIT_ASSERT_NOT_NULL(test, ops->lmtt_pte_encode);
KUNIT_EXPECT_NE(test, ops->lmtt_root_pd_level(), 0);
for (n = 0; n <= ops->lmtt_root_pd_level(); n++) {
KUNIT_EXPECT_NE_MSG(test, ops->lmtt_pte_num(n), 0,
"level=%u", n);
KUNIT_EXPECT_NE_MSG(test, ops->lmtt_pte_size(n), 0,
"level=%u", n);
KUNIT_EXPECT_NE_MSG(test, ops->lmtt_pte_encode(0, n), LMTT_PTE_INVALID,
"level=%u", n);
}
for (n = 0; n < ops->lmtt_root_pd_level(); n++) {
u64 addr = BIT_ULL(ops->lmtt_pte_shift(n));
KUNIT_EXPECT_NE_MSG(test, ops->lmtt_pte_shift(n), 0,
"level=%u", n);
KUNIT_EXPECT_EQ_MSG(test, ops->lmtt_pte_index(addr - 1, n), 0,
"addr=%#llx level=%u", addr, n);
KUNIT_EXPECT_EQ_MSG(test, ops->lmtt_pte_index(addr + 1, n), 1,
"addr=%#llx level=%u", addr, n);
KUNIT_EXPECT_EQ_MSG(test, ops->lmtt_pte_index(addr * 2 - 1, n), 1,
"addr=%#llx level=%u", addr, n);
KUNIT_EXPECT_EQ_MSG(test, ops->lmtt_pte_index(addr * 2, n), 2,
"addr=%#llx level=%u", addr, n);
}
}
static struct kunit_case lmtt_test_cases[] = {
KUNIT_CASE_PARAM(test_ops, lmtt_ops_gen_params),
{}
};
static struct kunit_suite lmtt_suite = {
.name = "lmtt",
.test_cases = lmtt_test_cases,
};
kunit_test_suites(&lmtt_suite);
@@ -0,0 +1,786 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2020-2022 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/visibility.h>
#include "tests/xe_kunit_helpers.h"
#include "tests/xe_pci_test.h"
#include "xe_pat.h"
#include "xe_pci.h"
#include "xe_pm.h"
static bool sanity_fence_failed(struct xe_device *xe, struct dma_fence *fence,
const char *str, struct kunit *test)
{
long ret;
if (IS_ERR(fence)) {
KUNIT_FAIL(test, "Failed to create fence for %s: %li\n", str,
PTR_ERR(fence));
return true;
}
if (!fence)
return true;
ret = dma_fence_wait_timeout(fence, false, 5 * HZ);
if (ret <= 0) {
KUNIT_FAIL(test, "Fence timed out for %s: %li\n", str, ret);
return true;
}
return false;
}
static int run_sanity_job(struct xe_migrate *m, struct xe_device *xe,
struct xe_bb *bb, u32 second_idx, const char *str,
struct kunit *test)
{
u64 batch_base = xe_migrate_batch_base(m, xe->info.has_usm);
struct xe_sched_job *job = xe_bb_create_migration_job(m->q, bb,
batch_base,
second_idx);
struct dma_fence *fence;
if (IS_ERR(job)) {
KUNIT_FAIL(test, "Failed to allocate fake pt: %li\n",
PTR_ERR(job));
return PTR_ERR(job);
}
xe_sched_job_arm(job);
fence = dma_fence_get(&job->drm.s_fence->finished);
xe_sched_job_push(job);
if (sanity_fence_failed(xe, fence, str, test))
return -ETIMEDOUT;
dma_fence_put(fence);
kunit_info(test, "%s: Job completed\n", str);
return 0;
}
#define check(_retval, _expected, str, _test) \
do { if ((_retval) != (_expected)) { \
KUNIT_FAIL(_test, "Sanity check failed: " str \
" expected %llx, got %llx\n", \
(u64)(_expected), (u64)(_retval)); \
} } while (0)
static void test_copy(struct xe_migrate *m, struct xe_bo *bo,
struct kunit *test, u32 region, struct drm_exec *exec)
{
struct xe_device *xe = tile_to_xe(m->tile);
u64 retval, expected = 0;
bool big = xe_bo_size(bo) >= SZ_2M;
struct dma_fence *fence;
const char *str = big ? "Copying big bo" : "Copying small bo";
int err;
struct xe_bo *remote = xe_bo_create_locked(xe, m->tile, NULL,
xe_bo_size(bo),
ttm_bo_type_kernel,
region |
XE_BO_FLAG_NEEDS_CPU_ACCESS |
XE_BO_FLAG_PINNED,
exec);
if (IS_ERR(remote)) {
KUNIT_FAIL(test, "Failed to allocate remote bo for %s: %pe\n",
str, remote);
return;
}
err = xe_bo_validate(remote, NULL, false, exec);
if (err) {
KUNIT_FAIL(test, "Failed to validate system bo for %s: %i\n",
str, err);
goto out_unlock;
}
err = xe_bo_vmap(remote);
if (err) {
KUNIT_FAIL(test, "Failed to vmap system bo for %s: %i\n",
str, err);
goto out_unlock;
}
xe_map_memset(xe, &remote->vmap, 0, 0xd0, xe_bo_size(remote));
fence = xe_migrate_clear(m, remote, remote->ttm.resource,
XE_MIGRATE_CLEAR_FLAG_FULL);
if (!sanity_fence_failed(xe, fence, big ? "Clearing remote big bo" :
"Clearing remote small bo", test)) {
retval = xe_map_rd(xe, &remote->vmap, 0, u64);
check(retval, expected, "remote first offset should be cleared",
test);
retval = xe_map_rd(xe, &remote->vmap, xe_bo_size(remote) - 8, u64);
check(retval, expected, "remote last offset should be cleared",
test);
}
dma_fence_put(fence);
/* Try to copy 0xc0 from remote to vram with 2MB or 64KiB/4KiB pages */
xe_map_memset(xe, &remote->vmap, 0, 0xc0, xe_bo_size(remote));
xe_map_memset(xe, &bo->vmap, 0, 0xd0, xe_bo_size(bo));
expected = 0xc0c0c0c0c0c0c0c0;
fence = xe_migrate_copy(m, remote, bo, remote->ttm.resource,
bo->ttm.resource, false);
if (!sanity_fence_failed(xe, fence, big ? "Copying big bo remote -> vram" :
"Copying small bo remote -> vram", test)) {
retval = xe_map_rd(xe, &bo->vmap, 0, u64);
check(retval, expected,
"remote -> vram bo first offset should be copied", test);
retval = xe_map_rd(xe, &bo->vmap, xe_bo_size(bo) - 8, u64);
check(retval, expected,
"remote -> vram bo offset should be copied", test);
}
dma_fence_put(fence);
/* And other way around.. slightly hacky.. */
xe_map_memset(xe, &remote->vmap, 0, 0xd0, xe_bo_size(remote));
xe_map_memset(xe, &bo->vmap, 0, 0xc0, xe_bo_size(bo));
fence = xe_migrate_copy(m, bo, remote, bo->ttm.resource,
remote->ttm.resource, false);
if (!sanity_fence_failed(xe, fence, big ? "Copying big bo vram -> remote" :
"Copying small bo vram -> remote", test)) {
retval = xe_map_rd(xe, &remote->vmap, 0, u64);
check(retval, expected,
"vram -> remote bo first offset should be copied", test);
retval = xe_map_rd(xe, &remote->vmap, xe_bo_size(bo) - 8, u64);
check(retval, expected,
"vram -> remote bo last offset should be copied", test);
}
dma_fence_put(fence);
xe_bo_vunmap(remote);
out_unlock:
xe_bo_unlock(remote);
xe_bo_put(remote);
}
static void test_copy_sysmem(struct xe_migrate *m, struct xe_bo *bo,
struct drm_exec *exec, struct kunit *test)
{
test_copy(m, bo, test, XE_BO_FLAG_SYSTEM, exec);
}
static void test_copy_vram(struct xe_migrate *m, struct xe_bo *bo,
struct drm_exec *exec, struct kunit *test)
{
u32 region;
if (bo->ttm.resource->mem_type == XE_PL_SYSTEM)
return;
if (bo->ttm.resource->mem_type == XE_PL_VRAM0)
region = XE_BO_FLAG_VRAM1;
else
region = XE_BO_FLAG_VRAM0;
test_copy(m, bo, test, region, exec);
}
static void xe_migrate_sanity_test(struct xe_migrate *m, struct kunit *test,
struct drm_exec *exec)
{
struct xe_tile *tile = m->tile;
struct xe_device *xe = tile_to_xe(tile);
struct xe_bo *pt, *bo = m->pt_bo, *big, *tiny;
struct xe_res_cursor src_it;
struct dma_fence *fence;
u64 retval, expected;
struct xe_bb *bb;
int err;
u8 id = tile->id;
err = xe_bo_vmap(bo);
if (err) {
KUNIT_FAIL(test, "Failed to vmap our pagetables: %li\n",
PTR_ERR(bo));
return;
}
big = xe_bo_create_pin_map(xe, tile, m->q->vm, SZ_4M,
ttm_bo_type_kernel,
XE_BO_FLAG_VRAM_IF_DGFX(tile),
exec);
if (IS_ERR(big)) {
KUNIT_FAIL(test, "Failed to allocate bo: %li\n", PTR_ERR(big));
goto vunmap;
}
pt = xe_bo_create_pin_map(xe, tile, m->q->vm, XE_PAGE_SIZE,
ttm_bo_type_kernel,
XE_BO_FLAG_VRAM_IF_DGFX(tile),
exec);
if (IS_ERR(pt)) {
KUNIT_FAIL(test, "Failed to allocate fake pt: %li\n",
PTR_ERR(pt));
goto free_big;
}
tiny = xe_bo_create_pin_map(xe, tile, m->q->vm,
2 * SZ_4K,
ttm_bo_type_kernel,
XE_BO_FLAG_VRAM_IF_DGFX(tile),
exec);
if (IS_ERR(tiny)) {
KUNIT_FAIL(test, "Failed to allocate tiny fake pt: %li\n",
PTR_ERR(tiny));
goto free_pt;
}
bb = xe_bb_new(tile->primary_gt, 32, xe->info.has_usm);
if (IS_ERR(bb)) {
KUNIT_FAIL(test, "Failed to create batchbuffer: %li\n",
PTR_ERR(bb));
goto free_tiny;
}
kunit_info(test, "Starting tests, top level PT addr: %lx, special pagetable base addr: %lx\n",
(unsigned long)xe_bo_main_addr(m->q->vm->pt_root[id]->bo, XE_PAGE_SIZE),
(unsigned long)xe_bo_main_addr(m->pt_bo, XE_PAGE_SIZE));
/* First part of the test, are we updating our pagetable bo with a new entry? */
xe_map_wr(xe, &bo->vmap, XE_PAGE_SIZE * (NUM_KERNEL_PDE - 1), u64,
0xdeaddeadbeefbeef);
expected = m->q->vm->pt_ops->pte_encode_bo(pt, 0, xe_cache_pat_idx(xe, XE_CACHE_WB), 0);
if (m->q->vm->flags & XE_VM_FLAG_64K)
expected |= XE_PTE_PS64;
if (xe_bo_is_vram(pt))
xe_res_first(pt->ttm.resource, 0, xe_bo_size(pt), &src_it);
else
xe_res_first_sg(xe_bo_sg(pt), 0, xe_bo_size(pt), &src_it);
emit_pte(m, bb, NUM_KERNEL_PDE - 1, xe_bo_is_vram(pt), false,
&src_it, XE_PAGE_SIZE, pt->ttm.resource);
run_sanity_job(m, xe, bb, bb->len, "Writing PTE for our fake PT", test);
retval = xe_map_rd(xe, &bo->vmap, XE_PAGE_SIZE * (NUM_KERNEL_PDE - 1),
u64);
check(retval, expected, "PTE entry write", test);
/* Now try to write data to our newly mapped 'pagetable', see if it succeeds */
bb->len = 0;
bb->cs[bb->len++] = MI_BATCH_BUFFER_END;
xe_map_wr(xe, &pt->vmap, 0, u32, 0xdeaddead);
expected = 0;
emit_clear(tile->primary_gt, bb, xe_migrate_vm_addr(NUM_KERNEL_PDE - 1, 0), 4, 4,
IS_DGFX(xe));
run_sanity_job(m, xe, bb, 1, "Writing to our newly mapped pagetable",
test);
retval = xe_map_rd(xe, &pt->vmap, 0, u32);
check(retval, expected, "Write to PT after adding PTE", test);
/* Sanity checks passed, try the full ones! */
/* Clear a small bo */
kunit_info(test, "Clearing small buffer object\n");
xe_map_memset(xe, &tiny->vmap, 0, 0x22, xe_bo_size(tiny));
expected = 0;
fence = xe_migrate_clear(m, tiny, tiny->ttm.resource,
XE_MIGRATE_CLEAR_FLAG_FULL);
if (sanity_fence_failed(xe, fence, "Clearing small bo", test))
goto out;
dma_fence_put(fence);
retval = xe_map_rd(xe, &tiny->vmap, 0, u32);
check(retval, expected, "Command clear small first value", test);
retval = xe_map_rd(xe, &tiny->vmap, xe_bo_size(tiny) - 4, u32);
check(retval, expected, "Command clear small last value", test);
kunit_info(test, "Copying small buffer object to system\n");
test_copy_sysmem(m, tiny, exec, test);
if (xe->info.tile_count > 1) {
kunit_info(test, "Copying small buffer object to other vram\n");
test_copy_vram(m, tiny, exec, test);
}
/* Clear a big bo */
kunit_info(test, "Clearing big buffer object\n");
xe_map_memset(xe, &big->vmap, 0, 0x11, xe_bo_size(big));
expected = 0;
fence = xe_migrate_clear(m, big, big->ttm.resource,
XE_MIGRATE_CLEAR_FLAG_FULL);
if (sanity_fence_failed(xe, fence, "Clearing big bo", test))
goto out;
dma_fence_put(fence);
retval = xe_map_rd(xe, &big->vmap, 0, u32);
check(retval, expected, "Command clear big first value", test);
retval = xe_map_rd(xe, &big->vmap, xe_bo_size(big) - 4, u32);
check(retval, expected, "Command clear big last value", test);
kunit_info(test, "Copying big buffer object to system\n");
test_copy_sysmem(m, big, exec, test);
if (xe->info.tile_count > 1) {
kunit_info(test, "Copying big buffer object to other vram\n");
test_copy_vram(m, big, exec, test);
}
out:
xe_bb_free(bb, NULL);
free_tiny:
xe_bo_unpin(tiny);
xe_bo_put(tiny);
free_pt:
xe_bo_unpin(pt);
xe_bo_put(pt);
free_big:
xe_bo_unpin(big);
xe_bo_put(big);
vunmap:
xe_bo_vunmap(m->pt_bo);
}
static int migrate_test_run_device(struct xe_device *xe)
{
struct kunit *test = kunit_get_current_test();
struct xe_tile *tile;
int id;
guard(xe_pm_runtime)(xe);
for_each_tile(tile, xe, id) {
struct xe_migrate *m = tile->migrate;
struct drm_exec *exec = XE_VALIDATION_OPT_OUT;
kunit_info(test, "Testing tile id %d.\n", id);
xe_vm_lock(m->q->vm, false);
xe_migrate_sanity_test(m, test, exec);
xe_vm_unlock(m->q->vm);
}
return 0;
}
static void xe_migrate_sanity_kunit(struct kunit *test)
{
struct xe_device *xe = test->priv;
migrate_test_run_device(xe);
}
static struct dma_fence *blt_copy(struct xe_tile *tile,
struct xe_bo *src_bo, struct xe_bo *dst_bo,
bool copy_only_ccs, const char *str, struct kunit *test)
{
struct xe_gt *gt = tile->primary_gt;
struct xe_migrate *m = tile->migrate;
struct xe_device *xe = gt_to_xe(gt);
struct dma_fence *fence = NULL;
u64 size = xe_bo_size(src_bo);
struct xe_res_cursor src_it, dst_it;
struct ttm_resource *src = src_bo->ttm.resource, *dst = dst_bo->ttm.resource;
u64 src_L0_ofs, dst_L0_ofs;
u32 src_L0_pt, dst_L0_pt;
u64 src_L0, dst_L0;
int err;
bool src_is_vram = mem_type_is_vram(src->mem_type);
bool dst_is_vram = mem_type_is_vram(dst->mem_type);
if (!src_is_vram)
xe_res_first_sg(xe_bo_sg(src_bo), 0, size, &src_it);
else
xe_res_first(src, 0, size, &src_it);
if (!dst_is_vram)
xe_res_first_sg(xe_bo_sg(dst_bo), 0, size, &dst_it);
else
xe_res_first(dst, 0, size, &dst_it);
while (size) {
u32 batch_size = 2; /* arb_clear() + MI_BATCH_BUFFER_END */
struct xe_sched_job *job;
struct xe_bb *bb;
u32 flush_flags = 0;
u32 update_idx;
u32 avail_pts = max_mem_transfer_per_pass(xe) / LEVEL0_PAGE_TABLE_ENCODE_SIZE;
u32 pte_flags;
src_L0 = xe_migrate_res_sizes(m, &src_it);
dst_L0 = xe_migrate_res_sizes(m, &dst_it);
src_L0 = min(src_L0, dst_L0);
pte_flags = src_is_vram ? (PTE_UPDATE_FLAG_IS_VRAM |
PTE_UPDATE_FLAG_IS_COMP_PTE) : 0;
batch_size += pte_update_size(m, pte_flags, src, &src_it, &src_L0,
&src_L0_ofs, &src_L0_pt, 0, 0,
avail_pts);
pte_flags = dst_is_vram ? (PTE_UPDATE_FLAG_IS_VRAM |
PTE_UPDATE_FLAG_IS_COMP_PTE) : 0;
batch_size += pte_update_size(m, pte_flags, dst, &dst_it, &src_L0,
&dst_L0_ofs, &dst_L0_pt, 0,
avail_pts, avail_pts);
/* Add copy commands size here */
batch_size += ((copy_only_ccs) ? 0 : emit_copy_cmd_len(xe)) +
((xe_device_has_flat_ccs(xe) && copy_only_ccs) ? EMIT_COPY_CCS_DW : 0);
bb = xe_bb_new(gt, batch_size, xe->info.has_usm);
if (IS_ERR(bb)) {
err = PTR_ERR(bb);
goto err_sync;
}
if (src_is_vram)
xe_res_next(&src_it, src_L0);
else
emit_pte(m, bb, src_L0_pt, src_is_vram, false,
&src_it, src_L0, src);
if (dst_is_vram)
xe_res_next(&dst_it, src_L0);
else
emit_pte(m, bb, dst_L0_pt, dst_is_vram, false,
&dst_it, src_L0, dst);
bb->cs[bb->len++] = MI_BATCH_BUFFER_END;
update_idx = bb->len;
if (!copy_only_ccs)
emit_copy(gt, bb, src_L0_ofs, dst_L0_ofs, src_L0, XE_PAGE_SIZE);
if (copy_only_ccs)
flush_flags = xe_migrate_ccs_copy(m, bb, src_L0_ofs,
src_is_vram, dst_L0_ofs,
dst_is_vram, src_L0, dst_L0_ofs,
copy_only_ccs);
job = xe_bb_create_migration_job(m->q, bb,
xe_migrate_batch_base(m, xe->info.has_usm),
update_idx);
if (IS_ERR(job)) {
err = PTR_ERR(job);
goto err;
}
xe_sched_job_add_migrate_flush(job, flush_flags);
mutex_lock(&m->job_mutex);
xe_sched_job_arm(job);
dma_fence_put(fence);
fence = dma_fence_get(&job->drm.s_fence->finished);
xe_sched_job_push(job);
dma_fence_put(m->fence);
m->fence = dma_fence_get(fence);
mutex_unlock(&m->job_mutex);
xe_bb_free(bb, fence);
size -= src_L0;
continue;
err:
xe_bb_free(bb, NULL);
err_sync:
if (fence) {
dma_fence_wait(fence, false);
dma_fence_put(fence);
}
return ERR_PTR(err);
}
return fence;
}
static void test_migrate(struct xe_device *xe, struct xe_tile *tile,
struct xe_bo *sys_bo, struct xe_bo *vram_bo, struct xe_bo *ccs_bo,
struct drm_exec *exec, struct kunit *test)
{
struct dma_fence *fence;
u64 expected, retval;
long timeout;
long ret;
expected = 0xd0d0d0d0d0d0d0d0;
xe_map_memset(xe, &sys_bo->vmap, 0, 0xd0, xe_bo_size(sys_bo));
fence = blt_copy(tile, sys_bo, vram_bo, false, "Blit copy from sysmem to vram", test);
if (!sanity_fence_failed(xe, fence, "Blit copy from sysmem to vram", test)) {
retval = xe_map_rd(xe, &vram_bo->vmap, 0, u64);
if (retval == expected)
KUNIT_FAIL(test, "Sanity check failed: VRAM must have compressed value\n");
}
dma_fence_put(fence);
kunit_info(test, "Evict vram buffer object\n");
ret = xe_bo_evict(vram_bo, exec);
if (ret) {
KUNIT_FAIL(test, "Failed to evict bo.\n");
return;
}
ret = xe_bo_vmap(vram_bo);
if (ret) {
KUNIT_FAIL(test, "Failed to vmap vram bo: %li\n", ret);
return;
}
retval = xe_map_rd(xe, &vram_bo->vmap, 0, u64);
check(retval, expected, "Clear evicted vram data first value", test);
retval = xe_map_rd(xe, &vram_bo->vmap, xe_bo_size(vram_bo) - 8, u64);
check(retval, expected, "Clear evicted vram data last value", test);
fence = blt_copy(tile, vram_bo, ccs_bo,
true, "Blit surf copy from vram to sysmem", test);
if (!sanity_fence_failed(xe, fence, "Clear ccs buffer data", test)) {
retval = xe_map_rd(xe, &ccs_bo->vmap, 0, u64);
check(retval, 0, "Clear ccs data first value", test);
retval = xe_map_rd(xe, &ccs_bo->vmap, xe_bo_size(ccs_bo) - 8, u64);
check(retval, 0, "Clear ccs data last value", test);
}
dma_fence_put(fence);
kunit_info(test, "Restore vram buffer object\n");
ret = xe_bo_validate(vram_bo, NULL, false, exec);
if (ret) {
KUNIT_FAIL(test, "Failed to validate vram bo for: %li\n", ret);
return;
}
/* Sync all migration blits */
timeout = dma_resv_wait_timeout(vram_bo->ttm.base.resv,
DMA_RESV_USAGE_KERNEL,
true,
5 * HZ);
if (timeout <= 0) {
KUNIT_FAIL(test, "Failed to sync bo eviction.\n");
return;
}
ret = xe_bo_vmap(vram_bo);
if (ret) {
KUNIT_FAIL(test, "Failed to vmap vram bo: %li\n", ret);
return;
}
retval = xe_map_rd(xe, &vram_bo->vmap, 0, u64);
check(retval, expected, "Restored value must be equal to initial value", test);
retval = xe_map_rd(xe, &vram_bo->vmap, xe_bo_size(vram_bo) - 8, u64);
check(retval, expected, "Restored value must be equal to initial value", test);
fence = blt_copy(tile, vram_bo, ccs_bo,
true, "Blit surf copy from vram to sysmem", test);
if (!sanity_fence_failed(xe, fence, "Clear ccs buffer data", test)) {
retval = xe_map_rd(xe, &ccs_bo->vmap, 0, u64);
check(retval, 0, "Clear ccs data first value", test);
retval = xe_map_rd(xe, &ccs_bo->vmap, xe_bo_size(ccs_bo) - 8, u64);
check(retval, 0, "Clear ccs data last value", test);
}
dma_fence_put(fence);
}
static void test_clear(struct xe_device *xe, struct xe_tile *tile,
struct xe_bo *sys_bo, struct xe_bo *vram_bo, struct kunit *test)
{
struct dma_fence *fence;
u64 expected, retval;
expected = 0xd0d0d0d0d0d0d0d0;
xe_map_memset(xe, &sys_bo->vmap, 0, 0xd0, xe_bo_size(sys_bo));
fence = blt_copy(tile, sys_bo, vram_bo, false, "Blit copy from sysmem to vram", test);
if (!sanity_fence_failed(xe, fence, "Blit copy from sysmem to vram", test)) {
retval = xe_map_rd(xe, &vram_bo->vmap, 0, u64);
if (retval == expected)
KUNIT_FAIL(test, "Sanity check failed: VRAM must have compressed value\n");
}
dma_fence_put(fence);
fence = blt_copy(tile, vram_bo, sys_bo, false, "Blit copy from vram to sysmem", test);
if (!sanity_fence_failed(xe, fence, "Blit copy from vram to sysmem", test)) {
retval = xe_map_rd(xe, &sys_bo->vmap, 0, u64);
check(retval, expected, "Decompressed value must be equal to initial value", test);
retval = xe_map_rd(xe, &sys_bo->vmap, xe_bo_size(sys_bo) - 8, u64);
check(retval, expected, "Decompressed value must be equal to initial value", test);
}
dma_fence_put(fence);
kunit_info(test, "Clear vram buffer object\n");
expected = 0x0000000000000000;
fence = xe_migrate_clear(tile->migrate, vram_bo, vram_bo->ttm.resource,
XE_MIGRATE_CLEAR_FLAG_FULL);
if (sanity_fence_failed(xe, fence, "Clear vram_bo", test))
return;
dma_fence_put(fence);
fence = blt_copy(tile, vram_bo, sys_bo,
false, "Blit copy from vram to sysmem", test);
if (!sanity_fence_failed(xe, fence, "Clear main buffer data", test)) {
retval = xe_map_rd(xe, &sys_bo->vmap, 0, u64);
check(retval, expected, "Clear main buffer first value", test);
retval = xe_map_rd(xe, &sys_bo->vmap, xe_bo_size(sys_bo) - 8, u64);
check(retval, expected, "Clear main buffer last value", test);
}
dma_fence_put(fence);
fence = blt_copy(tile, vram_bo, sys_bo,
true, "Blit surf copy from vram to sysmem", test);
if (!sanity_fence_failed(xe, fence, "Clear ccs buffer data", test)) {
retval = xe_map_rd(xe, &sys_bo->vmap, 0, u64);
check(retval, expected, "Clear ccs data first value", test);
retval = xe_map_rd(xe, &sys_bo->vmap, xe_bo_size(sys_bo) - 8, u64);
check(retval, expected, "Clear ccs data last value", test);
}
dma_fence_put(fence);
}
static void validate_ccs_test_run_tile(struct xe_device *xe, struct xe_tile *tile,
struct kunit *test)
{
struct xe_bo *sys_bo, *vram_bo = NULL, *ccs_bo = NULL;
unsigned int bo_flags = XE_BO_FLAG_VRAM_IF_DGFX(tile);
struct drm_exec *exec;
long ret;
sys_bo = xe_bo_create_user(xe, NULL, SZ_4M,
DRM_XE_GEM_CPU_CACHING_WC,
XE_BO_FLAG_SYSTEM |
XE_BO_FLAG_NEEDS_CPU_ACCESS |
XE_BO_FLAG_PINNED, NULL);
if (IS_ERR(sys_bo)) {
KUNIT_FAIL(test, "xe_bo_create() failed with err=%ld\n",
PTR_ERR(sys_bo));
return;
}
exec = XE_VALIDATION_OPT_OUT;
xe_bo_lock(sys_bo, false);
ret = xe_bo_validate(sys_bo, NULL, false, exec);
if (ret) {
KUNIT_FAIL(test, "Failed to validate system bo for: %li\n", ret);
goto free_sysbo;
}
ret = xe_bo_vmap(sys_bo);
if (ret) {
KUNIT_FAIL(test, "Failed to vmap system bo: %li\n", ret);
goto free_sysbo;
}
xe_bo_unlock(sys_bo);
ccs_bo = xe_bo_create_user(xe, NULL, SZ_4M,
DRM_XE_GEM_CPU_CACHING_WC,
bo_flags | XE_BO_FLAG_NEEDS_CPU_ACCESS |
XE_BO_FLAG_PINNED, NULL);
if (IS_ERR(ccs_bo)) {
KUNIT_FAIL(test, "xe_bo_create() failed with err=%ld\n",
PTR_ERR(ccs_bo));
return;
}
xe_bo_lock(ccs_bo, false);
ret = xe_bo_validate(ccs_bo, NULL, false, exec);
if (ret) {
KUNIT_FAIL(test, "Failed to validate system bo for: %li\n", ret);
goto free_ccsbo;
}
ret = xe_bo_vmap(ccs_bo);
if (ret) {
KUNIT_FAIL(test, "Failed to vmap system bo: %li\n", ret);
goto free_ccsbo;
}
xe_bo_unlock(ccs_bo);
vram_bo = xe_bo_create_user(xe, NULL, SZ_4M,
DRM_XE_GEM_CPU_CACHING_WC,
bo_flags | XE_BO_FLAG_NEEDS_CPU_ACCESS |
XE_BO_FLAG_PINNED, NULL);
if (IS_ERR(vram_bo)) {
KUNIT_FAIL(test, "xe_bo_create() failed with err=%ld\n",
PTR_ERR(vram_bo));
return;
}
xe_bo_lock(vram_bo, false);
ret = xe_bo_validate(vram_bo, NULL, false, exec);
if (ret) {
KUNIT_FAIL(test, "Failed to validate vram bo for: %li\n", ret);
goto free_vrambo;
}
ret = xe_bo_vmap(vram_bo);
if (ret) {
KUNIT_FAIL(test, "Failed to vmap vram bo: %li\n", ret);
goto free_vrambo;
}
test_clear(xe, tile, sys_bo, vram_bo, test);
test_migrate(xe, tile, sys_bo, vram_bo, ccs_bo, exec, test);
xe_bo_unlock(vram_bo);
xe_bo_lock(vram_bo, false);
xe_bo_vunmap(vram_bo);
xe_bo_unlock(vram_bo);
xe_bo_lock(ccs_bo, false);
xe_bo_vunmap(ccs_bo);
xe_bo_unlock(ccs_bo);
xe_bo_lock(sys_bo, false);
xe_bo_vunmap(sys_bo);
xe_bo_unlock(sys_bo);
free_vrambo:
xe_bo_put(vram_bo);
free_ccsbo:
xe_bo_put(ccs_bo);
free_sysbo:
xe_bo_put(sys_bo);
}
static int validate_ccs_test_run_device(struct xe_device *xe)
{
struct kunit *test = kunit_get_current_test();
struct xe_tile *tile;
int id;
if (!xe_device_has_flat_ccs(xe)) {
kunit_skip(test, "non-flat-ccs device\n");
return 0;
}
if (!(GRAPHICS_VER(xe) >= 20 && IS_DGFX(xe))) {
kunit_skip(test, "non-xe2 discrete device\n");
return 0;
}
guard(xe_pm_runtime)(xe);
for_each_tile(tile, xe, id)
validate_ccs_test_run_tile(xe, tile, test);
return 0;
}
static void xe_validate_ccs_kunit(struct kunit *test)
{
struct xe_device *xe = test->priv;
validate_ccs_test_run_device(xe);
}
static struct kunit_case xe_migrate_tests[] = {
KUNIT_CASE_PARAM(xe_migrate_sanity_kunit, xe_pci_live_device_gen_param),
KUNIT_CASE_PARAM(xe_validate_ccs_kunit, xe_pci_live_device_gen_param),
{}
};
VISIBLE_IF_KUNIT
struct kunit_suite xe_migrate_test_suite = {
.name = "xe_migrate",
.test_cases = xe_migrate_tests,
.init = xe_kunit_helper_xe_device_live_test_init,
};
EXPORT_SYMBOL_IF_KUNIT(xe_migrate_test_suite);
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/visibility.h>
#include "tests/xe_kunit_helpers.h"
#include "tests/xe_pci_test.h"
#include "tests/xe_test.h"
#include "xe_device.h"
#include "xe_gt.h"
#include "xe_mocs.h"
#include "xe_pci.h"
#include "xe_pm.h"
struct live_mocs {
struct xe_mocs_info table;
};
static int live_mocs_init(struct live_mocs *arg, struct xe_gt *gt)
{
unsigned int flags;
struct kunit *test = kunit_get_current_test();
memset(arg, 0, sizeof(*arg));
flags = get_mocs_settings(gt_to_xe(gt), &arg->table);
kunit_info(test, "gt %d", gt->info.id);
kunit_info(test, "gt type %d", gt->info.type);
kunit_info(test, "table size %d", arg->table.table_size);
kunit_info(test, "table uc_index %d", arg->table.uc_index);
kunit_info(test, "table num_mocs_regs %d", arg->table.num_mocs_regs);
return flags;
}
static void read_l3cc_table(struct xe_gt *gt,
const struct xe_mocs_info *info)
{
struct kunit *test = kunit_get_current_test();
u32 l3cc, l3cc_expected;
unsigned int i;
u32 reg_val;
CLASS(xe_force_wake, fw_ref)(gt_to_fw(gt), XE_FORCEWAKE_ALL);
if (!xe_force_wake_ref_has_domain(fw_ref.domains, XE_FORCEWAKE_ALL))
KUNIT_FAIL_AND_ABORT(test, "Forcewake Failed.\n");
for (i = 0; i < info->num_mocs_regs; i++) {
if (!(i & 1)) {
if (regs_are_mcr(gt))
reg_val = xe_gt_mcr_unicast_read_any(gt, XEHP_LNCFCMOCS(i >> 1));
else
reg_val = xe_mmio_read32(&gt->mmio, XELP_LNCFCMOCS(i >> 1));
mocs_dbg(gt, "reg_val=0x%x\n", reg_val);
} else {
/* Just reuse value read on previous iteration */
reg_val >>= 16;
}
l3cc_expected = get_entry_l3cc(info, i);
l3cc = reg_val & 0xffff;
mocs_dbg(gt, "[%u] expected=0x%x actual=0x%x\n",
i, l3cc_expected, l3cc);
KUNIT_EXPECT_EQ_MSG(test, l3cc_expected, l3cc,
"l3cc idx=%u has incorrect val.\n", i);
}
}
static void read_mocs_table(struct xe_gt *gt,
const struct xe_mocs_info *info)
{
struct kunit *test = kunit_get_current_test();
u32 mocs, mocs_expected;
unsigned int i;
u32 reg_val;
KUNIT_EXPECT_TRUE_MSG(test, info->unused_entries_index,
"Unused entries index should have been defined\n");
CLASS(xe_force_wake, fw_ref)(gt_to_fw(gt), XE_FW_GT);
KUNIT_ASSERT_NE_MSG(test, fw_ref.domains, 0, "Forcewake Failed.\n");
for (i = 0; i < info->num_mocs_regs; i++) {
if (regs_are_mcr(gt))
reg_val = xe_gt_mcr_unicast_read_any(gt, XEHP_GLOBAL_MOCS(i));
else
reg_val = xe_mmio_read32(&gt->mmio, XELP_GLOBAL_MOCS(i));
mocs_expected = get_entry_control(info, i);
mocs = reg_val;
mocs_dbg(gt, "[%u] expected=0x%x actual=0x%x\n",
i, mocs_expected, mocs);
KUNIT_EXPECT_EQ_MSG(test, mocs_expected, mocs,
"mocs reg 0x%x has incorrect val.\n", i);
}
}
static int mocs_kernel_test_run_device(struct xe_device *xe)
{
/* Basic check the system is configured with the expected mocs table */
struct live_mocs mocs;
struct xe_gt *gt;
unsigned int flags;
int id;
guard(xe_pm_runtime)(xe);
for_each_gt(gt, xe, id) {
flags = live_mocs_init(&mocs, gt);
if (flags & HAS_GLOBAL_MOCS)
read_mocs_table(gt, &mocs.table);
if (flags & HAS_LNCF_MOCS)
read_l3cc_table(gt, &mocs.table);
}
return 0;
}
static void xe_live_mocs_kernel_kunit(struct kunit *test)
{
struct xe_device *xe = test->priv;
if (IS_SRIOV_VF(xe))
kunit_skip(test, "this test is N/A for VF");
mocs_kernel_test_run_device(xe);
}
static int mocs_reset_test_run_device(struct xe_device *xe)
{
/* Check the mocs setup is retained over GT reset */
struct live_mocs mocs;
struct xe_gt *gt;
unsigned int flags;
int id;
struct kunit *test = kunit_get_current_test();
guard(xe_pm_runtime)(xe);
for_each_gt(gt, xe, id) {
flags = live_mocs_init(&mocs, gt);
kunit_info(test, "mocs_reset_test before reset\n");
if (flags & HAS_GLOBAL_MOCS)
read_mocs_table(gt, &mocs.table);
if (flags & HAS_LNCF_MOCS)
read_l3cc_table(gt, &mocs.table);
xe_gt_reset(gt);
kunit_info(test, "mocs_reset_test after reset\n");
if (flags & HAS_GLOBAL_MOCS)
read_mocs_table(gt, &mocs.table);
if (flags & HAS_LNCF_MOCS)
read_l3cc_table(gt, &mocs.table);
}
return 0;
}
static void xe_live_mocs_reset_kunit(struct kunit *test)
{
struct xe_device *xe = test->priv;
if (IS_SRIOV_VF(xe))
kunit_skip(test, "this test is N/A for VF");
mocs_reset_test_run_device(xe);
}
static struct kunit_case xe_mocs_tests[] = {
KUNIT_CASE_PARAM(xe_live_mocs_kernel_kunit, xe_pci_live_device_gen_param),
KUNIT_CASE_PARAM(xe_live_mocs_reset_kunit, xe_pci_live_device_gen_param),
{}
};
VISIBLE_IF_KUNIT
struct kunit_suite xe_mocs_test_suite = {
.name = "xe_mocs",
.test_cases = xe_mocs_tests,
.init = xe_kunit_helper_xe_device_live_test_init,
};
EXPORT_SYMBOL_IF_KUNIT(xe_mocs_test_suite);
@@ -0,0 +1,417 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include "tests/xe_pci_test.h"
#include "tests/xe_test.h"
#include <kunit/test-bug.h>
#include <kunit/test.h>
#include <kunit/visibility.h>
#define PLATFORM_CASE(platform__, graphics_step__) \
{ \
.platform = XE_ ## platform__, \
.subplatform = XE_SUBPLATFORM_NONE, \
.step = { .graphics = STEP_ ## graphics_step__ } \
}
#define SUBPLATFORM_CASE(platform__, subplatform__, graphics_step__) \
{ \
.platform = XE_ ## platform__, \
.subplatform = XE_SUBPLATFORM_ ## platform__ ## _ ## subplatform__, \
.step = { .graphics = STEP_ ## graphics_step__ } \
}
#define GMDID_CASE(platform__, graphics_verx100__, graphics_step__, \
media_verx100__, media_step__) \
{ \
.platform = XE_ ## platform__, \
.subplatform = XE_SUBPLATFORM_NONE, \
.graphics_verx100 = graphics_verx100__, \
.media_verx100 = media_verx100__, \
.step = { .graphics = STEP_ ## graphics_step__, \
.media = STEP_ ## media_step__ } \
}
static const struct xe_pci_fake_data cases[] = {
PLATFORM_CASE(TIGERLAKE, B0),
PLATFORM_CASE(DG1, A0),
PLATFORM_CASE(DG1, B0),
PLATFORM_CASE(ALDERLAKE_S, A0),
PLATFORM_CASE(ALDERLAKE_S, B0),
PLATFORM_CASE(ALDERLAKE_S, C0),
PLATFORM_CASE(ALDERLAKE_S, D0),
PLATFORM_CASE(ALDERLAKE_P, A0),
PLATFORM_CASE(ALDERLAKE_P, B0),
PLATFORM_CASE(ALDERLAKE_P, C0),
SUBPLATFORM_CASE(ALDERLAKE_S, RPLS, D0),
SUBPLATFORM_CASE(ALDERLAKE_P, RPLU, E0),
SUBPLATFORM_CASE(DG2, G10, C0),
SUBPLATFORM_CASE(DG2, G11, B1),
SUBPLATFORM_CASE(DG2, G12, A1),
GMDID_CASE(METEORLAKE, 1270, A0, 1300, A0),
GMDID_CASE(METEORLAKE, 1271, A0, 1300, A0),
GMDID_CASE(METEORLAKE, 1274, A0, 1300, A0),
GMDID_CASE(LUNARLAKE, 2004, A0, 2000, A0),
GMDID_CASE(LUNARLAKE, 2004, B0, 2000, A0),
GMDID_CASE(BATTLEMAGE, 2001, A0, 1301, A1),
GMDID_CASE(PANTHERLAKE, 3000, A0, 3000, A0),
};
KUNIT_ARRAY_PARAM(platform, cases, xe_pci_fake_data_desc);
/**
* xe_pci_fake_data_gen_params - Generate struct xe_pci_fake_data parameters
* @test: test context object
* @prev: the pointer to the previous parameter to iterate from or NULL
* @desc: output buffer with minimum size of KUNIT_PARAM_DESC_SIZE
*
* This function prepares struct xe_pci_fake_data parameter.
*
* To be used only as a parameter generator function in &KUNIT_CASE_PARAM.
*
* Return: pointer to the next parameter or NULL if no more parameters
*/
const void *xe_pci_fake_data_gen_params(struct kunit *test, const void *prev, char *desc)
{
return platform_gen_params(test, prev, desc);
}
EXPORT_SYMBOL_IF_KUNIT(xe_pci_fake_data_gen_params);
static const struct xe_device_desc *lookup_desc(enum xe_platform p)
{
const struct xe_device_desc *desc;
const struct pci_device_id *ids;
for (ids = pciidlist; ids->driver_data; ids++) {
desc = (const void *)ids->driver_data;
if (desc->platform == p)
return desc;
}
return NULL;
}
static const struct xe_subplatform_desc *lookup_sub_desc(enum xe_platform p, enum xe_subplatform s)
{
const struct xe_device_desc *desc = lookup_desc(p);
const struct xe_subplatform_desc *spd;
if (desc && desc->subplatforms)
for (spd = desc->subplatforms; spd->subplatform; spd++)
if (spd->subplatform == s)
return spd;
return NULL;
}
static const char *lookup_platform_name(enum xe_platform p)
{
const struct xe_device_desc *desc = lookup_desc(p);
return desc ? desc->platform_name : "INVALID";
}
static const char *__lookup_subplatform_name(enum xe_platform p, enum xe_subplatform s)
{
const struct xe_subplatform_desc *desc = lookup_sub_desc(p, s);
return desc ? desc->name : "INVALID";
}
static const char *lookup_subplatform_name(enum xe_platform p, enum xe_subplatform s)
{
return s == XE_SUBPLATFORM_NONE ? "" : __lookup_subplatform_name(p, s);
}
static const char *subplatform_prefix(enum xe_subplatform s)
{
return s == XE_SUBPLATFORM_NONE ? "" : " ";
}
static const char *step_prefix(enum intel_step step)
{
return step == STEP_NONE ? "" : " ";
}
static const char *step_name(enum intel_step step)
{
return step == STEP_NONE ? "" : xe_step_name(step);
}
static const char *sriov_prefix(enum xe_sriov_mode mode)
{
return mode <= XE_SRIOV_MODE_NONE ? "" : " ";
}
static const char *sriov_name(enum xe_sriov_mode mode)
{
return mode <= XE_SRIOV_MODE_NONE ? "" : xe_sriov_mode_to_string(mode);
}
static const char *lookup_graphics_name(unsigned int verx100)
{
const struct xe_ip *ip = find_graphics_ip(verx100);
return ip ? ip->name : "";
}
static const char *lookup_media_name(unsigned int verx100)
{
const struct xe_ip *ip = find_media_ip(verx100);
return ip ? ip->name : "";
}
/**
* xe_pci_fake_data_desc - Describe struct xe_pci_fake_data parameter
* @param: the &struct xe_pci_fake_data parameter to describe
* @desc: output buffer with minimum size of KUNIT_PARAM_DESC_SIZE
*
* This function prepares description of the struct xe_pci_fake_data parameter.
*
* It is tailored for use in parameterized KUnit tests where parameter generator
* is based on the struct xe_pci_fake_data arrays.
*/
void xe_pci_fake_data_desc(const struct xe_pci_fake_data *param, char *desc)
{
if (param->graphics_verx100 || param->media_verx100)
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s%s%s %u.%02u(%s)%s%s %u.%02u(%s)%s%s%s%s",
lookup_platform_name(param->platform),
subplatform_prefix(param->subplatform),
lookup_subplatform_name(param->platform, param->subplatform),
param->graphics_verx100 / 100, param->graphics_verx100 % 100,
lookup_graphics_name(param->graphics_verx100),
step_prefix(param->step.graphics), step_name(param->step.graphics),
param->media_verx100 / 100, param->media_verx100 % 100,
lookup_media_name(param->media_verx100),
step_prefix(param->step.media), step_name(param->step.media),
sriov_prefix(param->sriov_mode), sriov_name(param->sriov_mode));
else
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s%s%s%s%s%s%s",
lookup_platform_name(param->platform),
subplatform_prefix(param->subplatform),
lookup_subplatform_name(param->platform, param->subplatform),
step_prefix(param->step.graphics), step_name(param->step.graphics),
sriov_prefix(param->sriov_mode), sriov_name(param->sriov_mode));
}
EXPORT_SYMBOL_IF_KUNIT(xe_pci_fake_data_desc);
static void xe_ip_kunit_desc(const struct xe_ip *param, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%u.%02u %s",
param->verx100 / 100, param->verx100 % 100, param->name);
}
/*
* Pre-GMDID Graphics and Media IPs definitions.
*
* Mimic the way GMDID IPs are declared so the same
* param generator can be used for both
*/
static const struct xe_ip pre_gmdid_graphics_ips[] = {
{ 1200, "Xe_LP", &graphics_xelp },
{ 1210, "Xe_LP+", &graphics_xelp },
{ 1255, "Xe_HPG", &graphics_xehpg },
{ 1260, "Xe_HPC", &graphics_xehpc },
};
static const struct xe_ip pre_gmdid_media_ips[] = {
{ 1200, "Xe_M", &media_xem },
{ 1255, "Xe_HPM", &media_xem },
};
KUNIT_ARRAY_PARAM(pre_gmdid_graphics_ip, pre_gmdid_graphics_ips, xe_ip_kunit_desc);
KUNIT_ARRAY_PARAM(pre_gmdid_media_ip, pre_gmdid_media_ips, xe_ip_kunit_desc);
KUNIT_ARRAY_PARAM(graphics_ip, graphics_ips, xe_ip_kunit_desc);
KUNIT_ARRAY_PARAM(media_ip, media_ips, xe_ip_kunit_desc);
static void xe_pci_id_kunit_desc(const struct pci_device_id *param, char *desc)
{
const struct xe_device_desc *dev_desc =
(const struct xe_device_desc *)param->driver_data;
if (dev_desc)
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "0x%X (%s)",
param->device, dev_desc->platform_name);
}
KUNIT_ARRAY_PARAM(pci_id, pciidlist, xe_pci_id_kunit_desc);
/**
* xe_pci_graphics_ip_gen_param - Generate graphics struct xe_ip parameters
* @test: test context object
* @prev: the pointer to the previous parameter to iterate from or NULL
* @desc: output buffer with minimum size of KUNIT_PARAM_DESC_SIZE
*
* This function prepares struct xe_ip parameter.
*
* To be used only as a parameter generator function in &KUNIT_CASE_PARAM.
*
* Return: pointer to the next parameter or NULL if no more parameters
*/
const void *xe_pci_graphics_ip_gen_param(struct kunit *test, const void *prev, char *desc)
{
const void *next = pre_gmdid_graphics_ip_gen_params(test, prev, desc);
if (next)
return next;
if (is_insidevar(prev, pre_gmdid_graphics_ips))
prev = NULL;
return graphics_ip_gen_params(test, prev, desc);
}
EXPORT_SYMBOL_IF_KUNIT(xe_pci_graphics_ip_gen_param);
/**
* xe_pci_media_ip_gen_param - Generate media struct xe_ip parameters
* @test: test context object
* @prev: the pointer to the previous parameter to iterate from or NULL
* @desc: output buffer with minimum size of KUNIT_PARAM_DESC_SIZE
*
* This function prepares struct xe_ip parameter.
*
* To be used only as a parameter generator function in &KUNIT_CASE_PARAM.
*
* Return: pointer to the next parameter or NULL if no more parameters
*/
const void *xe_pci_media_ip_gen_param(struct kunit *test, const void *prev, char *desc)
{
const void *next = pre_gmdid_media_ip_gen_params(test, prev, desc);
if (next)
return next;
if (is_insidevar(prev, pre_gmdid_media_ips))
prev = NULL;
return media_ip_gen_params(test, prev, desc);
}
EXPORT_SYMBOL_IF_KUNIT(xe_pci_media_ip_gen_param);
/**
* xe_pci_id_gen_param - Generate struct pci_device_id parameters
* @test: test context object
* @prev: the pointer to the previous parameter to iterate from or NULL
* @desc: output buffer with minimum size of KUNIT_PARAM_DESC_SIZE
*
* This function prepares struct pci_device_id parameter.
*
* To be used only as a parameter generator function in &KUNIT_CASE_PARAM.
*
* Return: pointer to the next parameter or NULL if no more parameters
*/
const void *xe_pci_id_gen_param(struct kunit *test, const void *prev, char *desc)
{
const struct pci_device_id *pci = pci_id_gen_params(test, prev, desc);
return pci->driver_data ? pci : NULL;
}
EXPORT_SYMBOL_IF_KUNIT(xe_pci_id_gen_param);
static void fake_init_devid(struct xe_device *xe)
{
/* Nothing to do, just keep zero. */
}
static int fake_read_gmdid(struct xe_device *xe, enum xe_gmdid_type type,
u32 *ver, u32 *revid)
{
struct kunit *test = kunit_get_current_test();
struct xe_pci_fake_data *data = test->priv;
if (type == GMDID_MEDIA) {
*ver = data->media_verx100;
*revid = xe_step_to_gmdid(data->step.media);
} else {
*ver = data->graphics_verx100;
*revid = xe_step_to_gmdid(data->step.graphics);
}
return 0;
}
static void fake_xe_info_probe_tile_count(struct xe_device *xe)
{
/* Nothing to do, just use the statically defined value. */
}
int xe_pci_fake_device_init(struct xe_device *xe)
{
struct kunit *test = kunit_get_current_test();
struct xe_pci_fake_data *data = test->priv;
const struct pci_device_id *ent = pciidlist;
const struct xe_device_desc *desc;
const struct xe_subplatform_desc *subplatform_desc;
if (!data) {
desc = (const void *)ent->driver_data;
subplatform_desc = NULL;
goto done;
}
for (ent = pciidlist; ent->device; ent++) {
desc = (const void *)ent->driver_data;
if (desc->platform == data->platform)
break;
}
if (!ent->device)
return -ENODEV;
for (subplatform_desc = desc->subplatforms;
subplatform_desc && subplatform_desc->subplatform;
subplatform_desc++)
if (subplatform_desc->subplatform == data->subplatform)
break;
if (data->subplatform != XE_SUBPLATFORM_NONE && !subplatform_desc)
return -ENODEV;
done:
xe->sriov.__mode = data && data->sriov_mode ?
data->sriov_mode : XE_SRIOV_MODE_NONE;
kunit_activate_static_stub(test, init_devid, fake_init_devid);
kunit_activate_static_stub(test, read_gmdid, fake_read_gmdid);
kunit_activate_static_stub(test, xe_info_probe_tile_count,
fake_xe_info_probe_tile_count);
xe_info_init_early(xe, desc, subplatform_desc);
xe_info_init(xe, desc);
return 0;
}
EXPORT_SYMBOL_IF_KUNIT(xe_pci_fake_device_init);
/**
* xe_pci_live_device_gen_param - Helper to iterate Xe devices as KUnit parameters
* @test: test context object
* @prev: the previously returned value, or NULL for the first iteration
* @desc: the buffer for a parameter name
*
* Iterates over the available Xe devices on the system. Uses the device name
* as the parameter name.
*
* To be used only as a parameter generator function in &KUNIT_CASE_PARAM.
*
* Return: pointer to the next &struct xe_device ready to be used as a parameter
* or NULL if there are no more Xe devices on the system.
*/
const void *xe_pci_live_device_gen_param(struct kunit *test, const void *prev, char *desc)
{
const struct xe_device *xe = prev;
struct device *dev = xe ? xe->drm.dev : NULL;
struct device *next;
next = driver_find_next_device(&xe_pci_driver.driver, dev);
if (dev)
put_device(dev);
if (!next)
return NULL;
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", dev_name(next));
return pdev_to_xe_device(to_pci_dev(next));
}
EXPORT_SYMBOL_IF_KUNIT(xe_pci_live_device_gen_param);
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2023 Intel Corporation
*/
#include <drm/drm_drv.h>
#include <drm/drm_kunit_helpers.h>
#include <kunit/test.h>
#include "tests/xe_test.h"
#include "xe_device.h"
#include "xe_pci_test.h"
#include "xe_pci_types.h"
static void check_graphics_ip(struct kunit *test)
{
const struct xe_ip *param = test->param_value;
const struct xe_graphics_desc *graphics = param->desc;
u64 mask = graphics->hw_engine_mask;
u8 fuse_regs = graphics->num_geometry_xecore_fuse_regs +
graphics->num_compute_xecore_fuse_regs;
/* RCS, CCS, and BCS engines are allowed on the graphics IP */
mask &= ~(XE_HW_ENGINE_RCS_MASK |
XE_HW_ENGINE_CCS_MASK |
XE_HW_ENGINE_BCS_MASK);
/* Any remaining engines are an error */
KUNIT_ASSERT_EQ(test, mask, 0);
/*
* All graphics IP should have at least one geometry and/or compute
* XeCore fuse register.
*/
KUNIT_ASSERT_GE(test, fuse_regs, 1);
}
static void check_media_ip(struct kunit *test)
{
const struct xe_ip *param = test->param_value;
const struct xe_media_desc *media = param->desc;
u64 mask = media->hw_engine_mask;
/* VCS, VECS and GSCCS engines are allowed on the media IP */
mask &= ~(XE_HW_ENGINE_VCS_MASK |
XE_HW_ENGINE_VECS_MASK |
XE_HW_ENGINE_GSCCS_MASK);
/* Any remaining engines are an error */
KUNIT_ASSERT_EQ(test, mask, 0);
}
static void check_platform_desc(struct kunit *test)
{
const struct pci_device_id *pci = test->param_value;
const struct xe_device_desc *desc =
(const struct xe_device_desc *)pci->driver_data;
KUNIT_EXPECT_GT(test, desc->dma_mask_size, 0);
KUNIT_EXPECT_GT(test, (unsigned int)desc->max_gt_per_tile, 0);
KUNIT_EXPECT_LE(test, (unsigned int)desc->max_gt_per_tile, XE_MAX_GT_PER_TILE);
KUNIT_EXPECT_GT(test, desc->va_bits, 0);
KUNIT_EXPECT_LE(test, desc->va_bits, 64);
KUNIT_EXPECT_GT(test, desc->vm_max_level, 0);
}
static struct kunit_case xe_pci_tests[] = {
KUNIT_CASE_PARAM(check_graphics_ip, xe_pci_graphics_ip_gen_param),
KUNIT_CASE_PARAM(check_media_ip, xe_pci_media_ip_gen_param),
KUNIT_CASE_PARAM(check_platform_desc, xe_pci_id_gen_param),
{}
};
static struct kunit_suite xe_pci_test_suite = {
.name = "xe_pci",
.test_cases = xe_pci_tests,
};
kunit_test_suite(xe_pci_test_suite);
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0 AND MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef _XE_PCI_TEST_H_
#define _XE_PCI_TEST_H_
#include <linux/types.h>
#include <kunit/test.h>
#include "xe_platform_types.h"
#include "xe_sriov_types.h"
#include "xe_step_types.h"
struct xe_device;
struct xe_pci_fake_data {
enum xe_sriov_mode sriov_mode;
enum xe_platform platform;
enum xe_subplatform subplatform;
struct xe_step_info step;
u32 graphics_verx100;
u32 media_verx100;
};
int xe_pci_fake_device_init(struct xe_device *xe);
const void *xe_pci_fake_data_gen_params(struct kunit *test, const void *prev, char *desc);
void xe_pci_fake_data_desc(const struct xe_pci_fake_data *param, char *desc);
const void *xe_pci_graphics_ip_gen_param(struct kunit *test, const void *prev, char *desc);
const void *xe_pci_media_ip_gen_param(struct kunit *test, const void *prev, char *desc);
const void *xe_pci_id_gen_param(struct kunit *test, const void *prev, char *desc);
const void *xe_pci_live_device_gen_param(struct kunit *test, const void *prev, char *desc);
#endif
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2026 Intel Corporation
*/
#include "tests/xe_rtp_test.h"
#include <kunit/visibility.h>
/**
* xe_rtp_rule_matches - Check if a set of RTP rule set match against the
* device/GT/hwe
* @xe: The xe device
* @gt: The GT struct (may be NULL)
* @hwe: The hw_engine (may be NULL)
* @rules: The array of rules to match against
* @n_rules: Number of items in @rules
* @err: Pointer (may be NULL) to set error number.
*
* This parses the set of rules and check if they match against the passed
* parameters.
*
* If passed, @err is updated with a non-zero negative error number or zero if
* no errors were found during the parsing/evaluation of rules.
*
* Returns true if there is a match and false if there is no match or if an
* error was found.
*/
bool xe_rtp_rule_matches(const struct xe_device *xe,
struct xe_gt *gt,
struct xe_hw_engine *hwe,
const struct xe_rtp_rule *rules,
unsigned int n_rules,
int *err)
{
return rule_matches_with_err(xe, gt, hwe, rules, n_rules, err);
}
EXPORT_SYMBOL_IF_KUNIT(xe_rtp_rule_matches);
@@ -0,0 +1,678 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/string.h>
#include <linux/xarray.h>
#include <drm/drm_drv.h>
#include <drm/drm_kunit_helpers.h>
#include <kunit/static_stub.h>
#include <kunit/test.h>
#include "regs/xe_gt_regs.h"
#include "regs/xe_reg_defs.h"
#include "xe_device.h"
#include "xe_device_types.h"
#include "xe_gt_mcr.h"
#include "xe_kunit_helpers.h"
#include "xe_pci_test.h"
#include "xe_reg_sr.h"
#include "xe_rtp.h"
#include "xe_rtp_test.h"
#define REGULAR_REG1 XE_REG(1)
#define REGULAR_REG2 XE_REG(2)
#define REGULAR_REG3 XE_REG(3)
#define REGULAR_REG4 XE_REG(4)
#define BAD_REGULAR_REG5 XE_REG(5)
#define MCR_REG1 XE_REG_MCR(1)
#define MCR_REG2 XE_REG_MCR(2)
#define MCR_REG3 XE_REG_MCR(3)
#define BAD_MCR_REG4 XE_REG_MCR(4)
#define MCR_REG5 XE_REG_MCR(5)
#define MASKED_REG1 XE_REG(1, XE_REG_OPTION_MASKED)
#undef XE_REG_MCR
#define XE_REG_MCR(...) XE_REG(__VA_ARGS__, .mcr = 1)
struct rtp_rules_test_case {
const char *name;
bool expected_match;
int expected_err;
const struct xe_rtp_rule *rules;
u8 n_rules;
};
struct rtp_to_sr_test_case {
const char *name;
struct xe_reg expected_reg;
u32 expected_set_bits;
u32 expected_clr_bits;
unsigned long expected_count_sr_entries;
unsigned int expected_sr_errors;
unsigned long expected_active;
const struct xe_rtp_table_sr table;
};
struct rtp_test_case {
const char *name;
unsigned long expected_active;
const struct xe_rtp_table table;
};
static bool fake_xe_gt_mcr_check_reg(struct xe_gt *gt, struct xe_reg reg)
{
/*
* All supported platforms in this imaginary setup will always have REG4
* as a non-MCR register and REG5 as MCR, meaning that BAD_MCR_REG4 and
* BAD_REGULAR_REG5 represent programming errors to be captured by our
* tests.
*/
if (reg.raw == BAD_REGULAR_REG5.raw)
return true;
if (reg.raw == BAD_MCR_REG4.raw)
return false;
return reg.mcr;
}
static bool match_yes(const struct xe_device *xe, const struct xe_gt *gt,
const struct xe_hw_engine *hwe)
{
return true;
}
static bool match_no(const struct xe_device *xe, const struct xe_gt *gt,
const struct xe_hw_engine *hwe)
{
return false;
}
static const struct rtp_rules_test_case rtp_rules_cases[] = {
/*
* Single rules.
*
* TODO: Include other types of rules as well: GRAPHICS_VERSION(),
* MEDIA_VERSION(), etc.
*/
{
.name = "no",
.expected_match = false,
XE_RTP_RULES(FUNC(match_no)),
},
{
.name = "yes",
.expected_match = true,
XE_RTP_RULES(FUNC(match_yes)),
},
/* Conjunctions with 2 operands. */
{
.name = "no-and-no",
.expected_match = false,
XE_RTP_RULES(FUNC(match_no), FUNC(match_no)),
},
{
.name = "no-and-yes",
.expected_match = false,
XE_RTP_RULES(FUNC(match_no), FUNC(match_yes)),
},
{
.name = "yes-and-no",
.expected_match = false,
XE_RTP_RULES(FUNC(match_yes), FUNC(match_no)),
},
{
.name = "yes-and-yes",
.expected_match = true,
XE_RTP_RULES(FUNC(match_yes), FUNC(match_yes)),
},
/* Disjunctions with 2 operands. */
{
.name = "no-or-no",
.expected_match = false,
XE_RTP_RULES(FUNC(match_no), OR, FUNC(match_no)),
},
{
.name = "no-or-yes",
.expected_match = true,
XE_RTP_RULES(FUNC(match_no), OR, FUNC(match_yes)),
},
{
.name = "yes-or-no",
.expected_match = true,
XE_RTP_RULES(FUNC(match_yes), OR, FUNC(match_no)),
},
{
.name = "yes-or-yes",
.expected_match = true,
XE_RTP_RULES(FUNC(match_yes), OR, FUNC(match_yes)),
},
/* Conjunction and disjunctions. */
{
.name = "no-yes-or-yes-no",
.expected_match = false,
XE_RTP_RULES(FUNC(match_no), FUNC(match_yes), OR,
FUNC(match_yes), FUNC(match_no)),
},
{
.name = "no-yes-or-yes-yes",
.expected_match = true,
XE_RTP_RULES(FUNC(match_no), FUNC(match_yes), OR,
FUNC(match_yes), FUNC(match_yes)),
},
{
.name = "yes-yes-or-no-yes",
.expected_match = true,
XE_RTP_RULES(FUNC(match_yes), FUNC(match_yes), OR,
FUNC(match_no), FUNC(match_yes)),
},
{
.name = "yes-yes-or-yes-yes",
.expected_match = true,
XE_RTP_RULES(FUNC(match_yes), FUNC(match_yes), OR,
FUNC(match_yes), FUNC(match_yes)),
},
{
.name = "no-no-or-yes-or-no",
.expected_match = true,
XE_RTP_RULES(FUNC(match_no), FUNC(match_no), OR,
FUNC(match_yes), OR,
FUNC(match_no)),
},
/* Syntax errors. */
{
.name = "or",
.expected_match = false,
.expected_err = -EINVAL,
XE_RTP_RULES(OR),
},
{
.name = "or-yes",
.expected_match = true,
.expected_err = -EINVAL,
XE_RTP_RULES(OR, FUNC(match_yes)),
},
{
.name = "or-no",
.expected_match = false,
.expected_err = -EINVAL,
XE_RTP_RULES(OR, FUNC(match_no)),
},
{
.name = "yes-or",
.expected_match = true,
.expected_err = -EINVAL,
XE_RTP_RULES(FUNC(match_yes), OR),
},
{
.name = "no-or",
.expected_match = false,
.expected_err = -EINVAL,
XE_RTP_RULES(FUNC(match_no), OR),
},
{
.name = "no-or-or-yes",
.expected_match = true,
.expected_err = -EINVAL,
XE_RTP_RULES(FUNC(match_no), OR, OR, FUNC(match_yes)),
},
{
.name = "yes-or-or-no",
.expected_match = true,
.expected_err = -EINVAL,
XE_RTP_RULES(FUNC(match_yes), OR, OR, FUNC(match_no)),
},
{
.name = "no-or-or-no",
.expected_match = false,
.expected_err = -EINVAL,
XE_RTP_RULES(FUNC(match_no), OR, OR, FUNC(match_no)),
},
/* No match because hwe is NULL. */
{
.name = "missing-context-engine-class",
.expected_match = false,
XE_RTP_RULES(ENGINE_CLASS(RENDER)),
},
/*
* Missing context (hwe==NULL) does not cause parsing to stop, hence we
* expect a match.
*/
{
.name = "missing-context-engine-class-or-yes",
.expected_match = true,
XE_RTP_RULES(ENGINE_CLASS(RENDER), OR, FUNC(match_yes)),
},
/*
* Missing context (hwe==NULL) does not cause parsing to stop, hence we
* expect a syntax error.
*/
{
.name = "missing-context-engine-class-or-or-yes",
.expected_match = true,
.expected_err = -EINVAL,
XE_RTP_RULES(ENGINE_CLASS(RENDER), OR, OR, FUNC(match_yes)),
},
};
static void xe_rtp_rules_tests(struct kunit *test)
{
const struct rtp_rules_test_case *param = test->param_value;
struct xe_device *xe = test->priv;
struct xe_gt *gt = xe_device_get_root_tile(xe)->primary_gt;
int err;
bool match;
match = xe_rtp_rule_matches(xe, gt, NULL, param->rules, param->n_rules, &err);
KUNIT_EXPECT_EQ(test, match, param->expected_match);
KUNIT_EXPECT_EQ(test, err, param->expected_err);
}
static const struct rtp_to_sr_test_case rtp_to_sr_cases[] = {
{
.name = "coalesce-same-reg",
.expected_reg = REGULAR_REG1,
.expected_set_bits = REG_BIT(0) | REG_BIT(1),
.expected_clr_bits = REG_BIT(0) | REG_BIT(1),
.expected_active = BIT(0) | BIT(1),
.expected_count_sr_entries = 1,
/* Different bits on the same register: create a single entry */
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("basic-1"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(0)))
},
{ XE_RTP_NAME("basic-2"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(1)))
},
),
},
{
.name = "no-match-no-add",
.expected_reg = REGULAR_REG1,
.expected_set_bits = REG_BIT(0),
.expected_clr_bits = REG_BIT(0),
.expected_active = BIT(0),
.expected_count_sr_entries = 1,
/* Don't coalesce second entry since rules don't match */
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("basic-1"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(0)))
},
{ XE_RTP_NAME("basic-2"),
XE_RTP_RULES(FUNC(match_no)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(1)))
},
),
},
{
.name = "two-regs-two-entries",
.expected_reg = REGULAR_REG1,
.expected_set_bits = REG_BIT(0),
.expected_clr_bits = REG_BIT(0),
.expected_active = BIT(0) | BIT(1),
.expected_count_sr_entries = 2,
/* Same bits on different registers are not coalesced */
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("basic-1"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(0)))
},
{ XE_RTP_NAME("basic-2"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG2, REG_BIT(0)))
},
),
},
{
.name = "clr-one-set-other",
.expected_reg = REGULAR_REG1,
.expected_set_bits = REG_BIT(0),
.expected_clr_bits = REG_BIT(1) | REG_BIT(0),
.expected_active = BIT(0) | BIT(1),
.expected_count_sr_entries = 1,
/* Check clr vs set actions on different bits */
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("basic-1"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(0)))
},
{ XE_RTP_NAME("basic-2"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(CLR(REGULAR_REG1, REG_BIT(1)))
},
),
},
{
#define TEMP_MASK REG_GENMASK(10, 8)
#define TEMP_FIELD REG_FIELD_PREP(TEMP_MASK, 2)
.name = "set-field",
.expected_reg = REGULAR_REG1,
.expected_set_bits = TEMP_FIELD,
.expected_clr_bits = TEMP_MASK,
.expected_active = BIT(0),
.expected_count_sr_entries = 1,
/* Check FIELD_SET works */
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("basic-1"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(FIELD_SET(REGULAR_REG1,
TEMP_MASK, TEMP_FIELD))
},
),
#undef TEMP_MASK
#undef TEMP_FIELD
},
{
.name = "conflict-duplicate",
.expected_reg = REGULAR_REG1,
.expected_set_bits = REG_BIT(0),
.expected_clr_bits = REG_BIT(0),
.expected_active = BIT(0) | BIT(1),
.expected_count_sr_entries = 1,
.expected_sr_errors = 1,
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("basic-1"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(0)))
},
/* drop: setting same values twice */
{ XE_RTP_NAME("basic-2"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(0)))
},
),
},
{
.name = "conflict-not-disjoint",
.expected_reg = REGULAR_REG1,
.expected_set_bits = REG_BIT(0),
.expected_clr_bits = REG_BIT(0),
.expected_active = BIT(0) | BIT(1),
.expected_count_sr_entries = 1,
.expected_sr_errors = 1,
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("basic-1"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(0)))
},
/* drop: bits are not disjoint with previous entries */
{ XE_RTP_NAME("basic-2"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(CLR(REGULAR_REG1, REG_GENMASK(1, 0)))
},
),
},
{
.name = "conflict-reg-type",
.expected_reg = REGULAR_REG1,
.expected_set_bits = REG_BIT(0),
.expected_clr_bits = REG_BIT(0),
.expected_active = BIT(0) | BIT(1) | BIT(2),
.expected_count_sr_entries = 1,
.expected_sr_errors = 2,
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("basic-1"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(REGULAR_REG1, REG_BIT(0)))
},
/* drop: regular vs MCR */
{ XE_RTP_NAME("basic-2"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(MCR_REG1, REG_BIT(1)))
},
/* drop: regular vs masked */
{ XE_RTP_NAME("basic-3"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(MASKED_REG1, REG_BIT(0)))
},
),
},
{
.name = "bad-mcr-reg-forced-to-regular",
.expected_reg = REGULAR_REG4,
.expected_set_bits = REG_BIT(0),
.expected_clr_bits = REG_BIT(0),
.expected_active = BIT(0),
.expected_count_sr_entries = 1,
.expected_sr_errors = 1,
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("bad-mcr-regular-reg"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(BAD_MCR_REG4, REG_BIT(0)))
},
),
},
{
.name = "bad-regular-reg-forced-to-mcr",
.expected_reg = MCR_REG5,
.expected_set_bits = REG_BIT(0),
.expected_clr_bits = REG_BIT(0),
.expected_active = BIT(0),
.expected_count_sr_entries = 1,
.expected_sr_errors = 1,
.table = XE_RTP_TABLE_SR(
{ XE_RTP_NAME("bad-regular-reg"),
XE_RTP_RULES(FUNC(match_yes)),
XE_RTP_ACTIONS(SET(BAD_REGULAR_REG5, REG_BIT(0)))
},
),
},
};
static void xe_rtp_process_to_sr_tests(struct kunit *test)
{
const struct rtp_to_sr_test_case *param = test->param_value;
struct xe_device *xe = test->priv;
struct xe_gt *gt = xe_device_get_root_tile(xe)->primary_gt;
struct xe_reg_sr *reg_sr = &gt->reg_sr;
const struct xe_reg_sr_entry *sre, *sr_entry = NULL;
struct xe_rtp_process_ctx ctx = XE_RTP_PROCESS_CTX_INITIALIZER(gt);
unsigned long idx, count_sr_entries = 0, active = 0;
xe_reg_sr_init(reg_sr, "xe_rtp_to_sr_tests", xe);
xe_rtp_process_ctx_enable_active_tracking(&ctx, &active, param->table.n_entries);
xe_rtp_process_to_sr(&ctx, &param->table, reg_sr, false);
xa_for_each(&reg_sr->xa, idx, sre) {
if (idx == param->expected_reg.addr)
sr_entry = sre;
count_sr_entries++;
}
KUNIT_EXPECT_EQ(test, active, param->expected_active);
KUNIT_EXPECT_EQ(test, count_sr_entries, param->expected_count_sr_entries);
if (count_sr_entries) {
KUNIT_EXPECT_EQ(test, sr_entry->clr_bits, param->expected_clr_bits);
KUNIT_EXPECT_EQ(test, sr_entry->set_bits, param->expected_set_bits);
KUNIT_EXPECT_EQ(test, sr_entry->reg.raw, param->expected_reg.raw);
} else {
KUNIT_EXPECT_NULL(test, sr_entry);
}
KUNIT_EXPECT_EQ(test, reg_sr->errors, param->expected_sr_errors);
}
/*
* Entries below follow the logic used with xe_wa_oob.rules:
* 1) Entries with empty name are OR'ed: all entries marked active since the
* last entry with a name
* 2) There are no action associated with rules
*/
static const struct rtp_test_case rtp_cases[] = {
{
.name = "active1",
.expected_active = BIT(0),
.table = XE_RTP_TABLE(
{ XE_RTP_NAME("r1"),
XE_RTP_RULES(FUNC(match_yes)),
},
),
},
{
.name = "active2",
.expected_active = BIT(0) | BIT(1),
.table = XE_RTP_TABLE(
{ XE_RTP_NAME("r1"),
XE_RTP_RULES(FUNC(match_yes)),
},
{ XE_RTP_NAME("r2"),
XE_RTP_RULES(FUNC(match_yes)),
},
),
},
{
.name = "active-inactive",
.expected_active = BIT(0),
.table = XE_RTP_TABLE(
{ XE_RTP_NAME("r1"),
XE_RTP_RULES(FUNC(match_yes)),
},
{ XE_RTP_NAME("r2"),
XE_RTP_RULES(FUNC(match_no)),
},
),
},
{
.name = "inactive-active",
.expected_active = BIT(1),
.table = XE_RTP_TABLE(
{ XE_RTP_NAME("r1"),
XE_RTP_RULES(FUNC(match_no)),
},
{ XE_RTP_NAME("r2"),
XE_RTP_RULES(FUNC(match_yes)),
},
),
},
{
.name = "inactive-active-inactive",
.expected_active = BIT(1),
.table = XE_RTP_TABLE(
{ XE_RTP_NAME("r1"),
XE_RTP_RULES(FUNC(match_no)),
},
{ XE_RTP_NAME("r2"),
XE_RTP_RULES(FUNC(match_yes)),
},
{ XE_RTP_NAME("r3"),
XE_RTP_RULES(FUNC(match_no)),
},
),
},
{
.name = "inactive-inactive-inactive",
.expected_active = 0,
.table = XE_RTP_TABLE(
{ XE_RTP_NAME("r1"),
XE_RTP_RULES(FUNC(match_no)),
},
{ XE_RTP_NAME("r2"),
XE_RTP_RULES(FUNC(match_no)),
},
{ XE_RTP_NAME("r3"),
XE_RTP_RULES(FUNC(match_no)),
},
),
},
};
static void xe_rtp_process_tests(struct kunit *test)
{
const struct rtp_test_case *param = test->param_value;
struct xe_device *xe = test->priv;
struct xe_gt *gt = xe_device_get_root_tile(xe)->primary_gt;
struct xe_rtp_process_ctx ctx = XE_RTP_PROCESS_CTX_INITIALIZER(gt);
unsigned long active = 0;
xe_rtp_process_ctx_enable_active_tracking(&ctx, &active, param->table.n_entries);
xe_rtp_process(&ctx, &param->table);
KUNIT_EXPECT_EQ(test, active, param->expected_active);
}
static void rtp_rules_desc(const struct rtp_rules_test_case *t, char *desc)
{
strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(rtp_rules, rtp_rules_cases, rtp_rules_desc);
static void rtp_to_sr_desc(const struct rtp_to_sr_test_case *t, char *desc)
{
strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(rtp_to_sr, rtp_to_sr_cases, rtp_to_sr_desc);
static void rtp_desc(const struct rtp_test_case *t, char *desc)
{
strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(rtp, rtp_cases, rtp_desc);
static int xe_rtp_test_init(struct kunit *test)
{
struct xe_device *xe;
struct device *dev;
int ret;
dev = drm_kunit_helper_alloc_device(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
xe = xe_kunit_helper_alloc_xe_device(test, dev);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe);
/* Initialize an empty device */
test->priv = NULL;
ret = xe_pci_fake_device_init(xe);
KUNIT_ASSERT_EQ(test, ret, 0);
xe->drm.dev = dev;
test->priv = xe;
kunit_activate_static_stub(test, xe_gt_mcr_check_reg, fake_xe_gt_mcr_check_reg);
return 0;
}
static void xe_rtp_test_exit(struct kunit *test)
{
struct xe_device *xe = test->priv;
drm_kunit_helper_free_device(test, xe->drm.dev);
}
static struct kunit_case xe_rtp_tests[] = {
KUNIT_CASE_PARAM(xe_rtp_rules_tests, rtp_rules_gen_params),
KUNIT_CASE_PARAM(xe_rtp_process_to_sr_tests, rtp_to_sr_gen_params),
KUNIT_CASE_PARAM(xe_rtp_process_tests, rtp_gen_params),
{}
};
static struct kunit_suite xe_rtp_test_suite = {
.name = "xe_rtp",
.init = xe_rtp_test_init,
.exit = xe_rtp_test_exit,
.test_cases = xe_rtp_tests,
};
kunit_test_suite(xe_rtp_test_suite);
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 AND MIT */
/*
* Copyright © 2026 Intel Corporation
*/
#ifndef _XE_RTP_TEST_H_
#define _XE_RTP_TEST_H_
#include <linux/types.h>
struct xe_device;
struct xe_gt;
struct xe_hw_engine;
struct xe_rtp_rule;
bool xe_rtp_rule_matches(const struct xe_device *xe,
struct xe_gt *gt,
struct xe_hw_engine *hwe,
const struct xe_rtp_rule *rules,
unsigned int n_rules,
int *err);
#endif
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2024-2025 Intel Corporation
*/
#include <kunit/test.h>
#include "xe_device.h"
#include "xe_kunit_helpers.h"
#include "xe_pci_test.h"
static int pf_service_test_init(struct kunit *test)
{
struct xe_pci_fake_data fake = {
.sriov_mode = XE_SRIOV_MODE_PF,
.platform = XE_TIGERLAKE, /* some random platform */
.subplatform = XE_SUBPLATFORM_NONE,
};
struct xe_device *xe;
test->priv = &fake;
xe_kunit_helper_xe_device_test_init(test);
xe = test->priv;
KUNIT_ASSERT_EQ(test, xe_sriov_init(xe), 0);
xe_sriov_pf_service_init(xe);
/*
* sanity check:
* - all supported platforms VF/PF ABI versions must be defined
* - base version can't be newer than latest
*/
KUNIT_ASSERT_NE(test, 0, xe->sriov.pf.service.version.base.major);
KUNIT_ASSERT_NE(test, 0, xe->sriov.pf.service.version.latest.major);
KUNIT_ASSERT_LE(test, xe->sriov.pf.service.version.base.major,
xe->sriov.pf.service.version.latest.major);
if (xe->sriov.pf.service.version.base.major == xe->sriov.pf.service.version.latest.major)
KUNIT_ASSERT_LE(test, xe->sriov.pf.service.version.base.minor,
xe->sriov.pf.service.version.latest.minor);
return 0;
}
static void pf_negotiate_any(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe, VF2PF_HANDSHAKE_MAJOR_ANY,
VF2PF_HANDSHAKE_MINOR_ANY,
&major, &minor));
KUNIT_ASSERT_EQ(test, major, xe->sriov.pf.service.version.latest.major);
KUNIT_ASSERT_EQ(test, minor, xe->sriov.pf.service.version.latest.minor);
}
static void pf_negotiate_base_match(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.base.major,
xe->sriov.pf.service.version.base.minor,
&major, &minor));
KUNIT_ASSERT_EQ(test, major, xe->sriov.pf.service.version.base.major);
KUNIT_ASSERT_EQ(test, minor, xe->sriov.pf.service.version.base.minor);
}
static void pf_negotiate_base_newer(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.base.major,
xe->sriov.pf.service.version.base.minor + 1,
&major, &minor));
KUNIT_ASSERT_EQ(test, major, xe->sriov.pf.service.version.base.major);
KUNIT_ASSERT_GE(test, minor, xe->sriov.pf.service.version.base.minor);
if (xe->sriov.pf.service.version.base.major == xe->sriov.pf.service.version.latest.major)
KUNIT_ASSERT_LE(test, minor, xe->sriov.pf.service.version.latest.minor);
else
KUNIT_FAIL(test, "FIXME: don't know how to test multi-version yet!\n");
}
static void pf_negotiate_base_next(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.base.major + 1, 0,
&major, &minor));
KUNIT_ASSERT_GE(test, major, xe->sriov.pf.service.version.base.major);
KUNIT_ASSERT_LE(test, major, xe->sriov.pf.service.version.latest.major);
if (major == xe->sriov.pf.service.version.latest.major)
KUNIT_ASSERT_LE(test, minor, xe->sriov.pf.service.version.latest.minor);
else
KUNIT_FAIL(test, "FIXME: don't know how to test multi-version yet!\n");
}
static void pf_negotiate_base_older(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
if (!xe->sriov.pf.service.version.base.minor)
kunit_skip(test, "no older minor\n");
KUNIT_ASSERT_NE(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.base.major,
xe->sriov.pf.service.version.base.minor - 1,
&major, &minor));
}
static void pf_negotiate_base_prev(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
KUNIT_ASSERT_NE(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.base.major - 1, 1,
&major, &minor));
}
static void pf_negotiate_latest_match(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.latest.major,
xe->sriov.pf.service.version.latest.minor,
&major, &minor));
KUNIT_ASSERT_EQ(test, major, xe->sriov.pf.service.version.latest.major);
KUNIT_ASSERT_EQ(test, minor, xe->sriov.pf.service.version.latest.minor);
}
static void pf_negotiate_latest_newer(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.latest.major,
xe->sriov.pf.service.version.latest.minor + 1,
&major, &minor));
KUNIT_ASSERT_EQ(test, major, xe->sriov.pf.service.version.latest.major);
KUNIT_ASSERT_EQ(test, minor, xe->sriov.pf.service.version.latest.minor);
}
static void pf_negotiate_latest_next(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.latest.major + 1, 0,
&major, &minor));
KUNIT_ASSERT_EQ(test, major, xe->sriov.pf.service.version.latest.major);
KUNIT_ASSERT_EQ(test, minor, xe->sriov.pf.service.version.latest.minor);
}
static void pf_negotiate_latest_older(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
if (!xe->sriov.pf.service.version.latest.minor)
kunit_skip(test, "no older minor\n");
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.latest.major,
xe->sriov.pf.service.version.latest.minor - 1,
&major, &minor));
KUNIT_ASSERT_EQ(test, major, xe->sriov.pf.service.version.latest.major);
KUNIT_ASSERT_EQ(test, minor, xe->sriov.pf.service.version.latest.minor - 1);
}
static void pf_negotiate_latest_prev(struct kunit *test)
{
struct xe_device *xe = test->priv;
u32 major, minor;
if (xe->sriov.pf.service.version.base.major == xe->sriov.pf.service.version.latest.major)
kunit_skip(test, "no prev major");
KUNIT_ASSERT_EQ(test, 0,
pf_negotiate_version(xe,
xe->sriov.pf.service.version.latest.major - 1,
xe->sriov.pf.service.version.base.minor + 1,
&major, &minor));
KUNIT_ASSERT_EQ(test, major, xe->sriov.pf.service.version.latest.major - 1);
KUNIT_ASSERT_GE(test, major, xe->sriov.pf.service.version.base.major);
}
static struct kunit_case pf_service_test_cases[] = {
KUNIT_CASE(pf_negotiate_any),
KUNIT_CASE(pf_negotiate_base_match),
KUNIT_CASE(pf_negotiate_base_newer),
KUNIT_CASE(pf_negotiate_base_next),
KUNIT_CASE(pf_negotiate_base_older),
KUNIT_CASE(pf_negotiate_base_prev),
KUNIT_CASE(pf_negotiate_latest_match),
KUNIT_CASE(pf_negotiate_latest_newer),
KUNIT_CASE(pf_negotiate_latest_next),
KUNIT_CASE(pf_negotiate_latest_older),
KUNIT_CASE(pf_negotiate_latest_prev),
{}
};
static struct kunit_suite pf_service_suite = {
.name = "pf_service",
.test_cases = pf_service_test_cases,
.init = pf_service_test_init,
};
kunit_test_suite(pf_service_suite);
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0 AND MIT */
/*
* Copyright © 2022 Intel Corporation
*/
#ifndef _XE_TEST_H_
#define _XE_TEST_H_
#include <linux/types.h>
#if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
#include <kunit/test.h>
#include <kunit/test-bug.h>
/*
* Each test that provides a kunit private test structure, place a test id
* here and point the kunit->priv to an embedded struct xe_test_priv.
*/
enum xe_test_priv_id {
XE_TEST_LIVE_DMA_BUF,
XE_TEST_LIVE_MIGRATE,
};
/**
* struct xe_test_priv - Base class for test private info
* @id: enum xe_test_priv_id to identify the subclass.
*/
struct xe_test_priv {
enum xe_test_priv_id id;
};
#define XE_TEST_DECLARE(x) x
#define XE_TEST_ONLY(x) unlikely(x)
/**
* xe_cur_kunit_priv - Obtain the struct xe_test_priv pointed to by
* current->kunit->priv if it exists and is embedded in the expected subclass.
* @id: Id of the expected subclass.
*
* Return: NULL if the process is not a kunit test, and NULL if the
* current kunit->priv pointer is not pointing to an object of the expected
* subclass. A pointer to the embedded struct xe_test_priv otherwise.
*/
static inline struct xe_test_priv *
xe_cur_kunit_priv(enum xe_test_priv_id id)
{
struct xe_test_priv *priv;
if (!kunit_get_current_test())
return NULL;
priv = kunit_get_current_test()->priv;
return priv->id == id ? priv : NULL;
}
#else /* if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST) */
#define XE_TEST_DECLARE(x)
#define XE_TEST_ONLY(x) 0
#define xe_cur_kunit_priv(_id) NULL
#endif
#endif
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/module.h>
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("xe kunit tests");
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2023 Intel Corporation
*/
#include <drm/drm_drv.h>
#include <drm/drm_kunit_helpers.h>
#include <kunit/test.h>
#include "xe_device.h"
#include "xe_gt.h"
#include "xe_gt_mcr.h"
#include "xe_kunit_helpers.h"
#include "xe_pci_test.h"
#include "xe_reg_sr.h"
#include "xe_tuning.h"
#include "xe_wa.h"
static int xe_wa_test_init(struct kunit *test)
{
const struct xe_pci_fake_data *param = test->param_value;
struct xe_pci_fake_data data = *param;
struct device *dev;
struct xe_device *xe;
struct xe_gt *gt;
int id;
int ret;
dev = drm_kunit_helper_alloc_device(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
xe = xe_kunit_helper_alloc_xe_device(test, dev);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe);
test->priv = &data;
ret = xe_pci_fake_device_init(xe);
KUNIT_ASSERT_EQ(test, ret, 0);
/* Needed for sanitize_mcr(). */
for_each_gt(gt, xe, id) {
xe_gt_mcr_init_early(gt);
xe_gt_mmio_init(gt);
}
if (!param->graphics_verx100)
xe->info.step = param->step;
/* TODO: init hw engines for engine/LRC WAs */
xe->drm.dev = dev;
test->priv = xe;
return 0;
}
static void xe_wa_gt(struct kunit *test)
{
struct xe_device *xe = test->priv;
struct xe_gt *gt;
int id;
for_each_gt(gt, xe, id) {
xe_reg_sr_init(&gt->reg_sr, "GT", xe);
xe_wa_process_gt(gt);
xe_tuning_process_gt(gt);
KUNIT_EXPECT_EQ(test, gt->reg_sr.errors, 0);
}
}
static struct kunit_case xe_wa_tests[] = {
KUNIT_CASE_PARAM(xe_wa_gt, xe_pci_fake_data_gen_params),
{}
};
static struct kunit_suite xe_rtp_test_suite = {
.name = "xe_wa",
.init = xe_wa_test_init,
.test_cases = xe_wa_tests,
};
kunit_test_suite(xe_rtp_test_suite);