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/platform_device.h> |
14 | #include <linux/dma-mapping.h> |
15 | |
16 | #include <drm/drm_gem.h> |
17 | #include <drm/drm_atomic.h> |
18 | #include <drm/drm_atomic_helper.h> |
19 | #include <drm/drm_drv.h> |
20 | #include <drm/drm_fbdev_generic.h> |
21 | #include <drm/drm_file.h> |
22 | #include <drm/drm_gem_framebuffer_helper.h> |
23 | #include <drm/drm_ioctl.h> |
24 | #include <drm/drm_managed.h> |
25 | #include <drm/drm_probe_helper.h> |
26 | #include <drm/drm_gem_shmem_helper.h> |
27 | #include <drm/drm_vblank.h> |
28 | |
29 | #include "vkms_drv.h" |
30 | |
31 | #include <drm/drm_print.h> |
32 | #include <drm/drm_debugfs.h> |
33 | |
34 | #define DRIVER_NAME "vkms" |
35 | #define DRIVER_DESC "Virtual Kernel Mode Setting" |
36 | #define DRIVER_DATE "20180514" |
37 | #define DRIVER_MAJOR 1 |
38 | #define DRIVER_MINOR 0 |
39 | |
40 | static struct vkms_config *default_config; |
41 | |
42 | static bool enable_cursor = true; |
43 | module_param_named(enable_cursor, enable_cursor, bool, 0444); |
44 | MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support" ); |
45 | |
46 | static bool enable_writeback = true; |
47 | module_param_named(enable_writeback, enable_writeback, bool, 0444); |
48 | MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback connector support" ); |
49 | |
50 | static bool enable_overlay; |
51 | module_param_named(enable_overlay, enable_overlay, bool, 0444); |
52 | MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support" ); |
53 | |
54 | DEFINE_DRM_GEM_FOPS(vkms_driver_fops); |
55 | |
56 | static void vkms_release(struct drm_device *dev) |
57 | { |
58 | struct vkms_device *vkms = drm_device_to_vkms_device(dev); |
59 | |
60 | if (vkms->output.composer_workq) |
61 | destroy_workqueue(wq: vkms->output.composer_workq); |
62 | } |
63 | |
64 | static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state) |
65 | { |
66 | struct drm_device *dev = old_state->dev; |
67 | struct drm_crtc *crtc; |
68 | struct drm_crtc_state *old_crtc_state; |
69 | int i; |
70 | |
71 | drm_atomic_helper_commit_modeset_disables(dev, state: old_state); |
72 | |
73 | drm_atomic_helper_commit_planes(dev, state: old_state, flags: 0); |
74 | |
75 | drm_atomic_helper_commit_modeset_enables(dev, old_state); |
76 | |
77 | drm_atomic_helper_fake_vblank(state: old_state); |
78 | |
79 | drm_atomic_helper_commit_hw_done(state: old_state); |
80 | |
81 | drm_atomic_helper_wait_for_flip_done(dev, old_state); |
82 | |
83 | for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) { |
84 | struct vkms_crtc_state *vkms_state = |
85 | to_vkms_crtc_state(old_crtc_state); |
86 | |
87 | flush_work(work: &vkms_state->composer_work); |
88 | } |
89 | |
90 | drm_atomic_helper_cleanup_planes(dev, old_state); |
91 | } |
92 | |
93 | static int vkms_config_show(struct seq_file *m, void *data) |
94 | { |
95 | struct drm_debugfs_entry *entry = m->private; |
96 | struct drm_device *dev = entry->dev; |
97 | struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev); |
98 | |
99 | seq_printf(m, fmt: "writeback=%d\n" , vkmsdev->config->writeback); |
100 | seq_printf(m, fmt: "cursor=%d\n" , vkmsdev->config->cursor); |
101 | seq_printf(m, fmt: "overlay=%d\n" , vkmsdev->config->overlay); |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static const struct drm_debugfs_info vkms_config_debugfs_list[] = { |
107 | { "vkms_config" , vkms_config_show, 0 }, |
108 | }; |
109 | |
110 | static const struct drm_driver vkms_driver = { |
111 | .driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM, |
112 | .release = vkms_release, |
113 | .fops = &vkms_driver_fops, |
114 | DRM_GEM_SHMEM_DRIVER_OPS, |
115 | |
116 | .name = DRIVER_NAME, |
117 | .desc = DRIVER_DESC, |
118 | .date = DRIVER_DATE, |
119 | .major = DRIVER_MAJOR, |
120 | .minor = DRIVER_MINOR, |
121 | }; |
122 | |
123 | static int vkms_atomic_check(struct drm_device *dev, struct drm_atomic_state *state) |
124 | { |
125 | struct drm_crtc *crtc; |
126 | struct drm_crtc_state *new_crtc_state; |
127 | int i; |
128 | |
129 | for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { |
130 | if (!new_crtc_state->gamma_lut || !new_crtc_state->color_mgmt_changed) |
131 | continue; |
132 | |
133 | if (new_crtc_state->gamma_lut->length / sizeof(struct drm_color_lut *) |
134 | > VKMS_LUT_SIZE) |
135 | return -EINVAL; |
136 | } |
137 | |
138 | return drm_atomic_helper_check(dev, state); |
139 | } |
140 | |
141 | static const struct drm_mode_config_funcs vkms_mode_funcs = { |
142 | .fb_create = drm_gem_fb_create, |
143 | .atomic_check = vkms_atomic_check, |
144 | .atomic_commit = drm_atomic_helper_commit, |
145 | }; |
146 | |
147 | static const struct drm_mode_config_helper_funcs vkms_mode_config_helpers = { |
148 | .atomic_commit_tail = vkms_atomic_commit_tail, |
149 | }; |
150 | |
151 | static int vkms_modeset_init(struct vkms_device *vkmsdev) |
152 | { |
153 | struct drm_device *dev = &vkmsdev->drm; |
154 | int ret; |
155 | |
156 | ret = drmm_mode_config_init(dev); |
157 | if (ret) |
158 | return ret; |
159 | |
160 | dev->mode_config.funcs = &vkms_mode_funcs; |
161 | dev->mode_config.min_width = XRES_MIN; |
162 | dev->mode_config.min_height = YRES_MIN; |
163 | dev->mode_config.max_width = XRES_MAX; |
164 | dev->mode_config.max_height = YRES_MAX; |
165 | dev->mode_config.cursor_width = 512; |
166 | dev->mode_config.cursor_height = 512; |
167 | /* FIXME: There's a confusion between bpp and depth between this and |
168 | * fbdev helpers. We have to go with 0, meaning "pick the default", |
169 | * which ix XRGB8888 in all cases. */ |
170 | dev->mode_config.preferred_depth = 0; |
171 | dev->mode_config.helper_private = &vkms_mode_config_helpers; |
172 | |
173 | return vkms_output_init(vkmsdev, index: 0); |
174 | } |
175 | |
176 | static int vkms_create(struct vkms_config *config) |
177 | { |
178 | int ret; |
179 | struct platform_device *pdev; |
180 | struct vkms_device *vkms_device; |
181 | |
182 | pdev = platform_device_register_simple(DRIVER_NAME, id: -1, NULL, num: 0); |
183 | if (IS_ERR(ptr: pdev)) |
184 | return PTR_ERR(ptr: pdev); |
185 | |
186 | if (!devres_open_group(dev: &pdev->dev, NULL, GFP_KERNEL)) { |
187 | ret = -ENOMEM; |
188 | goto out_unregister; |
189 | } |
190 | |
191 | vkms_device = devm_drm_dev_alloc(&pdev->dev, &vkms_driver, |
192 | struct vkms_device, drm); |
193 | if (IS_ERR(ptr: vkms_device)) { |
194 | ret = PTR_ERR(ptr: vkms_device); |
195 | goto out_devres; |
196 | } |
197 | vkms_device->platform = pdev; |
198 | vkms_device->config = config; |
199 | config->dev = vkms_device; |
200 | |
201 | ret = dma_coerce_mask_and_coherent(dev: vkms_device->drm.dev, |
202 | DMA_BIT_MASK(64)); |
203 | |
204 | if (ret) { |
205 | DRM_ERROR("Could not initialize DMA support\n" ); |
206 | goto out_devres; |
207 | } |
208 | |
209 | ret = drm_vblank_init(dev: &vkms_device->drm, num_crtcs: 1); |
210 | if (ret) { |
211 | DRM_ERROR("Failed to vblank\n" ); |
212 | goto out_devres; |
213 | } |
214 | |
215 | ret = vkms_modeset_init(vkmsdev: vkms_device); |
216 | if (ret) |
217 | goto out_devres; |
218 | |
219 | drm_debugfs_add_files(dev: &vkms_device->drm, files: vkms_config_debugfs_list, |
220 | ARRAY_SIZE(vkms_config_debugfs_list)); |
221 | |
222 | ret = drm_dev_register(dev: &vkms_device->drm, flags: 0); |
223 | if (ret) |
224 | goto out_devres; |
225 | |
226 | drm_fbdev_generic_setup(dev: &vkms_device->drm, preferred_bpp: 0); |
227 | |
228 | return 0; |
229 | |
230 | out_devres: |
231 | devres_release_group(dev: &pdev->dev, NULL); |
232 | out_unregister: |
233 | platform_device_unregister(pdev); |
234 | return ret; |
235 | } |
236 | |
237 | static int __init vkms_init(void) |
238 | { |
239 | int ret; |
240 | struct vkms_config *config; |
241 | |
242 | config = kmalloc(size: sizeof(*config), GFP_KERNEL); |
243 | if (!config) |
244 | return -ENOMEM; |
245 | |
246 | default_config = config; |
247 | |
248 | config->cursor = enable_cursor; |
249 | config->writeback = enable_writeback; |
250 | config->overlay = enable_overlay; |
251 | |
252 | ret = vkms_create(config); |
253 | if (ret) |
254 | kfree(objp: config); |
255 | |
256 | return ret; |
257 | } |
258 | |
259 | static void vkms_destroy(struct vkms_config *config) |
260 | { |
261 | struct platform_device *pdev; |
262 | |
263 | if (!config->dev) { |
264 | DRM_INFO("vkms_device is NULL.\n" ); |
265 | return; |
266 | } |
267 | |
268 | pdev = config->dev->platform; |
269 | |
270 | drm_dev_unregister(dev: &config->dev->drm); |
271 | drm_atomic_helper_shutdown(dev: &config->dev->drm); |
272 | devres_release_group(dev: &pdev->dev, NULL); |
273 | platform_device_unregister(pdev); |
274 | |
275 | config->dev = NULL; |
276 | } |
277 | |
278 | static void __exit vkms_exit(void) |
279 | { |
280 | if (default_config->dev) |
281 | vkms_destroy(config: default_config); |
282 | |
283 | kfree(objp: default_config); |
284 | } |
285 | |
286 | module_init(vkms_init); |
287 | module_exit(vkms_exit); |
288 | |
289 | MODULE_AUTHOR("Haneen Mohammed <hamohammed.sa@gmail.com>" ); |
290 | MODULE_AUTHOR("Rodrigo Siqueira <rodrigosiqueiramelo@gmail.com>" ); |
291 | MODULE_DESCRIPTION(DRIVER_DESC); |
292 | MODULE_LICENSE("GPL" ); |
293 | |