summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/tests/drm_mm_test.c
blob: 4e9247cf9977f5677126ffbdcf56c97446c769b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Test cases for the drm_mm range manager
 *
 * Copyright (c) 2022 Arthur Grillo <arthur.grillo@usp.br>
 */

#include <kunit/test.h>

#include <linux/prime_numbers.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/vmalloc.h>
#include <linux/ktime.h>

#include <drm/drm_mm.h>

#include "../lib/drm_random.h"

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 },
	{}
};

static bool assert_no_holes(struct kunit *test, const struct drm_mm *mm)
{
	struct drm_mm_node *hole;
	u64 hole_start, __always_unused hole_end;
	unsigned long count;

	count = 0;
	drm_mm_for_each_hole(hole, mm, hole_start, hole_end)
		count++;
	if (count) {
		KUNIT_FAIL(test,
			   "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)) {
			KUNIT_FAIL(test, "Hole follows node, expected none!\n");
			return false;
		}
	}

	return true;
}

static bool assert_one_hole(struct kunit *test, 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)
				KUNIT_FAIL(test,
					   "empty mm has incorrect hole, found (%llx, %llx), expect (%llx, %llx)\n",
					   hole_start, hole_end, start, end);
			ok = false;
		}
		count++;
	}
	if (count != 1) {
		KUNIT_FAIL(test, "Expected to find one hole, found %lu instead\n", count);
		ok = false;
	}

	return ok;
}

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 kunit *test, 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) {
		KUNIT_FAIL(test, "node not allocated\n");
		ok = false;
	}

	if (node->size != size) {
		KUNIT_FAIL(test, "node has wrong size, found %llu, expected %llu\n",
			   node->size, size);
		ok = false;
	}

	if (misalignment(node, alignment)) {
		KUNIT_FAIL(test,
			   "node is misaligned, start %llx rem %llu, expected alignment %llu\n",
			   node->start, misalignment(node, alignment), alignment);
		ok = false;
	}

	if (node->color != color) {
		KUNIT_FAIL(test, "node has wrong color, found %lu, expected %lu\n",
			   node->color, color);
		ok = false;
	}

	return ok;
}

static void drm_test_mm_init(struct kunit *test)
{
	const unsigned int size = 4096;
	struct drm_mm mm;
	struct drm_mm_node tmp;

	/* Start with some simple checks on initialising the struct drm_mm */
	memset(&mm, 0, sizeof(mm));
	KUNIT_ASSERT_FALSE_MSG(test, drm_mm_initialized(&mm),
			       "zeroed mm claims to be initialized\n");

	memset(&mm, 0xff, sizeof(mm));
	drm_mm_init(&mm, 0, size);
	if (!drm_mm_initialized(&mm)) {
		KUNIT_FAIL(test, "mm claims not to be initialized\n");
		goto out;
	}

	if (!drm_mm_clean(&mm)) {
		KUNIT_FAIL(test, "mm not empty on creation\n");
		goto out;
	}

	/* After creation, it should all be one massive hole */
	if (!assert_one_hole(test, &mm, 0, size)) {
		KUNIT_FAIL(test, "");
		goto out;
	}

	memset(&tmp, 0, sizeof(tmp));
	tmp.start = 0;
	tmp.size = size;
	if (drm_mm_reserve_node(&mm, &tmp)) {
		KUNIT_FAIL(test, "failed to reserve whole drm_mm\n");
		goto out;
	}

	/* After filling the range entirely, there should be no holes */
	if (!assert_no_holes(test, &mm)) {
		KUNIT_FAIL(test, "");
		goto out;
	}

	/* And then after emptying it again, the massive hole should be back */
	drm_mm_remove_node(&tmp);
	if (!assert_one_hole(test, &mm, 0, size)) {
		KUNIT_FAIL(test, "");
		goto out;
	}

out:
	drm_mm_takedown(&mm);
}

static void drm_test_mm_debug(struct kunit *test)
{
	struct drm_mm mm;
	struct drm_mm_node nodes[2];

	/* 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;
	KUNIT_ASSERT_FALSE_MSG(test, drm_mm_reserve_node(&mm, &nodes[0]),
			       "failed to reserve node[0] {start=%lld, size=%lld)\n",
			       nodes[0].start, nodes[0].size);

	nodes[1].size = 1024;
	nodes[1].start = 4096 - 512 - nodes[1].size;
	KUNIT_ASSERT_FALSE_MSG(test, drm_mm_reserve_node(&mm, &nodes[1]),
			       "failed to reserve node[0] {start=%lld, size=%lld)\n",
			       nodes[0].start, nodes[0].size);
}

static bool expect_insert(struct kunit *test, 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) {
		KUNIT_FAIL(test,
			   "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(test, node, mm, size, alignment, color)) {
		drm_mm_remove_node(node);
		return false;
	}

	return true;
}

static void drm_test_mm_align_pot(struct kunit *test, int max)
{
	struct drm_mm mm;
	struct drm_mm_node *node, *next;
	int bit;

	/* 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) {
			KUNIT_FAIL(test, "failed to allocate node");
			goto out;
		}

		align = BIT_ULL(bit);
		size = BIT_ULL(bit - 1) + 1;
		if (!expect_insert(test, &mm, node, size, align, bit, &insert_modes[0])) {
			KUNIT_FAIL(test, "insert failed with alignment=%llx [%d]", align, bit);
			goto out;
		}

		cond_resched();
	}

out:
	drm_mm_for_each_node_safe(node, next, &mm) {
		drm_mm_remove_node(node);
		kfree(node);
	}
	drm_mm_takedown(&mm);
}

static void drm_test_mm_align32(struct kunit *test)
{
	drm_test_mm_align_pot(test, 32);
}

static void drm_test_mm_align64(struct kunit *test)
{
	drm_test_mm_align_pot(test, 64);
}

static void drm_test_mm_once(struct kunit *test, unsigned int mode)
{
	struct drm_mm mm;
	struct drm_mm_node rsvd_lo, rsvd_hi, node;

	drm_mm_init(&mm, 0, 7);

	memset(&rsvd_lo, 0, sizeof(rsvd_lo));
	rsvd_lo.start = 1;
	rsvd_lo.size = 1;
	if (drm_mm_reserve_node(&mm, &rsvd_lo)) {
		KUNIT_FAIL(test, "Could not reserve low node\n");
		goto err;
	}

	memset(&rsvd_hi, 0, sizeof(rsvd_hi));
	rsvd_hi.start = 5;
	rsvd_hi.size = 1;
	if (drm_mm_reserve_node(&mm, &rsvd_hi)) {
		KUNIT_FAIL(test, "Could not reserve low node\n");
		goto err_lo;
	}

	if (!drm_mm_hole_follows(&rsvd_lo) || !drm_mm_hole_follows(&rsvd_hi)) {
		KUNIT_FAIL(test, "Expected a hole after lo and high nodes!\n");
		goto err_hi;
	}

	memset(&node, 0, sizeof(node));
	if (drm_mm_insert_node_generic(&mm, &node, 2, 0, 0, mode)) {
		KUNIT_FAIL(test, "Could not insert the node into the available hole!\n");
		goto err_hi;
	}

	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);
}

static void drm_test_mm_lowest(struct kunit *test)
{
	drm_test_mm_once(test, DRM_MM_INSERT_LOW);
}

static void drm_test_mm_highest(struct kunit *test)
{
	drm_test_mm_once(test, DRM_MM_INSERT_HIGH);
}

static struct kunit_case drm_mm_tests[] = {
	KUNIT_CASE(drm_test_mm_init),
	KUNIT_CASE(drm_test_mm_debug),
	KUNIT_CASE(drm_test_mm_align32),
	KUNIT_CASE(drm_test_mm_align64),
	KUNIT_CASE(drm_test_mm_lowest),
	KUNIT_CASE(drm_test_mm_highest),
	{}
};

static struct kunit_suite drm_mm_test_suite = {
	.name = "drm_mm",
	.test_cases = drm_mm_tests,
};

kunit_test_suite(drm_mm_test_suite);

MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL");