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