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