1// SPDX-License-Identifier: GPL-2.0
2
3#include <drm/drm_atomic.h>
4#include <drm/drm_drv.h>
5#include <drm/drm_kunit_helpers.h>
6#include <drm/drm_managed.h>
7
8#include <kunit/resource.h>
9
10#include <linux/device.h>
11#include <linux/platform_device.h>
12
13#define KUNIT_DEVICE_NAME	"drm-kunit-mock-device"
14
15static const struct drm_mode_config_funcs drm_mode_config_funcs = {
16};
17
18static int fake_probe(struct platform_device *pdev)
19{
20	return 0;
21}
22
23static struct platform_driver fake_platform_driver = {
24	.probe	= fake_probe,
25	.driver = {
26		.name	= KUNIT_DEVICE_NAME,
27	},
28};
29
30static void kunit_action_platform_driver_unregister(void *ptr)
31{
32	struct platform_driver *drv = ptr;
33
34	platform_driver_unregister(drv);
35
36}
37
38static void kunit_action_platform_device_put(void *ptr)
39{
40	struct platform_device *pdev = ptr;
41
42	platform_device_put(pdev);
43}
44
45static void kunit_action_platform_device_del(void *ptr)
46{
47	struct platform_device *pdev = ptr;
48
49	platform_device_del(pdev);
50}
51
52/**
53 * drm_kunit_helper_alloc_device - Allocate a mock device for a KUnit test
54 * @test: The test context object
55 *
56 * This allocates a fake struct &device to create a mock for a KUnit
57 * test. The device will also be bound to a fake driver. It will thus be
58 * able to leverage the usual infrastructure and most notably the
59 * device-managed resources just like a "real" device.
60 *
61 * Resources will be cleaned up automatically, but the removal can be
62 * forced using @drm_kunit_helper_free_device.
63 *
64 * Returns:
65 * A pointer to the new device, or an ERR_PTR() otherwise.
66 */
67struct device *drm_kunit_helper_alloc_device(struct kunit *test)
68{
69	struct platform_device *pdev;
70	int ret;
71
72	ret = platform_driver_register(&fake_platform_driver);
73	KUNIT_ASSERT_EQ(test, ret, 0);
74
75	ret = kunit_add_action_or_reset(test,
76					kunit_action_platform_driver_unregister,
77					&fake_platform_driver);
78	KUNIT_ASSERT_EQ(test, ret, 0);
79
80	pdev = platform_device_alloc(KUNIT_DEVICE_NAME, PLATFORM_DEVID_NONE);
81	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
82
83	ret = kunit_add_action_or_reset(test,
84					kunit_action_platform_device_put,
85					pdev);
86	KUNIT_ASSERT_EQ(test, ret, 0);
87
88	ret = platform_device_add(pdev);
89	KUNIT_ASSERT_EQ(test, ret, 0);
90
91	ret = kunit_add_action_or_reset(test,
92					kunit_action_platform_device_del,
93					pdev);
94	KUNIT_ASSERT_EQ(test, ret, 0);
95
96	return &pdev->dev;
97}
98EXPORT_SYMBOL_GPL(drm_kunit_helper_alloc_device);
99
100/**
101 * drm_kunit_helper_free_device - Frees a mock device
102 * @test: The test context object
103 * @dev: The device to free
104 *
105 * Frees a device allocated with drm_kunit_helper_alloc_device().
106 */
107void drm_kunit_helper_free_device(struct kunit *test, struct device *dev)
108{
109	struct platform_device *pdev = to_platform_device(dev);
110
111	kunit_release_action(test,
112			     kunit_action_platform_device_del,
113			     pdev);
114
115	kunit_release_action(test,
116			     kunit_action_platform_device_put,
117			     pdev);
118
119	kunit_release_action(test,
120			     kunit_action_platform_driver_unregister,
121			     &fake_platform_driver);
122}
123EXPORT_SYMBOL_GPL(drm_kunit_helper_free_device);
124
125struct drm_device *
126__drm_kunit_helper_alloc_drm_device_with_driver(struct kunit *test,
127						struct device *dev,
128						size_t size, size_t offset,
129						const struct drm_driver *driver)
130{
131	struct drm_device *drm;
132	void *container;
133	int ret;
134
135	container = __devm_drm_dev_alloc(dev, driver, size, offset);
136	if (IS_ERR(container))
137		return ERR_CAST(container);
138
139	drm = container + offset;
140	drm->mode_config.funcs = &drm_mode_config_funcs;
141
142	ret = drmm_mode_config_init(drm);
143	if (ret)
144		return ERR_PTR(ret);
145
146	return drm;
147}
148EXPORT_SYMBOL_GPL(__drm_kunit_helper_alloc_drm_device_with_driver);
149
150static void action_drm_release_context(void *ptr)
151{
152	struct drm_modeset_acquire_ctx *ctx = ptr;
153
154	drm_modeset_drop_locks(ctx);
155	drm_modeset_acquire_fini(ctx);
156}
157
158/**
159 * drm_kunit_helper_acquire_ctx_alloc - Allocates an acquire context
160 * @test: The test context object
161 *
162 * Allocates and initializes a modeset acquire context.
163 *
164 * The context is tied to the kunit test context, so we must not call
165 * drm_modeset_acquire_fini() on it, it will be done so automatically.
166 *
167 * Returns:
168 * An ERR_PTR on error, a pointer to the newly allocated context otherwise
169 */
170struct drm_modeset_acquire_ctx *
171drm_kunit_helper_acquire_ctx_alloc(struct kunit *test)
172{
173	struct drm_modeset_acquire_ctx *ctx;
174	int ret;
175
176	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
177	KUNIT_ASSERT_NOT_NULL(test, ctx);
178
179	drm_modeset_acquire_init(ctx, 0);
180
181	ret = kunit_add_action_or_reset(test,
182					action_drm_release_context,
183					ctx);
184	if (ret)
185		return ERR_PTR(ret);
186
187	return ctx;
188}
189EXPORT_SYMBOL_GPL(drm_kunit_helper_acquire_ctx_alloc);
190
191static void kunit_action_drm_atomic_state_put(void *ptr)
192{
193	struct drm_atomic_state *state = ptr;
194
195	drm_atomic_state_put(state);
196}
197
198/**
199 * drm_kunit_helper_atomic_state_alloc - Allocates an atomic state
200 * @test: The test context object
201 * @drm: The device to alloc the state for
202 * @ctx: Locking context for that atomic update
203 *
204 * Allocates a empty atomic state.
205 *
206 * The state is tied to the kunit test context, so we must not call
207 * drm_atomic_state_put() on it, it will be done so automatically.
208 *
209 * Returns:
210 * An ERR_PTR on error, a pointer to the newly allocated state otherwise
211 */
212struct drm_atomic_state *
213drm_kunit_helper_atomic_state_alloc(struct kunit *test,
214				    struct drm_device *drm,
215				    struct drm_modeset_acquire_ctx *ctx)
216{
217	struct drm_atomic_state *state;
218	int ret;
219
220	state = drm_atomic_state_alloc(drm);
221	if (!state)
222		return ERR_PTR(-ENOMEM);
223
224	ret = kunit_add_action_or_reset(test,
225					kunit_action_drm_atomic_state_put,
226					state);
227	if (ret)
228		return ERR_PTR(ret);
229
230	state->acquire_ctx = ctx;
231
232	return state;
233}
234EXPORT_SYMBOL_GPL(drm_kunit_helper_atomic_state_alloc);
235
236MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>");
237MODULE_LICENSE("GPL");
238