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 */ 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 */ 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 * 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 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 * 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 * 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 * 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 */ 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 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 */ 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 * 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