xref: /linux/drivers/gpu/drm/tests/drm_gem_shmem_test.c (revision f4cdf7ca9a1fdcca413157df19753f388a5a224e)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * KUnit test suite for GEM objects backed by shmem buffers
4  *
5  * Copyright (C) 2023 Red Hat, Inc.
6  *
7  * Author: Marco Pagani <marpagan@redhat.com>
8  */
9 
10 #include <linux/dma-buf.h>
11 #include <linux/iosys-map.h>
12 #include <linux/sizes.h>
13 
14 #include <kunit/test.h>
15 
16 #include <drm/drm_device.h>
17 #include <drm/drm_drv.h>
18 #include <drm/drm_gem.h>
19 #include <drm/drm_gem_shmem_helper.h>
20 #include <drm/drm_kunit_helpers.h>
21 
22 MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
23 
24 #define TEST_SIZE		SZ_1M
25 #define TEST_BYTE		0xae
26 
27 /*
28  * Wrappers to avoid cast warnings when passing action functions
29  * directly to kunit_add_action().
30  */
31 KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *);
32 
33 KUNIT_DEFINE_ACTION_WRAPPER(sg_free_table_wrapper, sg_free_table,
34 			    struct sg_table *);
35 
36 KUNIT_DEFINE_ACTION_WRAPPER(drm_gem_shmem_free_wrapper, drm_gem_shmem_free,
37 			    struct drm_gem_shmem_object *);
38 
39 KUNIT_DEFINE_ACTION_WRAPPER(drm_gem_shmem_unpin_wrapper, drm_gem_shmem_unpin,
40 			    struct drm_gem_shmem_object *);
41 
42 /*
43  * Test creating a shmem GEM object backed by shmem buffer. The test
44  * case succeeds if the GEM object is successfully allocated with the
45  * shmem file node and object functions attributes set, and the size
46  * attribute is equal to the correct size.
47  */
48 static void drm_gem_shmem_test_obj_create(struct kunit *test)
49 {
50 	struct drm_device *drm_dev = test->priv;
51 	struct drm_gem_shmem_object *shmem;
52 
53 	shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE);
54 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem);
55 	KUNIT_EXPECT_EQ(test, shmem->base.size, TEST_SIZE);
56 	KUNIT_EXPECT_NOT_NULL(test, shmem->base.filp);
57 	KUNIT_EXPECT_NOT_NULL(test, shmem->base.funcs);
58 
59 	drm_gem_shmem_free(shmem);
60 }
61 
62 /*
63  * Test creating a shmem GEM object from a scatter/gather table exported
64  * via a DMA-BUF. The test case succeed if the GEM object is successfully
65  * created with the shmem file node attribute equal to NULL and the sgt
66  * attribute pointing to the scatter/gather table that has been imported.
67  */
68 static void drm_gem_shmem_test_obj_create_private(struct kunit *test)
69 {
70 	struct drm_device *drm_dev = test->priv;
71 	struct drm_gem_shmem_object *shmem;
72 	struct drm_gem_object *gem_obj;
73 	struct dma_buf buf_mock;
74 	struct dma_buf_attachment attach_mock;
75 	struct sg_table *sgt;
76 	char *buf;
77 	int ret;
78 
79 	/* Create a mock scatter/gather table */
80 	buf = kunit_kzalloc(test, TEST_SIZE, GFP_KERNEL);
81 	KUNIT_ASSERT_NOT_NULL(test, buf);
82 
83 	sgt = kzalloc_obj(*sgt);
84 	KUNIT_ASSERT_NOT_NULL(test, sgt);
85 
86 	ret = kunit_add_action_or_reset(test, kfree_wrapper, sgt);
87 	KUNIT_ASSERT_EQ(test, ret, 0);
88 
89 	ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
90 	KUNIT_ASSERT_EQ(test, ret, 0);
91 
92 	ret = kunit_add_action_or_reset(test, sg_free_table_wrapper, sgt);
93 	KUNIT_ASSERT_EQ(test, ret, 0);
94 
95 	sg_init_one(sgt->sgl, buf, TEST_SIZE);
96 
97 	/*
98 	 * Map the sgtables otherwise drm_gem_shmem_free will cause a warning on
99 	 * debug kernels.
100 	 */
101 	ret = dma_map_sgtable(drm_dev->dev, sgt, DMA_BIDIRECTIONAL, 0);
102 	KUNIT_ASSERT_EQ(test, ret, 0);
103 
104 	/* Init a mock DMA-BUF */
105 	buf_mock.size = TEST_SIZE;
106 	attach_mock.dmabuf = &buf_mock;
107 
108 	gem_obj = drm_gem_shmem_prime_import_sg_table(drm_dev, &attach_mock, sgt);
109 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gem_obj);
110 	KUNIT_EXPECT_EQ(test, gem_obj->size, TEST_SIZE);
111 	KUNIT_EXPECT_NULL(test, gem_obj->filp);
112 	KUNIT_EXPECT_NOT_NULL(test, gem_obj->funcs);
113 
114 	/* The scatter/gather table will be freed by drm_gem_shmem_free */
115 	kunit_remove_action(test, sg_free_table_wrapper, sgt);
116 	kunit_remove_action(test, kfree_wrapper, sgt);
117 
118 	shmem = to_drm_gem_shmem_obj(gem_obj);
119 	KUNIT_EXPECT_PTR_EQ(test, shmem->sgt, sgt);
120 
121 	drm_gem_shmem_free(shmem);
122 }
123 
124 /*
125  * Test pinning backing pages for a shmem GEM object. The test case
126  * succeeds if a suitable number of backing pages are allocated, and
127  * the pages table counter attribute is increased by one.
128  */
129 static void drm_gem_shmem_test_pin_pages(struct kunit *test)
130 {
131 	struct drm_device *drm_dev = test->priv;
132 	struct drm_gem_shmem_object *shmem;
133 	int i, ret;
134 
135 	shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE);
136 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem);
137 	KUNIT_EXPECT_NULL(test, shmem->pages);
138 	KUNIT_EXPECT_EQ(test, refcount_read(&shmem->pages_use_count), 0);
139 
140 	ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem);
141 	KUNIT_ASSERT_EQ(test, ret, 0);
142 
143 	ret = drm_gem_shmem_pin(shmem);
144 	KUNIT_ASSERT_EQ(test, ret, 0);
145 	KUNIT_ASSERT_NOT_NULL(test, shmem->pages);
146 	KUNIT_EXPECT_EQ(test, refcount_read(&shmem->pages_use_count), 1);
147 
148 	for (i = 0; i < (shmem->base.size >> PAGE_SHIFT); i++)
149 		KUNIT_ASSERT_NOT_NULL(test, shmem->pages[i]);
150 
151 	drm_gem_shmem_unpin(shmem);
152 	KUNIT_EXPECT_NULL(test, shmem->pages);
153 	KUNIT_EXPECT_EQ(test, refcount_read(&shmem->pages_use_count), 0);
154 }
155 
156 /*
157  * Test creating a virtual mapping for a shmem GEM object. The test
158  * case succeeds if the backing memory is mapped and the reference
159  * counter for virtual mapping is increased by one. Moreover, the test
160  * case writes and then reads a test pattern over the mapped memory.
161  */
162 static void drm_gem_shmem_test_vmap(struct kunit *test)
163 {
164 	struct drm_device *drm_dev = test->priv;
165 	struct drm_gem_shmem_object *shmem;
166 	struct iosys_map map;
167 	int ret, i;
168 
169 	shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE);
170 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem);
171 	KUNIT_EXPECT_NULL(test, shmem->vaddr);
172 	KUNIT_EXPECT_EQ(test, refcount_read(&shmem->vmap_use_count), 0);
173 
174 	ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem);
175 	KUNIT_ASSERT_EQ(test, ret, 0);
176 
177 	ret = drm_gem_shmem_vmap(shmem, &map);
178 	KUNIT_ASSERT_EQ(test, ret, 0);
179 	KUNIT_ASSERT_NOT_NULL(test, shmem->vaddr);
180 	KUNIT_ASSERT_FALSE(test, iosys_map_is_null(&map));
181 	KUNIT_EXPECT_EQ(test, refcount_read(&shmem->vmap_use_count), 1);
182 
183 	iosys_map_memset(&map, 0, TEST_BYTE, TEST_SIZE);
184 	for (i = 0; i < TEST_SIZE; i++)
185 		KUNIT_EXPECT_EQ(test, iosys_map_rd(&map, i, u8), TEST_BYTE);
186 
187 	drm_gem_shmem_vunmap(shmem, &map);
188 	KUNIT_EXPECT_NULL(test, shmem->vaddr);
189 	KUNIT_EXPECT_EQ(test, refcount_read(&shmem->vmap_use_count), 0);
190 }
191 
192 /*
193  * Test exporting a scatter/gather table of pinned pages suitable for
194  * PRIME usage from a shmem GEM object. The test case succeeds if a
195  * scatter/gather table large enough to accommodate the backing memory
196  * is successfully exported.
197  */
198 static void drm_gem_shmem_test_get_sg_table(struct kunit *test)
199 {
200 	struct drm_device *drm_dev = test->priv;
201 	struct drm_gem_shmem_object *shmem;
202 	struct sg_table *sgt;
203 	struct scatterlist *sg;
204 	unsigned int si, len = 0;
205 	int ret;
206 
207 	shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE);
208 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem);
209 
210 	ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem);
211 	KUNIT_ASSERT_EQ(test, ret, 0);
212 
213 	ret = drm_gem_shmem_pin(shmem);
214 	KUNIT_ASSERT_EQ(test, ret, 0);
215 
216 	ret = kunit_add_action_or_reset(test, drm_gem_shmem_unpin_wrapper, shmem);
217 	KUNIT_ASSERT_EQ(test, ret, 0);
218 
219 	sgt = drm_gem_shmem_get_sg_table(shmem);
220 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sgt);
221 	KUNIT_EXPECT_NULL(test, shmem->sgt);
222 
223 	ret = kunit_add_action_or_reset(test, kfree_wrapper, sgt);
224 	KUNIT_ASSERT_EQ(test, ret, 0);
225 
226 	ret = kunit_add_action_or_reset(test, sg_free_table_wrapper, sgt);
227 	KUNIT_ASSERT_EQ(test, ret, 0);
228 
229 	for_each_sgtable_sg(sgt, sg, si) {
230 		KUNIT_EXPECT_NOT_NULL(test, sg);
231 		len += sg->length;
232 	}
233 
234 	KUNIT_EXPECT_GE(test, len, TEST_SIZE);
235 }
236 
237 /*
238  * Test pinning pages and exporting a scatter/gather table suitable for
239  * driver usage from a shmem GEM object. The test case succeeds if the
240  * backing pages are pinned and a scatter/gather table large enough to
241  * accommodate the backing memory is successfully exported.
242  */
243 static void drm_gem_shmem_test_get_pages_sgt(struct kunit *test)
244 {
245 	struct drm_device *drm_dev = test->priv;
246 	struct drm_gem_shmem_object *shmem;
247 	struct sg_table *sgt;
248 	struct scatterlist *sg;
249 	unsigned int si, ret, len = 0;
250 
251 	shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE);
252 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem);
253 
254 	ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem);
255 	KUNIT_ASSERT_EQ(test, ret, 0);
256 
257 	/* The scatter/gather table will be freed by drm_gem_shmem_free */
258 	sgt = drm_gem_shmem_get_pages_sgt(shmem);
259 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sgt);
260 	KUNIT_ASSERT_NOT_NULL(test, shmem->pages);
261 	KUNIT_EXPECT_EQ(test, refcount_read(&shmem->pages_use_count), 1);
262 	KUNIT_EXPECT_PTR_EQ(test, sgt, shmem->sgt);
263 
264 	for_each_sgtable_sg(sgt, sg, si) {
265 		KUNIT_EXPECT_NOT_NULL(test, sg);
266 		len += sg->length;
267 	}
268 
269 	KUNIT_EXPECT_GE(test, len, TEST_SIZE);
270 }
271 
272 /*
273  * Test updating the madvise state of a shmem GEM object. The test
274  * case checks that the function for setting madv updates it only if
275  * its current value is greater or equal than zero and returns false
276  * if it has a negative value.
277  */
278 static void drm_gem_shmem_test_madvise(struct kunit *test)
279 {
280 	struct drm_device *drm_dev = test->priv;
281 	struct drm_gem_shmem_object *shmem;
282 	int ret;
283 
284 	shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE);
285 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem);
286 	KUNIT_ASSERT_EQ(test, shmem->madv, 0);
287 
288 	ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem);
289 	KUNIT_ASSERT_EQ(test, ret, 0);
290 
291 	ret = drm_gem_shmem_madvise(shmem, 1);
292 	KUNIT_EXPECT_TRUE(test, ret);
293 	KUNIT_ASSERT_EQ(test, shmem->madv, 1);
294 
295 	/* Set madv to a negative value */
296 	ret = drm_gem_shmem_madvise(shmem, -1);
297 	KUNIT_EXPECT_FALSE(test, ret);
298 	KUNIT_ASSERT_EQ(test, shmem->madv, -1);
299 
300 	/* Check that madv cannot be set back to a positive value */
301 	ret = drm_gem_shmem_madvise(shmem, 0);
302 	KUNIT_EXPECT_FALSE(test, ret);
303 	KUNIT_ASSERT_EQ(test, shmem->madv, -1);
304 }
305 
306 /*
307  * Test purging a shmem GEM object. First, assert that a newly created
308  * shmem GEM object is not purgeable. Then, set madvise to a positive
309  * value and call drm_gem_shmem_get_pages_sgt() to pin and dma-map the
310  * backing pages. Finally, assert that the shmem GEM object is now
311  * purgeable and purge it.
312  */
313 static void drm_gem_shmem_test_purge(struct kunit *test)
314 {
315 	struct drm_device *drm_dev = test->priv;
316 	struct drm_gem_shmem_object *shmem;
317 	struct sg_table *sgt;
318 	int ret;
319 
320 	shmem = drm_gem_shmem_create(drm_dev, TEST_SIZE);
321 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, shmem);
322 
323 	ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem);
324 	KUNIT_ASSERT_EQ(test, ret, 0);
325 
326 	ret = drm_gem_shmem_is_purgeable(shmem);
327 	KUNIT_EXPECT_FALSE(test, ret);
328 
329 	ret = drm_gem_shmem_madvise(shmem, 1);
330 	KUNIT_EXPECT_TRUE(test, ret);
331 
332 	/* The scatter/gather table will be freed by drm_gem_shmem_free */
333 	sgt = drm_gem_shmem_get_pages_sgt(shmem);
334 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sgt);
335 
336 	ret = drm_gem_shmem_is_purgeable(shmem);
337 	KUNIT_EXPECT_TRUE(test, ret);
338 
339 	ret = drm_gem_shmem_purge(shmem);
340 	KUNIT_ASSERT_EQ(test, ret, 0);
341 
342 	KUNIT_EXPECT_NULL(test, shmem->pages);
343 	KUNIT_EXPECT_NULL(test, shmem->sgt);
344 	KUNIT_EXPECT_EQ(test, shmem->madv, -1);
345 }
346 
347 static int drm_gem_shmem_test_init(struct kunit *test)
348 {
349 	struct device *dev;
350 	struct drm_device *drm_dev;
351 	int ret;
352 
353 	/* Allocate a parent device */
354 	dev = drm_kunit_helper_alloc_device(test);
355 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
356 
357 	/*
358 	 * Set the DMA mask to 64-bits to avoid intermittent failures calling
359 	 * drm_gem_shmem_get_pages_sgt().
360 	 */
361 	ret = dma_set_mask(dev, DMA_BIT_MASK(64));
362 	KUNIT_ASSERT_EQ(test, ret, 0);
363 
364 	/*
365 	 * The DRM core will automatically initialize the GEM core and create
366 	 * a DRM Memory Manager object which provides an address space pool
367 	 * for GEM objects allocation.
368 	 */
369 	drm_dev = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*drm_dev),
370 						      0, DRIVER_GEM);
371 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm_dev);
372 
373 	test->priv = drm_dev;
374 
375 	return 0;
376 }
377 
378 static struct kunit_case drm_gem_shmem_test_cases[] = {
379 	KUNIT_CASE(drm_gem_shmem_test_obj_create),
380 	KUNIT_CASE(drm_gem_shmem_test_obj_create_private),
381 	KUNIT_CASE(drm_gem_shmem_test_pin_pages),
382 	KUNIT_CASE(drm_gem_shmem_test_vmap),
383 	KUNIT_CASE(drm_gem_shmem_test_get_sg_table),
384 	KUNIT_CASE(drm_gem_shmem_test_get_pages_sgt),
385 	KUNIT_CASE(drm_gem_shmem_test_madvise),
386 	KUNIT_CASE(drm_gem_shmem_test_purge),
387 	{}
388 };
389 
390 static struct kunit_suite drm_gem_shmem_suite = {
391 	.name = "drm_gem_shmem",
392 	.init = drm_gem_shmem_test_init,
393 	.test_cases = drm_gem_shmem_test_cases
394 };
395 
396 kunit_test_suite(drm_gem_shmem_suite);
397 
398 MODULE_DESCRIPTION("KUnit test suite for GEM objects backed by shmem buffers");
399 MODULE_LICENSE("GPL");
400