A set of test cases to capture some annoying bugs and to ensure that correct behaviour does not regress whilst fixing those!
Signed-off-by: Chris Wilson chris@chris-wilson.co.uk --- drivers/gpu/drm/Kconfig | 13 + drivers/gpu/drm/Makefile | 2 + drivers/gpu/drm/test-drm_mm.c | 829 ++++++++++++++++++++++++++++++++++ tools/testing/selftests/Makefile | 1 + tools/testing/selftests/drm/drm_mm.sh | 11 + 5 files changed, 856 insertions(+) create mode 100644 drivers/gpu/drm/test-drm_mm.c create mode 100644 tools/testing/selftests/drm/drm_mm.sh
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 95fc0410e129..22b0f28c0137 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -48,6 +48,19 @@ config DRM_DEBUG_MM
If in doubt, say "N".
+config DRM_DEBUG_MM_SELFTEST + tristate "kselftests for DRM range manager (struct drm_mm)" + depends on DRM + depends on DEBUG_KERNEL + default n + help + This option provides a kernel module that can be used to test + the DRM range manager (drm_mm) and its API. This option is not + useful for distributions or general kernels, but only for kernel + developers working on DRM and associated drivers. + + Say N if you are unsure + config DRM_KMS_HELPER tristate depends on DRM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 883f3e75cfbc..57818abdb5b8 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -18,6 +18,8 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \ drm_plane.o drm_color_mgmt.o drm_print.o \ drm_dumb_buffers.o drm_mode_config.o
+obj-$(CONFIG_DRM_DEBUG_MM_SELFTEST) += test-drm_mm.o + drm-$(CONFIG_COMPAT) += drm_ioc32.o drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o drm-$(CONFIG_PCI) += ati_pcigart.o diff --git a/drivers/gpu/drm/test-drm_mm.c b/drivers/gpu/drm/test-drm_mm.c new file mode 100644 index 000000000000..c2ffb6a0f344 --- /dev/null +++ b/drivers/gpu/drm/test-drm_mm.c @@ -0,0 +1,829 @@ +/* + * Test cases for the drm_mm range manager + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/vmalloc.h> + +#include <drm/drm_mm.h> + +static unsigned long *primes; +static unsigned long prime_last, prime_sz; + +static unsigned long slow_next_prime_number(unsigned long x) +{ + for (;;) { + unsigned long y = int_sqrt(++x) + 1; + while (y > 1) { + if ((x % y) == 0) + break; + y--; + } + if (y == 1) + return x; + } +} + +static unsigned long mark_multiples(unsigned long x, + unsigned long *p, + unsigned long start, + unsigned long end) +{ + unsigned long m; + + m = 2*x; + if (m < start) + m = (start / x + 1) * x; + + while (m < end) { + __clear_bit(m, p); + m += x; + } + + return x; +} + +static unsigned long next_prime_number(unsigned long x) +{ + if (x == 1) + return 2; + + if (x >= prime_last) { + unsigned long sz, y; + unsigned long *nprimes; + + sz = x*x; + if (sz < x) + return slow_next_prime_number(x); + + sz = round_up(sz, BITS_PER_LONG); + nprimes = krealloc(primes, sz / sizeof(long), GFP_KERNEL); + if (!nprimes) + return slow_next_prime_number(x); + + /* Where memory permits, track the primes using the + * Sieve of Eratosthenes. + */ + memset(nprimes + prime_sz / BITS_PER_LONG, + 0xff, (sz - prime_sz) / sizeof(long)); + for (y = 2UL; y < sz; y = find_next_bit(nprimes, sz, y + 1)) + prime_last = mark_multiples(y, nprimes, prime_sz, sz); + + primes = nprimes; + prime_sz = sz; + } + + return find_next_bit(primes, prime_last, x + 1); +} + +#define for_each_prime(prime, max) \ + for (prime = 1; prime < (max); prime = next_prime_number(prime)) + +enum test { + SUBTEST_INIT, + SUBTEST_RESERVE, + SUBTEST_INSERT, + SUBTEST_ALIGN32, + SUBTEST_ALIGN64, + SUBTEST_EVICT, + SUBTEST_TOPDOWN, +}; + +static int subtest_init(void) +{ + struct drm_mm mm; + struct drm_mm_node *hole; + u64 start, end; + int ret = -EINVAL; + + drm_mm_init(&mm, 0, 4096); + if (!drm_mm_clean(&mm)) { + pr_err("mm not empty on creation\n"); + goto out; + } + + drm_mm_for_each_hole(hole, &mm, start, end) { + if (start != 0 || end != 4096) { + pr_err("empty mm has incorrect hole, found (%llx, %llx), expect (%llx, %llx)\n", + start, end, + 0ull, 4096ull); + goto out; + } + } + + ret = 0; +out: + if (ret) + drm_mm_debug_table(&mm, __func__); + drm_mm_takedown(&mm); + return ret; +} + +static int *random_order(int count) +{ + int *order; + int n; + + order = kmalloc_array(count, sizeof(*order), GFP_TEMPORARY); + if (!order) + return order; + + for (n = 0; n < count; n++) + order[n] = n; + + for (n = count-1; n > 1; n--) { + int r = get_random_int() % (n + 1); + if (r != n) { + int tmp = order[n]; + order[n] = order[r]; + order[r] = tmp; + } + } + + return order; +} + +static int __subtest_reserve(int count, u64 size) +{ + struct drm_mm mm; + struct drm_mm_node *node, *next; + int *order, n; + int ret; + + /* Fill a range with lots of nodes, check it doesn't fail too early */ + + ret = -ENOMEM; + order = random_order(count); + if (!order) + goto err; + + ret = -EINVAL; + drm_mm_init(&mm, 0, count * size); + if (!drm_mm_clean(&mm)) { + pr_err("mm not empty on creation\n"); + goto out; + } + + for (n = 0; n < count; n++) { + int err; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + ret = -ENOMEM; + goto out; + } + + node->start = order[n] * size; + node->size = size; + + err = drm_mm_reserve_node(&mm, node); + if (err) { + pr_err("reserve failed, step %d, start %llu\n", + n, node->start); + ret = err; + goto out; + } + } + + /* Repeated use should then fail */ + for (n = 0; n < count; n++) { + struct drm_mm_node tmp = { + .start = order[n] * size, + .size = 1 + }; + + if (!drm_mm_reserve_node(&mm, &tmp)) { + drm_mm_remove_node(&tmp); + pr_err("impossible reserve succeed, step %d, start %llu\n", + n, tmp.start); + goto out; + } + } + + ret = 0; +out: + list_for_each_entry_safe(node, next, + &mm.head_node.node_list, node_list) { + drm_mm_remove_node(node); + kfree(node); + } + drm_mm_takedown(&mm); + kfree(order); +err: + return ret; +} + +static int subtest_reserve(void) +{ + int n, ret; + + for (n = 1; n < 50; n++) { + ret = __subtest_reserve(8192, (1ull << n) - 1); + if (ret) + return ret; + + ret = __subtest_reserve(8192, 1ull << n); + if (ret) + return ret; + + ret = __subtest_reserve(8192, (1ull << n) + 1); + if (ret) + return ret; + } + + return 0; +} + +static int __subtest_insert(int count, u64 size) +{ + struct drm_mm mm; + struct drm_mm_node *nodes, *node, *next; + int *order, n, o = 0; + int ret; + + /* Fill a range with lots of nodes, check it doesn't fail too early */ + + ret = -ENOMEM; + nodes = vzalloc(count * sizeof(*nodes)); + if (!nodes) + goto err; + + order = random_order(count); + if (!order) + goto err_nodes; + + ret = -EINVAL; + drm_mm_init(&mm, 0, count * size); + if (!drm_mm_clean(&mm)) { + pr_err("mm not empty on creation\n"); + goto out; + } + + for (n = 0; n < count; n++) { + int err; + + err = drm_mm_insert_node(&mm, &nodes[n], size, 0, + DRM_MM_SEARCH_DEFAULT); + if (err) { + pr_err("insert failed, step %d, start %llu\n", + n, nodes[n].start); + ret = err; + goto out; + } + } + + /* Repeated use should then fail */ + if (1) { + struct drm_mm_node tmp; + + memset(&tmp, 0, sizeof(tmp)); + if (!drm_mm_insert_node(&mm, &tmp, size, 0, + DRM_MM_SEARCH_DEFAULT)) { + drm_mm_remove_node(&tmp); + pr_err("impossible insert succeed, step %d, start %llu\n", + n, tmp.start); + goto out; + } + } + + n = 0; + drm_mm_for_each_node(node, &mm) { + if (node->start != n * size) { + pr_err("node %d out of order, expected start %llx, found %llx\n", + n, n * size, node->start); + goto out; + } + + if (node->size != size) { + pr_err("node %d has wrong size, expected size %llx, found %llx\n", + n, size, node->size); + goto out; + } + + if (node->hole_follows) { + pr_err("node %d is followed by a hole!\n", n); + goto out; + } + + n++; + } + + for (n = 0; n < count; n++) { + drm_mm_for_each_node_in_range(node, &mm, n * size, (n + 1) * size) { + if (node->start != n * size) { + pr_err("lookup node %d out of order, expected start %llx, found %llx\n", + n, n * size, node->start); + goto out; + } + } + } + + /* Remove one and reinsert, as the only hole it should refill itself */ + for (n = 0; n < count; n++) { + int err; + + drm_mm_remove_node(&nodes[n]); + err = drm_mm_insert_node(&mm, &nodes[n], size, 0, + DRM_MM_SEARCH_DEFAULT); + if (err) { + pr_err("reinsert failed, step %d\n", n); + ret = err; + goto out; + } + + if (nodes[n].start != n * size) { + pr_err("reinsert node moved, step %d, expected %llx, found %llx\n", + n, n * size, nodes[n].start); + goto out; + } + } + + /* Remove several, reinsert, check full */ + for_each_prime(n, count) { + int m; + + for (m = 0; m < n; m++) { + node = &nodes[order[(o + m) % count]]; + drm_mm_remove_node(node); + } + + for (m = 0; m < n; m++) { + int err; + + node = &nodes[order[(o + m) % count]]; + err = drm_mm_insert_node(&mm, node, size, 0, + DRM_MM_SEARCH_DEFAULT); + if (err) { + pr_err("insert failed, step %d, start %llu\n", + n, node->start); + ret = err; + goto out; + } + } + + o += n; + + if (1) { + struct drm_mm_node tmp; + + memset(&tmp, 0, sizeof(tmp)); + if (!drm_mm_insert_node(&mm, &tmp, size, 0, + DRM_MM_SEARCH_DEFAULT)) { + drm_mm_remove_node(&tmp); + pr_err("impossible insert succeed, start %llu\n", + tmp.start); + goto out; + } + } + + m = 0; + drm_mm_for_each_node(node, &mm) { + if (node->start != m * size) { + pr_err("node %d out of order, expected start %llx, found %llx\n", + m, m * size, node->start); + goto out; + } + + if (node->size != size) { + pr_err("node %d has wrong size, expected size %llx, found %llx\n", + m, size, node->size); + goto out; + } + + if (node->hole_follows) { + pr_err("node %d is followed by a hole!\n", m); + goto out; + } + + m++; + } + } + + ret = 0; +out: + list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list) + drm_mm_remove_node(node); + drm_mm_takedown(&mm); + kfree(order); +err_nodes: + vfree(nodes); +err: + return ret; +} + +static int subtest_insert(void) +{ + int n, ret; + + for (n = 1; n < 50; n++) { + ret = __subtest_insert(8192, (1ull << n) - 1); + if (ret) + return ret; + + ret = __subtest_insert(8192, 1ull << n); + if (ret) + return ret; + + ret = __subtest_insert(8192, (1ull << n) + 1); + if (ret) + return ret; + } + + return 0; +} + +static int subtest_align_pot(int max) +{ + struct drm_mm mm; + struct drm_mm_node *node, *next; + int bit; + int ret = -EINVAL; + + drm_mm_init(&mm, 1, -2); + if (!drm_mm_clean(&mm)) { + pr_err("mm not empty on creation\n"); + goto out; + } + + for (bit = max - 1; bit; bit--) { + int err; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + ret = -ENOMEM; + goto out; + } + + err = drm_mm_insert_node_generic(&mm, node, 1, + BIT_ULL(bit), bit, + DRM_MM_SEARCH_DEFAULT, + DRM_MM_CREATE_DEFAULT); + if (err) { + pr_err("insert failed with alignment=%llx [%d]", + BIT_ULL(bit), bit); + ret = err; + goto out; + } + + if (node->start & (BIT_ULL(bit) - 1)) { + pr_err("node inserted into wrong location %llx, expected alignment to %llx [%d]\n", + node->start, BIT_ULL(bit), bit); + goto out; + } + } + + ret = 0; +out: + list_for_each_entry_safe(node, next, + &mm.head_node.node_list, node_list) { + drm_mm_remove_node(node); + kfree(node); + } + drm_mm_takedown(&mm); + return ret; +} + +static int subtest_align32(void) +{ + return subtest_align_pot(32); +} + +static int subtest_align64(void) +{ + return subtest_align_pot(64); +} + +static int subtest_evict(void) +{ + const int size = 8192; + struct drm_mm mm; + struct evict_node { + struct drm_mm_node node; + struct list_head link; + } *nodes; + struct drm_mm_node *node, *next; + int *order, n, m; + int ret; + + ret = -ENOMEM; + nodes = vzalloc(size * sizeof(*nodes)); + if (!nodes) + goto err; + + order = random_order(size); + if (!order) + goto err_nodes; + + ret = -EINVAL; + drm_mm_init(&mm, 0, size); + for (n = 0; n < size; n++) { + int err; + + err = drm_mm_insert_node(&mm, &nodes[n].node, 1, 0, + DRM_MM_SEARCH_DEFAULT); + if (err) { + pr_err("insert failed, step %d\n", n); + ret = err; + goto out; + } + } + + for (n = 1; n < size; n <<= 1) { + const int nsize = size / 2; + LIST_HEAD(evict_list); + struct evict_node *e, *en; + struct drm_mm_node tmp; + bool found = false; + int err; + + drm_mm_init_scan(&mm, nsize, n, 0); + for (m = 0; m < size; m++) { + e = &nodes[order[m]]; + list_add(&e->link, &evict_list); + if (drm_mm_scan_add_block(&e->node)) { + found = true; + break; + } + } + if (!found) { + pr_err("Failed to fail eviction: size=%d, align=%d\n", + nsize, n); + goto out; + } + + list_for_each_entry_safe(e, en, &evict_list, link) { + if (!drm_mm_scan_remove_block(&e->node)) + list_del(&e->link); + } + + list_for_each_entry(e, &evict_list, link) + drm_mm_remove_node(&e->node); + + memset(&tmp, 0, sizeof(tmp)); + err = drm_mm_insert_node(&mm, &tmp, nsize, n, + DRM_MM_SEARCH_DEFAULT); + if (err) { + pr_err("Failed to insert into eviction hole: size=%d, alignt=%d\n", + nsize, n); + goto out; + } + + if (tmp.start % n || tmp.size != nsize || tmp.hole_follows) { + pr_err("Inserted did not align to eviction hole: size=%lld [%d], align=%d, start=%llx, hole-follows?=%d\n", + tmp.size, nsize, n, tmp.start, tmp.hole_follows); + + drm_mm_remove_node(&tmp); + goto out; + } + + drm_mm_remove_node(&tmp); + 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); + ret = err; + goto out; + } + } + } + + for_each_prime(n, size) { + LIST_HEAD(evict_list); + struct evict_node *e, *en; + struct drm_mm_node tmp; + int nsize = (size - n - 1) / 2; + bool found = false; + int err; + + drm_mm_init_scan(&mm, nsize, n, 0); + for (m = 0; m < size; m++) { + e = &nodes[order[m]]; + list_add(&e->link, &evict_list); + if (drm_mm_scan_add_block(&e->node)) { + found = true; + break; + } + } + if (!found) { + pr_err("Failed to fail eviction: size=%d, align=%d\n", + nsize, n); + goto out; + } + + list_for_each_entry_safe(e, en, &evict_list, link) { + if (!drm_mm_scan_remove_block(&e->node)) + list_del(&e->link); + } + + list_for_each_entry(e, &evict_list, link) + drm_mm_remove_node(&e->node); + + memset(&tmp, 0, sizeof(tmp)); + err = drm_mm_insert_node(&mm, &tmp, nsize, n, + DRM_MM_SEARCH_DEFAULT); + if (err) { + pr_err("Failed to insert into eviction hole: size=%d, alignt=%d\n", + nsize, n); + goto out; + } + + if (tmp.start % n || tmp.size != nsize || tmp.hole_follows) { + pr_err("Inserted did not align to eviction hole: size=%lld [%d], align=%d, start=%llx, hole-follows?=%d\n", + tmp.size, nsize, n, tmp.start, tmp.hole_follows); + + drm_mm_remove_node(&tmp); + goto out; + } + + drm_mm_remove_node(&tmp); + 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); + ret = err; + goto out; + } + } + } + + ret = 0; +out: + list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list) + drm_mm_remove_node(node); + drm_mm_takedown(&mm); + kfree(order); +err_nodes: + vfree(nodes); +err: + return ret; +} + +static int subtest_topdown(void) +{ + const int size = 8192; + unsigned long *bitmap; + struct drm_mm mm; + struct drm_mm_node *nodes, *node, *next; + int *order, n, m, o = 0; + int ret; + + ret = -ENOMEM; + nodes = vzalloc(size * sizeof(*nodes)); + if (!nodes) + goto err; + + bitmap = kzalloc(size / BITS_PER_LONG * sizeof(unsigned long), + GFP_TEMPORARY); + if (!bitmap) + goto err_nodes; + + order = random_order(size); + if (!order) + goto err_bitmap; + + ret = -EINVAL; + drm_mm_init(&mm, 0, size); + for (n = 0; n < size; n++) { + int err; + + err = drm_mm_insert_node_generic(&mm, &nodes[n], 1, 0, 0, + DRM_MM_SEARCH_BELOW, + DRM_MM_CREATE_TOP); + if (err) { + pr_err("insert failed, step %d\n", n); + ret = err; + goto out; + } + + if (nodes[n].hole_follows) { + pr_err("hole after topdown insert %d, start=%llx\n", + n, nodes[n].start); + goto out; + } + } + + for_each_prime(n, size) { + for (m = 0; m < n; m++) { + node = &nodes[order[(o + m) % size]]; + drm_mm_remove_node(node); + __set_bit(node->start, bitmap); + } + + for (m = 0; m < n; m++) { + int err, last; + + node = &nodes[order[(o + m) % size]]; + err = drm_mm_insert_node_generic(&mm, node, 1, 0, 0, + DRM_MM_SEARCH_BELOW, + DRM_MM_CREATE_TOP); + if (err) { + pr_err("insert failed, step %d/%d\n", m, n); + ret = err; + goto out; + } + + if (node->hole_follows) { + pr_err("hole after topdown insert %d/%d, start=%llx\n", + m, n, node->start); + goto out; + } + + last = find_last_bit(bitmap, size); + if (node->start != last) { + pr_err("node %d/%d not inserted into upmost hole, expected %d, found %lld\n", + m, n, last, node->start); + goto out; + } + __clear_bit(last, bitmap); + } + + o += n; + } + + ret = 0; +out: + list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list) + drm_mm_remove_node(node); + drm_mm_takedown(&mm); + kfree(order); +err_bitmap: + kfree(bitmap); +err_nodes: + vfree(nodes); +err: + return ret; +} + +struct subtest { + const char *name; + int (*func)(void); + bool enabled; +} subtests[] = { + [SUBTEST_INIT] = { "init", subtest_init }, + [SUBTEST_RESERVE] = { "reserve", subtest_reserve }, + [SUBTEST_INSERT] = { "insert", subtest_insert }, + [SUBTEST_ALIGN32] = { "align32", subtest_align32 }, + [SUBTEST_ALIGN64] = { "align64", subtest_align64 }, + [SUBTEST_EVICT] = { "evict", subtest_evict }, + [SUBTEST_TOPDOWN] = { "topdown", subtest_topdown }, +}; + +/* Tests executed in reverse order (last in list goes first) */ +module_param_named(subtest__topdown, subtests[SUBTEST_TOPDOWN].enabled, bool, 0400); +module_param_named(subtest__evict, subtests[SUBTEST_EVICT].enabled, bool, 0400); +module_param_named(subtest__align64, subtests[SUBTEST_ALIGN64].enabled, bool, 0400); +module_param_named(subtest__align32, subtests[SUBTEST_ALIGN32].enabled, bool, 0400); +module_param_named(subtest__insert, subtests[SUBTEST_INSERT].enabled, bool, 0400); +module_param_named(subtest__reserve, subtests[SUBTEST_RESERVE].enabled, bool, 0400); +module_param_named(subtest__init, subtests[SUBTEST_INIT].enabled, bool, 0400); + +static void set_default_test_all(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(subtests); i++) + if (subtests[i].enabled) + return; + + for (i = 0; i < ARRAY_SIZE(subtests); i++) + subtests[i].enabled = true; +} + +static int __init test_drm_mm_init(void) +{ + int i; + + pr_info("Testing DRM range manger (struct drm_mm)\n"); + set_default_test_all(); + + for (i = 0; i < ARRAY_SIZE(subtests); i++) { + int err; + + if (!subtests[i].enabled) + continue; + + pr_debug("Running %s\n", subtests[i].name); + err = subtests[i].func(); + if (err) + return err; + } + + return 0; +} + +static void __exit test_drm_mm_exit(void) +{ + kfree(primes); +} + +module_init(test_drm_mm_init); +module_exit(test_drm_mm_exit); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL"); diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index f770dba2a6f6..f48229197fa8 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -1,6 +1,7 @@ TARGETS = breakpoints TARGETS += capabilities TARGETS += cpu-hotplug +TARGETS += drm TARGETS += efivarfs TARGETS += exec TARGETS += firmware diff --git a/tools/testing/selftests/drm/drm_mm.sh b/tools/testing/selftests/drm/drm_mm.sh new file mode 100644 index 000000000000..59c20ce50b10 --- /dev/null +++ b/tools/testing/selftests/drm/drm_mm.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Runs API tests for struct drm_mm (DRM range manager) + +if /sbin/modprobe -q test-drm_mm; then + /sbin/modprobe -q -r test-drm_mm + echo "drm/drm_mm: ok" +else + echo "drm/drm_mm: [FAIL]" + exit 1 +fi +
Build the struct drm_mm selftests so that we can trivially run them within our CI.
Signed-off-by: Chris Wilson chris@chris-wilson.co.uk --- drivers/gpu/drm/i915/Kconfig.debug | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/gpu/drm/i915/Kconfig.debug b/drivers/gpu/drm/i915/Kconfig.debug index f0e769290e0b..e1764ffa6fd0 100644 --- a/drivers/gpu/drm/i915/Kconfig.debug +++ b/drivers/gpu/drm/i915/Kconfig.debug @@ -22,6 +22,7 @@ config DRM_I915_DEBUG select X86_MSR # used by igt/pm_rpm select DRM_VGEM # used by igt/prime_vgem (dmabuf interop checks) select DRM_DEBUG_MM if DRM=y + select DRM_DEBUG_MM_SELFTEST select DRM_I915_SW_FENCE_DEBUG_OBJECTS default n help
Am 27.11.2016 um 15:08 schrieb Chris Wilson:
A set of test cases to capture some annoying bugs and to ensure that correct behaviour does not regress whilst fixing those!
Signed-off-by: Chris Wilson chris@chris-wilson.co.uk
Acked-by: Christian König christian.koenig@amd.com.
drivers/gpu/drm/Kconfig | 13 + drivers/gpu/drm/Makefile | 2 + drivers/gpu/drm/test-drm_mm.c | 829 ++++++++++++++++++++++++++++++++++ tools/testing/selftests/Makefile | 1 + tools/testing/selftests/drm/drm_mm.sh | 11 + 5 files changed, 856 insertions(+) create mode 100644 drivers/gpu/drm/test-drm_mm.c create mode 100644 tools/testing/selftests/drm/drm_mm.sh
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 95fc0410e129..22b0f28c0137 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -48,6 +48,19 @@ config DRM_DEBUG_MM
If in doubt, say "N".
+config DRM_DEBUG_MM_SELFTEST
- tristate "kselftests for DRM range manager (struct drm_mm)"
- depends on DRM
- depends on DEBUG_KERNEL
- default n
- help
This option provides a kernel module that can be used to test
the DRM range manager (drm_mm) and its API. This option is not
useful for distributions or general kernels, but only for kernel
developers working on DRM and associated drivers.
Say N if you are unsure
- config DRM_KMS_HELPER tristate depends on DRM
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 883f3e75cfbc..57818abdb5b8 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -18,6 +18,8 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \ drm_plane.o drm_color_mgmt.o drm_print.o \ drm_dumb_buffers.o drm_mode_config.o
+obj-$(CONFIG_DRM_DEBUG_MM_SELFTEST) += test-drm_mm.o
- drm-$(CONFIG_COMPAT) += drm_ioc32.o drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o drm-$(CONFIG_PCI) += ati_pcigart.o
diff --git a/drivers/gpu/drm/test-drm_mm.c b/drivers/gpu/drm/test-drm_mm.c new file mode 100644 index 000000000000..c2ffb6a0f344 --- /dev/null +++ b/drivers/gpu/drm/test-drm_mm.c @@ -0,0 +1,829 @@ +/*
- Test cases for the drm_mm range manager
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/module.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/vmalloc.h>
+#include <drm/drm_mm.h>
+static unsigned long *primes; +static unsigned long prime_last, prime_sz;
+static unsigned long slow_next_prime_number(unsigned long x) +{
- for (;;) {
unsigned long y = int_sqrt(++x) + 1;
while (y > 1) {
if ((x % y) == 0)
break;
y--;
}
if (y == 1)
return x;
- }
+}
+static unsigned long mark_multiples(unsigned long x,
unsigned long *p,
unsigned long start,
unsigned long end)
+{
- unsigned long m;
- m = 2*x;
- if (m < start)
m = (start / x + 1) * x;
- while (m < end) {
__clear_bit(m, p);
m += x;
- }
- return x;
+}
+static unsigned long next_prime_number(unsigned long x) +{
- if (x == 1)
return 2;
- if (x >= prime_last) {
unsigned long sz, y;
unsigned long *nprimes;
sz = x*x;
if (sz < x)
return slow_next_prime_number(x);
sz = round_up(sz, BITS_PER_LONG);
nprimes = krealloc(primes, sz / sizeof(long), GFP_KERNEL);
if (!nprimes)
return slow_next_prime_number(x);
/* Where memory permits, track the primes using the
* Sieve of Eratosthenes.
*/
memset(nprimes + prime_sz / BITS_PER_LONG,
0xff, (sz - prime_sz) / sizeof(long));
for (y = 2UL; y < sz; y = find_next_bit(nprimes, sz, y + 1))
prime_last = mark_multiples(y, nprimes, prime_sz, sz);
primes = nprimes;
prime_sz = sz;
- }
- return find_next_bit(primes, prime_last, x + 1);
+}
+#define for_each_prime(prime, max) \
- for (prime = 1; prime < (max); prime = next_prime_number(prime))
+enum test {
- SUBTEST_INIT,
- SUBTEST_RESERVE,
- SUBTEST_INSERT,
- SUBTEST_ALIGN32,
- SUBTEST_ALIGN64,
- SUBTEST_EVICT,
- SUBTEST_TOPDOWN,
+};
+static int subtest_init(void) +{
- struct drm_mm mm;
- struct drm_mm_node *hole;
- u64 start, end;
- int ret = -EINVAL;
- drm_mm_init(&mm, 0, 4096);
- if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
- }
- drm_mm_for_each_hole(hole, &mm, start, end) {
if (start != 0 || end != 4096) {
pr_err("empty mm has incorrect hole, found (%llx, %llx), expect (%llx, %llx)\n",
start, end,
0ull, 4096ull);
goto out;
}
- }
- ret = 0;
+out:
- if (ret)
drm_mm_debug_table(&mm, __func__);
- drm_mm_takedown(&mm);
- return ret;
+}
+static int *random_order(int count) +{
- int *order;
- int n;
- order = kmalloc_array(count, sizeof(*order), GFP_TEMPORARY);
- if (!order)
return order;
- for (n = 0; n < count; n++)
order[n] = n;
- for (n = count-1; n > 1; n--) {
int r = get_random_int() % (n + 1);
if (r != n) {
int tmp = order[n];
order[n] = order[r];
order[r] = tmp;
}
- }
- return order;
+}
+static int __subtest_reserve(int count, u64 size) +{
- struct drm_mm mm;
- struct drm_mm_node *node, *next;
- int *order, n;
- int ret;
- /* Fill a range with lots of nodes, check it doesn't fail too early */
- ret = -ENOMEM;
- order = random_order(count);
- if (!order)
goto err;
- ret = -EINVAL;
- drm_mm_init(&mm, 0, count * size);
- if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
- }
- for (n = 0; n < count; n++) {
int err;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
node->start = order[n] * size;
node->size = size;
err = drm_mm_reserve_node(&mm, node);
if (err) {
pr_err("reserve failed, step %d, start %llu\n",
n, node->start);
ret = err;
goto out;
}
- }
- /* Repeated use should then fail */
- for (n = 0; n < count; n++) {
struct drm_mm_node tmp = {
.start = order[n] * size,
.size = 1
};
if (!drm_mm_reserve_node(&mm, &tmp)) {
drm_mm_remove_node(&tmp);
pr_err("impossible reserve succeed, step %d, start %llu\n",
n, tmp.start);
goto out;
}
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next,
&mm.head_node.node_list, node_list) {
drm_mm_remove_node(node);
kfree(node);
- }
- drm_mm_takedown(&mm);
- kfree(order);
+err:
- return ret;
+}
+static int subtest_reserve(void) +{
- int n, ret;
- for (n = 1; n < 50; n++) {
ret = __subtest_reserve(8192, (1ull << n) - 1);
if (ret)
return ret;
ret = __subtest_reserve(8192, 1ull << n);
if (ret)
return ret;
ret = __subtest_reserve(8192, (1ull << n) + 1);
if (ret)
return ret;
- }
- return 0;
+}
+static int __subtest_insert(int count, u64 size) +{
- struct drm_mm mm;
- struct drm_mm_node *nodes, *node, *next;
- int *order, n, o = 0;
- int ret;
- /* Fill a range with lots of nodes, check it doesn't fail too early */
- ret = -ENOMEM;
- nodes = vzalloc(count * sizeof(*nodes));
- if (!nodes)
goto err;
- order = random_order(count);
- if (!order)
goto err_nodes;
- ret = -EINVAL;
- drm_mm_init(&mm, 0, count * size);
- if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
- }
- for (n = 0; n < count; n++) {
int err;
err = drm_mm_insert_node(&mm, &nodes[n], size, 0,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("insert failed, step %d, start %llu\n",
n, nodes[n].start);
ret = err;
goto out;
}
- }
- /* Repeated use should then fail */
- if (1) {
struct drm_mm_node tmp;
memset(&tmp, 0, sizeof(tmp));
if (!drm_mm_insert_node(&mm, &tmp, size, 0,
DRM_MM_SEARCH_DEFAULT)) {
drm_mm_remove_node(&tmp);
pr_err("impossible insert succeed, step %d, start %llu\n",
n, tmp.start);
goto out;
}
- }
- n = 0;
- drm_mm_for_each_node(node, &mm) {
if (node->start != n * size) {
pr_err("node %d out of order, expected start %llx, found %llx\n",
n, n * size, node->start);
goto out;
}
if (node->size != size) {
pr_err("node %d has wrong size, expected size %llx, found %llx\n",
n, size, node->size);
goto out;
}
if (node->hole_follows) {
pr_err("node %d is followed by a hole!\n", n);
goto out;
}
n++;
- }
- for (n = 0; n < count; n++) {
drm_mm_for_each_node_in_range(node, &mm, n * size, (n + 1) * size) {
if (node->start != n * size) {
pr_err("lookup node %d out of order, expected start %llx, found %llx\n",
n, n * size, node->start);
goto out;
}
}
- }
- /* Remove one and reinsert, as the only hole it should refill itself */
- for (n = 0; n < count; n++) {
int err;
drm_mm_remove_node(&nodes[n]);
err = drm_mm_insert_node(&mm, &nodes[n], size, 0,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("reinsert failed, step %d\n", n);
ret = err;
goto out;
}
if (nodes[n].start != n * size) {
pr_err("reinsert node moved, step %d, expected %llx, found %llx\n",
n, n * size, nodes[n].start);
goto out;
}
- }
- /* Remove several, reinsert, check full */
- for_each_prime(n, count) {
int m;
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % count]];
drm_mm_remove_node(node);
}
for (m = 0; m < n; m++) {
int err;
node = &nodes[order[(o + m) % count]];
err = drm_mm_insert_node(&mm, node, size, 0,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("insert failed, step %d, start %llu\n",
n, node->start);
ret = err;
goto out;
}
}
o += n;
if (1) {
struct drm_mm_node tmp;
memset(&tmp, 0, sizeof(tmp));
if (!drm_mm_insert_node(&mm, &tmp, size, 0,
DRM_MM_SEARCH_DEFAULT)) {
drm_mm_remove_node(&tmp);
pr_err("impossible insert succeed, start %llu\n",
tmp.start);
goto out;
}
}
m = 0;
drm_mm_for_each_node(node, &mm) {
if (node->start != m * size) {
pr_err("node %d out of order, expected start %llx, found %llx\n",
m, m * size, node->start);
goto out;
}
if (node->size != size) {
pr_err("node %d has wrong size, expected size %llx, found %llx\n",
m, size, node->size);
goto out;
}
if (node->hole_follows) {
pr_err("node %d is followed by a hole!\n", m);
goto out;
}
m++;
}
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list)
drm_mm_remove_node(node);
- drm_mm_takedown(&mm);
- kfree(order);
+err_nodes:
- vfree(nodes);
+err:
- return ret;
+}
+static int subtest_insert(void) +{
- int n, ret;
- for (n = 1; n < 50; n++) {
ret = __subtest_insert(8192, (1ull << n) - 1);
if (ret)
return ret;
ret = __subtest_insert(8192, 1ull << n);
if (ret)
return ret;
ret = __subtest_insert(8192, (1ull << n) + 1);
if (ret)
return ret;
- }
- return 0;
+}
+static int subtest_align_pot(int max) +{
- struct drm_mm mm;
- struct drm_mm_node *node, *next;
- int bit;
- int ret = -EINVAL;
- drm_mm_init(&mm, 1, -2);
- if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
- }
- for (bit = max - 1; bit; bit--) {
int err;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
err = drm_mm_insert_node_generic(&mm, node, 1,
BIT_ULL(bit), bit,
DRM_MM_SEARCH_DEFAULT,
DRM_MM_CREATE_DEFAULT);
if (err) {
pr_err("insert failed with alignment=%llx [%d]",
BIT_ULL(bit), bit);
ret = err;
goto out;
}
if (node->start & (BIT_ULL(bit) - 1)) {
pr_err("node inserted into wrong location %llx, expected alignment to %llx [%d]\n",
node->start, BIT_ULL(bit), bit);
goto out;
}
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next,
&mm.head_node.node_list, node_list) {
drm_mm_remove_node(node);
kfree(node);
- }
- drm_mm_takedown(&mm);
- return ret;
+}
+static int subtest_align32(void) +{
- return subtest_align_pot(32);
+}
+static int subtest_align64(void) +{
- return subtest_align_pot(64);
+}
+static int subtest_evict(void) +{
- const int size = 8192;
- struct drm_mm mm;
- struct evict_node {
struct drm_mm_node node;
struct list_head link;
- } *nodes;
- struct drm_mm_node *node, *next;
- int *order, n, m;
- int ret;
- ret = -ENOMEM;
- nodes = vzalloc(size * sizeof(*nodes));
- if (!nodes)
goto err;
- order = random_order(size);
- if (!order)
goto err_nodes;
- ret = -EINVAL;
- drm_mm_init(&mm, 0, size);
- for (n = 0; n < size; n++) {
int err;
err = drm_mm_insert_node(&mm, &nodes[n].node, 1, 0,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("insert failed, step %d\n", n);
ret = err;
goto out;
}
- }
- for (n = 1; n < size; n <<= 1) {
const int nsize = size / 2;
LIST_HEAD(evict_list);
struct evict_node *e, *en;
struct drm_mm_node tmp;
bool found = false;
int err;
drm_mm_init_scan(&mm, nsize, n, 0);
for (m = 0; m < size; m++) {
e = &nodes[order[m]];
list_add(&e->link, &evict_list);
if (drm_mm_scan_add_block(&e->node)) {
found = true;
break;
}
}
if (!found) {
pr_err("Failed to fail eviction: size=%d, align=%d\n",
nsize, n);
goto out;
}
list_for_each_entry_safe(e, en, &evict_list, link) {
if (!drm_mm_scan_remove_block(&e->node))
list_del(&e->link);
}
list_for_each_entry(e, &evict_list, link)
drm_mm_remove_node(&e->node);
memset(&tmp, 0, sizeof(tmp));
err = drm_mm_insert_node(&mm, &tmp, nsize, n,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("Failed to insert into eviction hole: size=%d, alignt=%d\n",
nsize, n);
goto out;
}
if (tmp.start % n || tmp.size != nsize || tmp.hole_follows) {
pr_err("Inserted did not align to eviction hole: size=%lld [%d], align=%d, start=%llx, hole-follows?=%d\n",
tmp.size, nsize, n, tmp.start, tmp.hole_follows);
drm_mm_remove_node(&tmp);
goto out;
}
drm_mm_remove_node(&tmp);
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);
ret = err;
goto out;
}
}
- }
- for_each_prime(n, size) {
LIST_HEAD(evict_list);
struct evict_node *e, *en;
struct drm_mm_node tmp;
int nsize = (size - n - 1) / 2;
bool found = false;
int err;
drm_mm_init_scan(&mm, nsize, n, 0);
for (m = 0; m < size; m++) {
e = &nodes[order[m]];
list_add(&e->link, &evict_list);
if (drm_mm_scan_add_block(&e->node)) {
found = true;
break;
}
}
if (!found) {
pr_err("Failed to fail eviction: size=%d, align=%d\n",
nsize, n);
goto out;
}
list_for_each_entry_safe(e, en, &evict_list, link) {
if (!drm_mm_scan_remove_block(&e->node))
list_del(&e->link);
}
list_for_each_entry(e, &evict_list, link)
drm_mm_remove_node(&e->node);
memset(&tmp, 0, sizeof(tmp));
err = drm_mm_insert_node(&mm, &tmp, nsize, n,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("Failed to insert into eviction hole: size=%d, alignt=%d\n",
nsize, n);
goto out;
}
if (tmp.start % n || tmp.size != nsize || tmp.hole_follows) {
pr_err("Inserted did not align to eviction hole: size=%lld [%d], align=%d, start=%llx, hole-follows?=%d\n",
tmp.size, nsize, n, tmp.start, tmp.hole_follows);
drm_mm_remove_node(&tmp);
goto out;
}
drm_mm_remove_node(&tmp);
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);
ret = err;
goto out;
}
}
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list)
drm_mm_remove_node(node);
- drm_mm_takedown(&mm);
- kfree(order);
+err_nodes:
- vfree(nodes);
+err:
- return ret;
+}
+static int subtest_topdown(void) +{
- const int size = 8192;
- unsigned long *bitmap;
- struct drm_mm mm;
- struct drm_mm_node *nodes, *node, *next;
- int *order, n, m, o = 0;
- int ret;
- ret = -ENOMEM;
- nodes = vzalloc(size * sizeof(*nodes));
- if (!nodes)
goto err;
- bitmap = kzalloc(size / BITS_PER_LONG * sizeof(unsigned long),
GFP_TEMPORARY);
- if (!bitmap)
goto err_nodes;
- order = random_order(size);
- if (!order)
goto err_bitmap;
- ret = -EINVAL;
- drm_mm_init(&mm, 0, size);
- for (n = 0; n < size; n++) {
int err;
err = drm_mm_insert_node_generic(&mm, &nodes[n], 1, 0, 0,
DRM_MM_SEARCH_BELOW,
DRM_MM_CREATE_TOP);
if (err) {
pr_err("insert failed, step %d\n", n);
ret = err;
goto out;
}
if (nodes[n].hole_follows) {
pr_err("hole after topdown insert %d, start=%llx\n",
n, nodes[n].start);
goto out;
}
- }
- for_each_prime(n, size) {
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % size]];
drm_mm_remove_node(node);
__set_bit(node->start, bitmap);
}
for (m = 0; m < n; m++) {
int err, last;
node = &nodes[order[(o + m) % size]];
err = drm_mm_insert_node_generic(&mm, node, 1, 0, 0,
DRM_MM_SEARCH_BELOW,
DRM_MM_CREATE_TOP);
if (err) {
pr_err("insert failed, step %d/%d\n", m, n);
ret = err;
goto out;
}
if (node->hole_follows) {
pr_err("hole after topdown insert %d/%d, start=%llx\n",
m, n, node->start);
goto out;
}
last = find_last_bit(bitmap, size);
if (node->start != last) {
pr_err("node %d/%d not inserted into upmost hole, expected %d, found %lld\n",
m, n, last, node->start);
goto out;
}
__clear_bit(last, bitmap);
}
o += n;
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list)
drm_mm_remove_node(node);
- drm_mm_takedown(&mm);
- kfree(order);
+err_bitmap:
- kfree(bitmap);
+err_nodes:
- vfree(nodes);
+err:
- return ret;
+}
+struct subtest {
- const char *name;
- int (*func)(void);
- bool enabled;
+} subtests[] = {
- [SUBTEST_INIT] = { "init", subtest_init },
- [SUBTEST_RESERVE] = { "reserve", subtest_reserve },
- [SUBTEST_INSERT] = { "insert", subtest_insert },
- [SUBTEST_ALIGN32] = { "align32", subtest_align32 },
- [SUBTEST_ALIGN64] = { "align64", subtest_align64 },
- [SUBTEST_EVICT] = { "evict", subtest_evict },
- [SUBTEST_TOPDOWN] = { "topdown", subtest_topdown },
+};
+/* Tests executed in reverse order (last in list goes first) */ +module_param_named(subtest__topdown, subtests[SUBTEST_TOPDOWN].enabled, bool, 0400); +module_param_named(subtest__evict, subtests[SUBTEST_EVICT].enabled, bool, 0400); +module_param_named(subtest__align64, subtests[SUBTEST_ALIGN64].enabled, bool, 0400); +module_param_named(subtest__align32, subtests[SUBTEST_ALIGN32].enabled, bool, 0400); +module_param_named(subtest__insert, subtests[SUBTEST_INSERT].enabled, bool, 0400); +module_param_named(subtest__reserve, subtests[SUBTEST_RESERVE].enabled, bool, 0400); +module_param_named(subtest__init, subtests[SUBTEST_INIT].enabled, bool, 0400);
+static void set_default_test_all(void) +{
- int i;
- for (i = 0; i < ARRAY_SIZE(subtests); i++)
if (subtests[i].enabled)
return;
- for (i = 0; i < ARRAY_SIZE(subtests); i++)
subtests[i].enabled = true;
+}
+static int __init test_drm_mm_init(void) +{
- int i;
- pr_info("Testing DRM range manger (struct drm_mm)\n");
- set_default_test_all();
- for (i = 0; i < ARRAY_SIZE(subtests); i++) {
int err;
if (!subtests[i].enabled)
continue;
pr_debug("Running %s\n", subtests[i].name);
err = subtests[i].func();
if (err)
return err;
- }
- return 0;
+}
+static void __exit test_drm_mm_exit(void) +{
- kfree(primes);
+}
+module_init(test_drm_mm_init); +module_exit(test_drm_mm_exit);
+MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL"); diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index f770dba2a6f6..f48229197fa8 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -1,6 +1,7 @@ TARGETS = breakpoints TARGETS += capabilities TARGETS += cpu-hotplug +TARGETS += drm TARGETS += efivarfs TARGETS += exec TARGETS += firmware diff --git a/tools/testing/selftests/drm/drm_mm.sh b/tools/testing/selftests/drm/drm_mm.sh new file mode 100644 index 000000000000..59c20ce50b10 --- /dev/null +++ b/tools/testing/selftests/drm/drm_mm.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Runs API tests for struct drm_mm (DRM range manager)
+if /sbin/modprobe -q test-drm_mm; then
/sbin/modprobe -q -r test-drm_mm
echo "drm/drm_mm: ok"
+else
echo "drm/drm_mm: [FAIL]"
exit 1
+fi
On Sun, Nov 27, 2016 at 02:08:34PM +0000, Chris Wilson wrote:
A set of test cases to capture some annoying bugs and to ensure that correct behaviour does not regress whilst fixing those!
Signed-off-by: Chris Wilson chris@chris-wilson.co.uk
Adding kselftest mailing list and cc. -Daniel
drivers/gpu/drm/Kconfig | 13 + drivers/gpu/drm/Makefile | 2 + drivers/gpu/drm/test-drm_mm.c | 829 ++++++++++++++++++++++++++++++++++ tools/testing/selftests/Makefile | 1 + tools/testing/selftests/drm/drm_mm.sh | 11 + 5 files changed, 856 insertions(+) create mode 100644 drivers/gpu/drm/test-drm_mm.c create mode 100644 tools/testing/selftests/drm/drm_mm.sh
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 95fc0410e129..22b0f28c0137 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -48,6 +48,19 @@ config DRM_DEBUG_MM
If in doubt, say "N".
+config DRM_DEBUG_MM_SELFTEST
- tristate "kselftests for DRM range manager (struct drm_mm)"
- depends on DRM
- depends on DEBUG_KERNEL
- default n
- help
This option provides a kernel module that can be used to test
the DRM range manager (drm_mm) and its API. This option is not
useful for distributions or general kernels, but only for kernel
developers working on DRM and associated drivers.
Say N if you are unsure
config DRM_KMS_HELPER tristate depends on DRM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 883f3e75cfbc..57818abdb5b8 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -18,6 +18,8 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \ drm_plane.o drm_color_mgmt.o drm_print.o \ drm_dumb_buffers.o drm_mode_config.o
+obj-$(CONFIG_DRM_DEBUG_MM_SELFTEST) += test-drm_mm.o
drm-$(CONFIG_COMPAT) += drm_ioc32.o drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o drm-$(CONFIG_PCI) += ati_pcigart.o diff --git a/drivers/gpu/drm/test-drm_mm.c b/drivers/gpu/drm/test-drm_mm.c new file mode 100644 index 000000000000..c2ffb6a0f344 --- /dev/null +++ b/drivers/gpu/drm/test-drm_mm.c @@ -0,0 +1,829 @@ +/*
- Test cases for the drm_mm range manager
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/module.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/vmalloc.h>
+#include <drm/drm_mm.h>
+static unsigned long *primes; +static unsigned long prime_last, prime_sz;
+static unsigned long slow_next_prime_number(unsigned long x) +{
- for (;;) {
unsigned long y = int_sqrt(++x) + 1;
while (y > 1) {
if ((x % y) == 0)
break;
y--;
}
if (y == 1)
return x;
- }
+}
+static unsigned long mark_multiples(unsigned long x,
unsigned long *p,
unsigned long start,
unsigned long end)
+{
- unsigned long m;
- m = 2*x;
- if (m < start)
m = (start / x + 1) * x;
- while (m < end) {
__clear_bit(m, p);
m += x;
- }
- return x;
+}
+static unsigned long next_prime_number(unsigned long x) +{
- if (x == 1)
return 2;
- if (x >= prime_last) {
unsigned long sz, y;
unsigned long *nprimes;
sz = x*x;
if (sz < x)
return slow_next_prime_number(x);
sz = round_up(sz, BITS_PER_LONG);
nprimes = krealloc(primes, sz / sizeof(long), GFP_KERNEL);
if (!nprimes)
return slow_next_prime_number(x);
/* Where memory permits, track the primes using the
* Sieve of Eratosthenes.
*/
memset(nprimes + prime_sz / BITS_PER_LONG,
0xff, (sz - prime_sz) / sizeof(long));
for (y = 2UL; y < sz; y = find_next_bit(nprimes, sz, y + 1))
prime_last = mark_multiples(y, nprimes, prime_sz, sz);
primes = nprimes;
prime_sz = sz;
- }
- return find_next_bit(primes, prime_last, x + 1);
+}
+#define for_each_prime(prime, max) \
- for (prime = 1; prime < (max); prime = next_prime_number(prime))
+enum test {
- SUBTEST_INIT,
- SUBTEST_RESERVE,
- SUBTEST_INSERT,
- SUBTEST_ALIGN32,
- SUBTEST_ALIGN64,
- SUBTEST_EVICT,
- SUBTEST_TOPDOWN,
+};
+static int subtest_init(void) +{
- struct drm_mm mm;
- struct drm_mm_node *hole;
- u64 start, end;
- int ret = -EINVAL;
- drm_mm_init(&mm, 0, 4096);
- if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
- }
- drm_mm_for_each_hole(hole, &mm, start, end) {
if (start != 0 || end != 4096) {
pr_err("empty mm has incorrect hole, found (%llx, %llx), expect (%llx, %llx)\n",
start, end,
0ull, 4096ull);
goto out;
}
- }
- ret = 0;
+out:
- if (ret)
drm_mm_debug_table(&mm, __func__);
- drm_mm_takedown(&mm);
- return ret;
+}
+static int *random_order(int count) +{
- int *order;
- int n;
- order = kmalloc_array(count, sizeof(*order), GFP_TEMPORARY);
- if (!order)
return order;
- for (n = 0; n < count; n++)
order[n] = n;
- for (n = count-1; n > 1; n--) {
int r = get_random_int() % (n + 1);
if (r != n) {
int tmp = order[n];
order[n] = order[r];
order[r] = tmp;
}
- }
- return order;
+}
+static int __subtest_reserve(int count, u64 size) +{
- struct drm_mm mm;
- struct drm_mm_node *node, *next;
- int *order, n;
- int ret;
- /* Fill a range with lots of nodes, check it doesn't fail too early */
- ret = -ENOMEM;
- order = random_order(count);
- if (!order)
goto err;
- ret = -EINVAL;
- drm_mm_init(&mm, 0, count * size);
- if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
- }
- for (n = 0; n < count; n++) {
int err;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
node->start = order[n] * size;
node->size = size;
err = drm_mm_reserve_node(&mm, node);
if (err) {
pr_err("reserve failed, step %d, start %llu\n",
n, node->start);
ret = err;
goto out;
}
- }
- /* Repeated use should then fail */
- for (n = 0; n < count; n++) {
struct drm_mm_node tmp = {
.start = order[n] * size,
.size = 1
};
if (!drm_mm_reserve_node(&mm, &tmp)) {
drm_mm_remove_node(&tmp);
pr_err("impossible reserve succeed, step %d, start %llu\n",
n, tmp.start);
goto out;
}
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next,
&mm.head_node.node_list, node_list) {
drm_mm_remove_node(node);
kfree(node);
- }
- drm_mm_takedown(&mm);
- kfree(order);
+err:
- return ret;
+}
+static int subtest_reserve(void) +{
- int n, ret;
- for (n = 1; n < 50; n++) {
ret = __subtest_reserve(8192, (1ull << n) - 1);
if (ret)
return ret;
ret = __subtest_reserve(8192, 1ull << n);
if (ret)
return ret;
ret = __subtest_reserve(8192, (1ull << n) + 1);
if (ret)
return ret;
- }
- return 0;
+}
+static int __subtest_insert(int count, u64 size) +{
- struct drm_mm mm;
- struct drm_mm_node *nodes, *node, *next;
- int *order, n, o = 0;
- int ret;
- /* Fill a range with lots of nodes, check it doesn't fail too early */
- ret = -ENOMEM;
- nodes = vzalloc(count * sizeof(*nodes));
- if (!nodes)
goto err;
- order = random_order(count);
- if (!order)
goto err_nodes;
- ret = -EINVAL;
- drm_mm_init(&mm, 0, count * size);
- if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
- }
- for (n = 0; n < count; n++) {
int err;
err = drm_mm_insert_node(&mm, &nodes[n], size, 0,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("insert failed, step %d, start %llu\n",
n, nodes[n].start);
ret = err;
goto out;
}
- }
- /* Repeated use should then fail */
- if (1) {
struct drm_mm_node tmp;
memset(&tmp, 0, sizeof(tmp));
if (!drm_mm_insert_node(&mm, &tmp, size, 0,
DRM_MM_SEARCH_DEFAULT)) {
drm_mm_remove_node(&tmp);
pr_err("impossible insert succeed, step %d, start %llu\n",
n, tmp.start);
goto out;
}
- }
- n = 0;
- drm_mm_for_each_node(node, &mm) {
if (node->start != n * size) {
pr_err("node %d out of order, expected start %llx, found %llx\n",
n, n * size, node->start);
goto out;
}
if (node->size != size) {
pr_err("node %d has wrong size, expected size %llx, found %llx\n",
n, size, node->size);
goto out;
}
if (node->hole_follows) {
pr_err("node %d is followed by a hole!\n", n);
goto out;
}
n++;
- }
- for (n = 0; n < count; n++) {
drm_mm_for_each_node_in_range(node, &mm, n * size, (n + 1) * size) {
if (node->start != n * size) {
pr_err("lookup node %d out of order, expected start %llx, found %llx\n",
n, n * size, node->start);
goto out;
}
}
- }
- /* Remove one and reinsert, as the only hole it should refill itself */
- for (n = 0; n < count; n++) {
int err;
drm_mm_remove_node(&nodes[n]);
err = drm_mm_insert_node(&mm, &nodes[n], size, 0,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("reinsert failed, step %d\n", n);
ret = err;
goto out;
}
if (nodes[n].start != n * size) {
pr_err("reinsert node moved, step %d, expected %llx, found %llx\n",
n, n * size, nodes[n].start);
goto out;
}
- }
- /* Remove several, reinsert, check full */
- for_each_prime(n, count) {
int m;
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % count]];
drm_mm_remove_node(node);
}
for (m = 0; m < n; m++) {
int err;
node = &nodes[order[(o + m) % count]];
err = drm_mm_insert_node(&mm, node, size, 0,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("insert failed, step %d, start %llu\n",
n, node->start);
ret = err;
goto out;
}
}
o += n;
if (1) {
struct drm_mm_node tmp;
memset(&tmp, 0, sizeof(tmp));
if (!drm_mm_insert_node(&mm, &tmp, size, 0,
DRM_MM_SEARCH_DEFAULT)) {
drm_mm_remove_node(&tmp);
pr_err("impossible insert succeed, start %llu\n",
tmp.start);
goto out;
}
}
m = 0;
drm_mm_for_each_node(node, &mm) {
if (node->start != m * size) {
pr_err("node %d out of order, expected start %llx, found %llx\n",
m, m * size, node->start);
goto out;
}
if (node->size != size) {
pr_err("node %d has wrong size, expected size %llx, found %llx\n",
m, size, node->size);
goto out;
}
if (node->hole_follows) {
pr_err("node %d is followed by a hole!\n", m);
goto out;
}
m++;
}
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list)
drm_mm_remove_node(node);
- drm_mm_takedown(&mm);
- kfree(order);
+err_nodes:
- vfree(nodes);
+err:
- return ret;
+}
+static int subtest_insert(void) +{
- int n, ret;
- for (n = 1; n < 50; n++) {
ret = __subtest_insert(8192, (1ull << n) - 1);
if (ret)
return ret;
ret = __subtest_insert(8192, 1ull << n);
if (ret)
return ret;
ret = __subtest_insert(8192, (1ull << n) + 1);
if (ret)
return ret;
- }
- return 0;
+}
+static int subtest_align_pot(int max) +{
- struct drm_mm mm;
- struct drm_mm_node *node, *next;
- int bit;
- int ret = -EINVAL;
- drm_mm_init(&mm, 1, -2);
- if (!drm_mm_clean(&mm)) {
pr_err("mm not empty on creation\n");
goto out;
- }
- for (bit = max - 1; bit; bit--) {
int err;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
goto out;
}
err = drm_mm_insert_node_generic(&mm, node, 1,
BIT_ULL(bit), bit,
DRM_MM_SEARCH_DEFAULT,
DRM_MM_CREATE_DEFAULT);
if (err) {
pr_err("insert failed with alignment=%llx [%d]",
BIT_ULL(bit), bit);
ret = err;
goto out;
}
if (node->start & (BIT_ULL(bit) - 1)) {
pr_err("node inserted into wrong location %llx, expected alignment to %llx [%d]\n",
node->start, BIT_ULL(bit), bit);
goto out;
}
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next,
&mm.head_node.node_list, node_list) {
drm_mm_remove_node(node);
kfree(node);
- }
- drm_mm_takedown(&mm);
- return ret;
+}
+static int subtest_align32(void) +{
- return subtest_align_pot(32);
+}
+static int subtest_align64(void) +{
- return subtest_align_pot(64);
+}
+static int subtest_evict(void) +{
- const int size = 8192;
- struct drm_mm mm;
- struct evict_node {
struct drm_mm_node node;
struct list_head link;
- } *nodes;
- struct drm_mm_node *node, *next;
- int *order, n, m;
- int ret;
- ret = -ENOMEM;
- nodes = vzalloc(size * sizeof(*nodes));
- if (!nodes)
goto err;
- order = random_order(size);
- if (!order)
goto err_nodes;
- ret = -EINVAL;
- drm_mm_init(&mm, 0, size);
- for (n = 0; n < size; n++) {
int err;
err = drm_mm_insert_node(&mm, &nodes[n].node, 1, 0,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("insert failed, step %d\n", n);
ret = err;
goto out;
}
- }
- for (n = 1; n < size; n <<= 1) {
const int nsize = size / 2;
LIST_HEAD(evict_list);
struct evict_node *e, *en;
struct drm_mm_node tmp;
bool found = false;
int err;
drm_mm_init_scan(&mm, nsize, n, 0);
for (m = 0; m < size; m++) {
e = &nodes[order[m]];
list_add(&e->link, &evict_list);
if (drm_mm_scan_add_block(&e->node)) {
found = true;
break;
}
}
if (!found) {
pr_err("Failed to fail eviction: size=%d, align=%d\n",
nsize, n);
goto out;
}
list_for_each_entry_safe(e, en, &evict_list, link) {
if (!drm_mm_scan_remove_block(&e->node))
list_del(&e->link);
}
list_for_each_entry(e, &evict_list, link)
drm_mm_remove_node(&e->node);
memset(&tmp, 0, sizeof(tmp));
err = drm_mm_insert_node(&mm, &tmp, nsize, n,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("Failed to insert into eviction hole: size=%d, alignt=%d\n",
nsize, n);
goto out;
}
if (tmp.start % n || tmp.size != nsize || tmp.hole_follows) {
pr_err("Inserted did not align to eviction hole: size=%lld [%d], align=%d, start=%llx, hole-follows?=%d\n",
tmp.size, nsize, n, tmp.start, tmp.hole_follows);
drm_mm_remove_node(&tmp);
goto out;
}
drm_mm_remove_node(&tmp);
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);
ret = err;
goto out;
}
}
- }
- for_each_prime(n, size) {
LIST_HEAD(evict_list);
struct evict_node *e, *en;
struct drm_mm_node tmp;
int nsize = (size - n - 1) / 2;
bool found = false;
int err;
drm_mm_init_scan(&mm, nsize, n, 0);
for (m = 0; m < size; m++) {
e = &nodes[order[m]];
list_add(&e->link, &evict_list);
if (drm_mm_scan_add_block(&e->node)) {
found = true;
break;
}
}
if (!found) {
pr_err("Failed to fail eviction: size=%d, align=%d\n",
nsize, n);
goto out;
}
list_for_each_entry_safe(e, en, &evict_list, link) {
if (!drm_mm_scan_remove_block(&e->node))
list_del(&e->link);
}
list_for_each_entry(e, &evict_list, link)
drm_mm_remove_node(&e->node);
memset(&tmp, 0, sizeof(tmp));
err = drm_mm_insert_node(&mm, &tmp, nsize, n,
DRM_MM_SEARCH_DEFAULT);
if (err) {
pr_err("Failed to insert into eviction hole: size=%d, alignt=%d\n",
nsize, n);
goto out;
}
if (tmp.start % n || tmp.size != nsize || tmp.hole_follows) {
pr_err("Inserted did not align to eviction hole: size=%lld [%d], align=%d, start=%llx, hole-follows?=%d\n",
tmp.size, nsize, n, tmp.start, tmp.hole_follows);
drm_mm_remove_node(&tmp);
goto out;
}
drm_mm_remove_node(&tmp);
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);
ret = err;
goto out;
}
}
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list)
drm_mm_remove_node(node);
- drm_mm_takedown(&mm);
- kfree(order);
+err_nodes:
- vfree(nodes);
+err:
- return ret;
+}
+static int subtest_topdown(void) +{
- const int size = 8192;
- unsigned long *bitmap;
- struct drm_mm mm;
- struct drm_mm_node *nodes, *node, *next;
- int *order, n, m, o = 0;
- int ret;
- ret = -ENOMEM;
- nodes = vzalloc(size * sizeof(*nodes));
- if (!nodes)
goto err;
- bitmap = kzalloc(size / BITS_PER_LONG * sizeof(unsigned long),
GFP_TEMPORARY);
- if (!bitmap)
goto err_nodes;
- order = random_order(size);
- if (!order)
goto err_bitmap;
- ret = -EINVAL;
- drm_mm_init(&mm, 0, size);
- for (n = 0; n < size; n++) {
int err;
err = drm_mm_insert_node_generic(&mm, &nodes[n], 1, 0, 0,
DRM_MM_SEARCH_BELOW,
DRM_MM_CREATE_TOP);
if (err) {
pr_err("insert failed, step %d\n", n);
ret = err;
goto out;
}
if (nodes[n].hole_follows) {
pr_err("hole after topdown insert %d, start=%llx\n",
n, nodes[n].start);
goto out;
}
- }
- for_each_prime(n, size) {
for (m = 0; m < n; m++) {
node = &nodes[order[(o + m) % size]];
drm_mm_remove_node(node);
__set_bit(node->start, bitmap);
}
for (m = 0; m < n; m++) {
int err, last;
node = &nodes[order[(o + m) % size]];
err = drm_mm_insert_node_generic(&mm, node, 1, 0, 0,
DRM_MM_SEARCH_BELOW,
DRM_MM_CREATE_TOP);
if (err) {
pr_err("insert failed, step %d/%d\n", m, n);
ret = err;
goto out;
}
if (node->hole_follows) {
pr_err("hole after topdown insert %d/%d, start=%llx\n",
m, n, node->start);
goto out;
}
last = find_last_bit(bitmap, size);
if (node->start != last) {
pr_err("node %d/%d not inserted into upmost hole, expected %d, found %lld\n",
m, n, last, node->start);
goto out;
}
__clear_bit(last, bitmap);
}
o += n;
- }
- ret = 0;
+out:
- list_for_each_entry_safe(node, next, &mm.head_node.node_list, node_list)
drm_mm_remove_node(node);
- drm_mm_takedown(&mm);
- kfree(order);
+err_bitmap:
- kfree(bitmap);
+err_nodes:
- vfree(nodes);
+err:
- return ret;
+}
+struct subtest {
- const char *name;
- int (*func)(void);
- bool enabled;
+} subtests[] = {
- [SUBTEST_INIT] = { "init", subtest_init },
- [SUBTEST_RESERVE] = { "reserve", subtest_reserve },
- [SUBTEST_INSERT] = { "insert", subtest_insert },
- [SUBTEST_ALIGN32] = { "align32", subtest_align32 },
- [SUBTEST_ALIGN64] = { "align64", subtest_align64 },
- [SUBTEST_EVICT] = { "evict", subtest_evict },
- [SUBTEST_TOPDOWN] = { "topdown", subtest_topdown },
+};
+/* Tests executed in reverse order (last in list goes first) */ +module_param_named(subtest__topdown, subtests[SUBTEST_TOPDOWN].enabled, bool, 0400); +module_param_named(subtest__evict, subtests[SUBTEST_EVICT].enabled, bool, 0400); +module_param_named(subtest__align64, subtests[SUBTEST_ALIGN64].enabled, bool, 0400); +module_param_named(subtest__align32, subtests[SUBTEST_ALIGN32].enabled, bool, 0400); +module_param_named(subtest__insert, subtests[SUBTEST_INSERT].enabled, bool, 0400); +module_param_named(subtest__reserve, subtests[SUBTEST_RESERVE].enabled, bool, 0400); +module_param_named(subtest__init, subtests[SUBTEST_INIT].enabled, bool, 0400);
+static void set_default_test_all(void) +{
- int i;
- for (i = 0; i < ARRAY_SIZE(subtests); i++)
if (subtests[i].enabled)
return;
- for (i = 0; i < ARRAY_SIZE(subtests); i++)
subtests[i].enabled = true;
+}
+static int __init test_drm_mm_init(void) +{
- int i;
- pr_info("Testing DRM range manger (struct drm_mm)\n");
- set_default_test_all();
- for (i = 0; i < ARRAY_SIZE(subtests); i++) {
int err;
if (!subtests[i].enabled)
continue;
pr_debug("Running %s\n", subtests[i].name);
err = subtests[i].func();
if (err)
return err;
- }
- return 0;
+}
+static void __exit test_drm_mm_exit(void) +{
- kfree(primes);
+}
+module_init(test_drm_mm_init); +module_exit(test_drm_mm_exit);
+MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL"); diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index f770dba2a6f6..f48229197fa8 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -1,6 +1,7 @@ TARGETS = breakpoints TARGETS += capabilities TARGETS += cpu-hotplug +TARGETS += drm TARGETS += efivarfs TARGETS += exec TARGETS += firmware diff --git a/tools/testing/selftests/drm/drm_mm.sh b/tools/testing/selftests/drm/drm_mm.sh new file mode 100644 index 000000000000..59c20ce50b10 --- /dev/null +++ b/tools/testing/selftests/drm/drm_mm.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Runs API tests for struct drm_mm (DRM range manager)
+if /sbin/modprobe -q test-drm_mm; then
/sbin/modprobe -q -r test-drm_mm
echo "drm/drm_mm: ok"
+else
echo "drm/drm_mm: [FAIL]"
exit 1
+fi
-- 2.10.2
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel@lists.freedesktop.org