| // SPDX-License-Identifier: GPL-2.0 | |
| #include <drm/drm_atomic.h> | |
| #include <drm/drm_atomic_helper.h> | |
| #include <drm/drm_drv.h> | |
| #include <drm/drm_edid.h> | |
| #include <drm/drm_fourcc.h> | |
| #include <drm/drm_kunit_helpers.h> | |
| #include <drm/drm_managed.h> | |
| #include <kunit/device.h> | |
| #include <kunit/resource.h> | |
| #include <linux/device.h> | |
| #include <linux/platform_device.h> | |
| #define KUNIT_DEVICE_NAME "drm-kunit-mock-device" | |
| static const struct drm_mode_config_funcs drm_mode_config_funcs = { | |
| .atomic_check = drm_atomic_helper_check, | |
| .atomic_commit = drm_atomic_helper_commit, | |
| }; | |
| /** | |
| * drm_kunit_helper_alloc_device - Allocate a mock device for a KUnit test | |
| * @test: The test context object | |
| * | |
| * This allocates a fake struct &device to create a mock for a KUnit | |
| * test. The device will also be bound to a fake driver. It will thus be | |
| * able to leverage the usual infrastructure and most notably the | |
| * device-managed resources just like a "real" device. | |
| * | |
| * Resources will be cleaned up automatically, but the removal can be | |
| * forced using @drm_kunit_helper_free_device. | |
| * | |
| * Returns: | |
| * A pointer to the new device, or an ERR_PTR() otherwise. | |
| */ | |
| struct device *drm_kunit_helper_alloc_device(struct kunit *test) | |
| { | |
| return kunit_device_register(test, KUNIT_DEVICE_NAME); | |
| } | |
| EXPORT_SYMBOL_GPL(drm_kunit_helper_alloc_device); | |
| /** | |
| * drm_kunit_helper_free_device - Frees a mock device | |
| * @test: The test context object | |
| * @dev: The device to free | |
| * | |
| * Frees a device allocated with drm_kunit_helper_alloc_device(). | |
| */ | |
| void drm_kunit_helper_free_device(struct kunit *test, struct device *dev) | |
| { | |
| kunit_device_unregister(test, dev); | |
| } | |
| EXPORT_SYMBOL_GPL(drm_kunit_helper_free_device); | |
| 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) | |
| { | |
| struct drm_device *drm; | |
| void *container; | |
| int ret; | |
| container = __devm_drm_dev_alloc(dev, driver, size, offset); | |
| if (IS_ERR(container)) | |
| return ERR_CAST(container); | |
| drm = container + offset; | |
| drm->mode_config.funcs = &drm_mode_config_funcs; | |
| ret = drmm_mode_config_init(drm); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| return drm; | |
| } | |
| EXPORT_SYMBOL_GPL(__drm_kunit_helper_alloc_drm_device_with_driver); | |
| static void kunit_action_drm_atomic_state_put(void *ptr) | |
| { | |
| struct drm_atomic_state *state = ptr; | |
| drm_atomic_state_put(state); | |
| } | |
| /** | |
| * drm_kunit_helper_atomic_state_alloc - Allocates an atomic state | |
| * @test: The test context object | |
| * @drm: The device to alloc the state for | |
| * @ctx: Locking context for that atomic update | |
| * | |
| * Allocates a empty atomic state. | |
| * | |
| * The state is tied to the kunit test context, so we must not call | |
| * drm_atomic_state_put() on it, it will be done so automatically. | |
| * | |
| * Returns: | |
| * An ERR_PTR on error, a pointer to the newly allocated state otherwise | |
| */ | |
| struct drm_atomic_state * | |
| drm_kunit_helper_atomic_state_alloc(struct kunit *test, | |
| struct drm_device *drm, | |
| struct drm_modeset_acquire_ctx *ctx) | |
| { | |
| struct drm_atomic_state *state; | |
| int ret; | |
| state = drm_atomic_state_alloc(drm); | |
| if (!state) | |
| return ERR_PTR(-ENOMEM); | |
| ret = kunit_add_action_or_reset(test, | |
| kunit_action_drm_atomic_state_put, | |
| state); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| state->acquire_ctx = ctx; | |
| return state; | |
| } | |
| EXPORT_SYMBOL_GPL(drm_kunit_helper_atomic_state_alloc); | |
| static const uint32_t default_plane_formats[] = { | |
| DRM_FORMAT_XRGB8888, | |
| }; | |
| static const uint64_t default_plane_modifiers[] = { | |
| DRM_FORMAT_MOD_LINEAR, | |
| DRM_FORMAT_MOD_INVALID | |
| }; | |
| static const struct drm_plane_helper_funcs default_plane_helper_funcs = { | |
| }; | |
| static const struct drm_plane_funcs default_plane_funcs = { | |
| .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, | |
| .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, | |
| .reset = drm_atomic_helper_plane_reset, | |
| }; | |
| /** | |
| * drm_kunit_helper_create_primary_plane - Creates a mock primary plane for a KUnit test | |
| * @test: The test context object | |
| * @drm: The device to alloc the plane for | |
| * @funcs: Callbacks for the new plane. Optional. | |
| * @helper_funcs: Helpers callbacks for the new plane. Optional. | |
| * @formats: array of supported formats (DRM_FORMAT\_\*). Optional. | |
| * @num_formats: number of elements in @formats | |
| * @modifiers: array of struct drm_format modifiers terminated by | |
| * DRM_FORMAT_MOD_INVALID. Optional. | |
| * | |
| * This allocates and initializes a mock struct &drm_plane meant to be | |
| * part of a mock device for a KUnit test. | |
| * | |
| * Resources will be cleaned up automatically. | |
| * | |
| * @funcs will default to the default helpers implementations. | |
| * @helper_funcs will default to an empty implementation. @formats will | |
| * default to XRGB8888 only. @modifiers will default to a linear | |
| * modifier only. | |
| * | |
| * Returns: | |
| * A pointer to the new plane, or an ERR_PTR() otherwise. | |
| */ | |
| 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) | |
| { | |
| struct drm_plane *plane; | |
| if (!funcs) | |
| funcs = &default_plane_funcs; | |
| if (!helper_funcs) | |
| helper_funcs = &default_plane_helper_funcs; | |
| if (!formats || !num_formats) { | |
| formats = default_plane_formats; | |
| num_formats = ARRAY_SIZE(default_plane_formats); | |
| } | |
| if (!modifiers) | |
| modifiers = default_plane_modifiers; | |
| plane = __drmm_universal_plane_alloc(drm, | |
| sizeof(struct drm_plane), 0, | |
| 0, | |
| funcs, | |
| formats, | |
| num_formats, | |
| default_plane_modifiers, | |
| DRM_PLANE_TYPE_PRIMARY, | |
| NULL); | |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane); | |
| drm_plane_helper_add(plane, helper_funcs); | |
| return plane; | |
| } | |
| EXPORT_SYMBOL_GPL(drm_kunit_helper_create_primary_plane); | |
| static const struct drm_crtc_helper_funcs default_crtc_helper_funcs = { | |
| }; | |
| static const struct drm_crtc_funcs default_crtc_funcs = { | |
| .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, | |
| .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, | |
| .reset = drm_atomic_helper_crtc_reset, | |
| }; | |
| /** | |
| * drm_kunit_helper_create_crtc - Creates a mock CRTC for a KUnit test | |
| * @test: The test context object | |
| * @drm: The device to alloc the plane for | |
| * @primary: Primary plane for CRTC | |
| * @cursor: Cursor plane for CRTC. Optional. | |
| * @funcs: Callbacks for the new plane. Optional. | |
| * @helper_funcs: Helpers callbacks for the new plane. Optional. | |
| * | |
| * This allocates and initializes a mock struct &drm_crtc meant to be | |
| * part of a mock device for a KUnit test. | |
| * | |
| * Resources will be cleaned up automatically. | |
| * | |
| * @funcs will default to the default helpers implementations. | |
| * @helper_funcs will default to an empty implementation. | |
| * | |
| * Returns: | |
| * A pointer to the new CRTC, or an ERR_PTR() otherwise. | |
| */ | |
| 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) | |
| { | |
| struct drm_crtc *crtc; | |
| int ret; | |
| if (!funcs) | |
| funcs = &default_crtc_funcs; | |
| if (!helper_funcs) | |
| helper_funcs = &default_crtc_helper_funcs; | |
| crtc = drmm_kzalloc(drm, sizeof(*crtc), GFP_KERNEL); | |
| KUNIT_ASSERT_NOT_NULL(test, crtc); | |
| ret = drmm_crtc_init_with_planes(drm, crtc, | |
| primary, | |
| cursor, | |
| funcs, | |
| NULL); | |
| KUNIT_ASSERT_EQ(test, ret, 0); | |
| drm_crtc_helper_add(crtc, helper_funcs); | |
| return crtc; | |
| } | |
| EXPORT_SYMBOL_GPL(drm_kunit_helper_create_crtc); | |
| static void kunit_action_drm_mode_destroy(void *ptr) | |
| { | |
| struct drm_display_mode *mode = ptr; | |
| drm_mode_destroy(NULL, mode); | |
| } | |
| /** | |
| * drm_kunit_add_mode_destroy_action() - Add a drm_destroy_mode kunit action | |
| * @test: The test context object | |
| * @mode: The drm_display_mode to destroy eventually | |
| * | |
| * Registers a kunit action that will destroy the drm_display_mode at | |
| * the end of the test. | |
| * | |
| * If an error occurs, the drm_display_mode will be destroyed. | |
| * | |
| * Returns: | |
| * 0 on success, an error code otherwise. | |
| */ | |
| int drm_kunit_add_mode_destroy_action(struct kunit *test, | |
| struct drm_display_mode *mode) | |
| { | |
| return kunit_add_action_or_reset(test, | |
| kunit_action_drm_mode_destroy, | |
| mode); | |
| } | |
| EXPORT_SYMBOL_GPL(drm_kunit_add_mode_destroy_action); | |
| /** | |
| * drm_kunit_display_mode_from_cea_vic() - return a mode for CEA VIC for a KUnit test | |
| * @test: The test context object | |
| * @dev: DRM device | |
| * @video_code: CEA VIC of the mode | |
| * | |
| * Creates a new mode matching the specified CEA VIC for a KUnit test. | |
| * | |
| * Resources will be cleaned up automatically. | |
| * | |
| * Returns: A new drm_display_mode on success or NULL on failure | |
| */ | |
| struct drm_display_mode * | |
| drm_kunit_display_mode_from_cea_vic(struct kunit *test, struct drm_device *dev, | |
| u8 video_code) | |
| { | |
| struct drm_display_mode *mode; | |
| int ret; | |
| mode = drm_display_mode_from_cea_vic(dev, video_code); | |
| if (!mode) | |
| return NULL; | |
| ret = kunit_add_action_or_reset(test, | |
| kunit_action_drm_mode_destroy, | |
| mode); | |
| if (ret) | |
| return NULL; | |
| return mode; | |
| } | |
| EXPORT_SYMBOL_GPL(drm_kunit_display_mode_from_cea_vic); | |
| MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>"); | |
| MODULE_DESCRIPTION("KUnit test suite helper functions"); | |
| MODULE_LICENSE("GPL"); |