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