mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
Initializing compile time constant struct or arrays from another such
variable is a gcc extension, while clang strictly requires a compile time
constant literal.
As reported by LKP:
>> drivers/gpu/drm/scheduler/tests/tests_scheduler.c:675:10: error: initializer element is not a compile-time constant
drm_sched_scheduler_two_clients_attr),
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/kunit/test.h:224:13: note: expanded from macro 'KUNIT_CASE_PARAM_ATTR'
.attr = attributes, .module_name = KBUILD_MODNAME}
^~~~~~~~~~
1 error generated.
vim +675 drivers/gpu/drm/scheduler/tests/tests_scheduler.c
671
672 static struct kunit_case drm_sched_scheduler_two_clients_tests[] = {
673 KUNIT_CASE_PARAM_ATTR(drm_sched_scheduler_two_clients_test,
674 drm_sched_scheduler_two_clients_gen_params,
> 675 drm_sched_scheduler_two_clients_attr),
676 {}
677 };
678
Fix it by using a compound literal as other tests do.
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202605220312.Pu7UO05u-lkp@intel.com/
Fixes: 97ef806a53 ("drm/sched: Add some scheduling quality unit tests")
Cc: Philipp Stanner <phasta@kernel.org>
Acked-by: Philipp Stanner <phasta@kernel.org>
Signed-off-by: Tvrtko Ursulin <tursulin@ursulin.net>
Link: https://lore.kernel.org/r/20260522090129.9385-1-tvrtko.ursulin@igalia.com
875 lines
22 KiB
C
875 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2025 Valve Corporation */
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/math64.h>
|
|
|
|
#include "sched_tests.h"
|
|
|
|
/*
|
|
* DRM scheduler tests exercise load balancing decisions ie. entity selection
|
|
* logic.
|
|
*/
|
|
|
|
static int drm_sched_scheduler_init(struct kunit *test)
|
|
{
|
|
struct drm_mock_scheduler *sched;
|
|
|
|
sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
|
|
sched->base.credit_limit = 1;
|
|
|
|
test->priv = sched;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int drm_sched_scheduler_init2(struct kunit *test)
|
|
{
|
|
struct drm_mock_scheduler *sched;
|
|
|
|
sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
|
|
sched->base.credit_limit = 2;
|
|
|
|
test->priv = sched;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void drm_sched_scheduler_exit(struct kunit *test)
|
|
{
|
|
struct drm_mock_scheduler *sched = test->priv;
|
|
|
|
drm_mock_sched_fini(sched);
|
|
}
|
|
|
|
static void drm_sched_scheduler_queue_overhead(struct kunit *test)
|
|
{
|
|
struct drm_mock_scheduler *sched = test->priv;
|
|
struct drm_mock_sched_entity *entity;
|
|
const unsigned int job_us = 1000;
|
|
const unsigned int jobs = 1000;
|
|
const unsigned int total_us = jobs * job_us;
|
|
struct drm_mock_sched_job *job, *first;
|
|
ktime_t start, end;
|
|
bool done;
|
|
int i;
|
|
|
|
/*
|
|
* Deep queue job at a time processing (single credit).
|
|
*
|
|
* This measures the overhead of picking and processing a job at a time
|
|
* by comparing the ideal total "GPU" time of all submitted jobs versus
|
|
* the time actually taken.
|
|
*/
|
|
|
|
KUNIT_ASSERT_EQ(test, sched->base.credit_limit, 1);
|
|
|
|
entity = drm_mock_sched_entity_new(test,
|
|
DRM_SCHED_PRIORITY_NORMAL,
|
|
sched);
|
|
|
|
for (i = 0; i <= jobs; i++) {
|
|
job = drm_mock_sched_job_new(test, entity);
|
|
if (i == 0)
|
|
first = job; /* Extra first job blocks the queue */
|
|
else
|
|
drm_mock_sched_job_set_duration_us(job, job_us);
|
|
drm_mock_sched_job_submit(job);
|
|
}
|
|
|
|
done = drm_mock_sched_job_wait_scheduled(first, HZ);
|
|
KUNIT_ASSERT_TRUE(test, done);
|
|
|
|
start = ktime_get();
|
|
i = drm_mock_sched_advance(sched, 1); /* Release the queue */
|
|
KUNIT_ASSERT_EQ(test, i, 1);
|
|
|
|
/* Wait with a safe margin to avoid every failing. */
|
|
done = drm_mock_sched_job_wait_finished(job,
|
|
usecs_to_jiffies(total_us) * 5);
|
|
end = ktime_get();
|
|
KUNIT_ASSERT_TRUE(test, done);
|
|
|
|
pr_info("Expected %uus, actual %lldus\n",
|
|
total_us,
|
|
ktime_to_us(ktime_sub(end, start)));
|
|
|
|
drm_mock_sched_entity_free(entity);
|
|
}
|
|
|
|
static void drm_sched_scheduler_ping_pong(struct kunit *test)
|
|
{
|
|
struct drm_mock_sched_job *job, *first, *prev = NULL;
|
|
struct drm_mock_scheduler *sched = test->priv;
|
|
struct drm_mock_sched_entity *entity[2];
|
|
const unsigned int job_us = 1000;
|
|
const unsigned int jobs = 1000;
|
|
const unsigned int total_us = jobs * job_us;
|
|
ktime_t start, end;
|
|
bool done;
|
|
int i;
|
|
|
|
/*
|
|
* Two entitites in inter-dependency chain.
|
|
*
|
|
* This measures the overhead of picking and processing a job at a time,
|
|
* where each job depends on the previous one from the diffferent
|
|
* entity, by comparing the ideal total "GPU" time of all submitted jobs
|
|
* versus the time actually taken.
|
|
*/
|
|
|
|
KUNIT_ASSERT_EQ(test, sched->base.credit_limit, 1);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(entity); i++)
|
|
entity[i] = drm_mock_sched_entity_new(test,
|
|
DRM_SCHED_PRIORITY_NORMAL,
|
|
sched);
|
|
|
|
for (i = 0; i <= jobs; i++) {
|
|
job = drm_mock_sched_job_new(test, entity[i & 1]);
|
|
if (i == 0)
|
|
first = job; /* Extra first job blocks the queue */
|
|
else
|
|
drm_mock_sched_job_set_duration_us(job, job_us);
|
|
if (prev)
|
|
drm_sched_job_add_dependency(&job->base,
|
|
dma_fence_get(&prev->base.s_fence->finished));
|
|
drm_mock_sched_job_submit(job);
|
|
prev = job;
|
|
}
|
|
|
|
done = drm_mock_sched_job_wait_scheduled(first, HZ);
|
|
KUNIT_ASSERT_TRUE(test, done);
|
|
|
|
start = ktime_get();
|
|
i = drm_mock_sched_advance(sched, 1); /* Release the queue */
|
|
KUNIT_ASSERT_EQ(test, i, 1);
|
|
|
|
/* Wait with a safe margin to avoid every failing. */
|
|
done = drm_mock_sched_job_wait_finished(job,
|
|
usecs_to_jiffies(total_us) * 5);
|
|
end = ktime_get();
|
|
KUNIT_ASSERT_TRUE(test, done);
|
|
|
|
pr_info("Expected %uus, actual %lldus\n",
|
|
total_us,
|
|
ktime_to_us(ktime_sub(end, start)));
|
|
|
|
for (i = 0; i < ARRAY_SIZE(entity); i++)
|
|
drm_mock_sched_entity_free(entity[i]);
|
|
}
|
|
|
|
static struct kunit_case drm_sched_scheduler_overhead_tests[] = {
|
|
KUNIT_CASE_SLOW(drm_sched_scheduler_queue_overhead),
|
|
KUNIT_CASE_SLOW(drm_sched_scheduler_ping_pong),
|
|
{}
|
|
};
|
|
|
|
static struct kunit_suite drm_sched_scheduler_overhead = {
|
|
.name = "drm_sched_scheduler_overhead_tests",
|
|
.init = drm_sched_scheduler_init,
|
|
.exit = drm_sched_scheduler_exit,
|
|
.test_cases = drm_sched_scheduler_overhead_tests,
|
|
};
|
|
|
|
/*
|
|
* struct drm_sched_client_params - describe a workload emitted from a client
|
|
*
|
|
* A simulated client will create an entity with a scheduling @priority and emit
|
|
* jobs in a loop where each iteration will consist of:
|
|
*
|
|
* 1. Submit @job_cnt jobs, each with a set duration of @job_us.
|
|
* 2. If @sync is true wait for last submitted job to finish.
|
|
* 3. Sleep for @wait_us micro-seconds.
|
|
* 4. Repeat.
|
|
*/
|
|
struct drm_sched_client_params {
|
|
enum drm_sched_priority priority;
|
|
unsigned int job_cnt;
|
|
unsigned int job_us;
|
|
bool sync;
|
|
unsigned int wait_us;
|
|
};
|
|
|
|
struct drm_sched_test_params {
|
|
const char *description;
|
|
unsigned int num_clients;
|
|
struct drm_sched_client_params client[2];
|
|
};
|
|
|
|
static const struct drm_sched_test_params drm_sched_cases[] = {
|
|
{
|
|
.description = "Normal priority and normal priority",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 8000,
|
|
.wait_us = 0,
|
|
.sync = false,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 8000,
|
|
.wait_us = 0,
|
|
.sync = false,
|
|
},
|
|
},
|
|
{
|
|
.description = "Normal priority and low priority",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 8000,
|
|
.wait_us = 0,
|
|
.sync = false,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_LOW,
|
|
.job_cnt = 1,
|
|
.job_us = 8000,
|
|
.wait_us = 0,
|
|
.sync = false,
|
|
},
|
|
},
|
|
{
|
|
.description = "High priority and normal priority",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_HIGH,
|
|
.job_cnt = 1,
|
|
.job_us = 8000,
|
|
.wait_us = 0,
|
|
.sync = false,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 8000,
|
|
.wait_us = 0,
|
|
.sync = false,
|
|
},
|
|
},
|
|
{
|
|
.description = "High priority and low priority",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_HIGH,
|
|
.job_cnt = 1,
|
|
.job_us = 8000,
|
|
.wait_us = 0,
|
|
.sync = false,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_LOW,
|
|
.job_cnt = 1,
|
|
.job_us = 8000,
|
|
.wait_us = 0,
|
|
.sync = false,
|
|
},
|
|
},
|
|
{
|
|
.description = "50% and 50%",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 1500,
|
|
.wait_us = 1500,
|
|
.sync = true,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 2500,
|
|
.wait_us = 2500,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "50% and 50% low priority",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 1500,
|
|
.wait_us = 1500,
|
|
.sync = true,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_LOW,
|
|
.job_cnt = 1,
|
|
.job_us = 2500,
|
|
.wait_us = 2500,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "50% high priority and 50%",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_HIGH,
|
|
.job_cnt = 1,
|
|
.job_us = 1500,
|
|
.wait_us = 1500,
|
|
.sync = true,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 2500,
|
|
.wait_us = 2500,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "Low priority hog and interactive client",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_LOW,
|
|
.job_cnt = 3,
|
|
.job_us = 2500,
|
|
.wait_us = 500,
|
|
.sync = false,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 500,
|
|
.wait_us = 10000,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "Heavy rendering and interactive client",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 3,
|
|
.job_us = 2500,
|
|
.wait_us = 2500,
|
|
.sync = true,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 1000,
|
|
.wait_us = 9000,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "Very heavy rendering and interactive client",
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 4,
|
|
.job_us = 50000,
|
|
.wait_us = 1,
|
|
.sync = true,
|
|
},
|
|
.client[1] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 1,
|
|
.job_us = 1000,
|
|
.wait_us = 9000,
|
|
.sync = true,
|
|
},
|
|
},
|
|
};
|
|
|
|
static void
|
|
drm_sched_desc(const struct drm_sched_test_params *params, char *desc)
|
|
{
|
|
strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
|
|
}
|
|
|
|
KUNIT_ARRAY_PARAM(drm_sched_scheduler_two_clients,
|
|
drm_sched_cases,
|
|
drm_sched_desc);
|
|
|
|
/*
|
|
* struct test_client_stats - track client stats
|
|
*
|
|
* For each client executing a simulated workload we track some timings for
|
|
* which we are interested in the minimum of all iterations (@min_us), maximum
|
|
* (@max_us) and the overall total for all iterations (@tot_us).
|
|
*/
|
|
struct test_client_stats {
|
|
unsigned int min_us;
|
|
unsigned int max_us;
|
|
unsigned long tot_us;
|
|
};
|
|
|
|
/*
|
|
* struct test_client - a simulated userspace client submitting scheduler work
|
|
*
|
|
* Each client executing a simulated workload is represented by one of these.
|
|
*
|
|
* Each of them instantiates a scheduling @entity and executes a workloads as
|
|
* defined in @params. Based on those @params the theoretical execution time of
|
|
* the client is calculated as @ideal_duration, while the actual wall time is
|
|
* tracked in @duration (calculated based on the @start and @end client time-
|
|
* stamps).
|
|
*
|
|
* Numerical @id is assigned to each for logging purposes.
|
|
*
|
|
* @worker and @work are used to provide an independent execution context from
|
|
* which scheduler jobs are submitted.
|
|
*
|
|
* During execution statistics on how long it took to submit and execute one
|
|
* iteration (whether or not synchronous) is kept in @cycle_time, while
|
|
* @latency_time tracks the @cycle_time minus the ideal duration of the one
|
|
* cycle.
|
|
*
|
|
* Once the client has completed the set number of iterations it will write the
|
|
* completion status into @done.
|
|
*/
|
|
struct test_client {
|
|
struct kunit *test; /* Backpointer to the kunit test. */
|
|
|
|
struct drm_mock_sched_entity *entity;
|
|
struct kthread_worker *worker;
|
|
struct kthread_work work;
|
|
|
|
struct drm_sched_client_params params;
|
|
|
|
unsigned int id;
|
|
ktime_t duration;
|
|
ktime_t ideal_duration;
|
|
unsigned int cycles;
|
|
unsigned int cycle;
|
|
ktime_t start;
|
|
ktime_t end;
|
|
bool done;
|
|
|
|
struct test_client_stats cycle_time;
|
|
struct test_client_stats latency_time;
|
|
};
|
|
|
|
static void
|
|
update_stats(struct test_client_stats *stats, unsigned int us)
|
|
{
|
|
if (us > stats->max_us)
|
|
stats->max_us = us;
|
|
if (us < stats->min_us)
|
|
stats->min_us = us;
|
|
stats->tot_us += us;
|
|
}
|
|
|
|
static unsigned int
|
|
get_stats_avg(struct test_client_stats *stats, unsigned int cycles)
|
|
{
|
|
return div_u64(stats->tot_us, cycles);
|
|
}
|
|
|
|
static void drm_sched_client_work(struct kthread_work *work)
|
|
{
|
|
struct test_client *client = container_of(work, typeof(*client), work);
|
|
const long sync_wait = MAX_SCHEDULE_TIMEOUT;
|
|
unsigned int cycle, work_us, period_us;
|
|
struct drm_mock_sched_job *job = NULL;
|
|
|
|
work_us = client->params.job_cnt * client->params.job_us;
|
|
period_us = work_us + client->params.wait_us;
|
|
client->cycles =
|
|
DIV_ROUND_UP((unsigned int)ktime_to_us(client->duration),
|
|
period_us);
|
|
client->ideal_duration = us_to_ktime(client->cycles * period_us);
|
|
|
|
client->start = ktime_get();
|
|
|
|
for (cycle = 0; cycle < client->cycles; cycle++) {
|
|
ktime_t cycle_time;
|
|
unsigned int batch;
|
|
unsigned long us;
|
|
|
|
if (READ_ONCE(client->done))
|
|
break;
|
|
|
|
cycle_time = ktime_get();
|
|
for (batch = 0; batch < client->params.job_cnt; batch++) {
|
|
job = drm_mock_sched_job_new(client->test,
|
|
client->entity);
|
|
drm_mock_sched_job_set_duration_us(job,
|
|
client->params.job_us);
|
|
drm_mock_sched_job_submit(job);
|
|
}
|
|
|
|
if (client->params.sync)
|
|
drm_mock_sched_job_wait_finished(job, sync_wait);
|
|
|
|
cycle_time = ktime_sub(ktime_get(), cycle_time);
|
|
us = ktime_to_us(cycle_time);
|
|
update_stats(&client->cycle_time, us);
|
|
if (ktime_to_us(cycle_time) >= (long)work_us)
|
|
us = ktime_to_us(cycle_time) - work_us;
|
|
else if (WARN_ON_ONCE(client->params.sync)) /* GPU job took less than expected. */
|
|
us = 0;
|
|
update_stats(&client->latency_time, us);
|
|
WRITE_ONCE(client->cycle, cycle);
|
|
|
|
if (READ_ONCE(client->done))
|
|
break;
|
|
|
|
if (client->params.wait_us)
|
|
fsleep(client->params.wait_us);
|
|
else if (!client->params.sync)
|
|
cond_resched(); /* Do not hog the CPU if fully async. */
|
|
}
|
|
|
|
client->done = drm_mock_sched_job_wait_finished(job, sync_wait);
|
|
client->end = ktime_get();
|
|
}
|
|
|
|
static const char *prio_str(enum drm_sched_priority prio)
|
|
{
|
|
switch (prio) {
|
|
case DRM_SCHED_PRIORITY_KERNEL:
|
|
return "kernel";
|
|
case DRM_SCHED_PRIORITY_LOW:
|
|
return "low";
|
|
case DRM_SCHED_PRIORITY_NORMAL:
|
|
return "normal";
|
|
case DRM_SCHED_PRIORITY_HIGH:
|
|
return "high";
|
|
default:
|
|
return "???";
|
|
}
|
|
}
|
|
|
|
static bool client_done(struct test_client *client)
|
|
{
|
|
return READ_ONCE(client->done); /* READ_ONCE to document lockless read from a loop. */
|
|
}
|
|
|
|
static void drm_sched_scheduler_two_clients_test(struct kunit *test)
|
|
{
|
|
const struct drm_sched_test_params *params = test->param_value;
|
|
struct drm_mock_scheduler *sched = test->priv;
|
|
struct test_client client[2] = { };
|
|
unsigned int prev_cycle[2] = { };
|
|
unsigned int i, j;
|
|
ktime_t start;
|
|
|
|
/*
|
|
* Same job stream from two clients.
|
|
*/
|
|
|
|
for (i = 0; i < ARRAY_SIZE(client); i++)
|
|
client[i].entity =
|
|
drm_mock_sched_entity_new(test,
|
|
params->client[i].priority,
|
|
sched);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(client); i++) {
|
|
client[i].test = test;
|
|
client[i].id = i;
|
|
client[i].duration = ms_to_ktime(1000);
|
|
client[i].params = params->client[i];
|
|
client[i].cycle_time.min_us = ~0U;
|
|
client[i].latency_time.min_us = ~0U;
|
|
client[i].worker =
|
|
kthread_create_worker(0, "%s-%u", __func__, i);
|
|
if (IS_ERR(client[i].worker)) {
|
|
for (j = 0; j < i; j++)
|
|
kthread_destroy_worker(client[j].worker);
|
|
KUNIT_FAIL(test, "Failed to create worker!\n");
|
|
}
|
|
|
|
kthread_init_work(&client[i].work, drm_sched_client_work);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(client); i++)
|
|
kthread_queue_work(client[i].worker, &client[i].work);
|
|
|
|
/*
|
|
* The clients (workers) can be a mix of async (deep submission queue),
|
|
* sync (one job at a time), or something in between. Therefore it is
|
|
* difficult to display a single metric representing their progress.
|
|
*
|
|
* Each struct drm_sched_client_params describes the actual submission
|
|
* pattern which happens in the following steps:
|
|
* 1. Submit N jobs
|
|
* 2. Wait for last submitted job to finish
|
|
* 3. Sleep for U micro-seconds
|
|
* 4. Goto 1. for C cycles
|
|
*
|
|
* Where number of cycles is calculated to match the target client
|
|
* duration from the respective struct drm_sched_test_params.
|
|
*
|
|
* To asses scheduling behaviour what we output for both clients is:
|
|
* - pct: Percentage progress of the jobs submitted
|
|
* - cps: "Cycles" per second (where one cycle is one complete
|
|
* iteration from the above)
|
|
* - qd: Number of outstanding jobs in the client/entity
|
|
*/
|
|
|
|
pr_info(" [pct] - Job sumission progress\n"
|
|
" [cps] - Cycles per second\n"
|
|
" [qd] - Number of outstanding jobs in the client/entity\n");
|
|
pr_info("%s:\n\t pct1 cps1 qd1; pct2 cps2 qd2\n",
|
|
params->description);
|
|
start = ktime_get();
|
|
while (!client_done(&client[0]) || !client_done(&client[1])) {
|
|
const unsigned int period_ms = 100;
|
|
const unsigned int frequency = 1000 / period_ms;
|
|
unsigned int pct[2], qd[2], cycle[2], cps[2];
|
|
|
|
for (i = 0; i < ARRAY_SIZE(client); i++) {
|
|
qd[i] = spsc_queue_count(&client[i].entity->base.job_queue);
|
|
cycle[i] = READ_ONCE(client[i].cycle);
|
|
cps[i] = DIV_ROUND_UP(100 * frequency *
|
|
(cycle[i] - prev_cycle[i]),
|
|
100);
|
|
if (client[i].cycles)
|
|
pct[i] = DIV_ROUND_UP(100 * (1 + cycle[i]),
|
|
client[i].cycles);
|
|
else
|
|
pct[i] = 0;
|
|
prev_cycle[i] = cycle[i];
|
|
}
|
|
|
|
if (client_done(&client[0]))
|
|
pr_info("\t+%6lldms: ; %3u %5u %4u\n",
|
|
ktime_to_ms(ktime_sub(ktime_get(), start)),
|
|
pct[1], cps[1], qd[1]);
|
|
else if (client_done(&client[1]))
|
|
pr_info("\t+%6lldms: %3u %5u %4u;\n",
|
|
ktime_to_ms(ktime_sub(ktime_get(), start)),
|
|
pct[0], cps[0], qd[0]);
|
|
else
|
|
pr_info("\t+%6lldms: %3u %5u %4u; %3u %5u %4u\n",
|
|
ktime_to_ms(ktime_sub(ktime_get(), start)),
|
|
pct[0], cps[0], qd[0],
|
|
pct[1], cps[1], qd[1]);
|
|
|
|
msleep(period_ms);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(client); i++) {
|
|
kthread_flush_work(&client[i].work);
|
|
kthread_destroy_worker(client[i].worker);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(client); i++)
|
|
KUNIT_ASSERT_TRUE(test, client[i].done);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(client); i++) {
|
|
pr_info(" %u: prio=%s sync=%u elapsed_ms=%lldms (ideal_ms=%lldms) cycle_time(min,avg,max)=%u,%u,%u us latency_time(min,avg,max)=%u,%u,%u us",
|
|
i,
|
|
prio_str(params->client[i].priority),
|
|
params->client[i].sync,
|
|
ktime_to_ms(ktime_sub(client[i].end, client[i].start)),
|
|
ktime_to_ms(client[i].ideal_duration),
|
|
client[i].cycle_time.min_us,
|
|
get_stats_avg(&client[i].cycle_time, client[i].cycles),
|
|
client[i].cycle_time.max_us,
|
|
client[i].latency_time.min_us,
|
|
get_stats_avg(&client[i].latency_time, client[i].cycles),
|
|
client[i].latency_time.max_us);
|
|
drm_mock_sched_entity_free(client[i].entity);
|
|
}
|
|
}
|
|
|
|
static struct kunit_case drm_sched_scheduler_two_clients_tests[] = {
|
|
KUNIT_CASE_PARAM_ATTR(drm_sched_scheduler_two_clients_test,
|
|
drm_sched_scheduler_two_clients_gen_params,
|
|
{ .speed = KUNIT_SPEED_SLOW }),
|
|
{}
|
|
};
|
|
|
|
static struct kunit_suite drm_sched_scheduler_two_clients1 = {
|
|
.name = "drm_sched_scheduler_two_clients_one_credit_tests",
|
|
.init = drm_sched_scheduler_init,
|
|
.exit = drm_sched_scheduler_exit,
|
|
.test_cases = drm_sched_scheduler_two_clients_tests,
|
|
};
|
|
|
|
static struct kunit_suite drm_sched_scheduler_two_clients2 = {
|
|
.name = "drm_sched_scheduler_two_clients_two_credits_tests",
|
|
.init = drm_sched_scheduler_init2,
|
|
.exit = drm_sched_scheduler_exit,
|
|
.test_cases = drm_sched_scheduler_two_clients_tests,
|
|
};
|
|
|
|
static const struct drm_sched_test_params drm_sched_many_cases[] = {
|
|
{
|
|
.description = "2 clients",
|
|
.num_clients = 2,
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 4,
|
|
.job_us = 1000,
|
|
.wait_us = 0,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "3 clients",
|
|
.num_clients = 3,
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 4,
|
|
.job_us = 1000,
|
|
.wait_us = 0,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "7 clients",
|
|
.num_clients = 7,
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 4,
|
|
.job_us = 1000,
|
|
.wait_us = 0,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "13 clients",
|
|
.num_clients = 13,
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 4,
|
|
.job_us = 1000,
|
|
.wait_us = 0,
|
|
.sync = true,
|
|
},
|
|
},
|
|
{
|
|
.description = "31 clients",
|
|
.num_clients = 31,
|
|
.client[0] = {
|
|
.priority = DRM_SCHED_PRIORITY_NORMAL,
|
|
.job_cnt = 2,
|
|
.job_us = 1000,
|
|
.wait_us = 0,
|
|
.sync = true,
|
|
},
|
|
},
|
|
};
|
|
|
|
KUNIT_ARRAY_PARAM(drm_sched_scheduler_many_clients,
|
|
drm_sched_many_cases,
|
|
drm_sched_desc);
|
|
|
|
static void drm_sched_scheduler_many_clients_test(struct kunit *test)
|
|
{
|
|
const struct drm_sched_test_params *params = test->param_value;
|
|
struct drm_mock_scheduler *sched = test->priv;
|
|
const unsigned int clients = params->num_clients;
|
|
unsigned int i, j, delta_total = 0, loops = 0;
|
|
struct test_client *client;
|
|
unsigned int *prev_cycle;
|
|
ktime_t start;
|
|
char *buf;
|
|
|
|
/*
|
|
* Many clients with deep-ish async queues.
|
|
*/
|
|
|
|
buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL);
|
|
KUNIT_ASSERT_NOT_NULL(test, buf);
|
|
client = kunit_kcalloc(test, clients, sizeof(*client), GFP_KERNEL);
|
|
KUNIT_ASSERT_NOT_NULL(test, client);
|
|
prev_cycle = kunit_kcalloc(test, clients, sizeof(*prev_cycle),
|
|
GFP_KERNEL);
|
|
KUNIT_ASSERT_NOT_NULL(test, prev_cycle);
|
|
|
|
for (i = 0; i < clients; i++)
|
|
client[i].entity =
|
|
drm_mock_sched_entity_new(test,
|
|
DRM_SCHED_PRIORITY_NORMAL,
|
|
sched);
|
|
|
|
for (i = 0; i < clients; i++) {
|
|
client[i].test = test;
|
|
client[i].id = i;
|
|
client[i].params = params->client[0];
|
|
client[i].duration = ms_to_ktime(1000 / clients);
|
|
client[i].cycle_time.min_us = ~0U;
|
|
client[i].latency_time.min_us = ~0U;
|
|
client[i].worker =
|
|
kthread_create_worker(0, "%s-%u", __func__, i);
|
|
if (IS_ERR(client[i].worker)) {
|
|
for (j = 0; j < i; j++)
|
|
kthread_destroy_worker(client[j].worker);
|
|
KUNIT_FAIL(test, "Failed to create worker!\n");
|
|
}
|
|
|
|
kthread_init_work(&client[i].work, drm_sched_client_work);
|
|
}
|
|
|
|
for (i = 0; i < clients; i++)
|
|
kthread_queue_work(client[i].worker, &client[i].work);
|
|
|
|
start = ktime_get();
|
|
pr_info("%u clients:\n\tt\t\tcycle:\t min avg max : ...\n", clients);
|
|
for (;;) {
|
|
unsigned int min = ~0;
|
|
unsigned int max = 0;
|
|
unsigned int total = 0;
|
|
bool done = true;
|
|
char pbuf[16];
|
|
|
|
memset(buf, 0, PAGE_SIZE);
|
|
for (i = 0; i < clients; i++) {
|
|
unsigned int cycle, cycles;
|
|
|
|
/* Read current progress from the threaded worker. */
|
|
cycle = READ_ONCE(client[i].cycle);
|
|
cycles = READ_ONCE(client[i].cycles);
|
|
|
|
snprintf(pbuf, sizeof(pbuf), " %3d", cycle);
|
|
strncat(buf, pbuf, PAGE_SIZE);
|
|
|
|
total += cycle;
|
|
if (cycle < min)
|
|
min = cycle;
|
|
if (cycle > max)
|
|
max = cycle;
|
|
|
|
if (!min || (cycle + 1) < cycles)
|
|
done = false;
|
|
}
|
|
|
|
loops++;
|
|
delta_total += max - min;
|
|
|
|
pr_info("\t+%6lldms\t\t %3u %3u %3u :%s\n",
|
|
ktime_to_ms(ktime_sub(ktime_get(), start)),
|
|
min, DIV_ROUND_UP(total, clients), max, buf);
|
|
|
|
if (done)
|
|
break;
|
|
|
|
msleep(100);
|
|
}
|
|
|
|
pr_info(" avg_max_min_delta(x100)=%u\n",
|
|
loops ? DIV_ROUND_UP(delta_total * 100, loops) : 0);
|
|
|
|
for (i = 0; i < clients; i++) {
|
|
kthread_flush_work(&client[i].work);
|
|
kthread_destroy_worker(client[i].worker);
|
|
}
|
|
|
|
for (i = 0; i < clients; i++)
|
|
drm_mock_sched_entity_free(client[i].entity);
|
|
}
|
|
|
|
static struct kunit_case drm_sched_scheduler_many_clients_tests[] = {
|
|
KUNIT_CASE_PARAM_ATTR(drm_sched_scheduler_many_clients_test,
|
|
drm_sched_scheduler_many_clients_gen_params,
|
|
{ .speed = KUNIT_SPEED_SLOW }),
|
|
{}
|
|
};
|
|
|
|
static struct kunit_suite drm_sched_scheduler_many_clients = {
|
|
.name = "drm_sched_scheduler_many_clients_tests",
|
|
.init = drm_sched_scheduler_init2,
|
|
.exit = drm_sched_scheduler_exit,
|
|
.test_cases = drm_sched_scheduler_many_clients_tests,
|
|
};
|
|
|
|
kunit_test_suites(&drm_sched_scheduler_overhead,
|
|
&drm_sched_scheduler_two_clients1,
|
|
&drm_sched_scheduler_two_clients2,
|
|
&drm_sched_scheduler_many_clients);
|