xref: /linux/drivers/gpu/drm/tests/drm_kunit_helpers.c (revision 785151f50ddacac06c7a3c5f3d31642794507fdf)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <drm/drm_atomic.h>
4 #include <drm/drm_atomic_helper.h>
5 #include <drm/drm_atomic_uapi.h>
6 #include <drm/drm_drv.h>
7 #include <drm/drm_edid.h>
8 #include <drm/drm_fourcc.h>
9 #include <drm/drm_kunit_helpers.h>
10 #include <drm/drm_managed.h>
11 
12 #include <kunit/device.h>
13 #include <kunit/resource.h>
14 
15 #include <linux/device.h>
16 #include <linux/platform_device.h>
17 
18 #define KUNIT_DEVICE_NAME	"drm-kunit-mock-device"
19 
20 static const struct drm_mode_config_funcs drm_mode_config_funcs = {
21 	.atomic_check	= drm_atomic_helper_check,
22 	.atomic_commit	= drm_atomic_helper_commit,
23 };
24 
25 /**
26  * drm_kunit_helper_alloc_device - Allocate a mock device for a KUnit test
27  * @test: The test context object
28  *
29  * This allocates a fake struct &device to create a mock for a KUnit
30  * test. The device will also be bound to a fake driver. It will thus be
31  * able to leverage the usual infrastructure and most notably the
32  * device-managed resources just like a "real" device.
33  *
34  * Resources will be cleaned up automatically, but the removal can be
35  * forced using @drm_kunit_helper_free_device.
36  *
37  * Returns:
38  * A pointer to the new device, or an ERR_PTR() otherwise.
39  */
drm_kunit_helper_alloc_device(struct kunit * test)40 struct device *drm_kunit_helper_alloc_device(struct kunit *test)
41 {
42 	return kunit_device_register(test, KUNIT_DEVICE_NAME);
43 }
44 EXPORT_SYMBOL_GPL(drm_kunit_helper_alloc_device);
45 
46 /**
47  * drm_kunit_helper_free_device - Frees a mock device
48  * @test: The test context object
49  * @dev: The device to free
50  *
51  * Frees a device allocated with drm_kunit_helper_alloc_device().
52  */
drm_kunit_helper_free_device(struct kunit * test,struct device * dev)53 void drm_kunit_helper_free_device(struct kunit *test, struct device *dev)
54 {
55 	kunit_device_unregister(test, dev);
56 }
57 EXPORT_SYMBOL_GPL(drm_kunit_helper_free_device);
58 
59 struct drm_device *
__drm_kunit_helper_alloc_drm_device_with_driver(struct kunit * test,struct device * dev,size_t size,size_t offset,const struct drm_driver * driver)60 __drm_kunit_helper_alloc_drm_device_with_driver(struct kunit *test,
61 						struct device *dev,
62 						size_t size, size_t offset,
63 						const struct drm_driver *driver)
64 {
65 	struct drm_device *drm;
66 	void *container;
67 	int ret;
68 
69 	container = __devm_drm_dev_alloc(dev, driver, size, offset);
70 	if (IS_ERR(container))
71 		return ERR_CAST(container);
72 
73 	drm = container + offset;
74 	drm->mode_config.funcs = &drm_mode_config_funcs;
75 
76 	ret = drmm_mode_config_init(drm);
77 	if (ret)
78 		return ERR_PTR(ret);
79 
80 	return drm;
81 }
82 EXPORT_SYMBOL_GPL(__drm_kunit_helper_alloc_drm_device_with_driver);
83 
kunit_action_drm_atomic_state_put(void * ptr)84 static void kunit_action_drm_atomic_state_put(void *ptr)
85 {
86 	struct drm_atomic_state *state = ptr;
87 
88 	drm_atomic_state_put(state);
89 }
90 
91 /**
92  * drm_kunit_helper_atomic_state_alloc - Allocates an atomic state
93  * @test: The test context object
94  * @drm: The device to alloc the state for
95  * @ctx: Locking context for that atomic update
96  *
97  * Allocates a empty atomic state.
98  *
99  * The state is tied to the kunit test context, so we must not call
100  * drm_atomic_state_put() on it, it will be done so automatically.
101  *
102  * Returns:
103  * An ERR_PTR on error, a pointer to the newly allocated state otherwise
104  */
105 struct drm_atomic_state *
drm_kunit_helper_atomic_state_alloc(struct kunit * test,struct drm_device * drm,struct drm_modeset_acquire_ctx * ctx)106 drm_kunit_helper_atomic_state_alloc(struct kunit *test,
107 				    struct drm_device *drm,
108 				    struct drm_modeset_acquire_ctx *ctx)
109 {
110 	struct drm_atomic_state *state;
111 	int ret;
112 
113 	state = drm_atomic_state_alloc(drm);
114 	if (!state)
115 		return ERR_PTR(-ENOMEM);
116 
117 	ret = kunit_add_action_or_reset(test,
118 					kunit_action_drm_atomic_state_put,
119 					state);
120 	if (ret)
121 		return ERR_PTR(ret);
122 
123 	state->acquire_ctx = ctx;
124 
125 	return state;
126 }
127 EXPORT_SYMBOL_GPL(drm_kunit_helper_atomic_state_alloc);
128 
129 static const uint32_t default_plane_formats[] = {
130 	DRM_FORMAT_XRGB8888,
131 };
132 
133 static const uint64_t default_plane_modifiers[] = {
134 	DRM_FORMAT_MOD_LINEAR,
135 	DRM_FORMAT_MOD_INVALID
136 };
137 
138 static const struct drm_plane_helper_funcs default_plane_helper_funcs = {
139 };
140 
141 static const struct drm_plane_funcs default_plane_funcs = {
142 	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
143 	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
144 	.reset			= drm_atomic_helper_plane_reset,
145 };
146 
147 /**
148  * drm_kunit_helper_create_primary_plane - Creates a mock primary plane for a KUnit test
149  * @test: The test context object
150  * @drm: The device to alloc the plane for
151  * @funcs: Callbacks for the new plane. Optional.
152  * @helper_funcs: Helpers callbacks for the new plane. Optional.
153  * @formats: array of supported formats (DRM_FORMAT\_\*). Optional.
154  * @num_formats: number of elements in @formats
155  * @modifiers: array of struct drm_format modifiers terminated by
156  *             DRM_FORMAT_MOD_INVALID. Optional.
157  *
158  * This allocates and initializes a mock struct &drm_plane meant to be
159  * part of a mock device for a KUnit test.
160  *
161  * Resources will be cleaned up automatically.
162  *
163  * @funcs will default to the default helpers implementations.
164  * @helper_funcs will default to an empty implementation. @formats will
165  * default to XRGB8888 only. @modifiers will default to a linear
166  * modifier only.
167  *
168  * Returns:
169  * A pointer to the new plane, or an ERR_PTR() otherwise.
170  */
171 struct drm_plane *
drm_kunit_helper_create_primary_plane(struct kunit * test,struct drm_device * drm,const struct drm_plane_funcs * funcs,const struct drm_plane_helper_funcs * helper_funcs,const uint32_t * formats,unsigned int num_formats,const uint64_t * modifiers)172 drm_kunit_helper_create_primary_plane(struct kunit *test,
173 				      struct drm_device *drm,
174 				      const struct drm_plane_funcs *funcs,
175 				      const struct drm_plane_helper_funcs *helper_funcs,
176 				      const uint32_t *formats,
177 				      unsigned int num_formats,
178 				      const uint64_t *modifiers)
179 {
180 	struct drm_plane *plane;
181 
182 	if (!funcs)
183 		funcs = &default_plane_funcs;
184 
185 	if (!helper_funcs)
186 		helper_funcs = &default_plane_helper_funcs;
187 
188 	if (!formats || !num_formats) {
189 		formats = default_plane_formats;
190 		num_formats = ARRAY_SIZE(default_plane_formats);
191 	}
192 
193 	if (!modifiers)
194 		modifiers = default_plane_modifiers;
195 
196 	plane = __drmm_universal_plane_alloc(drm,
197 					     sizeof(struct drm_plane), 0,
198 					     0,
199 					     funcs,
200 					     formats,
201 					     num_formats,
202 					     default_plane_modifiers,
203 					     DRM_PLANE_TYPE_PRIMARY,
204 					     NULL);
205 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
206 
207 	drm_plane_helper_add(plane, helper_funcs);
208 
209 	return plane;
210 }
211 EXPORT_SYMBOL_GPL(drm_kunit_helper_create_primary_plane);
212 
213 static const struct drm_crtc_helper_funcs default_crtc_helper_funcs = {
214 };
215 
216 static const struct drm_crtc_funcs default_crtc_funcs = {
217 	.atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
218 	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
219 	.reset                  = drm_atomic_helper_crtc_reset,
220 };
221 
222 /**
223  * drm_kunit_helper_create_crtc - Creates a mock CRTC for a KUnit test
224  * @test: The test context object
225  * @drm: The device to alloc the plane for
226  * @primary: Primary plane for CRTC
227  * @cursor: Cursor plane for CRTC. Optional.
228  * @funcs: Callbacks for the new plane. Optional.
229  * @helper_funcs: Helpers callbacks for the new plane. Optional.
230  *
231  * This allocates and initializes a mock struct &drm_crtc meant to be
232  * part of a mock device for a KUnit test.
233  *
234  * Resources will be cleaned up automatically.
235  *
236  * @funcs will default to the default helpers implementations.
237  * @helper_funcs will default to an empty implementation.
238  *
239  * Returns:
240  * A pointer to the new CRTC, or an ERR_PTR() otherwise.
241  */
242 struct drm_crtc *
drm_kunit_helper_create_crtc(struct kunit * test,struct drm_device * drm,struct drm_plane * primary,struct drm_plane * cursor,const struct drm_crtc_funcs * funcs,const struct drm_crtc_helper_funcs * helper_funcs)243 drm_kunit_helper_create_crtc(struct kunit *test,
244 			     struct drm_device *drm,
245 			     struct drm_plane *primary,
246 			     struct drm_plane *cursor,
247 			     const struct drm_crtc_funcs *funcs,
248 			     const struct drm_crtc_helper_funcs *helper_funcs)
249 {
250 	struct drm_crtc *crtc;
251 	int ret;
252 
253 	if (!funcs)
254 		funcs = &default_crtc_funcs;
255 
256 	if (!helper_funcs)
257 		helper_funcs = &default_crtc_helper_funcs;
258 
259 	crtc = drmm_kzalloc(drm, sizeof(*crtc), GFP_KERNEL);
260 	KUNIT_ASSERT_NOT_NULL(test, crtc);
261 
262 	ret = drmm_crtc_init_with_planes(drm, crtc,
263 					 primary,
264 					 cursor,
265 					 funcs,
266 					 NULL);
267 	KUNIT_ASSERT_EQ(test, ret, 0);
268 
269 	drm_crtc_helper_add(crtc, helper_funcs);
270 
271 	return crtc;
272 }
273 EXPORT_SYMBOL_GPL(drm_kunit_helper_create_crtc);
274 
275 /**
276  * drm_kunit_helper_enable_crtc_connector - Enables a CRTC -> Connector output
277  * @test: The test context object
278  * @drm: The device to alloc the plane for
279  * @crtc: The CRTC to enable
280  * @connector: The Connector to enable
281  * @mode: The display mode to configure the CRTC with
282  * @ctx: Locking context
283  *
284  * This function creates an atomic update to enable the route from @crtc
285  * to @connector, with the given @mode.
286  *
287  * Returns:
288  *
289  * A pointer to the new CRTC, or an ERR_PTR() otherwise. If the error
290  * returned is EDEADLK, the entire atomic sequence must be restarted.
291  */
drm_kunit_helper_enable_crtc_connector(struct kunit * test,struct drm_device * drm,struct drm_crtc * crtc,struct drm_connector * connector,const struct drm_display_mode * mode,struct drm_modeset_acquire_ctx * ctx)292 int drm_kunit_helper_enable_crtc_connector(struct kunit *test,
293 					   struct drm_device *drm,
294 					   struct drm_crtc *crtc,
295 					   struct drm_connector *connector,
296 					   const struct drm_display_mode *mode,
297 					   struct drm_modeset_acquire_ctx *ctx)
298 {
299 	struct drm_atomic_state *state;
300 	struct drm_connector_state *conn_state;
301 	struct drm_crtc_state *crtc_state;
302 	int ret;
303 
304 	state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx);
305 	if (IS_ERR(state))
306 		return PTR_ERR(state);
307 
308 	conn_state = drm_atomic_get_connector_state(state, connector);
309 	if (IS_ERR(conn_state))
310 		return PTR_ERR(conn_state);
311 
312 	ret = drm_atomic_set_crtc_for_connector(conn_state, crtc);
313 	if (ret)
314 		return ret;
315 
316 	crtc_state = drm_atomic_get_crtc_state(state, crtc);
317 	if (IS_ERR(crtc_state))
318 		return PTR_ERR(crtc_state);
319 
320 	ret = drm_atomic_set_mode_for_crtc(crtc_state, mode);
321 	if (ret)
322 		return ret;
323 
324 	crtc_state->enable = true;
325 	crtc_state->active = true;
326 
327 	ret = drm_atomic_commit(state);
328 	if (ret)
329 		return ret;
330 
331 	return 0;
332 }
333 EXPORT_SYMBOL_GPL(drm_kunit_helper_enable_crtc_connector);
334 
kunit_action_drm_mode_destroy(void * ptr)335 static void kunit_action_drm_mode_destroy(void *ptr)
336 {
337 	struct drm_display_mode *mode = ptr;
338 
339 	drm_mode_destroy(NULL, mode);
340 }
341 
342 /**
343  * drm_kunit_add_mode_destroy_action() - Add a drm_destroy_mode kunit action
344  * @test: The test context object
345  * @mode: The drm_display_mode to destroy eventually
346  *
347  * Registers a kunit action that will destroy the drm_display_mode at
348  * the end of the test.
349  *
350  * If an error occurs, the drm_display_mode will be destroyed.
351  *
352  * Returns:
353  * 0 on success, an error code otherwise.
354  */
drm_kunit_add_mode_destroy_action(struct kunit * test,struct drm_display_mode * mode)355 int drm_kunit_add_mode_destroy_action(struct kunit *test,
356 				      struct drm_display_mode *mode)
357 {
358 	return kunit_add_action_or_reset(test,
359 					 kunit_action_drm_mode_destroy,
360 					 mode);
361 }
362 EXPORT_SYMBOL_GPL(drm_kunit_add_mode_destroy_action);
363 
364 /**
365  * drm_kunit_display_mode_from_cea_vic() - return a mode for CEA VIC for a KUnit test
366  * @test: The test context object
367  * @dev: DRM device
368  * @video_code: CEA VIC of the mode
369  *
370  * Creates a new mode matching the specified CEA VIC for a KUnit test.
371  *
372  * Resources will be cleaned up automatically.
373  *
374  * Returns: A new drm_display_mode on success or NULL on failure
375  */
376 struct drm_display_mode *
drm_kunit_display_mode_from_cea_vic(struct kunit * test,struct drm_device * dev,u8 video_code)377 drm_kunit_display_mode_from_cea_vic(struct kunit *test, struct drm_device *dev,
378 				    u8 video_code)
379 {
380 	struct drm_display_mode *mode;
381 	int ret;
382 
383 	mode = drm_display_mode_from_cea_vic(dev, video_code);
384 	if (!mode)
385 		return NULL;
386 
387 	ret = kunit_add_action_or_reset(test,
388 					kunit_action_drm_mode_destroy,
389 					mode);
390 	if (ret)
391 		return NULL;
392 
393 	return mode;
394 }
395 EXPORT_SYMBOL_GPL(drm_kunit_display_mode_from_cea_vic);
396 
397 MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>");
398 MODULE_DESCRIPTION("KUnit test suite helper functions");
399 MODULE_LICENSE("GPL");
400