17fd56e02SRodrigo Siqueira // SPDX-License-Identifier: GPL-2.0+
21c7c5fd9SHaneen Mohammed
3ad9ff96fSHaneen Mohammed /**
4ad9ff96fSHaneen Mohammed * DOC: vkms (Virtual Kernel Modesetting)
5ad9ff96fSHaneen Mohammed *
6955fd0b7SGabriela Bittencourt * VKMS is a software-only model of a KMS driver that is useful for testing
7955fd0b7SGabriela Bittencourt * and for running X (or similar) on headless machines. VKMS aims to enable
8955fd0b7SGabriela Bittencourt * a virtual display with no need of a hardware display capability, releasing
9955fd0b7SGabriela Bittencourt * the GPU in DRM API tests.
10ad9ff96fSHaneen Mohammed */
11ad9ff96fSHaneen Mohammed
121c7c5fd9SHaneen Mohammed #include <linux/module.h>
13ce672a1bSSam Ravnborg #include <linux/platform_device.h>
1494e2ec3fSOleg Vasilev #include <linux/dma-mapping.h>
15ce672a1bSSam Ravnborg
1694e2ec3fSOleg Vasilev #include <drm/drm_gem.h>
173d08eb7dSDaniel Vetter #include <drm/drm_atomic.h>
18c04372eaSRodrigo Siqueira #include <drm/drm_atomic_helper.h>
19ce672a1bSSam Ravnborg #include <drm/drm_drv.h>
208ab59da2SThomas Zimmermann #include <drm/drm_fbdev_generic.h>
21ce672a1bSSam Ravnborg #include <drm/drm_file.h>
22ce672a1bSSam Ravnborg #include <drm/drm_gem_framebuffer_helper.h>
23ce672a1bSSam Ravnborg #include <drm/drm_ioctl.h>
24ac19f140SDaniel Vetter #include <drm/drm_managed.h>
25fcd70cd3SDaniel Vetter #include <drm/drm_probe_helper.h>
26488c888aSDaniel Vetter #include <drm/drm_gem_shmem_helper.h>
27ce672a1bSSam Ravnborg #include <drm/drm_vblank.h>
28ce672a1bSSam Ravnborg
291c7c5fd9SHaneen Mohammed #include "vkms_drv.h"
301c7c5fd9SHaneen Mohammed
31911684deSBeatriz Martins de Carvalho #include <drm/drm_print.h>
32911684deSBeatriz Martins de Carvalho #include <drm/drm_debugfs.h>
33911684deSBeatriz Martins de Carvalho
341c7c5fd9SHaneen Mohammed #define DRIVER_NAME "vkms"
351c7c5fd9SHaneen Mohammed #define DRIVER_DESC "Virtual Kernel Mode Setting"
361c7c5fd9SHaneen Mohammed #define DRIVER_DATE "20180514"
371c7c5fd9SHaneen Mohammed #define DRIVER_MAJOR 1
381c7c5fd9SHaneen Mohammed #define DRIVER_MINOR 0
391c7c5fd9SHaneen Mohammed
402df7af93SSumera Priyadarsini static struct vkms_config *default_config;
411c7c5fd9SHaneen Mohammed
422df7af93SSumera Priyadarsini static bool enable_cursor = true;
43b8789ea7SHaneen Mohammed module_param_named(enable_cursor, enable_cursor, bool, 0444);
44b8789ea7SHaneen Mohammed MODULE_PARM_DESC(enable_cursor, "Enable/Disable cursor support");
45b8789ea7SHaneen Mohammed
461e85b7d4SSumera Priyadarsini static bool enable_writeback = true;
471e85b7d4SSumera Priyadarsini module_param_named(enable_writeback, enable_writeback, bool, 0444);
481e85b7d4SSumera Priyadarsini MODULE_PARM_DESC(enable_writeback, "Enable/Disable writeback connector support");
491e85b7d4SSumera Priyadarsini
50310e506cSMelissa Wen static bool enable_overlay;
51310e506cSMelissa Wen module_param_named(enable_overlay, enable_overlay, bool, 0444);
52310e506cSMelissa Wen MODULE_PARM_DESC(enable_overlay, "Enable/Disable overlay support");
53310e506cSMelissa Wen
54488c888aSDaniel Vetter DEFINE_DRM_GEM_FOPS(vkms_driver_fops);
551c7c5fd9SHaneen Mohammed
vkms_release(struct drm_device * dev)561c7c5fd9SHaneen Mohammed static void vkms_release(struct drm_device *dev)
571c7c5fd9SHaneen Mohammed {
581a3c1959SBeatriz Martins de Carvalho struct vkms_device *vkms = drm_device_to_vkms_device(dev);
591c7c5fd9SHaneen Mohammed
602fe2a8f4SYuan Can if (vkms->output.composer_workq)
61a4e7e98eSRodrigo Siqueira destroy_workqueue(vkms->output.composer_workq);
621c7c5fd9SHaneen Mohammed }
631c7c5fd9SHaneen Mohammed
vkms_atomic_commit_tail(struct drm_atomic_state * old_state)643d08eb7dSDaniel Vetter static void vkms_atomic_commit_tail(struct drm_atomic_state *old_state)
653d08eb7dSDaniel Vetter {
663d08eb7dSDaniel Vetter struct drm_device *dev = old_state->dev;
675ef8100aSDaniel Vetter struct drm_crtc *crtc;
685ef8100aSDaniel Vetter struct drm_crtc_state *old_crtc_state;
695ef8100aSDaniel Vetter int i;
703d08eb7dSDaniel Vetter
713d08eb7dSDaniel Vetter drm_atomic_helper_commit_modeset_disables(dev, old_state);
723d08eb7dSDaniel Vetter
733d08eb7dSDaniel Vetter drm_atomic_helper_commit_planes(dev, old_state, 0);
743d08eb7dSDaniel Vetter
753d08eb7dSDaniel Vetter drm_atomic_helper_commit_modeset_enables(dev, old_state);
763d08eb7dSDaniel Vetter
773d08eb7dSDaniel Vetter drm_atomic_helper_fake_vblank(old_state);
783d08eb7dSDaniel Vetter
793d08eb7dSDaniel Vetter drm_atomic_helper_commit_hw_done(old_state);
803d08eb7dSDaniel Vetter
814922fd18SDaniel Vetter drm_atomic_helper_wait_for_flip_done(dev, old_state);
823d08eb7dSDaniel Vetter
835ef8100aSDaniel Vetter for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) {
845ef8100aSDaniel Vetter struct vkms_crtc_state *vkms_state =
855ef8100aSDaniel Vetter to_vkms_crtc_state(old_crtc_state);
865ef8100aSDaniel Vetter
87a4e7e98eSRodrigo Siqueira flush_work(&vkms_state->composer_work);
885ef8100aSDaniel Vetter }
895ef8100aSDaniel Vetter
903d08eb7dSDaniel Vetter drm_atomic_helper_cleanup_planes(dev, old_state);
913d08eb7dSDaniel Vetter }
923d08eb7dSDaniel Vetter
vkms_config_show(struct seq_file * m,void * data)93911684deSBeatriz Martins de Carvalho static int vkms_config_show(struct seq_file *m, void *data)
94911684deSBeatriz Martins de Carvalho {
9503d2673bSMaíra Canal struct drm_debugfs_entry *entry = m->private;
9603d2673bSMaíra Canal struct drm_device *dev = entry->dev;
97911684deSBeatriz Martins de Carvalho struct vkms_device *vkmsdev = drm_device_to_vkms_device(dev);
98911684deSBeatriz Martins de Carvalho
99911684deSBeatriz Martins de Carvalho seq_printf(m, "writeback=%d\n", vkmsdev->config->writeback);
100911684deSBeatriz Martins de Carvalho seq_printf(m, "cursor=%d\n", vkmsdev->config->cursor);
101911684deSBeatriz Martins de Carvalho seq_printf(m, "overlay=%d\n", vkmsdev->config->overlay);
102911684deSBeatriz Martins de Carvalho
103911684deSBeatriz Martins de Carvalho return 0;
104911684deSBeatriz Martins de Carvalho }
105911684deSBeatriz Martins de Carvalho
10603d2673bSMaíra Canal static const struct drm_debugfs_info vkms_config_debugfs_list[] = {
107911684deSBeatriz Martins de Carvalho { "vkms_config", vkms_config_show, 0 },
108911684deSBeatriz Martins de Carvalho };
109911684deSBeatriz Martins de Carvalho
11070a59dd8SDaniel Vetter static const struct drm_driver vkms_driver = {
1111c7c5fd9SHaneen Mohammed .driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM,
1121c7c5fd9SHaneen Mohammed .release = vkms_release,
1131c7c5fd9SHaneen Mohammed .fops = &vkms_driver_fops,
114488c888aSDaniel Vetter DRM_GEM_SHMEM_DRIVER_OPS,
1151c7c5fd9SHaneen Mohammed
1161c7c5fd9SHaneen Mohammed .name = DRIVER_NAME,
1171c7c5fd9SHaneen Mohammed .desc = DRIVER_DESC,
1181c7c5fd9SHaneen Mohammed .date = DRIVER_DATE,
1191c7c5fd9SHaneen Mohammed .major = DRIVER_MAJOR,
1201c7c5fd9SHaneen Mohammed .minor = DRIVER_MINOR,
1211c7c5fd9SHaneen Mohammed };
1221c7c5fd9SHaneen Mohammed
vkms_atomic_check(struct drm_device * dev,struct drm_atomic_state * state)123*db1f254fSArthur Grillo static int vkms_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
124*db1f254fSArthur Grillo {
125*db1f254fSArthur Grillo struct drm_crtc *crtc;
126*db1f254fSArthur Grillo struct drm_crtc_state *new_crtc_state;
127*db1f254fSArthur Grillo int i;
128*db1f254fSArthur Grillo
129*db1f254fSArthur Grillo for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
130*db1f254fSArthur Grillo if (!new_crtc_state->gamma_lut || !new_crtc_state->color_mgmt_changed)
131*db1f254fSArthur Grillo continue;
132*db1f254fSArthur Grillo
133*db1f254fSArthur Grillo if (new_crtc_state->gamma_lut->length / sizeof(struct drm_color_lut *)
134*db1f254fSArthur Grillo > VKMS_LUT_SIZE)
135*db1f254fSArthur Grillo return -EINVAL;
136*db1f254fSArthur Grillo }
137*db1f254fSArthur Grillo
138*db1f254fSArthur Grillo return drm_atomic_helper_check(dev, state);
139*db1f254fSArthur Grillo }
140*db1f254fSArthur Grillo
141c04372eaSRodrigo Siqueira static const struct drm_mode_config_funcs vkms_mode_funcs = {
14242ac0321SRodrigo Siqueira .fb_create = drm_gem_fb_create,
143*db1f254fSArthur Grillo .atomic_check = vkms_atomic_check,
144c04372eaSRodrigo Siqueira .atomic_commit = drm_atomic_helper_commit,
145c04372eaSRodrigo Siqueira };
146c04372eaSRodrigo Siqueira
1473d08eb7dSDaniel Vetter static const struct drm_mode_config_helper_funcs vkms_mode_config_helpers = {
1483d08eb7dSDaniel Vetter .atomic_commit_tail = vkms_atomic_commit_tail,
1493d08eb7dSDaniel Vetter };
1503d08eb7dSDaniel Vetter
vkms_modeset_init(struct vkms_device * vkmsdev)151854502faSRodrigo Siqueira static int vkms_modeset_init(struct vkms_device *vkmsdev)
152854502faSRodrigo Siqueira {
153854502faSRodrigo Siqueira struct drm_device *dev = &vkmsdev->drm;
15417e05aebSMaíra Canal int ret;
155854502faSRodrigo Siqueira
15617e05aebSMaíra Canal ret = drmm_mode_config_init(dev);
15717e05aebSMaíra Canal if (ret)
15817e05aebSMaíra Canal return ret;
15917e05aebSMaíra Canal
160854502faSRodrigo Siqueira dev->mode_config.funcs = &vkms_mode_funcs;
161854502faSRodrigo Siqueira dev->mode_config.min_width = XRES_MIN;
162854502faSRodrigo Siqueira dev->mode_config.min_height = YRES_MIN;
163854502faSRodrigo Siqueira dev->mode_config.max_width = XRES_MAX;
164854502faSRodrigo Siqueira dev->mode_config.max_height = YRES_MAX;
16506a28f90SMelissa Wen dev->mode_config.cursor_width = 512;
16606a28f90SMelissa Wen dev->mode_config.cursor_height = 512;
16723d4e55fSDaniel Vetter /* FIXME: There's a confusion between bpp and depth between this and
16823d4e55fSDaniel Vetter * fbdev helpers. We have to go with 0, meaning "pick the default",
16923d4e55fSDaniel Vetter * which ix XRGB8888 in all cases. */
17023d4e55fSDaniel Vetter dev->mode_config.preferred_depth = 0;
1713d08eb7dSDaniel Vetter dev->mode_config.helper_private = &vkms_mode_config_helpers;
172854502faSRodrigo Siqueira
173e9d85f73SRodrigo Siqueira return vkms_output_init(vkmsdev, 0);
174854502faSRodrigo Siqueira }
175854502faSRodrigo Siqueira
vkms_create(struct vkms_config * config)1762df7af93SSumera Priyadarsini static int vkms_create(struct vkms_config *config)
1771c7c5fd9SHaneen Mohammed {
1781c7c5fd9SHaneen Mohammed int ret;
17953d77aaaSDaniel Vetter struct platform_device *pdev;
1802df7af93SSumera Priyadarsini struct vkms_device *vkms_device;
1811c7c5fd9SHaneen Mohammed
18253d77aaaSDaniel Vetter pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
18353d77aaaSDaniel Vetter if (IS_ERR(pdev))
18453d77aaaSDaniel Vetter return PTR_ERR(pdev);
1851c7c5fd9SHaneen Mohammed
18653d77aaaSDaniel Vetter if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) {
18753d77aaaSDaniel Vetter ret = -ENOMEM;
18853d77aaaSDaniel Vetter goto out_unregister;
1891c7c5fd9SHaneen Mohammed }
1901c7c5fd9SHaneen Mohammed
19153d77aaaSDaniel Vetter vkms_device = devm_drm_dev_alloc(&pdev->dev, &vkms_driver,
19253d77aaaSDaniel Vetter struct vkms_device, drm);
19353d77aaaSDaniel Vetter if (IS_ERR(vkms_device)) {
19453d77aaaSDaniel Vetter ret = PTR_ERR(vkms_device);
19553d77aaaSDaniel Vetter goto out_devres;
19653d77aaaSDaniel Vetter }
19753d77aaaSDaniel Vetter vkms_device->platform = pdev;
1982df7af93SSumera Priyadarsini vkms_device->config = config;
1992df7af93SSumera Priyadarsini config->dev = vkms_device;
200633873e6SEmil Velikov
20194e2ec3fSOleg Vasilev ret = dma_coerce_mask_and_coherent(vkms_device->drm.dev,
20294e2ec3fSOleg Vasilev DMA_BIT_MASK(64));
20394e2ec3fSOleg Vasilev
20494e2ec3fSOleg Vasilev if (ret) {
20594e2ec3fSOleg Vasilev DRM_ERROR("Could not initialize DMA support\n");
20653d77aaaSDaniel Vetter goto out_devres;
20794e2ec3fSOleg Vasilev }
20894e2ec3fSOleg Vasilev
2093a070992SRodrigo Siqueira ret = drm_vblank_init(&vkms_device->drm, 1);
2103a070992SRodrigo Siqueira if (ret) {
2113a070992SRodrigo Siqueira DRM_ERROR("Failed to vblank\n");
21253d77aaaSDaniel Vetter goto out_devres;
2133a070992SRodrigo Siqueira }
2143a070992SRodrigo Siqueira
215854502faSRodrigo Siqueira ret = vkms_modeset_init(vkms_device);
216854502faSRodrigo Siqueira if (ret)
21753d77aaaSDaniel Vetter goto out_devres;
2181c7c5fd9SHaneen Mohammed
21903d2673bSMaíra Canal drm_debugfs_add_files(&vkms_device->drm, vkms_config_debugfs_list,
22003d2673bSMaíra Canal ARRAY_SIZE(vkms_config_debugfs_list));
22103d2673bSMaíra Canal
2221c7c5fd9SHaneen Mohammed ret = drm_dev_register(&vkms_device->drm, 0);
2231c7c5fd9SHaneen Mohammed if (ret)
22453d77aaaSDaniel Vetter goto out_devres;
2251c7c5fd9SHaneen Mohammed
226ea40d785SDaniel Vetter drm_fbdev_generic_setup(&vkms_device->drm, 0);
227ea40d785SDaniel Vetter
2281c7c5fd9SHaneen Mohammed return 0;
2291c7c5fd9SHaneen Mohammed
23053d77aaaSDaniel Vetter out_devres:
23153d77aaaSDaniel Vetter devres_release_group(&pdev->dev, NULL);
232633873e6SEmil Velikov out_unregister:
23353d77aaaSDaniel Vetter platform_device_unregister(pdev);
2341c7c5fd9SHaneen Mohammed return ret;
2351c7c5fd9SHaneen Mohammed }
2361c7c5fd9SHaneen Mohammed
vkms_init(void)2372df7af93SSumera Priyadarsini static int __init vkms_init(void)
2382df7af93SSumera Priyadarsini {
2390d0b368bSYuan Can int ret;
24085dd1dd6SColin Ian King struct vkms_config *config;
24185dd1dd6SColin Ian King
24285dd1dd6SColin Ian King config = kmalloc(sizeof(*config), GFP_KERNEL);
24385dd1dd6SColin Ian King if (!config)
24485dd1dd6SColin Ian King return -ENOMEM;
2452df7af93SSumera Priyadarsini
2462df7af93SSumera Priyadarsini default_config = config;
2472df7af93SSumera Priyadarsini
2482df7af93SSumera Priyadarsini config->cursor = enable_cursor;
2491e85b7d4SSumera Priyadarsini config->writeback = enable_writeback;
250310e506cSMelissa Wen config->overlay = enable_overlay;
2512df7af93SSumera Priyadarsini
2520d0b368bSYuan Can ret = vkms_create(config);
2530d0b368bSYuan Can if (ret)
2540d0b368bSYuan Can kfree(config);
2550d0b368bSYuan Can
2560d0b368bSYuan Can return ret;
2572df7af93SSumera Priyadarsini }
2582df7af93SSumera Priyadarsini
vkms_destroy(struct vkms_config * config)2592df7af93SSumera Priyadarsini static void vkms_destroy(struct vkms_config *config)
2601c7c5fd9SHaneen Mohammed {
26153d77aaaSDaniel Vetter struct platform_device *pdev;
26253d77aaaSDaniel Vetter
2632df7af93SSumera Priyadarsini if (!config->dev) {
2641c7c5fd9SHaneen Mohammed DRM_INFO("vkms_device is NULL.\n");
2651c7c5fd9SHaneen Mohammed return;
2661c7c5fd9SHaneen Mohammed }
2671c7c5fd9SHaneen Mohammed
2682df7af93SSumera Priyadarsini pdev = config->dev->platform;
26953d77aaaSDaniel Vetter
2702df7af93SSumera Priyadarsini drm_dev_unregister(&config->dev->drm);
2712df7af93SSumera Priyadarsini drm_atomic_helper_shutdown(&config->dev->drm);
27253d77aaaSDaniel Vetter devres_release_group(&pdev->dev, NULL);
27353d77aaaSDaniel Vetter platform_device_unregister(pdev);
2742df7af93SSumera Priyadarsini
2752df7af93SSumera Priyadarsini config->dev = NULL;
2762df7af93SSumera Priyadarsini }
2772df7af93SSumera Priyadarsini
vkms_exit(void)2782df7af93SSumera Priyadarsini static void __exit vkms_exit(void)
2792df7af93SSumera Priyadarsini {
2802df7af93SSumera Priyadarsini if (default_config->dev)
2812df7af93SSumera Priyadarsini vkms_destroy(default_config);
2822df7af93SSumera Priyadarsini
2832df7af93SSumera Priyadarsini kfree(default_config);
2841c7c5fd9SHaneen Mohammed }
2851c7c5fd9SHaneen Mohammed
2861c7c5fd9SHaneen Mohammed module_init(vkms_init);
2871c7c5fd9SHaneen Mohammed module_exit(vkms_exit);
2881c7c5fd9SHaneen Mohammed
289c91b007eSRodrigo Siqueira MODULE_AUTHOR("Haneen Mohammed <hamohammed.sa@gmail.com>");
290c91b007eSRodrigo Siqueira MODULE_AUTHOR("Rodrigo Siqueira <rodrigosiqueiramelo@gmail.com>");
2911c7c5fd9SHaneen Mohammed MODULE_DESCRIPTION(DRIVER_DESC);
2921c7c5fd9SHaneen Mohammed MODULE_LICENSE("GPL");
293