Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0+
2
3/**
4 * DOC: vkms (Virtual Kernel Modesetting)
5 *
6 * VKMS is a software-only model of a KMS driver that is useful for testing
7 * and for running X (or similar) on headless machines. VKMS aims to enable
8 * a virtual display with no need of a hardware display capability, releasing
9 * the GPU in DRM API tests.
10 */
11
12#include <linux/module.h>
13#include <linux/device/faux.h>
14#include <linux/dma-mapping.h>
15
16#include <drm/clients/drm_client_setup.h>
17#include <drm/drm_gem.h>
18#include <drm/drm_atomic.h>
19#include <drm/drm_atomic_helper.h>
20#include <drm/drm_drv.h>
21#include <drm/drm_fbdev_shmem.h>
22#include <drm/drm_file.h>
23#include <drm/drm_gem_framebuffer_helper.h>
24#include <drm/drm_ioctl.h>
25#include <drm/drm_managed.h>
26#include <drm/drm_probe_helper.h>
27#include <drm/drm_gem_shmem_helper.h>
28#include <drm/drm_vblank.h>
29
30#include "vkms_config.h"
31#include "vkms_drv.h"
32
33#define DRIVER_NAME "vkms"
34#define DRIVER_DESC "Virtual Kernel Mode Setting"
35#define DRIVER_MAJOR 1
36#define DRIVER_MINOR 0
37
38static struct vkms_config *default_config;
39
40static bool enable_cursor = true;
41module_param_named(enable_cursor, enable_cursor, bool, 0444);
42MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
43
44static bool enable_writeback = true;
45module_param_named(enable_writeback, enable_writeback, bool, 0444);
46MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback connector support");
47
48static bool enable_overlay;
49module_param_named(enable_overlay, enable_overlay, bool, 0444);
50MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
51
52DEFINE_DRM_GEM_FOPS(vkms_driver_fops);
53
54static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
55{
56 struct drm_device *dev = old_state->dev;
57 struct drm_crtc *crtc;
58 struct drm_crtc_state *old_crtc_state;
59 int i;
60
61 drm_atomic_helper_commit_modeset_disables(dev, old_state);
62
63 drm_atomic_helper_commit_planes(dev, old_state, 0);
64
65 drm_atomic_helper_commit_modeset_enables(dev, old_state);
66
67 drm_atomic_helper_fake_vblank(old_state);
68
69 drm_atomic_helper_commit_hw_done(old_state);
70
71 drm_atomic_helper_wait_for_flip_done(dev, old_state);
72
73 for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) {
74 struct vkms_crtc_state *vkms_state = to_vkms_crtc_state(old_crtc_state);
75
76 flush_work(&vkms_state->composer_work);
77 }
78
79 drm_atomic_helper_cleanup_planes(dev, old_state);
80}
81
82static const struct drm_driver vkms_driver = {
83 .driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM,
84 .fops = &vkms_driver_fops,
85 DRM_GEM_SHMEM_DRIVER_OPS,
86 DRM_FBDEV_SHMEM_DRIVER_OPS,
87
88 .name = DRIVER_NAME,
89 .desc = DRIVER_DESC,
90 .major = DRIVER_MAJOR,
91 .minor = DRIVER_MINOR,
92};
93
94static int vkms_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
95{
96 struct drm_crtc *crtc;
97 struct drm_crtc_state *new_crtc_state;
98 int i;
99
100 for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
101 if (!new_crtc_state->gamma_lut || !new_crtc_state->color_mgmt_changed)
102 continue;
103
104 if (new_crtc_state->gamma_lut->length / sizeof(struct drm_color_lut *)
105 > VKMS_LUT_SIZE)
106 return -EINVAL;
107 }
108
109 return drm_atomic_helper_check(dev, state);
110}
111
112static const struct drm_mode_config_funcs vkms_mode_funcs = {
113 .fb_create = drm_gem_fb_create,
114 .atomic_check = vkms_atomic_check,
115 .atomic_commit = drm_atomic_helper_commit,
116};
117
118static const struct drm_mode_config_helper_funcs vkms_mode_config_helpers = {
119 .atomic_commit_tail = vkms_atomic_commit_tail,
120};
121
122static int vkms_modeset_init(struct vkms_device *vkmsdev)
123{
124 struct drm_device *dev = &vkmsdev->drm;
125 int ret;
126
127 ret = drmm_mode_config_init(dev);
128 if (ret)
129 return ret;
130
131 dev->mode_config.funcs = &vkms_mode_funcs;
132 dev->mode_config.min_width = XRES_MIN;
133 dev->mode_config.min_height = YRES_MIN;
134 dev->mode_config.max_width = XRES_MAX;
135 dev->mode_config.max_height = YRES_MAX;
136 dev->mode_config.cursor_width = 512;
137 dev->mode_config.cursor_height = 512;
138 /*
139 * FIXME: There's a confusion between bpp and depth between this and
140 * fbdev helpers. We have to go with 0, meaning "pick the default",
141 * which is XRGB8888 in all cases.
142 */
143 dev->mode_config.preferred_depth = 0;
144 dev->mode_config.helper_private = &vkms_mode_config_helpers;
145
146 return vkms_output_init(vkmsdev);
147}
148
149static int vkms_create(struct vkms_config *config)
150{
151 int ret;
152 struct faux_device *fdev;
153 struct vkms_device *vkms_device;
154 const char *dev_name;
155
156 dev_name = vkms_config_get_device_name(config);
157 fdev = faux_device_create(dev_name, NULL, NULL);
158 if (!fdev)
159 return -ENODEV;
160
161 if (!devres_open_group(&fdev->dev, NULL, GFP_KERNEL)) {
162 ret = -ENOMEM;
163 goto out_unregister;
164 }
165
166 vkms_device = devm_drm_dev_alloc(&fdev->dev, &vkms_driver,
167 struct vkms_device, drm);
168 if (IS_ERR(vkms_device)) {
169 ret = PTR_ERR(vkms_device);
170 goto out_devres;
171 }
172 vkms_device->faux_dev = fdev;
173 vkms_device->config = config;
174 config->dev = vkms_device;
175
176 ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev,
177 DMA_BIT_MASK(64));
178
179 if (ret) {
180 DRM_ERROR("Could not initialize DMA support\n");
181 goto out_devres;
182 }
183
184 ret = drm_vblank_init(&vkms_device->drm,
185 vkms_config_get_num_crtcs(config));
186 if (ret) {
187 DRM_ERROR("Failed to vblank\n");
188 goto out_devres;
189 }
190
191 ret = vkms_modeset_init(vkms_device);
192 if (ret)
193 goto out_devres;
194
195 vkms_config_register_debugfs(vkms_device);
196
197 ret = drm_dev_register(&vkms_device->drm, 0);
198 if (ret)
199 goto out_devres;
200
201 drm_client_setup(&vkms_device->drm, NULL);
202
203 return 0;
204
205out_devres:
206 devres_release_group(&fdev->dev, NULL);
207out_unregister:
208 faux_device_destroy(fdev);
209 return ret;
210}
211
212static int __init vkms_init(void)
213{
214 int ret;
215 struct vkms_config *config;
216
217 config = vkms_config_default_create(enable_cursor, enable_writeback, enable_overlay);
218 if (IS_ERR(config))
219 return PTR_ERR(config);
220
221 ret = vkms_create(config);
222 if (ret) {
223 vkms_config_destroy(config);
224 return ret;
225 }
226
227 default_config = config;
228
229 return 0;
230}
231
232static void vkms_destroy(struct vkms_config *config)
233{
234 struct faux_device *fdev;
235
236 if (!config->dev) {
237 DRM_INFO("vkms_device is NULL.\n");
238 return;
239 }
240
241 fdev = config->dev->faux_dev;
242
243 drm_dev_unregister(&config->dev->drm);
244 drm_atomic_helper_shutdown(&config->dev->drm);
245 devres_release_group(&fdev->dev, NULL);
246 faux_device_destroy(fdev);
247
248 config->dev = NULL;
249}
250
251static void __exit vkms_exit(void)
252{
253 if (!default_config)
254 return;
255
256 vkms_destroy(default_config);
257 vkms_config_destroy(default_config);
258}
259
260module_init(vkms_init);
261module_exit(vkms_exit);
262
263MODULE_AUTHOR("Haneen Mohammed <hamohammed.sa@gmail.com>");
264MODULE_AUTHOR("Rodrigo Siqueira <rodrigosiqueiramelo@gmail.com>");
265MODULE_DESCRIPTION(DRIVER_DESC);
266MODULE_LICENSE("GPL");