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