alistair23-linux/drivers/gpu/drm/selftests/test-drm_mm.c
Chris Wilson 83bc4ec372 drm/mm: Add a search-by-address variant to only inspect a single hole
Searching for an available hole by address is slow, as there no
guarantee that a hole will be available and so we must walk over all
nodes in the rbtree before we determine the search was futile. In many
cases, the caller doesn't strictly care for the highest available hole
and was just opportunistically laying out the address space in a
preferred order. In such cases, the caller can accept any address and
would rather do so then do a slow walk.

To be able to mix search strategies, the caller wants to tell the drm_mm
how long to spend on the search. Without a good guide for what should be
the best split, start with a request to try once at most. That is return
the top-most (or lowest) hole if it fulfils the alignment and size
requirements.

v2: Documentation, by why of example (selftests) and kerneldoc.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
Reviewed-by: Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20180521082131.13744-2-chris@chris-wilson.co.uk
2018-05-24 15:04:30 +01:00

2383 lines
53 KiB
C

/*
* Test cases for the drm_mm range manager
*/
#define pr_fmt(fmt) "drm_mm: " fmt
#include <linux/module.h>
#include <linux/prime_numbers.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/vmalloc.h>
#include <drm/drm_mm.h>
#include "../lib/drm_random.h"
#define TESTS "drm_mm_selftests.h"
#include "drm_selftest.h"
static unsigned int random_seed;
static unsigned int max_iterations = 8192;
static unsigned int max_prime = 128;
enum {
BEST,
BOTTOMUP,
TOPDOWN,
EVICT,
};
static const struct insert_mode {
const char *name;
enum drm_mm_insert_mode mode;
} insert_modes[] = {
[BEST] = { "best", DRM_MM_INSERT_BEST },
[BOTTOMUP] = { "bottom-up", DRM_MM_INSERT_LOW },
[TOPDOWN] = { "top-down", DRM_MM_INSERT_HIGH },
[EVICT] = { "evict", DRM_MM_INSERT_EVICT },
{}
}, evict_modes[] = {
{ "bottom-up", DRM_MM_INSERT_LOW },
{ "top-down", DRM_MM_INSERT_HIGH },
{}
};
static int igt_sanitycheck(void *ignored)
{
pr_info("%s - ok!\n", __func__);
return 0;
}
static bool assert_no_holes(const struct drm_mm *mm)
{
struct drm_mm_node *hole;
u64 hole_start, hole_end;
unsigned long count;
count = 0;
drm_mm_for_each_hole(hole, mm, hole_start, hole_end)
count++;
if (count) {
pr_err("Expected to find no holes (after reserve), found %lu instead\n", count);
return false;
}
drm_mm_for_each_node(hole, mm) {
if (drm_mm_hole_follows(hole)) {
pr_err("Hole follows node, expected none!\n");
return false;
}
}
return true;
}
static bool assert_one_hole(const struct drm_mm *mm, u64 start, u64 end)
{
struct drm_mm_node *hole;
u64 hole_start, hole_end;
unsigned long count;
bool ok = true;
if (end <= start)
return true;
count = 0;
drm_mm_for_each_hole(hole, mm, hole_start, hole_end) {
if (start != hole_start || end != hole_end) {
if (ok)
pr_err("empty mm has incorrect hole, found (%llx, %llx), expect (%llx, %llx)\n",
hole_start, hole_end,
start, end);
ok = false;
}
count++;
}
if (count != 1) {
pr_err("Expected to find one hole, found %lu instead\n", count);
ok = false;
}
return ok;
}
static bool assert_continuous(const struct drm_mm *mm, u64 size)
{
struct drm_mm_node *node, *check, *found;
unsigned long n;
u64 addr;
if (!assert_no_holes(mm))
return false;
n = 0;
addr = 0;
drm_mm_for_each_node(node, mm) {
if (node->start != addr) {
pr_err("node[%ld] list out of order, expected %llx found %llx\n",
n, addr, node->start);
return false;
}
if (node->size != size) {
pr_err("node[%ld].size incorrect, expected %llx, found %llx\n",
n, size, node->size);
return false;
}
if (drm_mm_hole_follows(node)) {
pr_err("node[%ld] is followed by a hole!\n", n);
return false;
}
found = NULL;
drm_mm_for_each_node_in_range(check, mm, addr, addr + size) {
if (node != check) {
pr_err("lookup return wrong node, expected start %llx, found %llx\n",
node->start, check->start);
return false;
}
found = check;
}
if (!found) {
pr_err("lookup failed for node %llx + %llx\n",
addr, size);
return false;
}
addr += size;
n++;
}
return true;
}
static u64 misalignment(struct drm_mm_node *node, u64 alignment)
{
u64 rem;
if (!alignment)
return 0;
div64_u64_rem(node->start, alignment, &rem);
return rem;
}
static bool assert_node(struct drm_mm_node *node, struct drm_mm *mm,
u64 size, u64 alignment, unsigned long color)
{
bool ok = true;
if (!drm_mm_node_allocated(node) || node->mm != mm) {
pr_err("node not allocated\n");
ok = false;
}
if (node->size != size) {
pr_err("node has wrong size, found %llu, expected %llu\n",
node->size, size);
ok = false;
}
if (misalignment(node, alignment)) {
pr_err("node is misaligned, start %llx rem %llu, expected alignment %llu\n",
node->start, misalignment(node, alignment), alignment);
ok = false;
}
if (node->color != color) {
pr_err("node has wrong color, found %lu, expected %lu\n",
node->color, color);
ok = false;
}
return ok;
}
#define show_mm(mm) do { \
struct drm_printer __p = drm_debug_printer(__func__); \
drm_mm_print((mm), &__p); } while (0)
static int igt_init(void *ignored)
{
const unsigned int size = 4096;
struct drm_mm mm;
struct drm_mm_node tmp;
int ret = -EINVAL;
/* Start with some simple checks on initialising the struct drm_mm */
memset(&mm, 0, sizeof(mm));
if (drm_mm_initialized(&mm)) {
pr_err("zeroed mm claims to be initialized\n");
return ret;
}
memset(&mm, 0xff, sizeof(mm));
drm_mm_init(&mm, 0, size);
if (!drm_mm_initialized(&mm)) {
pr_err("mm claims not to be initialized\n");
goto out;
}
if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
}
/* After creation, it should all be one massive hole */
if (!assert_one_hole(&mm, 0, size)) {
ret = -EINVAL;
goto out;
}
memset(&tmp, 0, sizeof(tmp));
tmp.start = 0;
tmp.size = size;
ret = drm_mm_reserve_node(&mm, &tmp);
if (ret) {
pr_err("failed to reserve whole drm_mm\n");
goto out;
}
/* After filling the range entirely, there should be no holes */
if (!assert_no_holes(&mm)) {
ret = -EINVAL;
goto out;
}
/* And then after emptying it again, the massive hole should be back */
drm_mm_remove_node(&tmp);
if (!assert_one_hole(&mm, 0, size)) {
ret = -EINVAL;
goto out;
}
out:
if (ret)
show_mm(&mm);
drm_mm_takedown(&mm);
return ret;
}
static int igt_debug(void *ignored)
{
struct drm_mm mm;
struct drm_mm_node nodes[2];
int ret;
/* Create a small drm_mm with a couple of nodes and a few holes, and
* check that the debug iterator doesn't explode over a trivial drm_mm.
*/
drm_mm_init(&mm, 0, 4096);
memset(nodes, 0, sizeof(nodes));
nodes[0].start = 512;
nodes[0].size = 1024;
ret = drm_mm_reserve_node(&mm, &nodes[0]);
if (ret) {
pr_err("failed to reserve node[0] {start=%lld, size=%lld)\n",
nodes[0].start, nodes[0].size);
return ret;
}
nodes[1].size = 1024;
nodes[1].start = 4096 - 512 - nodes[1].size;
ret = drm_mm_reserve_node(&mm, &nodes[1]);
if (ret) {
pr_err("failed to reserve node[1] {start=%lld, size=%lld)\n",
nodes[1].start, nodes[1].size);
return ret;
}
show_mm(&mm);
return 0;
}
static struct drm_mm_node *set_node(struct drm_mm_node *node,
u64 start, u64 size)
{
node->start = start;
node->size = size;
return node;
}
static bool expect_reserve_fail(struct drm_mm *mm, struct drm_mm_node *node)
{
int err;
err = drm_mm_reserve_node(mm, node);
if (likely(err == -ENOSPC))
return true;
if (!err) {
pr_err("impossible reserve succeeded, node %llu + %llu\n",
node->start, node->size);
drm_mm_remove_node(node);
} else {
pr_err("impossible reserve failed with wrong error %d [expected %d], node %llu + %llu\n",
err, -ENOSPC, node->start, node->size);
}
return false;
}
static bool check_reserve_boundaries(struct drm_mm *mm,
unsigned int count,
u64 size)
{
const struct boundary {
u64 start, size;
const char *name;
} boundaries[] = {
#define B(st, sz) { (st), (sz), "{ " #st ", " #sz "}" }
B(0, 0),
B(-size, 0),
B(size, 0),
B(size * count, 0),
B(-size, size),
B(-size, -size),
B(-size, 2*size),
B(0, -size),
B(size, -size),
B(count*size, size),
B(count*size, -size),
B(count*size, count*size),
B(count*size, -count*size),
B(count*size, -(count+1)*size),
B((count+1)*size, size),
B((count+1)*size, -size),
B((count+1)*size, -2*size),
#undef B
};
struct drm_mm_node tmp = {};
int n;
for (n = 0; n < ARRAY_SIZE(boundaries); n++) {
if (!expect_reserve_fail(mm,
set_node(&tmp,
boundaries[n].start,
boundaries[n].size))) {
pr_err("boundary[%d:%s] failed, count=%u, size=%lld\n",
n, boundaries[n].name, count, size);
return false;
}
}
return true;
}
static int __igt_reserve(unsigned int count, u64 size)
{
DRM_RND_STATE(prng, random_seed);
struct drm_mm mm;
struct drm_mm_node tmp, *nodes, *node, *next;
unsigned int *order, n, m, o = 0;
int ret, err;
/* For exercising drm_mm_reserve_node(), we want to check that
* reservations outside of the drm_mm range are rejected, and to
* overlapping and otherwise already occupied ranges. Afterwards,
* the tree and nodes should be intact.
*/
DRM_MM_BUG_ON(!count);
DRM_MM_BUG_ON(!size);
ret = -ENOMEM;
order = drm_random_order(count, &prng);
if (!order)
goto err;
nodes = vzalloc(sizeof(*nodes) * count);
if (!nodes)
goto err_order;
ret = -EINVAL;
drm_mm_init(&mm, 0, count * size);
if (!check_reserve_boundaries(&mm, count, size))
goto out;
for (n = 0; n < count; n++) {
nodes[n].start = order[n] * size;
nodes[n].size = size;
err = drm_mm_reserve_node(&mm, &nodes[n]);
if (err) {
pr_err("reserve failed, step %d, start %llu\n",
n, nodes[n].start);
ret = err;
goto out;
}
if (!drm_mm_node_allocated(&nodes[n])) {
pr_err("reserved node not allocated! step %d, start %llu\n",
n, nodes[n].start);
goto out;
}
if (!expect_reserve_fail(&mm, &nodes[n]))
goto out;
}
/* After random insertion the nodes should be in order */
if (!assert_continuous(&mm, size))
goto out;
/* Repeated use should then fail */
drm_random_reorder(order, count, &prng);
for (n = 0; n < count; n++) {
if (!expect_reserve_fail(&mm,
set_node(&tmp, order[n] * size, 1)))
goto out;
/* Remove and reinsert should work */
drm_mm_remove_node(&nodes[order[n]]);
err = drm_mm_reserve_node(&mm, &nodes[order[n]]);
if (err) {
pr_err("reserve failed, step %d, start %llu\n",
n, nodes[n].start);
ret = err;
goto out;
}
}
if (!assert_continuous(&mm, size))
goto out;
/* Overlapping use should then fail */
for (n = 0; n < count; n++) {
if (!expect_reserve_fail(&mm, set_node(&tmp, 0, size*count)))
goto out;
}
for (n = 0; n < count; n++) {
if (!expect_reserve_fail(&mm,
set_node(&tmp,
size * n,
size * (count - n))))
goto out;
}
/* Remove several, reinsert, check full */
for_each_prime_number(n, min(max_prime, count)) {
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % count]];
drm_mm_remove_node(node);
}
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % count]];
err = drm_mm_reserve_node(&mm, node);
if (err) {
pr_err("reserve failed, step %d/%d, start %llu\n",
m, n, node->start);
ret = err;
goto out;
}
}
o += n;
if (!assert_continuous(&mm, size))
goto out;
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
vfree(nodes);
err_order:
kfree(order);
err:
return ret;
}
static int igt_reserve(void *ignored)
{
const unsigned int count = min_t(unsigned int, BIT(10), max_iterations);
int n, ret;
for_each_prime_number_from(n, 1, 54) {
u64 size = BIT_ULL(n);
ret = __igt_reserve(count, size - 1);
if (ret)
return ret;
ret = __igt_reserve(count, size);
if (ret)
return ret;
ret = __igt_reserve(count, size + 1);
if (ret)
return ret;
cond_resched();
}
return 0;
}
static bool expect_insert(struct drm_mm *mm, struct drm_mm_node *node,
u64 size, u64 alignment, unsigned long color,
const struct insert_mode *mode)
{
int err;
err = drm_mm_insert_node_generic(mm, node,
size, alignment, color,
mode->mode);
if (err) {
pr_err("insert (size=%llu, alignment=%llu, color=%lu, mode=%s) failed with err=%d\n",
size, alignment, color, mode->name, err);
return false;
}
if (!assert_node(node, mm, size, alignment, color)) {
drm_mm_remove_node(node);
return false;
}
return true;
}
static bool expect_insert_fail(struct drm_mm *mm, u64 size)
{
struct drm_mm_node tmp = {};
int err;
err = drm_mm_insert_node(mm, &tmp, size);
if (likely(err == -ENOSPC))
return true;
if (!err) {
pr_err("impossible insert succeeded, node %llu + %llu\n",
tmp.start, tmp.size);
drm_mm_remove_node(&tmp);
} else {
pr_err("impossible insert failed with wrong error %d [expected %d], size %llu\n",
err, -ENOSPC, size);
}
return false;
}
static int __igt_insert(unsigned int count, u64 size, bool replace)
{
DRM_RND_STATE(prng, random_seed);
const struct insert_mode *mode;
struct drm_mm mm;
struct drm_mm_node *nodes, *node, *next;
unsigned int *order, n, m, o = 0;
int ret;
/* Fill a range with lots of nodes, check it doesn't fail too early */
DRM_MM_BUG_ON(!count);
DRM_MM_BUG_ON(!size);
ret = -ENOMEM;
nodes = vmalloc(count * sizeof(*nodes));
if (!nodes)
goto err;
order = drm_random_order(count, &prng);
if (!order)
goto err_nodes;
ret = -EINVAL;
drm_mm_init(&mm, 0, count * size);
for (mode = insert_modes; mode->name; mode++) {
for (n = 0; n < count; n++) {
struct drm_mm_node tmp;
node = replace ? &tmp : &nodes[n];
memset(node, 0, sizeof(*node));
if (!expect_insert(&mm, node, size, 0, n, mode)) {
pr_err("%s insert failed, size %llu step %d\n",
mode->name, size, n);
goto out;
}
if (replace) {
drm_mm_replace_node(&tmp, &nodes[n]);
if (drm_mm_node_allocated(&tmp)) {
pr_err("replaced old-node still allocated! step %d\n",
n);
goto out;
}
if (!assert_node(&nodes[n], &mm, size, 0, n)) {
pr_err("replaced node did not inherit parameters, size %llu step %d\n",
size, n);
goto out;
}
if (tmp.start != nodes[n].start) {
pr_err("replaced node mismatch location expected [%llx + %llx], found [%llx + %llx]\n",
tmp.start, size,
nodes[n].start, nodes[n].size);
goto out;
}
}
}
/* After random insertion the nodes should be in order */
if (!assert_continuous(&mm, size))
goto out;
/* Repeated use should then fail */
if (!expect_insert_fail(&mm, size))
goto out;
/* Remove one and reinsert, as the only hole it should refill itself */
for (n = 0; n < count; n++) {
u64 addr = nodes[n].start;
drm_mm_remove_node(&nodes[n]);
if (!expect_insert(&mm, &nodes[n], size, 0, n, mode)) {
pr_err("%s reinsert failed, size %llu step %d\n",
mode->name, size, n);
goto out;
}
if (nodes[n].start != addr) {
pr_err("%s reinsert node moved, step %d, expected %llx, found %llx\n",
mode->name, n, addr, nodes[n].start);
goto out;
}
if (!assert_continuous(&mm, size))
goto out;
}
/* Remove several, reinsert, check full */
for_each_prime_number(n, min(max_prime, count)) {
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % count]];
drm_mm_remove_node(node);
}
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % count]];
if (!expect_insert(&mm, node, size, 0, n, mode)) {
pr_err("%s multiple reinsert failed, size %llu step %d\n",
mode->name, size, n);
goto out;
}
}
o += n;
if (!assert_continuous(&mm, size))
goto out;
if (!expect_insert_fail(&mm, size))
goto out;
}
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
DRM_MM_BUG_ON(!drm_mm_clean(&mm));
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
kfree(order);
err_nodes:
vfree(nodes);
err:
return ret;
}
static int igt_insert(void *ignored)
{
const unsigned int count = min_t(unsigned int, BIT(10), max_iterations);
unsigned int n;
int ret;
for_each_prime_number_from(n, 1, 54) {
u64 size = BIT_ULL(n);
ret = __igt_insert(count, size - 1, false);
if (ret)
return ret;
ret = __igt_insert(count, size, false);
if (ret)
return ret;
ret = __igt_insert(count, size + 1, false);
if (ret)
return ret;
cond_resched();
}
return 0;
}
static int igt_replace(void *ignored)
{
const unsigned int count = min_t(unsigned int, BIT(10), max_iterations);
unsigned int n;
int ret;
/* Reuse igt_insert to exercise replacement by inserting a dummy node,
* then replacing it with the intended node. We want to check that
* the tree is intact and all the information we need is carried
* across to the target node.
*/
for_each_prime_number_from(n, 1, 54) {
u64 size = BIT_ULL(n);
ret = __igt_insert(count, size - 1, true);
if (ret)
return ret;
ret = __igt_insert(count, size, true);
if (ret)
return ret;
ret = __igt_insert(count, size + 1, true);
if (ret)
return ret;
cond_resched();
}
return 0;
}
static bool expect_insert_in_range(struct drm_mm *mm, struct drm_mm_node *node,
u64 size, u64 alignment, unsigned long color,
u64 range_start, u64 range_end,
const struct insert_mode *mode)
{
int err;
err = drm_mm_insert_node_in_range(mm, node,
size, alignment, color,
range_start, range_end,
mode->mode);
if (err) {
pr_err("insert (size=%llu, alignment=%llu, color=%lu, mode=%s) nto range [%llx, %llx] failed with err=%d\n",
size, alignment, color, mode->name,
range_start, range_end, err);
return false;
}
if (!assert_node(node, mm, size, alignment, color)) {
drm_mm_remove_node(node);
return false;
}
return true;
}
static bool expect_insert_in_range_fail(struct drm_mm *mm,
u64 size,
u64 range_start,
u64 range_end)
{
struct drm_mm_node tmp = {};
int err;
err = drm_mm_insert_node_in_range(mm, &tmp,
size, 0, 0,
range_start, range_end,
0);
if (likely(err == -ENOSPC))
return true;
if (!err) {
pr_err("impossible insert succeeded, node %llx + %llu, range [%llx, %llx]\n",
tmp.start, tmp.size, range_start, range_end);
drm_mm_remove_node(&tmp);
} else {
pr_err("impossible insert failed with wrong error %d [expected %d], size %llu, range [%llx, %llx]\n",
err, -ENOSPC, size, range_start, range_end);
}
return false;
}
static bool assert_contiguous_in_range(struct drm_mm *mm,
u64 size,
u64 start,
u64 end)
{
struct drm_mm_node *node;
unsigned int n;
if (!expect_insert_in_range_fail(mm, size, start, end))
return false;
n = div64_u64(start + size - 1, size);
drm_mm_for_each_node(node, mm) {
if (node->start < start || node->start + node->size > end) {
pr_err("node %d out of range, address [%llx + %llu], range [%llx, %llx]\n",
n, node->start, node->start + node->size, start, end);
return false;
}
if (node->start != n * size) {
pr_err("node %d out of order, expected start %llx, found %llx\n",
n, n * size, node->start);
return false;
}
if (node->size != size) {
pr_err("node %d has wrong size, expected size %llx, found %llx\n",
n, size, node->size);
return false;
}
if (drm_mm_hole_follows(node) &&
drm_mm_hole_node_end(node) < end) {
pr_err("node %d is followed by a hole!\n", n);
return false;
}
n++;
}
if (start > 0) {
node = __drm_mm_interval_first(mm, 0, start - 1);
if (node->allocated) {
pr_err("node before start: node=%llx+%llu, start=%llx\n",
node->start, node->size, start);
return false;
}
}
if (end < U64_MAX) {
node = __drm_mm_interval_first(mm, end, U64_MAX);
if (node->allocated) {
pr_err("node after end: node=%llx+%llu, end=%llx\n",
node->start, node->size, end);
return false;
}
}
return true;
}
static int __igt_insert_range(unsigned int count, u64 size, u64 start, u64 end)
{
const struct insert_mode *mode;
struct drm_mm mm;
struct drm_mm_node *nodes, *node, *next;
unsigned int n, start_n, end_n;
int ret;
DRM_MM_BUG_ON(!count);
DRM_MM_BUG_ON(!size);
DRM_MM_BUG_ON(end <= start);
/* Very similar to __igt_insert(), but now instead of populating the
* full range of the drm_mm, we try to fill a small portion of it.
*/
ret = -ENOMEM;
nodes = vzalloc(count * sizeof(*nodes));
if (!nodes)
goto err;
ret = -EINVAL;
drm_mm_init(&mm, 0, count * size);
start_n = div64_u64(start + size - 1, size);
end_n = div64_u64(end - size, size);
for (mode = insert_modes; mode->name; mode++) {
for (n = start_n; n <= end_n; n++) {
if (!expect_insert_in_range(&mm, &nodes[n],
size, size, n,
start, end, mode)) {
pr_err("%s insert failed, size %llu, step %d [%d, %d], range [%llx, %llx]\n",
mode->name, size, n,
start_n, end_n,
start, end);
goto out;
}
}
if (!assert_contiguous_in_range(&mm, size, start, end)) {
pr_err("%s: range [%llx, %llx] not full after initialisation, size=%llu\n",
mode->name, start, end, size);
goto out;
}
/* Remove one and reinsert, it should refill itself */
for (n = start_n; n <= end_n; n++) {
u64 addr = nodes[n].start;
drm_mm_remove_node(&nodes[n]);
if (!expect_insert_in_range(&mm, &nodes[n],
size, size, n,
start, end, mode)) {
pr_err("%s reinsert failed, step %d\n", mode->name, n);
goto out;
}
if (nodes[n].start != addr) {
pr_err("%s reinsert node moved, step %d, expected %llx, found %llx\n",
mode->name, n, addr, nodes[n].start);
goto out;
}
}
if (!assert_contiguous_in_range(&mm, size, start, end)) {
pr_err("%s: range [%llx, %llx] not full after reinsertion, size=%llu\n",
mode->name, start, end, size);
goto out;
}
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
DRM_MM_BUG_ON(!drm_mm_clean(&mm));
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
vfree(nodes);
err:
return ret;
}
static int insert_outside_range(void)
{
struct drm_mm mm;
const unsigned int start = 1024;
const unsigned int end = 2048;
const unsigned int size = end - start;
drm_mm_init(&mm, start, size);
if (!expect_insert_in_range_fail(&mm, 1, 0, start))
return -EINVAL;
if (!expect_insert_in_range_fail(&mm, size,
start - size/2, start + (size+1)/2))
return -EINVAL;
if (!expect_insert_in_range_fail(&mm, size,
end - (size+1)/2, end + size/2))
return -EINVAL;
if (!expect_insert_in_range_fail(&mm, 1, end, end + size))
return -EINVAL;
drm_mm_takedown(&mm);
return 0;
}
static int igt_insert_range(void *ignored)
{
const unsigned int count = min_t(unsigned int, BIT(13), max_iterations);
unsigned int n;
int ret;
/* Check that requests outside the bounds of drm_mm are rejected. */
ret = insert_outside_range();
if (ret)
return ret;
for_each_prime_number_from(n, 1, 50) {
const u64 size = BIT_ULL(n);
const u64 max = count * size;
ret = __igt_insert_range(count, size, 0, max);
if (ret)
return ret;
ret = __igt_insert_range(count, size, 1, max);
if (ret)
return ret;
ret = __igt_insert_range(count, size, 0, max - 1);
if (ret)
return ret;
ret = __igt_insert_range(count, size, 0, max/2);
if (ret)
return ret;
ret = __igt_insert_range(count, size, max/2, max);
if (ret)
return ret;
ret = __igt_insert_range(count, size, max/4+1, 3*max/4-1);
if (ret)
return ret;
cond_resched();
}
return 0;
}
static int igt_align(void *ignored)
{
const struct insert_mode *mode;
const unsigned int max_count = min(8192u, max_prime);
struct drm_mm mm;
struct drm_mm_node *nodes, *node, *next;
unsigned int prime;
int ret = -EINVAL;
/* For each of the possible insertion modes, we pick a few
* arbitrary alignments and check that the inserted node
* meets our requirements.
*/
nodes = vzalloc(max_count * sizeof(*nodes));
if (!nodes)
goto err;
drm_mm_init(&mm, 1, U64_MAX - 2);
for (mode = insert_modes; mode->name; mode++) {
unsigned int i = 0;
for_each_prime_number_from(prime, 1, max_count) {
u64 size = next_prime_number(prime);
if (!expect_insert(&mm, &nodes[i],
size, prime, i,
mode)) {
pr_err("%s insert failed with alignment=%d",
mode->name, prime);
goto out;
}
i++;
}
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
DRM_MM_BUG_ON(!drm_mm_clean(&mm));
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
vfree(nodes);
err:
return ret;
}
static int igt_align_pot(int max)
{
struct drm_mm mm;
struct drm_mm_node *node, *next;
int bit;
int ret = -EINVAL;
/* Check that we can align to the full u64 address space */
drm_mm_init(&mm, 1, U64_MAX - 2);
for (bit = max - 1; bit; bit--) {
u64 align, size;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
align = BIT_ULL(bit);
size = BIT_ULL(bit-1) + 1;
if (!expect_insert(&mm, node,
size, align, bit,
&insert_modes[0])) {
pr_err("insert failed with alignment=%llx [%d]",
align, bit);
goto out;
}
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm) {
drm_mm_remove_node(node);
kfree(node);
}
drm_mm_takedown(&mm);
return ret;
}
static int igt_align32(void *ignored)
{
return igt_align_pot(32);
}
static int igt_align64(void *ignored)
{
return igt_align_pot(64);
}
static void show_scan(const struct drm_mm_scan *scan)
{
pr_info("scan: hit [%llx, %llx], size=%lld, align=%lld, color=%ld\n",
scan->hit_start, scan->hit_end,
scan->size, scan->alignment, scan->color);
}
static void show_holes(const struct drm_mm *mm, int count)
{
u64 hole_start, hole_end;
struct drm_mm_node *hole;
drm_mm_for_each_hole(hole, mm, hole_start, hole_end) {
struct drm_mm_node *next = list_next_entry(hole, node_list);
const char *node1 = NULL, *node2 = NULL;
if (hole->allocated)
node1 = kasprintf(GFP_KERNEL,
"[%llx + %lld, color=%ld], ",
hole->start, hole->size, hole->color);
if (next->allocated)
node2 = kasprintf(GFP_KERNEL,
", [%llx + %lld, color=%ld]",
next->start, next->size, next->color);
pr_info("%sHole [%llx - %llx, size %lld]%s\n",
node1,
hole_start, hole_end, hole_end - hole_start,
node2);
kfree(node2);
kfree(node1);
if (!--count)
break;
}
}
struct evict_node {
struct drm_mm_node node;
struct list_head link;
};
static bool evict_nodes(struct drm_mm_scan *scan,
struct evict_node *nodes,
unsigned int *order,
unsigned int count,
bool use_color,
struct list_head *evict_list)
{
struct evict_node *e, *en;
unsigned int i;
for (i = 0; i < count; i++) {
e = &nodes[order ? order[i] : i];
list_add(&e->link, evict_list);
if (drm_mm_scan_add_block(scan, &e->node))
break;
}
list_for_each_entry_safe(e, en, evict_list, link) {
if (!drm_mm_scan_remove_block(scan, &e->node))
list_del(&e->link);
}
if (list_empty(evict_list)) {
pr_err("Failed to find eviction: size=%lld [avail=%d], align=%lld (color=%lu)\n",
scan->size, count, scan->alignment, scan->color);
return false;
}
list_for_each_entry(e, evict_list, link)
drm_mm_remove_node(&e->node);
if (use_color) {
struct drm_mm_node *node;
while ((node = drm_mm_scan_color_evict(scan))) {
e = container_of(node, typeof(*e), node);
drm_mm_remove_node(&e->node);
list_add(&e->link, evict_list);
}
} else {
if (drm_mm_scan_color_evict(scan)) {
pr_err("drm_mm_scan_color_evict unexpectedly reported overlapping nodes!\n");
return false;
}
}
return true;
}
static bool evict_nothing(struct drm_mm *mm,
unsigned int total_size,
struct evict_node *nodes)
{
struct drm_mm_scan scan;
LIST_HEAD(evict_list);
struct evict_node *e;
struct drm_mm_node *node;
unsigned int n;
drm_mm_scan_init(&scan, mm, 1, 0, 0, 0);
for (n = 0; n < total_size; n++) {
e = &nodes[n];
list_add(&e->link, &evict_list);
drm_mm_scan_add_block(&scan, &e->node);
}
list_for_each_entry(e, &evict_list, link)
drm_mm_scan_remove_block(&scan, &e->node);
for (n = 0; n < total_size; n++) {
e = &nodes[n];
if (!drm_mm_node_allocated(&e->node)) {
pr_err("node[%d] no longer allocated!\n", n);
return false;
}
e->link.next = NULL;
}
drm_mm_for_each_node(node, mm) {
e = container_of(node, typeof(*e), node);
e->link.next = &e->link;
}
for (n = 0; n < total_size; n++) {
e = &nodes[n];
if (!e->link.next) {
pr_err("node[%d] no longer connected!\n", n);
return false;
}
}
return assert_continuous(mm, nodes[0].node.size);
}
static bool evict_everything(struct drm_mm *mm,
unsigned int total_size,
struct evict_node *nodes)
{
struct drm_mm_scan scan;
LIST_HEAD(evict_list);
struct evict_node *e;
unsigned int n;
int err;
drm_mm_scan_init(&scan, mm, total_size, 0, 0, 0);
for (n = 0; n < total_size; n++) {
e = &nodes[n];
list_add(&e->link, &evict_list);
if (drm_mm_scan_add_block(&scan, &e->node))
break;
}
err = 0;
list_for_each_entry(e, &evict_list, link) {
if (!drm_mm_scan_remove_block(&scan, &e->node)) {
if (!err) {
pr_err("Node %lld not marked for eviction!\n",
e->node.start);
err = -EINVAL;
}
}
}
if (err)
return false;
list_for_each_entry(e, &evict_list, link)
drm_mm_remove_node(&e->node);
if (!assert_one_hole(mm, 0, total_size))
return false;
list_for_each_entry(e, &evict_list, link) {
err = drm_mm_reserve_node(mm, &e->node);
if (err) {
pr_err("Failed to reinsert node after eviction: start=%llx\n",
e->node.start);
return false;
}
}
return assert_continuous(mm, nodes[0].node.size);
}
static int evict_something(struct drm_mm *mm,
u64 range_start, u64 range_end,
struct evict_node *nodes,
unsigned int *order,
unsigned int count,
unsigned int size,
unsigned int alignment,
const struct insert_mode *mode)
{
struct drm_mm_scan scan;
LIST_HEAD(evict_list);
struct evict_node *e;
struct drm_mm_node tmp;
int err;
drm_mm_scan_init_with_range(&scan, mm,
size, alignment, 0,
range_start, range_end,
mode->mode);
if (!evict_nodes(&scan,
nodes, order, count, false,
&evict_list))
return -EINVAL;
memset(&tmp, 0, sizeof(tmp));
err = drm_mm_insert_node_generic(mm, &tmp, size, alignment, 0,
DRM_MM_INSERT_EVICT);
if (err) {
pr_err("Failed to insert into eviction hole: size=%d, align=%d\n",
size, alignment);
show_scan(&scan);
show_holes(mm, 3);
return err;
}
if (tmp.start < range_start || tmp.start + tmp.size > range_end) {
pr_err("Inserted [address=%llu + %llu] did not fit into the request range [%llu, %llu]\n",
tmp.start, tmp.size, range_start, range_end);
err = -EINVAL;
}
if (!assert_node(&tmp, mm, size, alignment, 0) ||
drm_mm_hole_follows(&tmp)) {
pr_err("Inserted did not fill the eviction hole: size=%lld [%d], align=%d [rem=%lld], start=%llx, hole-follows?=%d\n",
tmp.size, size,
alignment, misalignment(&tmp, alignment),
tmp.start, drm_mm_hole_follows(&tmp));
err = -EINVAL;
}
drm_mm_remove_node(&tmp);
if (err)
return err;
list_for_each_entry(e, &evict_list, link) {
err = drm_mm_reserve_node(mm, &e->node);
if (err) {
pr_err("Failed to reinsert node after eviction: start=%llx\n",
e->node.start);
return err;
}
}
if (!assert_continuous(mm, nodes[0].node.size)) {
pr_err("range is no longer continuous\n");
return -EINVAL;
}
return 0;
}
static int igt_evict(void *ignored)
{
DRM_RND_STATE(prng, random_seed);
const unsigned int size = 8192;
const struct insert_mode *mode;
struct drm_mm mm;
struct evict_node *nodes;
struct drm_mm_node *node, *next;
unsigned int *order, n;
int ret, err;
/* Here we populate a full drm_mm and then try and insert a new node
* by evicting other nodes in a random order. The drm_mm_scan should
* pick the first matching hole it finds from the random list. We
* repeat that for different allocation strategies, alignments and
* sizes to try and stress the hole finder.
*/
ret = -ENOMEM;
nodes = vzalloc(size * sizeof(*nodes));
if (!nodes)
goto err;
order = drm_random_order(size, &prng);
if (!order)
goto err_nodes;
ret = -EINVAL;
drm_mm_init(&mm, 0, size);
for (n = 0; n < size; n++) {
err = drm_mm_insert_node(&mm, &nodes[n].node, 1);
if (err) {
pr_err("insert failed, step %d\n", n);
ret = err;
goto out;
}
}
/* First check that using the scanner doesn't break the mm */
if (!evict_nothing(&mm, size, nodes)) {
pr_err("evict_nothing() failed\n");
goto out;
}
if (!evict_everything(&mm, size, nodes)) {
pr_err("evict_everything() failed\n");
goto out;
}
for (mode = evict_modes; mode->name; mode++) {
for (n = 1; n <= size; n <<= 1) {
drm_random_reorder(order, size, &prng);
err = evict_something(&mm, 0, U64_MAX,
nodes, order, size,
n, 1,
mode);
if (err) {
pr_err("%s evict_something(size=%u) failed\n",
mode->name, n);
ret = err;
goto out;
}
}
for (n = 1; n < size; n <<= 1) {
drm_random_reorder(order, size, &prng);
err = evict_something(&mm, 0, U64_MAX,
nodes, order, size,
size/2, n,
mode);
if (err) {
pr_err("%s evict_something(size=%u, alignment=%u) failed\n",
mode->name, size/2, n);
ret = err;
goto out;
}
}
for_each_prime_number_from(n, 1, min(size, max_prime)) {
unsigned int nsize = (size - n + 1) / 2;
DRM_MM_BUG_ON(!nsize);
drm_random_reorder(order, size, &prng);
err = evict_something(&mm, 0, U64_MAX,
nodes, order, size,
nsize, n,
mode);
if (err) {
pr_err("%s evict_something(size=%u, alignment=%u) failed\n",
mode->name, nsize, n);
ret = err;
goto out;
}
}
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
kfree(order);
err_nodes:
vfree(nodes);
err:
return ret;
}
static int igt_evict_range(void *ignored)
{
DRM_RND_STATE(prng, random_seed);
const unsigned int size = 8192;
const unsigned int range_size = size / 2;
const unsigned int range_start = size / 4;
const unsigned int range_end = range_start + range_size;
const struct insert_mode *mode;
struct drm_mm mm;
struct evict_node *nodes;
struct drm_mm_node *node, *next;
unsigned int *order, n;
int ret, err;
/* Like igt_evict() but now we are limiting the search to a
* small portion of the full drm_mm.
*/
ret = -ENOMEM;
nodes = vzalloc(size * sizeof(*nodes));
if (!nodes)
goto err;
order = drm_random_order(size, &prng);
if (!order)
goto err_nodes;
ret = -EINVAL;
drm_mm_init(&mm, 0, size);
for (n = 0; n < size; n++) {
err = drm_mm_insert_node(&mm, &nodes[n].node, 1);
if (err) {
pr_err("insert failed, step %d\n", n);
ret = err;
goto out;
}
}
for (mode = evict_modes; mode->name; mode++) {
for (n = 1; n <= range_size; n <<= 1) {
drm_random_reorder(order, size, &prng);
err = evict_something(&mm, range_start, range_end,
nodes, order, size,
n, 1,
mode);
if (err) {
pr_err("%s evict_something(size=%u) failed with range [%u, %u]\n",
mode->name, n, range_start, range_end);
goto out;
}
}
for (n = 1; n <= range_size; n <<= 1) {
drm_random_reorder(order, size, &prng);
err = evict_something(&mm, range_start, range_end,
nodes, order, size,
range_size/2, n,
mode);
if (err) {
pr_err("%s evict_something(size=%u, alignment=%u) failed with range [%u, %u]\n",
mode->name, range_size/2, n, range_start, range_end);
goto out;
}
}
for_each_prime_number_from(n, 1, min(range_size, max_prime)) {
unsigned int nsize = (range_size - n + 1) / 2;
DRM_MM_BUG_ON(!nsize);
drm_random_reorder(order, size, &prng);
err = evict_something(&mm, range_start, range_end,
nodes, order, size,
nsize, n,
mode);
if (err) {
pr_err("%s evict_something(size=%u, alignment=%u) failed with range [%u, %u]\n",
mode->name, nsize, n, range_start, range_end);
goto out;
}
}
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
kfree(order);
err_nodes:
vfree(nodes);
err:
return ret;
}
static unsigned int node_index(const struct drm_mm_node *node)
{
return div64_u64(node->start, node->size);
}
static int igt_topdown(void *ignored)
{
const struct insert_mode *topdown = &insert_modes[TOPDOWN];
DRM_RND_STATE(prng, random_seed);
const unsigned int count = 8192;
unsigned int size;
unsigned long *bitmap = NULL;
struct drm_mm mm;
struct drm_mm_node *nodes, *node, *next;
unsigned int *order, n, m, o = 0;
int ret;
/* When allocating top-down, we expect to be returned a node
* from a suitable hole at the top of the drm_mm. We check that
* the returned node does match the highest available slot.
*/
ret = -ENOMEM;
nodes = vzalloc(count * sizeof(*nodes));
if (!nodes)
goto err;
bitmap = kzalloc(count / BITS_PER_LONG * sizeof(unsigned long),
GFP_KERNEL);
if (!bitmap)
goto err_nodes;
order = drm_random_order(count, &prng);
if (!order)
goto err_bitmap;
ret = -EINVAL;
for (size = 1; size <= 64; size <<= 1) {
drm_mm_init(&mm, 0, size*count);
for (n = 0; n < count; n++) {
if (!expect_insert(&mm, &nodes[n],
size, 0, n,
topdown)) {
pr_err("insert failed, size %u step %d\n", size, n);
goto out;
}
if (drm_mm_hole_follows(&nodes[n])) {
pr_err("hole after topdown insert %d, start=%llx\n, size=%u",
n, nodes[n].start, size);
goto out;
}
if (!assert_one_hole(&mm, 0, size*(count - n - 1)))
goto out;
}
if (!assert_continuous(&mm, size))
goto out;
drm_random_reorder(order, count, &prng);
for_each_prime_number_from(n, 1, min(count, max_prime)) {
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % count]];
drm_mm_remove_node(node);
__set_bit(node_index(node), bitmap);
}
for (m = 0; m < n; m++) {
unsigned int last;
node = &nodes[order[(o + m) % count]];
if (!expect_insert(&mm, node,
size, 0, 0,
topdown)) {
pr_err("insert failed, step %d/%d\n", m, n);
goto out;
}
if (drm_mm_hole_follows(node)) {
pr_err("hole after topdown insert %d/%d, start=%llx\n",
m, n, node->start);
goto out;
}
last = find_last_bit(bitmap, count);
if (node_index(node) != last) {
pr_err("node %d/%d, size %d, not inserted into upmost hole, expected %d, found %d\n",
m, n, size, last, node_index(node));
goto out;
}
__clear_bit(last, bitmap);
}
DRM_MM_BUG_ON(find_first_bit(bitmap, count) != count);
o += n;
}
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
DRM_MM_BUG_ON(!drm_mm_clean(&mm));
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
kfree(order);
err_bitmap:
kfree(bitmap);
err_nodes:
vfree(nodes);
err:
return ret;
}
static int igt_bottomup(void *ignored)
{
const struct insert_mode *bottomup = &insert_modes[BOTTOMUP];
DRM_RND_STATE(prng, random_seed);
const unsigned int count = 8192;
unsigned int size;
unsigned long *bitmap;
struct drm_mm mm;
struct drm_mm_node *nodes, *node, *next;
unsigned int *order, n, m, o = 0;
int ret;
/* Like igt_topdown, but instead of searching for the last hole,
* we search for the first.
*/
ret = -ENOMEM;
nodes = vzalloc(count * sizeof(*nodes));
if (!nodes)
goto err;
bitmap = kzalloc(count / BITS_PER_LONG * sizeof(unsigned long),
GFP_KERNEL);
if (!bitmap)
goto err_nodes;
order = drm_random_order(count, &prng);
if (!order)
goto err_bitmap;
ret = -EINVAL;
for (size = 1; size <= 64; size <<= 1) {
drm_mm_init(&mm, 0, size*count);
for (n = 0; n < count; n++) {
if (!expect_insert(&mm, &nodes[n],
size, 0, n,
bottomup)) {
pr_err("bottomup insert failed, size %u step %d\n", size, n);
goto out;
}
if (!assert_one_hole(&mm, size*(n + 1), size*count))
goto out;
}
if (!assert_continuous(&mm, size))
goto out;
drm_random_reorder(order, count, &prng);
for_each_prime_number_from(n, 1, min(count, max_prime)) {
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % count]];
drm_mm_remove_node(node);
__set_bit(node_index(node), bitmap);
}
for (m = 0; m < n; m++) {
unsigned int first;
node = &nodes[order[(o + m) % count]];
if (!expect_insert(&mm, node,
size, 0, 0,
bottomup)) {
pr_err("insert failed, step %d/%d\n", m, n);
goto out;
}
first = find_first_bit(bitmap, count);
if (node_index(node) != first) {
pr_err("node %d/%d not inserted into bottom hole, expected %d, found %d\n",
m, n, first, node_index(node));
goto out;
}
__clear_bit(first, bitmap);
}
DRM_MM_BUG_ON(find_first_bit(bitmap, count) != count);
o += n;
}
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
DRM_MM_BUG_ON(!drm_mm_clean(&mm));
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
kfree(order);
err_bitmap:
kfree(bitmap);
err_nodes:
vfree(nodes);
err:
return ret;
}
static int __igt_once(unsigned int mode)
{
struct drm_mm mm;
struct drm_mm_node rsvd_lo, rsvd_hi, node;
int err;
drm_mm_init(&mm, 0, 7);
memset(&rsvd_lo, 0, sizeof(rsvd_lo));
rsvd_lo.start = 1;
rsvd_lo.size = 1;
err = drm_mm_reserve_node(&mm, &rsvd_lo);
if (err) {
pr_err("Could not reserve low node\n");
goto err;
}
memset(&rsvd_hi, 0, sizeof(rsvd_hi));
rsvd_hi.start = 5;
rsvd_hi.size = 1;
err = drm_mm_reserve_node(&mm, &rsvd_hi);
if (err) {
pr_err("Could not reserve low node\n");
goto err_lo;
}
if (!drm_mm_hole_follows(&rsvd_lo) || !drm_mm_hole_follows(&rsvd_hi)) {
pr_err("Expected a hole after lo and high nodes!\n");
err = -EINVAL;
goto err_hi;
}
memset(&node, 0, sizeof(node));
err = drm_mm_insert_node_generic(&mm, &node,
2, 0, 0,
mode | DRM_MM_INSERT_ONCE);
if (!err) {
pr_err("Unexpectedly inserted the node into the wrong hole: node.start=%llx\n",
node.start);
err = -EINVAL;
goto err_node;
}
err = drm_mm_insert_node_generic(&mm, &node, 2, 0, 0, mode);
if (err) {
pr_err("Could not insert the node into the available hole!\n");
err = -EINVAL;
goto err_hi;
}
err_node:
drm_mm_remove_node(&node);
err_hi:
drm_mm_remove_node(&rsvd_hi);
err_lo:
drm_mm_remove_node(&rsvd_lo);
err:
drm_mm_takedown(&mm);
return err;
}
static int igt_lowest(void *ignored)
{
return __igt_once(DRM_MM_INSERT_LOW);
}
static int igt_highest(void *ignored)
{
return __igt_once(DRM_MM_INSERT_HIGH);
}
static void separate_adjacent_colors(const struct drm_mm_node *node,
unsigned long color,
u64 *start,
u64 *end)
{
if (node->allocated && node->color != color)
++*start;
node = list_next_entry(node, node_list);
if (node->allocated && node->color != color)
--*end;
}
static bool colors_abutt(const struct drm_mm_node *node)
{
if (!drm_mm_hole_follows(node) &&
list_next_entry(node, node_list)->allocated) {
pr_err("colors abutt; %ld [%llx + %llx] is next to %ld [%llx + %llx]!\n",
node->color, node->start, node->size,
list_next_entry(node, node_list)->color,
list_next_entry(node, node_list)->start,
list_next_entry(node, node_list)->size);
return true;
}
return false;
}
static int igt_color(void *ignored)
{
const unsigned int count = min(4096u, max_iterations);
const struct insert_mode *mode;
struct drm_mm mm;
struct drm_mm_node *node, *nn;
unsigned int n;
int ret = -EINVAL, err;
/* Color adjustment complicates everything. First we just check
* that when we insert a node we apply any color_adjustment callback.
* The callback we use should ensure that there is a gap between
* any two nodes, and so after each insertion we check that those
* holes are inserted and that they are preserved.
*/
drm_mm_init(&mm, 0, U64_MAX);
for (n = 1; n <= count; n++) {
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
if (!expect_insert(&mm, node,
n, 0, n,
&insert_modes[0])) {
pr_err("insert failed, step %d\n", n);
kfree(node);
goto out;
}
}
drm_mm_for_each_node_safe(node, nn, &mm) {
if (node->color != node->size) {
pr_err("invalid color stored: expected %lld, found %ld\n",
node->size, node->color);
goto out;
}
drm_mm_remove_node(node);
kfree(node);
}
/* Now, let's start experimenting with applying a color callback */
mm.color_adjust = separate_adjacent_colors;
for (mode = insert_modes; mode->name; mode++) {
u64 last;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
node->size = 1 + 2*count;
node->color = node->size;
err = drm_mm_reserve_node(&mm, node);
if (err) {
pr_err("initial reserve failed!\n");
ret = err;
goto out;
}
last = node->start + node->size;
for (n = 1; n <= count; n++) {
int rem;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
node->start = last;
node->size = n + count;
node->color = node->size;
err = drm_mm_reserve_node(&mm, node);
if (err != -ENOSPC) {
pr_err("reserve %d did not report color overlap! err=%d\n",
n, err);
goto out;
}
node->start += n + 1;
rem = misalignment(node, n + count);
node->start += n + count - rem;
err = drm_mm_reserve_node(&mm, node);
if (err) {
pr_err("reserve %d failed, err=%d\n", n, err);
ret = err;
goto out;
}
last = node->start + node->size;
}
for (n = 1; n <= count; n++) {
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
if (!expect_insert(&mm, node,
n, n, n,
mode)) {
pr_err("%s insert failed, step %d\n",
mode->name, n);
kfree(node);
goto out;
}
}
drm_mm_for_each_node_safe(node, nn, &mm) {
u64 rem;
if (node->color != node->size) {
pr_err("%s invalid color stored: expected %lld, found %ld\n",
mode->name, node->size, node->color);
goto out;
}
if (colors_abutt(node))
goto out;
div64_u64_rem(node->start, node->size, &rem);
if (rem) {
pr_err("%s colored node misaligned, start=%llx expected alignment=%lld [rem=%lld]\n",
mode->name, node->start, node->size, rem);
goto out;
}
drm_mm_remove_node(node);
kfree(node);
}
cond_resched();
}
ret = 0;
out:
drm_mm_for_each_node_safe(node, nn, &mm) {
drm_mm_remove_node(node);
kfree(node);
}
drm_mm_takedown(&mm);
return ret;
}
static int evict_color(struct drm_mm *mm,
u64 range_start, u64 range_end,
struct evict_node *nodes,
unsigned int *order,
unsigned int count,
unsigned int size,
unsigned int alignment,
unsigned long color,
const struct insert_mode *mode)
{
struct drm_mm_scan scan;
LIST_HEAD(evict_list);
struct evict_node *e;
struct drm_mm_node tmp;
int err;
drm_mm_scan_init_with_range(&scan, mm,
size, alignment, color,
range_start, range_end,
mode->mode);
if (!evict_nodes(&scan,
nodes, order, count, true,
&evict_list))
return -EINVAL;
memset(&tmp, 0, sizeof(tmp));
err = drm_mm_insert_node_generic(mm, &tmp, size, alignment, color,
DRM_MM_INSERT_EVICT);
if (err) {
pr_err("Failed to insert into eviction hole: size=%d, align=%d, color=%lu, err=%d\n",
size, alignment, color, err);
show_scan(&scan);
show_holes(mm, 3);
return err;
}
if (tmp.start < range_start || tmp.start + tmp.size > range_end) {
pr_err("Inserted [address=%llu + %llu] did not fit into the request range [%llu, %llu]\n",
tmp.start, tmp.size, range_start, range_end);
err = -EINVAL;
}
if (colors_abutt(&tmp))
err = -EINVAL;
if (!assert_node(&tmp, mm, size, alignment, color)) {
pr_err("Inserted did not fit the eviction hole: size=%lld [%d], align=%d [rem=%lld], start=%llx\n",
tmp.size, size,
alignment, misalignment(&tmp, alignment), tmp.start);
err = -EINVAL;
}
drm_mm_remove_node(&tmp);
if (err)
return err;
list_for_each_entry(e, &evict_list, link) {
err = drm_mm_reserve_node(mm, &e->node);
if (err) {
pr_err("Failed to reinsert node after eviction: start=%llx\n",
e->node.start);
return err;
}
}
cond_resched();
return 0;
}
static int igt_color_evict(void *ignored)
{
DRM_RND_STATE(prng, random_seed);
const unsigned int total_size = min(8192u, max_iterations);
const struct insert_mode *mode;
unsigned long color = 0;
struct drm_mm mm;
struct evict_node *nodes;
struct drm_mm_node *node, *next;
unsigned int *order, n;
int ret, err;
/* Check that the drm_mm_scan also honours color adjustment when
* choosing its victims to create a hole. Our color_adjust does not
* allow two nodes to be placed together without an intervening hole
* enlarging the set of victims that must be evicted.
*/
ret = -ENOMEM;
nodes = vzalloc(total_size * sizeof(*nodes));
if (!nodes)
goto err;
order = drm_random_order(total_size, &prng);
if (!order)
goto err_nodes;
ret = -EINVAL;
drm_mm_init(&mm, 0, 2*total_size - 1);
mm.color_adjust = separate_adjacent_colors;
for (n = 0; n < total_size; n++) {
if (!expect_insert(&mm, &nodes[n].node,
1, 0, color++,
&insert_modes[0])) {
pr_err("insert failed, step %d\n", n);
goto out;
}
}
for (mode = evict_modes; mode->name; mode++) {
for (n = 1; n <= total_size; n <<= 1) {
drm_random_reorder(order, total_size, &prng);
err = evict_color(&mm, 0, U64_MAX,
nodes, order, total_size,
n, 1, color++,
mode);
if (err) {
pr_err("%s evict_color(size=%u) failed\n",
mode->name, n);
goto out;
}
}
for (n = 1; n < total_size; n <<= 1) {
drm_random_reorder(order, total_size, &prng);
err = evict_color(&mm, 0, U64_MAX,
nodes, order, total_size,
total_size/2, n, color++,
mode);
if (err) {
pr_err("%s evict_color(size=%u, alignment=%u) failed\n",
mode->name, total_size/2, n);
goto out;
}
}
for_each_prime_number_from(n, 1, min(total_size, max_prime)) {
unsigned int nsize = (total_size - n + 1) / 2;
DRM_MM_BUG_ON(!nsize);
drm_random_reorder(order, total_size, &prng);
err = evict_color(&mm, 0, U64_MAX,
nodes, order, total_size,
nsize, n, color++,
mode);
if (err) {
pr_err("%s evict_color(size=%u, alignment=%u) failed\n",
mode->name, nsize, n);
goto out;
}
}
cond_resched();
}
ret = 0;
out:
if (ret)
show_mm(&mm);
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
kfree(order);
err_nodes:
vfree(nodes);
err:
return ret;
}
static int igt_color_evict_range(void *ignored)
{
DRM_RND_STATE(prng, random_seed);
const unsigned int total_size = 8192;
const unsigned int range_size = total_size / 2;
const unsigned int range_start = total_size / 4;
const unsigned int range_end = range_start + range_size;
const struct insert_mode *mode;
unsigned long color = 0;
struct drm_mm mm;
struct evict_node *nodes;
struct drm_mm_node *node, *next;
unsigned int *order, n;
int ret, err;
/* Like igt_color_evict(), but limited to small portion of the full
* drm_mm range.
*/
ret = -ENOMEM;
nodes = vzalloc(total_size * sizeof(*nodes));
if (!nodes)
goto err;
order = drm_random_order(total_size, &prng);
if (!order)
goto err_nodes;
ret = -EINVAL;
drm_mm_init(&mm, 0, 2*total_size - 1);
mm.color_adjust = separate_adjacent_colors;
for (n = 0; n < total_size; n++) {
if (!expect_insert(&mm, &nodes[n].node,
1, 0, color++,
&insert_modes[0])) {
pr_err("insert failed, step %d\n", n);
goto out;
}
}
for (mode = evict_modes; mode->name; mode++) {
for (n = 1; n <= range_size; n <<= 1) {
drm_random_reorder(order, range_size, &prng);
err = evict_color(&mm, range_start, range_end,
nodes, order, total_size,
n, 1, color++,
mode);
if (err) {
pr_err("%s evict_color(size=%u) failed for range [%x, %x]\n",
mode->name, n, range_start, range_end);
goto out;
}
}
for (n = 1; n < range_size; n <<= 1) {
drm_random_reorder(order, total_size, &prng);
err = evict_color(&mm, range_start, range_end,
nodes, order, total_size,
range_size/2, n, color++,
mode);
if (err) {
pr_err("%s evict_color(size=%u, alignment=%u) failed for range [%x, %x]\n",
mode->name, total_size/2, n, range_start, range_end);
goto out;
}
}
for_each_prime_number_from(n, 1, min(range_size, max_prime)) {
unsigned int nsize = (range_size - n + 1) / 2;
DRM_MM_BUG_ON(!nsize);
drm_random_reorder(order, total_size, &prng);
err = evict_color(&mm, range_start, range_end,
nodes, order, total_size,
nsize, n, color++,
mode);
if (err) {
pr_err("%s evict_color(size=%u, alignment=%u) failed for range [%x, %x]\n",
mode->name, nsize, n, range_start, range_end);
goto out;
}
}
cond_resched();
}
ret = 0;
out:
if (ret)
show_mm(&mm);
drm_mm_for_each_node_safe(node, next, &mm)
drm_mm_remove_node(node);
drm_mm_takedown(&mm);
kfree(order);
err_nodes:
vfree(nodes);
err:
return ret;
}
#include "drm_selftest.c"
static int __init test_drm_mm_init(void)
{
int err;
while (!random_seed)
random_seed = get_random_int();
pr_info("Testing DRM range manger (struct drm_mm), with random_seed=0x%x max_iterations=%u max_prime=%u\n",
random_seed, max_iterations, max_prime);
err = run_selftests(selftests, ARRAY_SIZE(selftests), NULL);
return err > 0 ? 0 : err;
}
static void __exit test_drm_mm_exit(void)
{
}
module_init(test_drm_mm_init);
module_exit(test_drm_mm_exit);
module_param(random_seed, uint, 0400);
module_param(max_iterations, uint, 0400);
module_param(max_prime, uint, 0400);
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL");