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