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,3 @@
CONFIG_KUNIT=y
CONFIG_DRM=y
CONFIG_DRM_TTM_KUNIT_TEST=y
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0 AND MIT
obj-$(CONFIG_DRM_TTM_KUNIT_TEST) += \
ttm_device_test.o \
ttm_pool_test.o \
ttm_resource_test.o \
ttm_tt_test.o \
ttm_bo_test.o \
ttm_bo_validate_test.o \
ttm_mock_manager.o \
ttm_kunit_helpers.o
@@ -0,0 +1,27 @@
TODO
=====
- Add a test case where the only evictable BO is busy
- Update eviction tests so they use parametrized "from" memory type
- Improve mock manager's implementation, e.g. allocate a block of
dummy memory that can be used when testing page mapping functions
- Suggestion: Add test cases with external BOs
- Suggestion: randomize the number and size of tested buffers in
ttm_bo_validate()
- Agree on the naming convention
- Rewrite the mock manager: drop use_tt and manage mock memory using
drm_mm manager
Notes and gotchas
=================
- These tests are built and run with a UML kernel, because
1) We are interested in hardware-independent testing
2) We don't want to have actual DRM devices interacting with TTM
at the same time as the test one. Getting these to work in
parallel would require some time (...and that's a "todo" in itself!)
- Triggering ttm_bo_vm_ops callbacks from KUnit (i.e. kernel) might be
a challenge, but is worth trying. Look at selftests like
i915/gem/selftests/i915_gem_mman.c for inspiration
- The test suite uses UML where ioremap() call returns NULL, meaning that
ttm_bo_ioremap() can't be tested, unless we find a way to stub it
@@ -0,0 +1,637 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/dma-resv.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/ww_mutex.h>
#include <drm/ttm/ttm_resource.h>
#include <drm/ttm/ttm_placement.h>
#include <drm/ttm/ttm_tt.h>
#include "ttm_kunit_helpers.h"
#define BO_SIZE SZ_8K
#ifdef CONFIG_PREEMPT_RT
#define ww_mutex_base_lock(b) rt_mutex_lock(b)
#else
#define ww_mutex_base_lock(b) mutex_lock(b)
#endif
struct ttm_bo_test_case {
const char *description;
bool interruptible;
bool no_wait;
};
static const struct ttm_bo_test_case ttm_bo_reserved_cases[] = {
{
.description = "Cannot be interrupted and sleeps",
.interruptible = false,
.no_wait = false,
},
{
.description = "Cannot be interrupted, locks straight away",
.interruptible = false,
.no_wait = true,
},
{
.description = "Can be interrupted, sleeps",
.interruptible = true,
.no_wait = false,
},
};
static void ttm_bo_init_case_desc(const struct ttm_bo_test_case *t,
char *desc)
{
strscpy(desc, t->description, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(ttm_bo_reserve, ttm_bo_reserved_cases, ttm_bo_init_case_desc);
static void ttm_bo_reserve_optimistic_no_ticket(struct kunit *test)
{
const struct ttm_bo_test_case *params = test->param_value;
struct ttm_buffer_object *bo;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_bo_reserve(bo, params->interruptible, params->no_wait, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
dma_resv_unlock(bo->base.resv);
}
static void ttm_bo_reserve_locked_no_sleep(struct kunit *test)
{
struct ttm_buffer_object *bo;
bool interruptible = false;
bool no_wait = true;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
/* Let's lock it beforehand */
dma_resv_lock(bo->base.resv, NULL);
err = ttm_bo_reserve(bo, interruptible, no_wait, NULL);
dma_resv_unlock(bo->base.resv);
KUNIT_ASSERT_EQ(test, err, -EBUSY);
}
static void ttm_bo_reserve_no_wait_ticket(struct kunit *test)
{
struct ttm_buffer_object *bo;
struct ww_acquire_ctx ctx;
bool interruptible = false;
bool no_wait = true;
int err;
ww_acquire_init(&ctx, &reservation_ww_class);
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_bo_reserve(bo, interruptible, no_wait, &ctx);
KUNIT_ASSERT_EQ(test, err, -EBUSY);
ww_acquire_fini(&ctx);
}
static void ttm_bo_reserve_double_resv(struct kunit *test)
{
struct ttm_buffer_object *bo;
struct ww_acquire_ctx ctx;
bool interruptible = false;
bool no_wait = false;
int err;
ww_acquire_init(&ctx, &reservation_ww_class);
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_bo_reserve(bo, interruptible, no_wait, &ctx);
KUNIT_ASSERT_EQ(test, err, 0);
err = ttm_bo_reserve(bo, interruptible, no_wait, &ctx);
dma_resv_unlock(bo->base.resv);
ww_acquire_fini(&ctx);
KUNIT_ASSERT_EQ(test, err, -EALREADY);
}
/*
* A test case heavily inspired by ww_test_edeadlk_normal(). It injects
* a deadlock by manipulating the sequence number of the context that holds
* dma_resv lock of bo2 so the other context is "wounded" and has to back off
* (indicated by -EDEADLK). The subtest checks if ttm_bo_reserve() properly
* propagates that error.
*/
static void ttm_bo_reserve_deadlock(struct kunit *test)
{
struct ttm_buffer_object *bo1, *bo2;
struct ww_acquire_ctx ctx1, ctx2;
bool interruptible = false;
bool no_wait = false;
int err;
bo1 = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
bo2 = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
ww_acquire_init(&ctx1, &reservation_ww_class);
ww_mutex_base_lock(&bo2->base.resv->lock.base);
/* The deadlock will be caught by WW mutex, don't warn about it */
lock_release(&bo2->base.resv->lock.base.dep_map, 1);
bo2->base.resv->lock.ctx = &ctx2;
ctx2 = ctx1;
ctx2.stamp--; /* Make the context holding the lock younger */
err = ttm_bo_reserve(bo1, interruptible, no_wait, &ctx1);
KUNIT_ASSERT_EQ(test, err, 0);
err = ttm_bo_reserve(bo2, interruptible, no_wait, &ctx1);
KUNIT_ASSERT_EQ(test, err, -EDEADLK);
dma_resv_unlock(bo1->base.resv);
ww_acquire_fini(&ctx1);
}
#if IS_BUILTIN(CONFIG_DRM_TTM_KUNIT_TEST)
struct signal_timer {
struct timer_list timer;
struct ww_acquire_ctx *ctx;
};
static void signal_for_ttm_bo_reserve(struct timer_list *t)
{
struct signal_timer *s_timer = timer_container_of(s_timer, t, timer);
struct task_struct *task = s_timer->ctx->task;
do_send_sig_info(SIGTERM, SEND_SIG_PRIV, task, PIDTYPE_PID);
}
static int threaded_ttm_bo_reserve(void *arg)
{
struct ttm_buffer_object *bo = arg;
struct signal_timer s_timer;
struct ww_acquire_ctx ctx;
bool interruptible = true;
bool no_wait = false;
int err;
ww_acquire_init(&ctx, &reservation_ww_class);
/* Prepare a signal that will interrupt the reservation attempt */
timer_setup_on_stack(&s_timer.timer, &signal_for_ttm_bo_reserve, 0);
s_timer.ctx = &ctx;
mod_timer(&s_timer.timer, msecs_to_jiffies(100));
err = ttm_bo_reserve(bo, interruptible, no_wait, &ctx);
timer_delete_sync(&s_timer.timer);
timer_destroy_on_stack(&s_timer.timer);
ww_acquire_fini(&ctx);
return err;
}
static void ttm_bo_reserve_interrupted(struct kunit *test)
{
struct ttm_buffer_object *bo;
struct task_struct *task;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
task = kthread_create(threaded_ttm_bo_reserve, bo, "ttm-bo-reserve");
if (IS_ERR(task))
KUNIT_FAIL(test, "Couldn't create ttm bo reserve task\n");
/* Take a lock so the threaded reserve has to wait */
dma_resv_lock(bo->base.resv, NULL);
wake_up_process(task);
msleep(20);
err = kthread_stop(task);
dma_resv_unlock(bo->base.resv);
KUNIT_ASSERT_EQ(test, err, -ERESTARTSYS);
}
#endif /* IS_BUILTIN(CONFIG_DRM_TTM_KUNIT_TEST) */
static void ttm_bo_unreserve_basic(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_buffer_object *bo;
struct ttm_device *ttm_dev;
struct ttm_resource *res1, *res2;
struct ttm_place *place;
struct ttm_resource_manager *man;
unsigned int bo_prio = TTM_MAX_BO_PRIORITY - 1;
u32 mem_type = TTM_PL_SYSTEM;
int err;
place = ttm_place_kunit_init(test, mem_type, 0);
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
priv->ttm_dev = ttm_dev;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
bo->priority = bo_prio;
err = ttm_resource_alloc(bo, place, &res1, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
bo->resource = res1;
/* Add a dummy resource to populate LRU */
ttm_resource_alloc(bo, place, &res2, NULL);
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_unreserve(bo);
man = ttm_manager_type(priv->ttm_dev, mem_type);
KUNIT_ASSERT_EQ(test,
list_is_last(&res1->lru.link, &man->lru[bo->priority]), 1);
ttm_resource_free(bo, &res2);
ttm_resource_free(bo, &res1);
}
static void ttm_bo_unreserve_pinned(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_buffer_object *bo;
struct ttm_device *ttm_dev;
struct ttm_resource *res1, *res2;
struct ttm_place *place;
u32 mem_type = TTM_PL_SYSTEM;
int err;
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
priv->ttm_dev = ttm_dev;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
place = ttm_place_kunit_init(test, mem_type, 0);
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_pin(bo);
err = ttm_resource_alloc(bo, place, &res1, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
bo->resource = res1;
/* Add a dummy resource to the pinned list */
err = ttm_resource_alloc(bo, place, &res2, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_EQ(test,
list_is_last(&res2->lru.link, &priv->ttm_dev->unevictable), 1);
ttm_bo_unreserve(bo);
KUNIT_ASSERT_EQ(test,
list_is_last(&res1->lru.link, &priv->ttm_dev->unevictable), 1);
ttm_resource_free(bo, &res1);
ttm_resource_free(bo, &res2);
}
static void ttm_bo_unreserve_bulk(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_lru_bulk_move lru_bulk_move;
struct ttm_lru_bulk_move_pos *pos;
struct ttm_buffer_object *bo1, *bo2;
struct ttm_resource *res1, *res2;
struct ttm_device *ttm_dev;
struct ttm_place *place;
struct dma_resv *resv;
u32 mem_type = TTM_PL_SYSTEM;
unsigned int bo_priority = 0;
int err;
ttm_lru_bulk_move_init(&lru_bulk_move);
place = ttm_place_kunit_init(test, mem_type, 0);
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
resv = kunit_kzalloc(test, sizeof(*resv), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, resv);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
priv->ttm_dev = ttm_dev;
dma_resv_init(resv);
bo1 = ttm_bo_kunit_init(test, test->priv, BO_SIZE, resv);
bo2 = ttm_bo_kunit_init(test, test->priv, BO_SIZE, resv);
dma_resv_lock(bo1->base.resv, NULL);
ttm_bo_set_bulk_move(bo1, &lru_bulk_move);
dma_resv_unlock(bo1->base.resv);
err = ttm_resource_alloc(bo1, place, &res1, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
bo1->resource = res1;
dma_resv_lock(bo2->base.resv, NULL);
ttm_bo_set_bulk_move(bo2, &lru_bulk_move);
dma_resv_unlock(bo2->base.resv);
err = ttm_resource_alloc(bo2, place, &res2, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
bo2->resource = res2;
ttm_bo_reserve(bo1, false, false, NULL);
ttm_bo_unreserve(bo1);
pos = &lru_bulk_move.pos[mem_type][bo_priority];
KUNIT_ASSERT_PTR_EQ(test, res1, pos->last);
ttm_resource_free(bo1, &res1);
ttm_resource_free(bo2, &res2);
dma_resv_fini(resv);
}
static void ttm_bo_fini_basic(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_buffer_object *bo;
struct ttm_resource *res;
struct ttm_device *ttm_dev;
struct ttm_place *place;
u32 mem_type = TTM_PL_SYSTEM;
int err;
place = ttm_place_kunit_init(test, mem_type, 0);
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
priv->ttm_dev = ttm_dev;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
bo->type = ttm_bo_type_device;
err = ttm_resource_alloc(bo, place, &res, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
bo->resource = res;
dma_resv_lock(bo->base.resv, NULL);
err = ttm_tt_create(bo, false);
dma_resv_unlock(bo->base.resv);
KUNIT_EXPECT_EQ(test, err, 0);
ttm_bo_fini(bo);
}
static const char *mock_name(struct dma_fence *f)
{
return "kunit-ttm-bo-put";
}
static const struct dma_fence_ops mock_fence_ops = {
.get_driver_name = mock_name,
.get_timeline_name = mock_name,
};
static void ttm_bo_fini_shared_resv(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_buffer_object *bo;
struct dma_resv *external_resv;
struct dma_fence *fence;
/* A dummy DMA fence lock */
spinlock_t fence_lock;
struct ttm_device *ttm_dev;
int err;
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
priv->ttm_dev = ttm_dev;
external_resv = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, external_resv);
dma_resv_init(external_resv);
fence = kunit_kzalloc(test, sizeof(*fence), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, fence);
spin_lock_init(&fence_lock);
dma_fence_init(fence, &mock_fence_ops, &fence_lock, 0, 0);
dma_resv_lock(external_resv, NULL);
dma_resv_reserve_fences(external_resv, 1);
dma_resv_add_fence(external_resv, fence, DMA_RESV_USAGE_BOOKKEEP);
dma_resv_unlock(external_resv);
dma_fence_signal(fence);
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
bo->type = ttm_bo_type_device;
bo->base.resv = external_resv;
ttm_bo_fini(bo);
}
static void ttm_bo_pin_basic(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_buffer_object *bo;
struct ttm_device *ttm_dev;
unsigned int no_pins = 3;
int err;
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
priv->ttm_dev = ttm_dev;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
for (int i = 0; i < no_pins; i++) {
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_pin(bo);
dma_resv_unlock(bo->base.resv);
}
KUNIT_ASSERT_EQ(test, bo->pin_count, no_pins);
}
static void ttm_bo_pin_unpin_resource(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_lru_bulk_move lru_bulk_move;
struct ttm_lru_bulk_move_pos *pos;
struct ttm_buffer_object *bo;
struct ttm_resource *res;
struct ttm_device *ttm_dev;
struct ttm_place *place;
u32 mem_type = TTM_PL_SYSTEM;
unsigned int bo_priority = 0;
int err;
ttm_lru_bulk_move_init(&lru_bulk_move);
place = ttm_place_kunit_init(test, mem_type, 0);
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
priv->ttm_dev = ttm_dev;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_resource_alloc(bo, place, &res, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
bo->resource = res;
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_set_bulk_move(bo, &lru_bulk_move);
ttm_bo_pin(bo);
dma_resv_unlock(bo->base.resv);
pos = &lru_bulk_move.pos[mem_type][bo_priority];
KUNIT_ASSERT_EQ(test, bo->pin_count, 1);
KUNIT_ASSERT_NULL(test, pos->first);
KUNIT_ASSERT_NULL(test, pos->last);
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_unpin(bo);
dma_resv_unlock(bo->base.resv);
KUNIT_ASSERT_PTR_EQ(test, res, pos->last);
KUNIT_ASSERT_EQ(test, bo->pin_count, 0);
ttm_resource_free(bo, &res);
}
static void ttm_bo_multiple_pin_one_unpin(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_lru_bulk_move lru_bulk_move;
struct ttm_lru_bulk_move_pos *pos;
struct ttm_buffer_object *bo;
struct ttm_resource *res;
struct ttm_device *ttm_dev;
struct ttm_place *place;
u32 mem_type = TTM_PL_SYSTEM;
unsigned int bo_priority = 0;
int err;
ttm_lru_bulk_move_init(&lru_bulk_move);
place = ttm_place_kunit_init(test, mem_type, 0);
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
priv->ttm_dev = ttm_dev;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_resource_alloc(bo, place, &res, NULL);
KUNIT_ASSERT_EQ(test, err, 0);
bo->resource = res;
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_set_bulk_move(bo, &lru_bulk_move);
/* Multiple pins */
ttm_bo_pin(bo);
ttm_bo_pin(bo);
dma_resv_unlock(bo->base.resv);
pos = &lru_bulk_move.pos[mem_type][bo_priority];
KUNIT_ASSERT_EQ(test, bo->pin_count, 2);
KUNIT_ASSERT_NULL(test, pos->first);
KUNIT_ASSERT_NULL(test, pos->last);
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_unpin(bo);
dma_resv_unlock(bo->base.resv);
KUNIT_ASSERT_EQ(test, bo->pin_count, 1);
KUNIT_ASSERT_NULL(test, pos->first);
KUNIT_ASSERT_NULL(test, pos->last);
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_unpin(bo);
dma_resv_unlock(bo->base.resv);
ttm_resource_free(bo, &res);
}
static struct kunit_case ttm_bo_test_cases[] = {
KUNIT_CASE_PARAM(ttm_bo_reserve_optimistic_no_ticket,
ttm_bo_reserve_gen_params),
KUNIT_CASE(ttm_bo_reserve_locked_no_sleep),
KUNIT_CASE(ttm_bo_reserve_no_wait_ticket),
KUNIT_CASE(ttm_bo_reserve_double_resv),
#if IS_BUILTIN(CONFIG_DRM_TTM_KUNIT_TEST)
KUNIT_CASE(ttm_bo_reserve_interrupted),
#endif
KUNIT_CASE(ttm_bo_reserve_deadlock),
KUNIT_CASE(ttm_bo_unreserve_basic),
KUNIT_CASE(ttm_bo_unreserve_pinned),
KUNIT_CASE(ttm_bo_unreserve_bulk),
KUNIT_CASE(ttm_bo_fini_basic),
KUNIT_CASE(ttm_bo_fini_shared_resv),
KUNIT_CASE(ttm_bo_pin_basic),
KUNIT_CASE(ttm_bo_pin_unpin_resource),
KUNIT_CASE(ttm_bo_multiple_pin_one_unpin),
{}
};
static struct kunit_suite ttm_bo_test_suite = {
.name = "ttm_bo",
.init = ttm_test_devices_init,
.exit = ttm_test_devices_fini,
.test_cases = ttm_bo_test_cases,
};
kunit_test_suites(&ttm_bo_test_suite);
MODULE_DESCRIPTION("KUnit tests for ttm_bo APIs");
MODULE_LICENSE("GPL and additional rights");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <drm/ttm/ttm_resource.h>
#include <drm/ttm/ttm_device.h>
#include <drm/ttm/ttm_placement.h>
#include "ttm_kunit_helpers.h"
#include "../ttm_pool_internal.h"
struct ttm_device_test_case {
const char *description;
unsigned int alloc_flags;
bool pools_init_expected;
};
static void ttm_device_init_basic(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_device *ttm_dev;
struct ttm_resource_manager *ttm_sys_man;
int err;
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_EXPECT_PTR_EQ(test, ttm_dev->funcs, &ttm_dev_funcs);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev->wq);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev->man_drv[TTM_PL_SYSTEM]);
ttm_sys_man = &ttm_dev->sysman;
KUNIT_ASSERT_NOT_NULL(test, ttm_sys_man);
KUNIT_EXPECT_TRUE(test, ttm_sys_man->use_tt);
KUNIT_EXPECT_TRUE(test, ttm_sys_man->use_type);
KUNIT_ASSERT_NOT_NULL(test, ttm_sys_man->func);
KUNIT_EXPECT_PTR_EQ(test, ttm_dev->dev_mapping,
priv->drm->anon_inode->i_mapping);
ttm_device_fini(ttm_dev);
}
static void ttm_device_init_multiple(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_device *ttm_devs;
unsigned int i, num_dev = 3;
int err;
ttm_devs = kunit_kcalloc(test, num_dev, sizeof(*ttm_devs), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_devs);
for (i = 0; i < num_dev; i++) {
err = ttm_device_kunit_init(priv, &ttm_devs[i], 0);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_EXPECT_PTR_EQ(test, ttm_devs[i].dev_mapping,
priv->drm->anon_inode->i_mapping);
KUNIT_ASSERT_NOT_NULL(test, ttm_devs[i].wq);
KUNIT_EXPECT_PTR_EQ(test, ttm_devs[i].funcs, &ttm_dev_funcs);
KUNIT_ASSERT_NOT_NULL(test, ttm_devs[i].man_drv[TTM_PL_SYSTEM]);
}
KUNIT_ASSERT_EQ(test, list_count_nodes(&ttm_devs[0].device_list), num_dev);
for (i = 0; i < num_dev; i++)
ttm_device_fini(&ttm_devs[i]);
}
static void ttm_device_fini_basic(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct ttm_device *ttm_dev;
struct ttm_resource_manager *man;
int err;
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
man = ttm_manager_type(ttm_dev, TTM_PL_SYSTEM);
KUNIT_ASSERT_NOT_NULL(test, man);
ttm_device_fini(ttm_dev);
KUNIT_ASSERT_FALSE(test, man->use_type);
KUNIT_ASSERT_TRUE(test, list_empty(&man->lru[0]));
KUNIT_ASSERT_NULL(test, ttm_dev->man_drv[TTM_PL_SYSTEM]);
}
static void ttm_device_init_no_vma_man(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
struct drm_device *drm = priv->drm;
struct ttm_device *ttm_dev;
struct drm_vma_offset_manager *vma_man;
int err;
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
/* Let's pretend there's no VMA manager allocated */
vma_man = drm->vma_offset_manager;
drm->vma_offset_manager = NULL;
err = ttm_device_kunit_init(priv, ttm_dev, 0);
KUNIT_EXPECT_EQ(test, err, -EINVAL);
/* Bring the manager back for a graceful cleanup */
drm->vma_offset_manager = vma_man;
}
static const struct ttm_device_test_case ttm_device_cases[] = {
{
.description = "No DMA allocations, no DMA32 required",
.pools_init_expected = false,
},
{
.description = "DMA allocations, DMA32 required",
.alloc_flags = TTM_ALLOCATION_POOL_USE_DMA_ALLOC |
TTM_ALLOCATION_POOL_USE_DMA32,
.pools_init_expected = true,
},
{
.description = "No DMA allocations, DMA32 required",
.alloc_flags = TTM_ALLOCATION_POOL_USE_DMA32,
.pools_init_expected = false,
},
{
.description = "DMA allocations, no DMA32 required",
.alloc_flags = TTM_ALLOCATION_POOL_USE_DMA_ALLOC,
.pools_init_expected = true,
},
};
static void ttm_device_case_desc(const struct ttm_device_test_case *t, char *desc)
{
strscpy(desc, t->description, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(ttm_device, ttm_device_cases, ttm_device_case_desc);
static void ttm_device_init_pools(struct kunit *test)
{
struct ttm_test_devices *priv = test->priv;
const struct ttm_device_test_case *params = test->param_value;
struct ttm_device *ttm_dev;
struct ttm_pool *pool;
struct ttm_pool_type pt;
int err;
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(priv, ttm_dev, params->alloc_flags);
KUNIT_ASSERT_EQ(test, err, 0);
pool = &ttm_dev->pool;
KUNIT_ASSERT_NOT_NULL(test, pool);
KUNIT_EXPECT_PTR_EQ(test, pool->dev, priv->dev);
KUNIT_EXPECT_EQ(test, pool->alloc_flags, params->alloc_flags);
if (params->pools_init_expected) {
for (int i = 0; i < TTM_NUM_CACHING_TYPES; ++i) {
for (int j = 0; j < NR_PAGE_ORDERS; ++j) {
pt = pool->caching[i].orders[j];
KUNIT_EXPECT_PTR_EQ(test, pt.pool, pool);
KUNIT_EXPECT_EQ(test, pt.caching, i);
KUNIT_EXPECT_EQ(test, pt.order, j);
if (ttm_pool_uses_dma_alloc(pool))
KUNIT_ASSERT_FALSE(test,
list_lru_count(&pt.pages));
}
}
}
ttm_device_fini(ttm_dev);
}
static struct kunit_case ttm_device_test_cases[] = {
KUNIT_CASE(ttm_device_init_basic),
KUNIT_CASE(ttm_device_init_multiple),
KUNIT_CASE(ttm_device_fini_basic),
KUNIT_CASE(ttm_device_init_no_vma_man),
KUNIT_CASE_PARAM(ttm_device_init_pools, ttm_device_gen_params),
{}
};
static struct kunit_suite ttm_device_test_suite = {
.name = "ttm_device",
.init = ttm_test_devices_init,
.exit = ttm_test_devices_fini,
.test_cases = ttm_device_test_cases,
};
kunit_test_suites(&ttm_device_test_suite);
MODULE_DESCRIPTION("KUnit tests for ttm_device APIs");
MODULE_LICENSE("GPL and additional rights");
@@ -0,0 +1,304 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/export.h>
#include <drm/ttm/ttm_tt.h>
#include "ttm_kunit_helpers.h"
static const struct ttm_place sys_place = {
.fpfn = 0,
.lpfn = 0,
.mem_type = TTM_PL_SYSTEM,
.flags = TTM_PL_FLAG_FALLBACK,
};
static const struct ttm_place mock1_place = {
.fpfn = 0,
.lpfn = 0,
.mem_type = TTM_PL_MOCK1,
.flags = TTM_PL_FLAG_FALLBACK,
};
static const struct ttm_place mock2_place = {
.fpfn = 0,
.lpfn = 0,
.mem_type = TTM_PL_MOCK2,
.flags = TTM_PL_FLAG_FALLBACK,
};
static struct ttm_placement sys_placement = {
.num_placement = 1,
.placement = &sys_place,
};
static struct ttm_placement bad_placement = {
.num_placement = 1,
.placement = &mock1_place,
};
static struct ttm_placement mock_placement = {
.num_placement = 1,
.placement = &mock2_place,
};
static struct ttm_tt *ttm_tt_simple_create(struct ttm_buffer_object *bo, u32 page_flags)
{
struct ttm_tt *tt;
tt = kzalloc_obj(*tt);
ttm_tt_init(tt, bo, page_flags, ttm_cached, 0);
return tt;
}
static void ttm_tt_simple_destroy(struct ttm_device *bdev, struct ttm_tt *ttm)
{
kfree(ttm);
}
static int mock_move(struct ttm_buffer_object *bo, bool evict,
struct ttm_operation_ctx *ctx,
struct ttm_resource *new_mem,
struct ttm_place *hop)
{
struct ttm_resource *old_mem = bo->resource;
if (!old_mem || (old_mem->mem_type == TTM_PL_SYSTEM && !bo->ttm)) {
ttm_bo_move_null(bo, new_mem);
return 0;
}
if (bo->resource->mem_type == TTM_PL_VRAM &&
new_mem->mem_type == TTM_PL_SYSTEM) {
hop->mem_type = TTM_PL_TT;
hop->flags = TTM_PL_FLAG_TEMPORARY;
hop->fpfn = 0;
hop->lpfn = 0;
return -EMULTIHOP;
}
if ((old_mem->mem_type == TTM_PL_SYSTEM &&
new_mem->mem_type == TTM_PL_TT) ||
(old_mem->mem_type == TTM_PL_TT &&
new_mem->mem_type == TTM_PL_SYSTEM)) {
ttm_bo_move_null(bo, new_mem);
return 0;
}
return ttm_bo_move_memcpy(bo, ctx, new_mem);
}
static void mock_evict_flags(struct ttm_buffer_object *bo,
struct ttm_placement *placement)
{
switch (bo->resource->mem_type) {
case TTM_PL_VRAM:
case TTM_PL_SYSTEM:
*placement = sys_placement;
break;
case TTM_PL_TT:
*placement = mock_placement;
break;
case TTM_PL_MOCK1:
/* Purge objects coming from this domain */
break;
}
}
static void bad_evict_flags(struct ttm_buffer_object *bo,
struct ttm_placement *placement)
{
*placement = bad_placement;
}
static int ttm_device_kunit_init_with_funcs(struct ttm_test_devices *priv,
struct ttm_device *ttm,
unsigned int alloc_flags,
struct ttm_device_funcs *funcs)
{
struct drm_device *drm = priv->drm;
int err;
err = ttm_device_init(ttm, funcs, drm->dev,
drm->anon_inode->i_mapping,
drm->vma_offset_manager,
alloc_flags);
return err;
}
struct ttm_device_funcs ttm_dev_funcs = {
.ttm_tt_create = ttm_tt_simple_create,
.ttm_tt_destroy = ttm_tt_simple_destroy,
.move = mock_move,
.eviction_valuable = ttm_bo_eviction_valuable,
.evict_flags = mock_evict_flags,
};
EXPORT_SYMBOL_GPL(ttm_dev_funcs);
int ttm_device_kunit_init(struct ttm_test_devices *priv,
struct ttm_device *ttm,
unsigned int alloc_flags)
{
return ttm_device_kunit_init_with_funcs(priv, ttm, alloc_flags,
&ttm_dev_funcs);
}
EXPORT_SYMBOL_GPL(ttm_device_kunit_init);
struct ttm_device_funcs ttm_dev_funcs_bad_evict = {
.ttm_tt_create = ttm_tt_simple_create,
.ttm_tt_destroy = ttm_tt_simple_destroy,
.move = mock_move,
.eviction_valuable = ttm_bo_eviction_valuable,
.evict_flags = bad_evict_flags,
};
EXPORT_SYMBOL_GPL(ttm_dev_funcs_bad_evict);
int ttm_device_kunit_init_bad_evict(struct ttm_test_devices *priv,
struct ttm_device *ttm)
{
return ttm_device_kunit_init_with_funcs(priv, ttm, 0,
&ttm_dev_funcs_bad_evict);
}
EXPORT_SYMBOL_GPL(ttm_device_kunit_init_bad_evict);
struct ttm_buffer_object *ttm_bo_kunit_init(struct kunit *test,
struct ttm_test_devices *devs,
size_t size,
struct dma_resv *obj)
{
struct drm_gem_object gem_obj = { };
struct ttm_buffer_object *bo;
int err;
bo = kunit_kzalloc(test, sizeof(*bo), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, bo);
bo->base = gem_obj;
if (obj)
bo->base.resv = obj;
err = drm_gem_object_init(devs->drm, &bo->base, size);
KUNIT_ASSERT_EQ(test, err, 0);
bo->bdev = devs->ttm_dev;
bo->destroy = dummy_ttm_bo_destroy;
kref_init(&bo->kref);
return bo;
}
EXPORT_SYMBOL_GPL(ttm_bo_kunit_init);
struct ttm_place *ttm_place_kunit_init(struct kunit *test, u32 mem_type, u32 flags)
{
struct ttm_place *place;
place = kunit_kzalloc(test, sizeof(*place), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, place);
place->mem_type = mem_type;
place->flags = flags;
return place;
}
EXPORT_SYMBOL_GPL(ttm_place_kunit_init);
void dummy_ttm_bo_destroy(struct ttm_buffer_object *bo)
{
drm_gem_object_release(&bo->base);
}
EXPORT_SYMBOL_GPL(dummy_ttm_bo_destroy);
struct ttm_test_devices *ttm_test_devices_basic(struct kunit *test)
{
struct ttm_test_devices *devs;
devs = kunit_kzalloc(test, sizeof(*devs), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, devs);
devs->dev = drm_kunit_helper_alloc_device(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, devs->dev);
/* Set mask for alloc_coherent mappings to enable ttm_pool_alloc testing */
devs->dev->coherent_dma_mask = -1;
devs->drm = __drm_kunit_helper_alloc_drm_device(test, devs->dev,
sizeof(*devs->drm), 0,
DRIVER_GEM);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, devs->drm);
return devs;
}
EXPORT_SYMBOL_GPL(ttm_test_devices_basic);
struct ttm_test_devices *ttm_test_devices_all(struct kunit *test)
{
struct ttm_test_devices *devs;
struct ttm_device *ttm_dev;
int err;
devs = ttm_test_devices_basic(test);
ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ttm_dev);
err = ttm_device_kunit_init(devs, ttm_dev, 0);
KUNIT_ASSERT_EQ(test, err, 0);
devs->ttm_dev = ttm_dev;
return devs;
}
EXPORT_SYMBOL_GPL(ttm_test_devices_all);
void ttm_test_devices_put(struct kunit *test, struct ttm_test_devices *devs)
{
if (devs->ttm_dev)
ttm_device_fini(devs->ttm_dev);
drm_kunit_helper_free_device(test, devs->dev);
}
EXPORT_SYMBOL_GPL(ttm_test_devices_put);
int ttm_test_devices_init(struct kunit *test)
{
struct ttm_test_devices *priv;
priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, priv);
priv = ttm_test_devices_basic(test);
test->priv = priv;
return 0;
}
EXPORT_SYMBOL_GPL(ttm_test_devices_init);
int ttm_test_devices_all_init(struct kunit *test)
{
struct ttm_test_devices *priv;
priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, priv);
priv = ttm_test_devices_all(test);
test->priv = priv;
return 0;
}
EXPORT_SYMBOL_GPL(ttm_test_devices_all_init);
void ttm_test_devices_fini(struct kunit *test)
{
ttm_test_devices_put(test, test->priv);
}
EXPORT_SYMBOL_GPL(ttm_test_devices_fini);
MODULE_DESCRIPTION("TTM KUnit test helper functions");
MODULE_LICENSE("GPL and additional rights");
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0 AND MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef TTM_KUNIT_HELPERS_H
#define TTM_KUNIT_HELPERS_H
#include <drm/drm_drv.h>
#include <drm/ttm/ttm_device.h>
#include <drm/ttm/ttm_bo.h>
#include <drm/ttm/ttm_placement.h>
#include <drm/drm_kunit_helpers.h>
#include <kunit/test.h>
#define TTM_PL_MOCK1 (TTM_PL_PRIV + 1)
#define TTM_PL_MOCK2 (TTM_PL_PRIV + 2)
extern struct ttm_device_funcs ttm_dev_funcs;
extern struct ttm_device_funcs ttm_dev_funcs_bad_evict;
struct ttm_test_devices {
struct drm_device *drm;
struct device *dev;
struct ttm_device *ttm_dev;
};
/* Building blocks for test-specific init functions */
int ttm_device_kunit_init(struct ttm_test_devices *priv,
struct ttm_device *ttm,
unsigned int alloc_flags);
int ttm_device_kunit_init_bad_evict(struct ttm_test_devices *priv,
struct ttm_device *ttm);
struct ttm_buffer_object *ttm_bo_kunit_init(struct kunit *test,
struct ttm_test_devices *devs,
size_t size,
struct dma_resv *obj);
struct ttm_place *ttm_place_kunit_init(struct kunit *test, u32 mem_type,
u32 flags);
void dummy_ttm_bo_destroy(struct ttm_buffer_object *bo);
struct ttm_test_devices *ttm_test_devices_basic(struct kunit *test);
struct ttm_test_devices *ttm_test_devices_all(struct kunit *test);
void ttm_test_devices_put(struct kunit *test, struct ttm_test_devices *devs);
/* Generic init/fini for tests that only need DRM/TTM devices */
int ttm_test_devices_init(struct kunit *test);
int ttm_test_devices_all_init(struct kunit *test);
void ttm_test_devices_fini(struct kunit *test);
#endif // TTM_KUNIT_HELPERS_H
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/export.h>
#include <linux/module.h>
#include <drm/ttm/ttm_resource.h>
#include <drm/ttm/ttm_device.h>
#include <drm/ttm/ttm_placement.h>
#include "ttm_mock_manager.h"
static inline struct ttm_mock_manager *
to_mock_mgr(struct ttm_resource_manager *man)
{
return container_of(man, struct ttm_mock_manager, man);
}
static inline struct ttm_mock_resource *
to_mock_mgr_resource(struct ttm_resource *res)
{
return container_of(res, struct ttm_mock_resource, base);
}
static int ttm_mock_manager_alloc(struct ttm_resource_manager *man,
struct ttm_buffer_object *bo,
const struct ttm_place *place,
struct ttm_resource **res)
{
struct ttm_mock_manager *manager = to_mock_mgr(man);
struct ttm_mock_resource *mock_res;
struct gpu_buddy *mm = &manager->mm;
u64 lpfn, fpfn, alloc_size;
int err;
mock_res = kzalloc_obj(*mock_res);
if (!mock_res)
return -ENOMEM;
fpfn = 0;
lpfn = man->size;
ttm_resource_init(bo, place, &mock_res->base);
INIT_LIST_HEAD(&mock_res->blocks);
if (place->flags & TTM_PL_FLAG_TOPDOWN)
mock_res->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
if (place->flags & TTM_PL_FLAG_CONTIGUOUS)
mock_res->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
alloc_size = (uint64_t)mock_res->base.size;
mutex_lock(&manager->lock);
err = gpu_buddy_alloc_blocks(mm, fpfn, lpfn, alloc_size,
manager->default_page_size,
&mock_res->blocks,
mock_res->flags);
if (err)
goto error_free_blocks;
mutex_unlock(&manager->lock);
*res = &mock_res->base;
return 0;
error_free_blocks:
gpu_buddy_free_list(mm, &mock_res->blocks, 0);
ttm_resource_fini(man, &mock_res->base);
mutex_unlock(&manager->lock);
return err;
}
static void ttm_mock_manager_free(struct ttm_resource_manager *man,
struct ttm_resource *res)
{
struct ttm_mock_manager *manager = to_mock_mgr(man);
struct ttm_mock_resource *mock_res = to_mock_mgr_resource(res);
struct gpu_buddy *mm = &manager->mm;
mutex_lock(&manager->lock);
gpu_buddy_free_list(mm, &mock_res->blocks, 0);
mutex_unlock(&manager->lock);
ttm_resource_fini(man, res);
kfree(mock_res);
}
static const struct ttm_resource_manager_func ttm_mock_manager_funcs = {
.alloc = ttm_mock_manager_alloc,
.free = ttm_mock_manager_free,
};
int ttm_mock_manager_init(struct ttm_device *bdev, u32 mem_type, u32 size)
{
struct ttm_mock_manager *manager;
struct ttm_resource_manager *base;
int err;
manager = kzalloc_obj(*manager);
if (!manager)
return -ENOMEM;
mutex_init(&manager->lock);
err = gpu_buddy_init(&manager->mm, size, PAGE_SIZE);
if (err) {
kfree(manager);
return err;
}
manager->default_page_size = PAGE_SIZE;
base = &manager->man;
base->func = &ttm_mock_manager_funcs;
base->use_tt = true;
ttm_resource_manager_init(base, bdev, size);
ttm_set_driver_manager(bdev, mem_type, base);
ttm_resource_manager_set_used(base, true);
return 0;
}
EXPORT_SYMBOL_GPL(ttm_mock_manager_init);
void ttm_mock_manager_fini(struct ttm_device *bdev, u32 mem_type)
{
struct ttm_resource_manager *man;
struct ttm_mock_manager *mock_man;
int err;
man = ttm_manager_type(bdev, mem_type);
mock_man = to_mock_mgr(man);
err = ttm_resource_manager_evict_all(bdev, man);
if (err)
return;
ttm_resource_manager_set_used(man, false);
mutex_lock(&mock_man->lock);
gpu_buddy_fini(&mock_man->mm);
mutex_unlock(&mock_man->lock);
ttm_set_driver_manager(bdev, mem_type, NULL);
}
EXPORT_SYMBOL_GPL(ttm_mock_manager_fini);
static int ttm_bad_manager_alloc(struct ttm_resource_manager *man,
struct ttm_buffer_object *bo,
const struct ttm_place *place,
struct ttm_resource **res)
{
return -ENOSPC;
}
static int ttm_busy_manager_alloc(struct ttm_resource_manager *man,
struct ttm_buffer_object *bo,
const struct ttm_place *place,
struct ttm_resource **res)
{
return -EBUSY;
}
static void ttm_bad_manager_free(struct ttm_resource_manager *man,
struct ttm_resource *res)
{
}
static bool ttm_bad_manager_compatible(struct ttm_resource_manager *man,
struct ttm_resource *res,
const struct ttm_place *place,
size_t size)
{
return true;
}
static const struct ttm_resource_manager_func ttm_bad_manager_funcs = {
.alloc = ttm_bad_manager_alloc,
.free = ttm_bad_manager_free,
.compatible = ttm_bad_manager_compatible
};
static const struct ttm_resource_manager_func ttm_bad_busy_manager_funcs = {
.alloc = ttm_busy_manager_alloc,
.free = ttm_bad_manager_free,
.compatible = ttm_bad_manager_compatible
};
int ttm_bad_manager_init(struct ttm_device *bdev, u32 mem_type, u32 size)
{
struct ttm_resource_manager *man;
man = kzalloc_obj(*man);
if (!man)
return -ENOMEM;
man->func = &ttm_bad_manager_funcs;
ttm_resource_manager_init(man, bdev, size);
ttm_set_driver_manager(bdev, mem_type, man);
ttm_resource_manager_set_used(man, true);
return 0;
}
EXPORT_SYMBOL_GPL(ttm_bad_manager_init);
int ttm_busy_manager_init(struct ttm_device *bdev, u32 mem_type, u32 size)
{
struct ttm_resource_manager *man;
ttm_bad_manager_init(bdev, mem_type, size);
man = ttm_manager_type(bdev, mem_type);
man->func = &ttm_bad_busy_manager_funcs;
return 0;
}
EXPORT_SYMBOL_GPL(ttm_busy_manager_init);
void ttm_bad_manager_fini(struct ttm_device *bdev, uint32_t mem_type)
{
struct ttm_resource_manager *man;
man = ttm_manager_type(bdev, mem_type);
ttm_resource_manager_set_used(man, false);
ttm_set_driver_manager(bdev, mem_type, NULL);
kfree(man);
}
EXPORT_SYMBOL_GPL(ttm_bad_manager_fini);
MODULE_DESCRIPTION("KUnit tests for ttm with mock resource managers");
MODULE_LICENSE("GPL and additional rights");
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0 AND MIT */
/*
* Copyright © 2023 Intel Corporation
*/
#ifndef TTM_MOCK_MANAGER_H
#define TTM_MOCK_MANAGER_H
#include <linux/gpu_buddy.h>
struct ttm_mock_manager {
struct ttm_resource_manager man;
struct gpu_buddy mm;
u64 default_page_size;
/* protects allocations of mock buffer objects */
struct mutex lock;
};
struct ttm_mock_resource {
struct ttm_resource base;
struct list_head blocks;
unsigned long flags;
};
int ttm_mock_manager_init(struct ttm_device *bdev, u32 mem_type, u32 size);
int ttm_bad_manager_init(struct ttm_device *bdev, u32 mem_type, u32 size);
int ttm_busy_manager_init(struct ttm_device *bdev, u32 mem_type, u32 size);
void ttm_mock_manager_fini(struct ttm_device *bdev, u32 mem_type);
void ttm_bad_manager_fini(struct ttm_device *bdev, u32 mem_type);
#endif // TTM_MOCK_MANAGER_H
@@ -0,0 +1,431 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/mm.h>
#include <drm/ttm/ttm_tt.h>
#include <drm/ttm/ttm_pool.h>
#include "ttm_kunit_helpers.h"
#include "../ttm_pool_internal.h"
struct ttm_pool_test_case {
const char *description;
unsigned int order;
unsigned int alloc_flags;
};
struct ttm_pool_test_priv {
struct ttm_test_devices *devs;
/* Used to create mock ttm_tts */
struct ttm_buffer_object *mock_bo;
};
static struct ttm_operation_ctx simple_ctx = {
.interruptible = true,
.no_wait_gpu = false,
};
static int ttm_pool_test_init(struct kunit *test)
{
struct ttm_pool_test_priv *priv;
priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, priv);
priv->devs = ttm_test_devices_basic(test);
test->priv = priv;
return 0;
}
static void ttm_pool_test_fini(struct kunit *test)
{
struct ttm_pool_test_priv *priv = test->priv;
ttm_test_devices_put(test, priv->devs);
}
static struct ttm_tt *ttm_tt_kunit_init(struct kunit *test,
u32 page_flags,
enum ttm_caching caching,
size_t size)
{
struct ttm_pool_test_priv *priv = test->priv;
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
int err;
bo = ttm_bo_kunit_init(test, priv->devs, size, NULL);
KUNIT_ASSERT_NOT_NULL(test, bo);
priv->mock_bo = bo;
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
err = ttm_tt_init(tt, priv->mock_bo, page_flags, caching, 0);
KUNIT_ASSERT_EQ(test, err, 0);
return tt;
}
static struct ttm_pool *ttm_pool_pre_populated(struct kunit *test,
size_t size,
enum ttm_caching caching)
{
struct ttm_pool_test_priv *priv = test->priv;
struct ttm_test_devices *devs = priv->devs;
struct ttm_pool *pool;
struct ttm_tt *tt;
int err;
tt = ttm_tt_kunit_init(test, 0, caching, size);
KUNIT_ASSERT_NOT_NULL(test, tt);
pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, pool);
ttm_pool_init(pool, devs->dev, NUMA_NO_NODE, TTM_ALLOCATION_POOL_USE_DMA_ALLOC);
err = ttm_pool_alloc(pool, tt, &simple_ctx);
KUNIT_ASSERT_EQ(test, err, 0);
ttm_pool_free(pool, tt);
ttm_tt_fini(tt);
return pool;
}
static const struct ttm_pool_test_case ttm_pool_basic_cases[] = {
{
.description = "One page",
.order = 0,
},
{
.description = "More than one page",
.order = 2,
},
{
.description = "Above the allocation limit",
.order = MAX_PAGE_ORDER + 1,
},
{
.description = "One page, with coherent DMA mappings enabled",
.order = 0,
.alloc_flags = TTM_ALLOCATION_POOL_USE_DMA_ALLOC,
},
{
.description = "Above the allocation limit, with coherent DMA mappings enabled",
.order = MAX_PAGE_ORDER + 1,
.alloc_flags = TTM_ALLOCATION_POOL_USE_DMA_ALLOC,
},
};
static void ttm_pool_alloc_case_desc(const struct ttm_pool_test_case *t,
char *desc)
{
strscpy(desc, t->description, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(ttm_pool_alloc_basic, ttm_pool_basic_cases,
ttm_pool_alloc_case_desc);
static void ttm_pool_alloc_basic(struct kunit *test)
{
struct ttm_pool_test_priv *priv = test->priv;
struct ttm_test_devices *devs = priv->devs;
const struct ttm_pool_test_case *params = test->param_value;
struct ttm_tt *tt;
struct ttm_pool *pool;
struct page *fst_page, *last_page;
enum ttm_caching caching = ttm_uncached;
unsigned int expected_num_pages = 1 << params->order;
size_t size = expected_num_pages * PAGE_SIZE;
int err;
tt = ttm_tt_kunit_init(test, 0, caching, size);
KUNIT_ASSERT_NOT_NULL(test, tt);
pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, pool);
ttm_pool_init(pool, devs->dev, NUMA_NO_NODE, params->alloc_flags);
KUNIT_ASSERT_PTR_EQ(test, pool->dev, devs->dev);
KUNIT_ASSERT_EQ(test, pool->nid, NUMA_NO_NODE);
KUNIT_ASSERT_EQ(test, pool->alloc_flags, params->alloc_flags);
err = ttm_pool_alloc(pool, tt, &simple_ctx);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_EQ(test, tt->num_pages, expected_num_pages);
fst_page = tt->pages[0];
last_page = tt->pages[tt->num_pages - 1];
if (params->order <= MAX_PAGE_ORDER) {
if (ttm_pool_uses_dma_alloc(pool)) {
KUNIT_ASSERT_NOT_NULL(test, (void *)fst_page->private);
KUNIT_ASSERT_NOT_NULL(test, (void *)last_page->private);
} else {
KUNIT_ASSERT_EQ(test, fst_page->private, params->order);
}
} else {
if (ttm_pool_uses_dma_alloc(pool)) {
KUNIT_ASSERT_NOT_NULL(test, (void *)fst_page->private);
KUNIT_ASSERT_NULL(test, (void *)last_page->private);
} else {
/*
* We expect to alloc one big block, followed by
* order 0 blocks
*/
KUNIT_ASSERT_EQ(test, fst_page->private,
min_t(unsigned int, MAX_PAGE_ORDER,
params->order));
KUNIT_ASSERT_EQ(test, last_page->private, 0);
}
}
ttm_pool_free(pool, tt);
ttm_tt_fini(tt);
ttm_pool_fini(pool);
}
static void ttm_pool_alloc_basic_dma_addr(struct kunit *test)
{
struct ttm_pool_test_priv *priv = test->priv;
struct ttm_test_devices *devs = priv->devs;
const struct ttm_pool_test_case *params = test->param_value;
struct ttm_tt *tt;
struct ttm_pool *pool;
struct ttm_buffer_object *bo;
dma_addr_t dma1, dma2;
enum ttm_caching caching = ttm_uncached;
unsigned int expected_num_pages = 1 << params->order;
size_t size = expected_num_pages * PAGE_SIZE;
int err;
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
bo = ttm_bo_kunit_init(test, devs, size, NULL);
KUNIT_ASSERT_NOT_NULL(test, bo);
err = ttm_sg_tt_init(tt, bo, 0, caching);
KUNIT_ASSERT_EQ(test, err, 0);
pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, pool);
ttm_pool_init(pool, devs->dev, NUMA_NO_NODE, TTM_ALLOCATION_POOL_USE_DMA_ALLOC);
err = ttm_pool_alloc(pool, tt, &simple_ctx);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_EQ(test, tt->num_pages, expected_num_pages);
dma1 = tt->dma_address[0];
dma2 = tt->dma_address[tt->num_pages - 1];
KUNIT_ASSERT_NOT_NULL(test, (void *)(uintptr_t)dma1);
KUNIT_ASSERT_NOT_NULL(test, (void *)(uintptr_t)dma2);
ttm_pool_free(pool, tt);
ttm_tt_fini(tt);
ttm_pool_fini(pool);
}
static void ttm_pool_alloc_order_caching_match(struct kunit *test)
{
struct ttm_tt *tt;
struct ttm_pool *pool;
struct ttm_pool_type *pt;
enum ttm_caching caching = ttm_uncached;
unsigned int order = 0;
size_t size = PAGE_SIZE;
int err;
pool = ttm_pool_pre_populated(test, size, caching);
pt = &pool->caching[caching].orders[order];
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt->pages));
tt = ttm_tt_kunit_init(test, 0, caching, size);
KUNIT_ASSERT_NOT_NULL(test, tt);
err = ttm_pool_alloc(pool, tt, &simple_ctx);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_TRUE(test, !list_lru_count(&pt->pages));
ttm_pool_free(pool, tt);
ttm_tt_fini(tt);
ttm_pool_fini(pool);
}
static void ttm_pool_alloc_caching_mismatch(struct kunit *test)
{
struct ttm_tt *tt;
struct ttm_pool *pool;
struct ttm_pool_type *pt_pool, *pt_tt;
enum ttm_caching tt_caching = ttm_uncached;
enum ttm_caching pool_caching = ttm_cached;
size_t size = PAGE_SIZE;
unsigned int order = 0;
int err;
pool = ttm_pool_pre_populated(test, size, pool_caching);
pt_pool = &pool->caching[pool_caching].orders[order];
pt_tt = &pool->caching[tt_caching].orders[order];
tt = ttm_tt_kunit_init(test, 0, tt_caching, size);
KUNIT_ASSERT_NOT_NULL(test, tt);
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt_pool->pages));
KUNIT_ASSERT_TRUE(test, !list_lru_count(&pt_tt->pages));
err = ttm_pool_alloc(pool, tt, &simple_ctx);
KUNIT_ASSERT_EQ(test, err, 0);
ttm_pool_free(pool, tt);
ttm_tt_fini(tt);
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt_pool->pages));
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt_tt->pages));
ttm_pool_fini(pool);
}
static void ttm_pool_alloc_order_mismatch(struct kunit *test)
{
struct ttm_tt *tt;
struct ttm_pool *pool;
struct ttm_pool_type *pt_pool, *pt_tt;
enum ttm_caching caching = ttm_uncached;
unsigned int order = 2;
size_t fst_size = (1 << order) * PAGE_SIZE;
size_t snd_size = PAGE_SIZE;
int err;
pool = ttm_pool_pre_populated(test, fst_size, caching);
pt_pool = &pool->caching[caching].orders[order];
pt_tt = &pool->caching[caching].orders[0];
tt = ttm_tt_kunit_init(test, 0, caching, snd_size);
KUNIT_ASSERT_NOT_NULL(test, tt);
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt_pool->pages));
KUNIT_ASSERT_TRUE(test, !list_lru_count(&pt_tt->pages));
err = ttm_pool_alloc(pool, tt, &simple_ctx);
KUNIT_ASSERT_EQ(test, err, 0);
ttm_pool_free(pool, tt);
ttm_tt_fini(tt);
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt_pool->pages));
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt_tt->pages));
ttm_pool_fini(pool);
}
static void ttm_pool_free_dma_alloc(struct kunit *test)
{
struct ttm_pool_test_priv *priv = test->priv;
struct ttm_test_devices *devs = priv->devs;
struct ttm_tt *tt;
struct ttm_pool *pool;
struct ttm_pool_type *pt;
enum ttm_caching caching = ttm_uncached;
unsigned int order = 2;
size_t size = (1 << order) * PAGE_SIZE;
tt = ttm_tt_kunit_init(test, 0, caching, size);
KUNIT_ASSERT_NOT_NULL(test, tt);
pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, pool);
ttm_pool_init(pool, devs->dev, NUMA_NO_NODE, TTM_ALLOCATION_POOL_USE_DMA_ALLOC);
ttm_pool_alloc(pool, tt, &simple_ctx);
pt = &pool->caching[caching].orders[order];
KUNIT_ASSERT_TRUE(test, !list_lru_count(&pt->pages));
ttm_pool_free(pool, tt);
ttm_tt_fini(tt);
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt->pages));
ttm_pool_fini(pool);
}
static void ttm_pool_free_no_dma_alloc(struct kunit *test)
{
struct ttm_pool_test_priv *priv = test->priv;
struct ttm_test_devices *devs = priv->devs;
struct ttm_tt *tt;
struct ttm_pool *pool;
enum ttm_caching caching = ttm_uncached;
unsigned int order = 2;
size_t size = (1 << order) * PAGE_SIZE;
tt = ttm_tt_kunit_init(test, 0, caching, size);
KUNIT_ASSERT_NOT_NULL(test, tt);
pool = kunit_kzalloc(test, sizeof(*pool), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, pool);
ttm_pool_init(pool, devs->dev, NUMA_NO_NODE, 0);
ttm_pool_alloc(pool, tt, &simple_ctx);
ttm_pool_free(pool, tt);
ttm_tt_fini(tt);
ttm_pool_fini(pool);
}
static void ttm_pool_fini_basic(struct kunit *test)
{
struct ttm_pool *pool;
struct ttm_pool_type *pt;
enum ttm_caching caching = ttm_uncached;
unsigned int order = 0;
size_t size = PAGE_SIZE;
pool = ttm_pool_pre_populated(test, size, caching);
pt = &pool->caching[caching].orders[order];
KUNIT_ASSERT_FALSE(test, !list_lru_count(&pt->pages));
ttm_pool_fini(pool);
KUNIT_ASSERT_TRUE(test, !list_lru_count(&pt->pages));
}
static struct kunit_case ttm_pool_test_cases[] = {
KUNIT_CASE_PARAM(ttm_pool_alloc_basic, ttm_pool_alloc_basic_gen_params),
KUNIT_CASE_PARAM(ttm_pool_alloc_basic_dma_addr,
ttm_pool_alloc_basic_gen_params),
KUNIT_CASE(ttm_pool_alloc_order_caching_match),
KUNIT_CASE(ttm_pool_alloc_caching_mismatch),
KUNIT_CASE(ttm_pool_alloc_order_mismatch),
KUNIT_CASE(ttm_pool_free_dma_alloc),
KUNIT_CASE(ttm_pool_free_no_dma_alloc),
KUNIT_CASE(ttm_pool_fini_basic),
{}
};
static struct kunit_suite ttm_pool_test_suite = {
.name = "ttm_pool",
.init = ttm_pool_test_init,
.exit = ttm_pool_test_fini,
.test_cases = ttm_pool_test_cases,
};
kunit_test_suites(&ttm_pool_test_suite);
MODULE_DESCRIPTION("KUnit tests for ttm_pool APIs");
MODULE_LICENSE("GPL and additional rights");
@@ -0,0 +1,337 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <drm/ttm/ttm_resource.h>
#include "ttm_kunit_helpers.h"
#define RES_SIZE SZ_4K
#define TTM_PRIV_DUMMY_REG (TTM_NUM_MEM_TYPES - 1)
struct ttm_resource_test_case {
const char *description;
u32 mem_type;
u32 flags;
};
struct ttm_resource_test_priv {
struct ttm_test_devices *devs;
struct ttm_buffer_object *bo;
struct ttm_place *place;
};
static const struct ttm_resource_manager_func ttm_resource_manager_mock_funcs = { };
static int ttm_resource_test_init(struct kunit *test)
{
struct ttm_resource_test_priv *priv;
priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, priv);
priv->devs = ttm_test_devices_all(test);
KUNIT_ASSERT_NOT_NULL(test, priv->devs);
test->priv = priv;
return 0;
}
static void ttm_resource_test_fini(struct kunit *test)
{
struct ttm_resource_test_priv *priv = test->priv;
ttm_test_devices_put(test, priv->devs);
}
static void ttm_init_test_mocks(struct kunit *test,
struct ttm_resource_test_priv *priv,
u32 mem_type, u32 flags)
{
size_t size = RES_SIZE;
/* Make sure we have what we need for a good BO mock */
KUNIT_ASSERT_NOT_NULL(test, priv->devs->ttm_dev);
priv->bo = ttm_bo_kunit_init(test, priv->devs, size, NULL);
priv->place = ttm_place_kunit_init(test, mem_type, flags);
}
static void ttm_init_test_manager(struct kunit *test,
struct ttm_resource_test_priv *priv,
u32 mem_type)
{
struct ttm_device *ttm_dev = priv->devs->ttm_dev;
struct ttm_resource_manager *man;
size_t size = SZ_16K;
man = kunit_kzalloc(test, sizeof(*man), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, man);
man->use_tt = false;
man->func = &ttm_resource_manager_mock_funcs;
ttm_resource_manager_init(man, ttm_dev, size);
ttm_set_driver_manager(ttm_dev, mem_type, man);
ttm_resource_manager_set_used(man, true);
}
static const struct ttm_resource_test_case ttm_resource_cases[] = {
{
.description = "Init resource in TTM_PL_SYSTEM",
.mem_type = TTM_PL_SYSTEM,
},
{
.description = "Init resource in TTM_PL_VRAM",
.mem_type = TTM_PL_VRAM,
},
{
.description = "Init resource in a private placement",
.mem_type = TTM_PRIV_DUMMY_REG,
},
{
.description = "Init resource in TTM_PL_SYSTEM, set placement flags",
.mem_type = TTM_PL_SYSTEM,
.flags = TTM_PL_FLAG_TOPDOWN,
},
};
static void ttm_resource_case_desc(const struct ttm_resource_test_case *t, char *desc)
{
strscpy(desc, t->description, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(ttm_resource, ttm_resource_cases, ttm_resource_case_desc);
static void ttm_resource_init_basic(struct kunit *test)
{
const struct ttm_resource_test_case *params = test->param_value;
struct ttm_resource_test_priv *priv = test->priv;
struct ttm_resource *res;
struct ttm_buffer_object *bo;
struct ttm_place *place;
struct ttm_resource_manager *man;
u64 expected_usage;
ttm_init_test_mocks(test, priv, params->mem_type, params->flags);
bo = priv->bo;
place = priv->place;
if (params->mem_type > TTM_PL_SYSTEM)
ttm_init_test_manager(test, priv, params->mem_type);
res = kunit_kzalloc(test, sizeof(*res), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, res);
man = ttm_manager_type(priv->devs->ttm_dev, place->mem_type);
expected_usage = man->usage + RES_SIZE;
KUNIT_ASSERT_TRUE(test, list_empty(&man->lru[bo->priority]));
ttm_resource_init(bo, place, res);
KUNIT_ASSERT_EQ(test, res->start, 0);
KUNIT_ASSERT_EQ(test, res->size, RES_SIZE);
KUNIT_ASSERT_EQ(test, res->mem_type, place->mem_type);
KUNIT_ASSERT_EQ(test, res->placement, place->flags);
KUNIT_ASSERT_PTR_EQ(test, res->bo, bo);
KUNIT_ASSERT_NULL(test, res->bus.addr);
KUNIT_ASSERT_EQ(test, res->bus.offset, 0);
KUNIT_ASSERT_FALSE(test, res->bus.is_iomem);
KUNIT_ASSERT_EQ(test, res->bus.caching, ttm_cached);
KUNIT_ASSERT_EQ(test, man->usage, expected_usage);
KUNIT_ASSERT_TRUE(test, list_is_singular(&man->lru[bo->priority]));
ttm_resource_fini(man, res);
}
static void ttm_resource_init_pinned(struct kunit *test)
{
struct ttm_resource_test_priv *priv = test->priv;
struct ttm_resource *res;
struct ttm_buffer_object *bo;
struct ttm_place *place;
struct ttm_resource_manager *man;
ttm_init_test_mocks(test, priv, TTM_PL_SYSTEM, 0);
bo = priv->bo;
place = priv->place;
man = ttm_manager_type(priv->devs->ttm_dev, place->mem_type);
res = kunit_kzalloc(test, sizeof(*res), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, res);
KUNIT_ASSERT_TRUE(test, list_empty(&bo->bdev->unevictable));
dma_resv_lock(bo->base.resv, NULL);
ttm_bo_pin(bo);
ttm_resource_init(bo, place, res);
KUNIT_ASSERT_TRUE(test, list_is_singular(&bo->bdev->unevictable));
ttm_bo_unpin(bo);
ttm_resource_fini(man, res);
dma_resv_unlock(bo->base.resv);
KUNIT_ASSERT_TRUE(test, list_empty(&bo->bdev->unevictable));
}
static void ttm_resource_fini_basic(struct kunit *test)
{
struct ttm_resource_test_priv *priv = test->priv;
struct ttm_resource *res;
struct ttm_buffer_object *bo;
struct ttm_place *place;
struct ttm_resource_manager *man;
ttm_init_test_mocks(test, priv, TTM_PL_SYSTEM, 0);
bo = priv->bo;
place = priv->place;
man = ttm_manager_type(priv->devs->ttm_dev, place->mem_type);
res = kunit_kzalloc(test, sizeof(*res), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, res);
ttm_resource_init(bo, place, res);
ttm_resource_fini(man, res);
KUNIT_ASSERT_TRUE(test, list_empty(&res->lru.link));
KUNIT_ASSERT_EQ(test, man->usage, 0);
}
static void ttm_resource_manager_init_basic(struct kunit *test)
{
struct ttm_resource_test_priv *priv = test->priv;
struct ttm_resource_manager *man;
size_t size = SZ_16K;
int i;
man = kunit_kzalloc(test, sizeof(*man), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, man);
ttm_resource_manager_init(man, priv->devs->ttm_dev, size);
KUNIT_ASSERT_PTR_EQ(test, man->bdev, priv->devs->ttm_dev);
KUNIT_ASSERT_EQ(test, man->size, size);
KUNIT_ASSERT_EQ(test, man->usage, 0);
for (i = 0; i < TTM_NUM_MOVE_FENCES; i++)
KUNIT_ASSERT_NULL(test, man->eviction_fences[i]);
for (int i = 0; i < TTM_MAX_BO_PRIORITY; ++i)
KUNIT_ASSERT_TRUE(test, list_empty(&man->lru[i]));
}
static void ttm_resource_manager_usage_basic(struct kunit *test)
{
struct ttm_resource_test_priv *priv = test->priv;
struct ttm_resource *res;
struct ttm_buffer_object *bo;
struct ttm_place *place;
struct ttm_resource_manager *man;
u64 actual_usage;
ttm_init_test_mocks(test, priv, TTM_PL_SYSTEM, TTM_PL_FLAG_TOPDOWN);
bo = priv->bo;
place = priv->place;
res = kunit_kzalloc(test, sizeof(*res), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, res);
man = ttm_manager_type(priv->devs->ttm_dev, place->mem_type);
ttm_resource_init(bo, place, res);
actual_usage = ttm_resource_manager_usage(man);
KUNIT_ASSERT_EQ(test, actual_usage, RES_SIZE);
ttm_resource_fini(man, res);
}
static void ttm_resource_manager_set_used_basic(struct kunit *test)
{
struct ttm_resource_test_priv *priv = test->priv;
struct ttm_resource_manager *man;
man = ttm_manager_type(priv->devs->ttm_dev, TTM_PL_SYSTEM);
KUNIT_ASSERT_TRUE(test, man->use_type);
ttm_resource_manager_set_used(man, false);
KUNIT_ASSERT_FALSE(test, man->use_type);
}
static void ttm_sys_man_alloc_basic(struct kunit *test)
{
struct ttm_resource_test_priv *priv = test->priv;
struct ttm_resource_manager *man;
struct ttm_buffer_object *bo;
struct ttm_place *place;
struct ttm_resource *res;
u32 mem_type = TTM_PL_SYSTEM;
int ret;
ttm_init_test_mocks(test, priv, mem_type, 0);
bo = priv->bo;
place = priv->place;
man = ttm_manager_type(priv->devs->ttm_dev, mem_type);
ret = man->func->alloc(man, bo, place, &res);
KUNIT_ASSERT_EQ(test, ret, 0);
KUNIT_ASSERT_EQ(test, res->size, RES_SIZE);
KUNIT_ASSERT_EQ(test, res->mem_type, mem_type);
KUNIT_ASSERT_PTR_EQ(test, res->bo, bo);
ttm_resource_fini(man, res);
}
static void ttm_sys_man_free_basic(struct kunit *test)
{
struct ttm_resource_test_priv *priv = test->priv;
struct ttm_resource_manager *man;
struct ttm_buffer_object *bo;
struct ttm_place *place;
struct ttm_resource *res;
u32 mem_type = TTM_PL_SYSTEM;
ttm_init_test_mocks(test, priv, mem_type, 0);
bo = priv->bo;
place = priv->place;
res = kunit_kzalloc(test, sizeof(*res), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, res);
ttm_resource_alloc(bo, place, &res, NULL);
man = ttm_manager_type(priv->devs->ttm_dev, mem_type);
man->func->free(man, res);
KUNIT_ASSERT_TRUE(test, list_empty(&man->lru[bo->priority]));
KUNIT_ASSERT_EQ(test, man->usage, 0);
}
static struct kunit_case ttm_resource_test_cases[] = {
KUNIT_CASE_PARAM(ttm_resource_init_basic, ttm_resource_gen_params),
KUNIT_CASE(ttm_resource_init_pinned),
KUNIT_CASE(ttm_resource_fini_basic),
KUNIT_CASE(ttm_resource_manager_init_basic),
KUNIT_CASE(ttm_resource_manager_usage_basic),
KUNIT_CASE(ttm_resource_manager_set_used_basic),
KUNIT_CASE(ttm_sys_man_alloc_basic),
KUNIT_CASE(ttm_sys_man_free_basic),
{}
};
static struct kunit_suite ttm_resource_test_suite = {
.name = "ttm_resource",
.init = ttm_resource_test_init,
.exit = ttm_resource_test_fini,
.test_cases = ttm_resource_test_cases,
};
kunit_test_suites(&ttm_resource_test_suite);
MODULE_DESCRIPTION("KUnit tests for ttm_resource and ttm_sys_man APIs");
MODULE_LICENSE("GPL and additional rights");
@@ -0,0 +1,402 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/shmem_fs.h>
#include <drm/ttm/ttm_tt.h>
#include "ttm_kunit_helpers.h"
#define BO_SIZE SZ_4K
struct ttm_tt_test_case {
const char *description;
u32 size;
u32 extra_pages_num;
};
static const struct ttm_tt_test_case ttm_tt_init_basic_cases[] = {
{
.description = "Page-aligned size",
.size = SZ_4K,
},
{
.description = "Extra pages requested",
.size = SZ_4K,
.extra_pages_num = 1,
},
};
static void ttm_tt_init_case_desc(const struct ttm_tt_test_case *t,
char *desc)
{
strscpy(desc, t->description, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(ttm_tt_init_basic, ttm_tt_init_basic_cases,
ttm_tt_init_case_desc);
static void ttm_tt_init_basic(struct kunit *test)
{
const struct ttm_tt_test_case *params = test->param_value;
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
u32 page_flags = TTM_TT_FLAG_ZERO_ALLOC;
enum ttm_caching caching = ttm_cached;
u32 extra_pages = params->extra_pages_num;
int num_pages = params->size >> PAGE_SHIFT;
int err;
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
bo = ttm_bo_kunit_init(test, test->priv, params->size, NULL);
err = ttm_tt_init(tt, bo, page_flags, caching, extra_pages);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_EQ(test, tt->num_pages, num_pages + extra_pages);
KUNIT_ASSERT_EQ(test, tt->page_flags, page_flags);
KUNIT_ASSERT_EQ(test, tt->caching, caching);
KUNIT_ASSERT_NULL(test, tt->dma_address);
KUNIT_ASSERT_NULL(test, tt->swap_storage);
}
static void ttm_tt_init_misaligned(struct kunit *test)
{
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
enum ttm_caching caching = ttm_cached;
u32 size = SZ_8K;
int num_pages = (size + SZ_4K) >> PAGE_SHIFT;
int err;
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
bo = ttm_bo_kunit_init(test, test->priv, size, NULL);
/* Make the object size misaligned */
bo->base.size += 1;
err = ttm_tt_init(tt, bo, 0, caching, 0);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_EQ(test, tt->num_pages, num_pages);
}
static void ttm_tt_fini_basic(struct kunit *test)
{
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
enum ttm_caching caching = ttm_cached;
int err;
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_tt_init(tt, bo, 0, caching, 0);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_NOT_NULL(test, tt->pages);
ttm_tt_fini(tt);
KUNIT_ASSERT_NULL(test, tt->pages);
}
static void ttm_tt_fini_sg(struct kunit *test)
{
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
enum ttm_caching caching = ttm_cached;
int err;
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_sg_tt_init(tt, bo, 0, caching);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_NOT_NULL(test, tt->dma_address);
ttm_tt_fini(tt);
KUNIT_ASSERT_NULL(test, tt->dma_address);
}
static void ttm_tt_fini_shmem(struct kunit *test)
{
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
struct file *shmem;
enum ttm_caching caching = ttm_cached;
int err;
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_tt_init(tt, bo, 0, caching, 0);
KUNIT_ASSERT_EQ(test, err, 0);
shmem = shmem_file_setup("ttm swap", BO_SIZE, EMPTY_VMA_FLAGS);
tt->swap_storage = shmem;
ttm_tt_fini(tt);
KUNIT_ASSERT_NULL(test, tt->swap_storage);
}
static void ttm_tt_create_basic(struct kunit *test)
{
struct ttm_buffer_object *bo;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
bo->type = ttm_bo_type_device;
dma_resv_lock(bo->base.resv, NULL);
err = ttm_tt_create(bo, false);
dma_resv_unlock(bo->base.resv);
KUNIT_EXPECT_EQ(test, err, 0);
KUNIT_EXPECT_NOT_NULL(test, bo->ttm);
/* Free manually, as it was allocated outside of KUnit */
kfree(bo->ttm);
}
static void ttm_tt_create_invalid_bo_type(struct kunit *test)
{
struct ttm_buffer_object *bo;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
bo->type = ttm_bo_type_sg + 1;
dma_resv_lock(bo->base.resv, NULL);
err = ttm_tt_create(bo, false);
dma_resv_unlock(bo->base.resv);
KUNIT_EXPECT_EQ(test, err, -EINVAL);
KUNIT_EXPECT_NULL(test, bo->ttm);
}
static void ttm_tt_create_ttm_exists(struct kunit *test)
{
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
enum ttm_caching caching = ttm_cached;
int err;
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
err = ttm_tt_init(tt, bo, 0, caching, 0);
KUNIT_ASSERT_EQ(test, err, 0);
bo->ttm = tt;
dma_resv_lock(bo->base.resv, NULL);
err = ttm_tt_create(bo, false);
dma_resv_unlock(bo->base.resv);
/* Expect to keep the previous TTM */
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_PTR_EQ(test, tt, bo->ttm);
}
static struct ttm_tt *ttm_tt_null_create(struct ttm_buffer_object *bo,
u32 page_flags)
{
return NULL;
}
static struct ttm_device_funcs ttm_dev_empty_funcs = {
.ttm_tt_create = ttm_tt_null_create,
};
static void ttm_tt_create_failed(struct kunit *test)
{
const struct ttm_test_devices *devs = test->priv;
struct ttm_buffer_object *bo;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
/* Update ttm_device_funcs so we don't alloc ttm_tt */
devs->ttm_dev->funcs = &ttm_dev_empty_funcs;
dma_resv_lock(bo->base.resv, NULL);
err = ttm_tt_create(bo, false);
dma_resv_unlock(bo->base.resv);
KUNIT_ASSERT_EQ(test, err, -ENOMEM);
}
static void ttm_tt_destroy_basic(struct kunit *test)
{
const struct ttm_test_devices *devs = test->priv;
struct ttm_buffer_object *bo;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
dma_resv_lock(bo->base.resv, NULL);
err = ttm_tt_create(bo, false);
dma_resv_unlock(bo->base.resv);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_NOT_NULL(test, bo->ttm);
ttm_tt_destroy(devs->ttm_dev, bo->ttm);
}
static void ttm_tt_populate_null_ttm(struct kunit *test)
{
const struct ttm_test_devices *devs = test->priv;
struct ttm_operation_ctx ctx = { };
int err;
err = ttm_tt_populate(devs->ttm_dev, NULL, &ctx);
KUNIT_ASSERT_EQ(test, err, -EINVAL);
}
static void ttm_tt_populate_populated_ttm(struct kunit *test)
{
const struct ttm_test_devices *devs = test->priv;
struct ttm_operation_ctx ctx = { };
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
struct page *populated_page;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
err = ttm_tt_init(tt, bo, 0, ttm_cached, 0);
KUNIT_ASSERT_EQ(test, err, 0);
err = ttm_tt_populate(devs->ttm_dev, tt, &ctx);
KUNIT_ASSERT_EQ(test, err, 0);
populated_page = *tt->pages;
err = ttm_tt_populate(devs->ttm_dev, tt, &ctx);
KUNIT_ASSERT_PTR_EQ(test, populated_page, *tt->pages);
}
static void ttm_tt_unpopulate_basic(struct kunit *test)
{
const struct ttm_test_devices *devs = test->priv;
struct ttm_operation_ctx ctx = { };
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
err = ttm_tt_init(tt, bo, 0, ttm_cached, 0);
KUNIT_ASSERT_EQ(test, err, 0);
err = ttm_tt_populate(devs->ttm_dev, tt, &ctx);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_TRUE(test, ttm_tt_is_populated(tt));
ttm_tt_unpopulate(devs->ttm_dev, tt);
KUNIT_ASSERT_FALSE(test, ttm_tt_is_populated(tt));
}
static void ttm_tt_unpopulate_empty_ttm(struct kunit *test)
{
const struct ttm_test_devices *devs = test->priv;
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
int err;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
err = ttm_tt_init(tt, bo, 0, ttm_cached, 0);
KUNIT_ASSERT_EQ(test, err, 0);
ttm_tt_unpopulate(devs->ttm_dev, tt);
/* Expect graceful handling of unpopulated TTs */
}
static void ttm_tt_swapin_basic(struct kunit *test)
{
const struct ttm_test_devices *devs = test->priv;
int expected_num_pages = BO_SIZE >> PAGE_SHIFT;
struct ttm_operation_ctx ctx = { };
struct ttm_buffer_object *bo;
struct ttm_tt *tt;
int err, num_pages;
bo = ttm_bo_kunit_init(test, test->priv, BO_SIZE, NULL);
tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, tt);
err = ttm_tt_init(tt, bo, 0, ttm_cached, 0);
KUNIT_ASSERT_EQ(test, err, 0);
err = ttm_tt_populate(devs->ttm_dev, tt, &ctx);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_TRUE(test, ttm_tt_is_populated(tt));
num_pages = ttm_tt_swapout(devs->ttm_dev, tt, GFP_KERNEL);
KUNIT_ASSERT_EQ(test, num_pages, expected_num_pages);
KUNIT_ASSERT_NOT_NULL(test, tt->swap_storage);
KUNIT_ASSERT_TRUE(test, tt->page_flags & TTM_TT_FLAG_SWAPPED);
/* Swapout depopulates TT, allocate pages and then swap them in */
err = ttm_pool_alloc(&devs->ttm_dev->pool, tt, &ctx);
KUNIT_ASSERT_EQ(test, err, 0);
err = ttm_tt_swapin(tt);
KUNIT_ASSERT_EQ(test, err, 0);
KUNIT_ASSERT_NULL(test, tt->swap_storage);
KUNIT_ASSERT_FALSE(test, tt->page_flags & TTM_TT_FLAG_SWAPPED);
}
static struct kunit_case ttm_tt_test_cases[] = {
KUNIT_CASE_PARAM(ttm_tt_init_basic, ttm_tt_init_basic_gen_params),
KUNIT_CASE(ttm_tt_init_misaligned),
KUNIT_CASE(ttm_tt_fini_basic),
KUNIT_CASE(ttm_tt_fini_sg),
KUNIT_CASE(ttm_tt_fini_shmem),
KUNIT_CASE(ttm_tt_create_basic),
KUNIT_CASE(ttm_tt_create_invalid_bo_type),
KUNIT_CASE(ttm_tt_create_ttm_exists),
KUNIT_CASE(ttm_tt_create_failed),
KUNIT_CASE(ttm_tt_destroy_basic),
KUNIT_CASE(ttm_tt_populate_null_ttm),
KUNIT_CASE(ttm_tt_populate_populated_ttm),
KUNIT_CASE(ttm_tt_unpopulate_basic),
KUNIT_CASE(ttm_tt_unpopulate_empty_ttm),
KUNIT_CASE(ttm_tt_swapin_basic),
{}
};
static struct kunit_suite ttm_tt_test_suite = {
.name = "ttm_tt",
.init = ttm_test_devices_all_init,
.exit = ttm_test_devices_fini,
.test_cases = ttm_tt_test_cases,
};
kunit_test_suites(&ttm_tt_test_suite);
MODULE_DESCRIPTION("KUnit tests for ttm_tt APIs");
MODULE_LICENSE("GPL and additional rights");