#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/ktime.h>
#include <linux/math64.h>
#include "sched_tests.h"
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;
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;
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);
KUNIT_ASSERT_EQ(test, i, 1);
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;
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;
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);
KUNIT_ASSERT_EQ(test, i, 1);
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 {
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 {
unsigned int min_us;
unsigned int max_us;
unsigned long tot_us;
};
struct test_client {
struct 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))
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();
}
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);
}
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;
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);
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;
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;
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);