1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Test cases for the drm_framebuffer functions
4  *
5  * Copyright (c) 2022 Maíra Canal <[email protected]>
6  */
7 
8 #include <kunit/test.h>
9 
10 #include <drm/drm_device.h>
11 #include <drm/drm_drv.h>
12 #include <drm/drm_mode.h>
13 #include <drm/drm_framebuffer.h>
14 #include <drm/drm_fourcc.h>
15 #include <drm/drm_kunit_helpers.h>
16 #include <drm/drm_print.h>
17 
18 #include "../drm_crtc_internal.h"
19 
20 #define MIN_WIDTH 4
21 #define MAX_WIDTH 4096
22 #define MIN_HEIGHT 4
23 #define MAX_HEIGHT 4096
24 
25 #define DRM_MODE_FB_INVALID BIT(2)
26 
27 struct drm_framebuffer_test {
28 	int buffer_created;
29 	struct drm_mode_fb_cmd2 cmd;
30 	const char *name;
31 };
32 
33 static const struct drm_framebuffer_test drm_framebuffer_create_cases[] = {
34 { .buffer_created = 1, .name = "ABGR8888 normal sizes",
35 	.cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_ABGR8888,
36 		 .handles = { 1, 0, 0 }, .pitches = { 4 * 600, 0, 0 },
37 	}
38 },
39 { .buffer_created = 1, .name = "ABGR8888 max sizes",
40 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
41 		 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 },
42 	}
43 },
44 { .buffer_created = 1, .name = "ABGR8888 pitch greater than min required",
45 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
46 		 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH + 1, 0, 0 },
47 	}
48 },
49 { .buffer_created = 0, .name = "ABGR8888 pitch less than min required",
50 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
51 		 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH - 1, 0, 0 },
52 	}
53 },
54 { .buffer_created = 0, .name = "ABGR8888 Invalid width",
55 	.cmd = { .width = MAX_WIDTH + 1, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
56 		 .handles = { 1, 0, 0 }, .pitches = { 4 * (MAX_WIDTH + 1), 0, 0 },
57 	}
58 },
59 { .buffer_created = 0, .name = "ABGR8888 Invalid buffer handle",
60 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
61 		 .handles = { 0, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 },
62 	}
63 },
64 { .buffer_created = 0, .name = "No pixel format",
65 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = 0,
66 		 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 },
67 	}
68 },
69 { .buffer_created = 0, .name = "ABGR8888 Width 0",
70 	.cmd = { .width = 0, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
71 		 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 },
72 	}
73 },
74 { .buffer_created = 0, .name = "ABGR8888 Height 0",
75 	.cmd = { .width = MAX_WIDTH, .height = 0, .pixel_format = DRM_FORMAT_ABGR8888,
76 		 .handles = { 1, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 },
77 	}
78 },
79 { .buffer_created = 0, .name = "ABGR8888 Out of bound height * pitch combination",
80 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
81 		 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX - 1, 0, 0 },
82 		 .pitches = { 4 * MAX_WIDTH, 0, 0 },
83 	}
84 },
85 { .buffer_created = 1, .name = "ABGR8888 Large buffer offset",
86 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
87 		 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 },
88 		 .pitches = { 4 * MAX_WIDTH, 0, 0 },
89 	}
90 },
91 
92 /*
93  * All entries in members that represents per-plane values (@modifier, @handles,
94  * @pitches and @offsets) must be zero when unused.
95  */
96 { .buffer_created = 0, .name = "ABGR8888 Buffer offset for inexistent plane",
97 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
98 		 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, UINT_MAX / 2, 0 },
99 		 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
100 	}
101 },
102 
103 { .buffer_created = 0, .name = "ABGR8888 Invalid flag",
104 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
105 		 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 },
106 		 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_INVALID,
107 	}
108 },
109 { .buffer_created = 1, .name = "ABGR8888 Set DRM_MODE_FB_MODIFIERS without modifiers",
110 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
111 		 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 },
112 		 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
113 	}
114 },
115 { .buffer_created = 1, .name = "ABGR8888 Valid buffer modifier",
116 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
117 		 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 },
118 		 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
119 		 .modifier = { AFBC_FORMAT_MOD_YTR, 0, 0 },
120 	}
121 },
122 { .buffer_created = 0,
123 	.name = "ABGR8888 Invalid buffer modifier(DRM_FORMAT_MOD_SAMSUNG_64_32_TILE)",
124 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
125 		 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 },
126 		 .pitches = { 4 * MAX_WIDTH, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
127 		 .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 },
128 	}
129 },
130 { .buffer_created = 1, .name = "ABGR8888 Extra pitches without DRM_MODE_FB_MODIFIERS",
131 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
132 		 .handles = { 1, 0, 0 }, .offsets = { UINT_MAX / 2, 0, 0 },
133 		 .pitches = { 4 * MAX_WIDTH, 4 * MAX_WIDTH, 0 },
134 	}
135 },
136 { .buffer_created = 0, .name = "ABGR8888 Extra pitches with DRM_MODE_FB_MODIFIERS",
137 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_ABGR8888,
138 		 .handles = { 1, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
139 		 .pitches = { 4 * MAX_WIDTH, 4 * MAX_WIDTH, 0 },
140 	}
141 },
142 { .buffer_created = 1, .name = "NV12 Normal sizes",
143 	.cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_NV12,
144 		 .handles = { 1, 1, 0 }, .pitches = { 600, 600, 0 },
145 	}
146 },
147 { .buffer_created = 1, .name = "NV12 Max sizes",
148 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12,
149 		 .handles = { 1, 1, 0 }, .pitches = { MAX_WIDTH, MAX_WIDTH, 0 },
150 	}
151 },
152 { .buffer_created = 0, .name = "NV12 Invalid pitch",
153 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12,
154 		 .handles = { 1, 1, 0 }, .pitches = { MAX_WIDTH, MAX_WIDTH - 1, 0 },
155 	}
156 },
157 { .buffer_created = 0, .name = "NV12 Invalid modifier/missing DRM_MODE_FB_MODIFIERS flag",
158 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12,
159 		 .handles = { 1, 1, 0 }, .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 },
160 		 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 },
161 	}
162 },
163 { .buffer_created = 0, .name = "NV12 different  modifier per-plane",
164 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12,
165 		 .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
166 		 .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0, 0 },
167 		 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 },
168 	}
169 },
170 { .buffer_created = 1, .name = "NV12 with DRM_FORMAT_MOD_SAMSUNG_64_32_TILE",
171 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12,
172 		 .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
173 		 .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE,
174 			 DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0 },
175 		 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 },
176 	}
177 },
178 { .buffer_created = 0, .name = "NV12 Valid modifiers without DRM_MODE_FB_MODIFIERS",
179 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12,
180 		 .handles = { 1, 1, 0 }, .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE,
181 						       DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, 0 },
182 		 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 },
183 	}
184 },
185 { .buffer_created = 0, .name = "NV12 Modifier for inexistent plane",
186 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12,
187 		 .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
188 		 .modifier = { DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, DRM_FORMAT_MOD_SAMSUNG_64_32_TILE,
189 			       DRM_FORMAT_MOD_SAMSUNG_64_32_TILE },
190 		 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 },
191 	}
192 },
193 { .buffer_created = 0, .name = "NV12 Handle for inexistent plane",
194 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_NV12,
195 		 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS,
196 		 .pitches = { MAX_WIDTH, MAX_WIDTH, 0 },
197 	}
198 },
199 { .buffer_created = 1, .name = "NV12 Handle for inexistent plane without DRM_MODE_FB_MODIFIERS",
200 	.cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_NV12,
201 		 .handles = { 1, 1, 1 }, .pitches = { 600, 600, 600 },
202 	}
203 },
204 { .buffer_created = 1, .name = "YVU420 DRM_MODE_FB_MODIFIERS set without modifier",
205 	.cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_YVU420,
206 		 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS,
207 		 .pitches = { 600, 300, 300 },
208 	}
209 },
210 { .buffer_created = 1, .name = "YVU420 Normal sizes",
211 	.cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_YVU420,
212 		 .handles = { 1, 1, 1 }, .pitches = { 600, 300, 300 },
213 	}
214 },
215 { .buffer_created = 1, .name = "YVU420 Max sizes",
216 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
217 		 .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2),
218 						      DIV_ROUND_UP(MAX_WIDTH, 2) },
219 	}
220 },
221 { .buffer_created = 0, .name = "YVU420 Invalid pitch",
222 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
223 		 .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) - 1,
224 						      DIV_ROUND_UP(MAX_WIDTH, 2) },
225 	}
226 },
227 { .buffer_created = 1, .name = "YVU420 Different pitches",
228 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
229 		 .handles = { 1, 1, 1 }, .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) + 1,
230 						      DIV_ROUND_UP(MAX_WIDTH, 2) + 7 },
231 	}
232 },
233 { .buffer_created = 1, .name = "YVU420 Different buffer offsets/pitches",
234 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
235 		 .handles = { 1, 1, 1 }, .offsets = { MAX_WIDTH, MAX_WIDTH  +
236 			 MAX_WIDTH * MAX_HEIGHT, MAX_WIDTH  + 2 * MAX_WIDTH * MAX_HEIGHT },
237 		 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2) + 1,
238 			 DIV_ROUND_UP(MAX_WIDTH, 2) + 7 },
239 	}
240 },
241 { .buffer_created = 0,
242 	.name = "YVU420 Modifier set just for plane 0, without DRM_MODE_FB_MODIFIERS",
243 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
244 		 .handles = { 1, 1, 1 }, .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 },
245 		 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) },
246 	}
247 },
248 { .buffer_created = 0,
249 	.name = "YVU420 Modifier set just for planes 0, 1, without DRM_MODE_FB_MODIFIERS",
250 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
251 		 .handles = { 1, 1, 1 },
252 		 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 },
253 		 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) },
254 	}
255 },
256 { .buffer_created = 0,
257 	.name = "YVU420 Modifier set just for plane 0, 1, with DRM_MODE_FB_MODIFIERS",
258 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
259 		 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS,
260 		 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 },
261 		 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) },
262 	}
263 },
264 { .buffer_created = 1, .name = "YVU420 Valid modifier",
265 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
266 		 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS,
267 		 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE,
268 			 AFBC_FORMAT_MOD_SPARSE },
269 		 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) },
270 	}
271 },
272 { .buffer_created = 0, .name = "YVU420 Different modifiers per plane",
273 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
274 		 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS,
275 		 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_YTR,
276 			       AFBC_FORMAT_MOD_SPARSE },
277 		 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) },
278 	}
279 },
280 { .buffer_created = 0, .name = "YVU420 Modifier for inexistent plane",
281 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YVU420,
282 		 .handles = { 1, 1, 1 }, .flags = DRM_MODE_FB_MODIFIERS,
283 		 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE,
284 			 AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE },
285 		 .pitches = { MAX_WIDTH, DIV_ROUND_UP(MAX_WIDTH, 2), DIV_ROUND_UP(MAX_WIDTH, 2) },
286 	}
287 },
288 { .buffer_created = 0, .name = "YUV420_10BIT Invalid modifier(DRM_FORMAT_MOD_LINEAR)",
289 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_YUV420_10BIT,
290 		 .handles = { 1, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
291 		 .modifier = { DRM_FORMAT_MOD_LINEAR, 0, 0 },
292 		 .pitches = { MAX_WIDTH, 0, 0 },
293 	}
294 },
295 { .buffer_created = 1, .name = "X0L2 Normal sizes",
296 	.cmd = { .width = 600, .height = 600, .pixel_format = DRM_FORMAT_X0L2,
297 		 .handles = { 1, 0, 0 }, .pitches = { 1200, 0, 0 }
298 	}
299 },
300 { .buffer_created = 1, .name = "X0L2 Max sizes",
301 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2,
302 		 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH, 0, 0 }
303 	}
304 },
305 { .buffer_created = 0, .name = "X0L2 Invalid pitch",
306 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2,
307 		 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH - 1, 0, 0 }
308 	}
309 },
310 { .buffer_created = 1, .name = "X0L2 Pitch greater than minimum required",
311 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2,
312 		 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }
313 	}
314 },
315 { .buffer_created = 0, .name = "X0L2 Handle for inexistent plane",
316 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2,
317 		 .handles = { 1, 1, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
318 		 .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }
319 	}
320 },
321 { .buffer_created = 1,
322 	.name = "X0L2 Offset for inexistent plane, without DRM_MODE_FB_MODIFIERS set",
323 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2,
324 		 .handles = { 1, 0, 0 }, .offsets = { 0, 0, 3 },
325 		 .pitches = { 2 * MAX_WIDTH + 1, 0, 0 }
326 	}
327 },
328 { .buffer_created = 0, .name = "X0L2 Modifier without DRM_MODE_FB_MODIFIERS set",
329 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2,
330 		 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 },
331 		 .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 },
332 	}
333 },
334 { .buffer_created = 1, .name = "X0L2 Valid modifier",
335 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT, .pixel_format = DRM_FORMAT_X0L2,
336 		 .handles = { 1, 0, 0 }, .pitches = { 2 * MAX_WIDTH + 1, 0, 0 },
337 		 .modifier = { AFBC_FORMAT_MOD_SPARSE, 0, 0 }, .flags = DRM_MODE_FB_MODIFIERS,
338 	}
339 },
340 { .buffer_created = 0, .name = "X0L2 Modifier for inexistent plane",
341 	.cmd = { .width = MAX_WIDTH, .height = MAX_HEIGHT,
342 		 .pixel_format = DRM_FORMAT_X0L2, .handles = { 1, 0, 0 },
343 		 .pitches = { 2 * MAX_WIDTH + 1, 0, 0 },
344 		 .modifier = { AFBC_FORMAT_MOD_SPARSE, AFBC_FORMAT_MOD_SPARSE, 0 },
345 		 .flags = DRM_MODE_FB_MODIFIERS,
346 	}
347 },
348 };
349 
350 /*
351  * This struct is intended to provide a way to mocked functions communicate
352  * with the outer test when it can't be achieved by using its return value. In
353  * this way, the functions that receive the mocked drm_device, for example, can
354  * grab a reference to this and actually return something to be used on some
355  * expectation.
356  */
357 struct drm_framebuffer_test_priv {
358 	struct drm_device dev;
359 	bool buffer_created;
360 };
361 
362 static struct drm_framebuffer *fb_create_mock(struct drm_device *dev,
363 					      struct drm_file *file_priv,
364 					      const struct drm_mode_fb_cmd2 *mode_cmd)
365 {
366 	struct drm_framebuffer_test_priv *priv = container_of(dev, typeof(*priv), dev);
367 
368 	priv->buffer_created = true;
369 	return ERR_PTR(-EINVAL);
370 }
371 
372 static struct drm_mode_config_funcs mock_config_funcs = {
373 	.fb_create = fb_create_mock,
374 };
375 
376 static int drm_framebuffer_test_init(struct kunit *test)
377 {
378 	struct device *parent;
379 	struct drm_framebuffer_test_priv *priv;
380 	struct drm_device *dev;
381 
382 	parent = drm_kunit_helper_alloc_device(test);
383 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
384 
385 	priv = drm_kunit_helper_alloc_drm_device(test, parent, typeof(*priv),
386 						 dev, 0);
387 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
388 	dev = &priv->dev;
389 
390 	dev->mode_config.min_width = MIN_WIDTH;
391 	dev->mode_config.max_width = MAX_WIDTH;
392 	dev->mode_config.min_height = MIN_HEIGHT;
393 	dev->mode_config.max_height = MAX_HEIGHT;
394 	dev->mode_config.funcs = &mock_config_funcs;
395 
396 	test->priv = priv;
397 	return 0;
398 }
399 
400 static void drm_test_framebuffer_create(struct kunit *test)
401 {
402 	const struct drm_framebuffer_test *params = test->param_value;
403 	struct drm_framebuffer_test_priv *priv = test->priv;
404 	struct drm_device *dev = &priv->dev;
405 
406 	priv->buffer_created = false;
407 	drm_internal_framebuffer_create(dev, &params->cmd, NULL);
408 	KUNIT_EXPECT_EQ(test, params->buffer_created, priv->buffer_created);
409 }
410 
411 static void drm_framebuffer_test_to_desc(const struct drm_framebuffer_test *t, char *desc)
412 {
413 	strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
414 }
415 
416 KUNIT_ARRAY_PARAM(drm_framebuffer_create, drm_framebuffer_create_cases,
417 		  drm_framebuffer_test_to_desc);
418 
419 /* Tries to create a framebuffer with modifiers without drm_device supporting it */
420 static void drm_test_framebuffer_modifiers_not_supported(struct kunit *test)
421 {
422 	struct drm_framebuffer_test_priv *priv = test->priv;
423 	struct drm_device *dev = &priv->dev;
424 	struct drm_framebuffer *fb;
425 
426 	/* A valid cmd with modifier */
427 	struct drm_mode_fb_cmd2 cmd = {
428 		.width = MAX_WIDTH, .height = MAX_HEIGHT,
429 		.pixel_format = DRM_FORMAT_ABGR8888, .handles = { 1, 0, 0 },
430 		.offsets = { UINT_MAX / 2, 0, 0 }, .pitches = { 4 * MAX_WIDTH, 0, 0 },
431 		.flags = DRM_MODE_FB_MODIFIERS,
432 	};
433 
434 	priv->buffer_created = false;
435 	dev->mode_config.fb_modifiers_not_supported = 1;
436 
437 	fb = drm_internal_framebuffer_create(dev, &cmd, NULL);
438 	KUNIT_EXPECT_EQ(test, priv->buffer_created, false);
439 	KUNIT_EXPECT_EQ(test, PTR_ERR(fb), -EINVAL);
440 }
441 
442 /* Parameters for testing drm_framebuffer_check_src_coords function */
443 struct drm_framebuffer_check_src_coords_case {
444 	const char *name;
445 	const int expect;
446 	const unsigned int fb_size;
447 	const uint32_t src_x;
448 	const uint32_t src_y;
449 
450 	/* Deltas to be applied on source */
451 	const uint32_t dsrc_w;
452 	const uint32_t dsrc_h;
453 };
454 
455 static const struct drm_framebuffer_check_src_coords_case
456 drm_framebuffer_check_src_coords_cases[] = {
457 	{ .name = "Success: source fits into fb",
458 	  .expect = 0,
459 	},
460 	{ .name = "Fail: overflowing fb with x-axis coordinate",
461 	  .expect = -ENOSPC, .src_x = 1, .fb_size = UINT_MAX,
462 	},
463 	{ .name = "Fail: overflowing fb with y-axis coordinate",
464 	  .expect = -ENOSPC, .src_y = 1, .fb_size = UINT_MAX,
465 	},
466 	{ .name = "Fail: overflowing fb with source width",
467 	  .expect = -ENOSPC, .dsrc_w = 1, .fb_size = UINT_MAX - 1,
468 	},
469 	{ .name = "Fail: overflowing fb with source height",
470 	  .expect = -ENOSPC, .dsrc_h = 1, .fb_size = UINT_MAX - 1,
471 	},
472 };
473 
474 static void drm_test_framebuffer_check_src_coords(struct kunit *test)
475 {
476 	const struct drm_framebuffer_check_src_coords_case *params = test->param_value;
477 	const uint32_t src_x = params->src_x;
478 	const uint32_t src_y = params->src_y;
479 	const uint32_t src_w = (params->fb_size << 16) + params->dsrc_w;
480 	const uint32_t src_h = (params->fb_size << 16) + params->dsrc_h;
481 	const struct drm_framebuffer fb = {
482 		.width  = params->fb_size,
483 		.height = params->fb_size
484 	};
485 	int ret;
486 
487 	ret = drm_framebuffer_check_src_coords(src_x, src_y, src_w, src_h, &fb);
488 	KUNIT_EXPECT_EQ(test, ret, params->expect);
489 }
490 
491 static void
492 check_src_coords_test_to_desc(const struct drm_framebuffer_check_src_coords_case *t,
493 			      char *desc)
494 {
495 	strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
496 }
497 
498 KUNIT_ARRAY_PARAM(check_src_coords, drm_framebuffer_check_src_coords_cases,
499 		  check_src_coords_test_to_desc);
500 
501 static struct kunit_case drm_framebuffer_tests[] = {
502 	KUNIT_CASE_PARAM(drm_test_framebuffer_check_src_coords, check_src_coords_gen_params),
503 	KUNIT_CASE_PARAM(drm_test_framebuffer_create, drm_framebuffer_create_gen_params),
504 	KUNIT_CASE(drm_test_framebuffer_modifiers_not_supported),
505 	{ }
506 };
507 
508 static struct kunit_suite drm_framebuffer_test_suite = {
509 	.name = "drm_framebuffer",
510 	.init = drm_framebuffer_test_init,
511 	.test_cases = drm_framebuffer_tests,
512 };
513 
514 kunit_test_suite(drm_framebuffer_test_suite);
515 
516 MODULE_DESCRIPTION("Test cases for the drm_framebuffer functions");
517 MODULE_LICENSE("GPL");
518