Skip to content

Commit 76c56a5

Browse files
committed
drm/hyperv: Add DRM driver for hyperv synthetic video device
DRM driver for hyperv synthetic video device, based on hyperv_fb framebuffer driver. Also added config option "DRM_HYPERV" to enabled this driver. v2: - Add support for gen2 VM - Fixed review comments v3: - Split into multiple files as suggested by Thomas Zimmermann - Fixed hibernation issue as suggested by Dexuan Cui - Use ioremap_cache as suggested by Dexuan Cui - Incorporated other review comments v4: - Fix bitrotted code - Review comments - Updated the copyright and license to match hyperv_fb v5: - Address review comments and rebased with drm-misc-next v6: - Minor code/comment improvement as suggested by Dexuan Cui Signed-off-by: Deepak Rawat <[email protected]> Acked-by: Thomas Zimmermann <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/[email protected]
1 parent abb50d6 commit 76c56a5

File tree

7 files changed

+1091
-0
lines changed

7 files changed

+1091
-0
lines changed

drivers/gpu/drm/Kconfig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,19 @@ source "drivers/gpu/drm/xlnx/Kconfig"
379379

380380
source "drivers/gpu/drm/gud/Kconfig"
381381

382+
config DRM_HYPERV
383+
tristate "DRM Support for Hyper-V synthetic video device"
384+
depends on DRM && PCI && MMU && HYPERV
385+
select DRM_KMS_HELPER
386+
select DRM_GEM_SHMEM_HELPER
387+
help
388+
This is a KMS driver for Hyper-V synthetic video device. Choose this
389+
option if you would like to enable drm driver for Hyper-V virtual
390+
machine. Unselect Hyper-V framebuffer driver (CONFIG_FB_HYPERV) so
391+
that DRM driver is used by default.
392+
393+
If M is selected the module will be called hyperv_drm.
394+
382395
# Keep legacy drivers last
383396

384397
menuconfig DRM_LEGACY

drivers/gpu/drm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,4 @@ obj-$(CONFIG_DRM_MCDE) += mcde/
126126
obj-$(CONFIG_DRM_TIDSS) += tidss/
127127
obj-y += xlnx/
128128
obj-y += gud/
129+
obj-$(CONFIG_DRM_HYPERV) += hyperv/

drivers/gpu/drm/hyperv/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
3+
hyperv_drm-y := \
4+
hyperv_drm_drv.o \
5+
hyperv_drm_modeset.o \
6+
hyperv_drm_proto.o
7+
8+
obj-$(CONFIG_DRM_HYPERV) += hyperv_drm.o

drivers/gpu/drm/hyperv/hyperv_drm.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* SPDX-License-Identifier: GPL-2.0-only */
2+
/*
3+
* Copyright 2021 Microsoft
4+
*/
5+
6+
#ifndef _HYPERV_DRM_H_
7+
#define _HYPERV_DRM_H_
8+
9+
#define VMBUS_MAX_PACKET_SIZE 0x4000
10+
11+
struct hyperv_drm_device {
12+
/* drm */
13+
struct drm_device dev;
14+
struct drm_simple_display_pipe pipe;
15+
struct drm_connector connector;
16+
17+
/* mode */
18+
u32 screen_width_max;
19+
u32 screen_height_max;
20+
u32 preferred_width;
21+
u32 preferred_height;
22+
u32 screen_depth;
23+
24+
/* hw */
25+
struct resource *mem;
26+
void __iomem *vram;
27+
unsigned long fb_base;
28+
unsigned long fb_size;
29+
struct completion wait;
30+
u32 synthvid_version;
31+
u32 mmio_megabytes;
32+
33+
u8 init_buf[VMBUS_MAX_PACKET_SIZE];
34+
u8 recv_buf[VMBUS_MAX_PACKET_SIZE];
35+
36+
struct hv_device *hdev;
37+
};
38+
39+
#define to_hv(_dev) container_of(_dev, struct hyperv_drm_device, dev)
40+
41+
/* hyperv_drm_modeset */
42+
int hyperv_mode_config_init(struct hyperv_drm_device *hv);
43+
44+
/* hyperv_drm_proto */
45+
int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp);
46+
int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
47+
u32 w, u32 h, u32 pitch);
48+
int hyperv_update_dirt(struct hv_device *hdev, struct drm_rect *rect);
49+
int hyperv_connect_vsp(struct hv_device *hdev);
50+
51+
#endif
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Copyright 2021 Microsoft
4+
*/
5+
6+
#include <linux/efi.h>
7+
#include <linux/hyperv.h>
8+
#include <linux/module.h>
9+
#include <linux/pci.h>
10+
11+
#include <drm/drm_aperture.h>
12+
#include <drm/drm_atomic_helper.h>
13+
#include <drm/drm_drv.h>
14+
#include <drm/drm_fb_helper.h>
15+
#include <drm/drm_gem_shmem_helper.h>
16+
#include <drm/drm_simple_kms_helper.h>
17+
18+
#include "hyperv_drm.h"
19+
20+
#define DRIVER_NAME "hyperv_drm"
21+
#define DRIVER_DESC "DRM driver for Hyper-V synthetic video device"
22+
#define DRIVER_DATE "2020"
23+
#define DRIVER_MAJOR 1
24+
#define DRIVER_MINOR 0
25+
26+
#define PCI_VENDOR_ID_MICROSOFT 0x1414
27+
#define PCI_DEVICE_ID_HYPERV_VIDEO 0x5353
28+
29+
DEFINE_DRM_GEM_FOPS(hv_fops);
30+
31+
static struct drm_driver hyperv_driver = {
32+
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
33+
34+
.name = DRIVER_NAME,
35+
.desc = DRIVER_DESC,
36+
.date = DRIVER_DATE,
37+
.major = DRIVER_MAJOR,
38+
.minor = DRIVER_MINOR,
39+
40+
.fops = &hv_fops,
41+
DRM_GEM_SHMEM_DRIVER_OPS,
42+
};
43+
44+
static int hyperv_pci_probe(struct pci_dev *pdev,
45+
const struct pci_device_id *ent)
46+
{
47+
return 0;
48+
}
49+
50+
static void hyperv_pci_remove(struct pci_dev *pdev)
51+
{
52+
}
53+
54+
static const struct pci_device_id hyperv_pci_tbl[] = {
55+
{
56+
.vendor = PCI_VENDOR_ID_MICROSOFT,
57+
.device = PCI_DEVICE_ID_HYPERV_VIDEO,
58+
},
59+
{ /* end of list */ }
60+
};
61+
62+
/*
63+
* PCI stub to support gen1 VM.
64+
*/
65+
static struct pci_driver hyperv_pci_driver = {
66+
.name = KBUILD_MODNAME,
67+
.id_table = hyperv_pci_tbl,
68+
.probe = hyperv_pci_probe,
69+
.remove = hyperv_pci_remove,
70+
};
71+
72+
static int hyperv_setup_gen1(struct hyperv_drm_device *hv)
73+
{
74+
struct drm_device *dev = &hv->dev;
75+
struct pci_dev *pdev;
76+
int ret;
77+
78+
pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT,
79+
PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
80+
if (!pdev) {
81+
drm_err(dev, "Unable to find PCI Hyper-V video\n");
82+
return -ENODEV;
83+
}
84+
85+
ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "hypervdrmfb");
86+
if (ret) {
87+
drm_err(dev, "Not able to remove boot fb\n");
88+
return ret;
89+
}
90+
91+
if (pci_request_region(pdev, 0, DRIVER_NAME) != 0)
92+
drm_warn(dev, "Cannot request framebuffer, boot fb still active?\n");
93+
94+
if ((pdev->resource[0].flags & IORESOURCE_MEM) == 0) {
95+
drm_err(dev, "Resource at bar 0 is not IORESOURCE_MEM\n");
96+
ret = -ENODEV;
97+
goto error;
98+
}
99+
100+
hv->fb_base = pci_resource_start(pdev, 0);
101+
hv->fb_size = pci_resource_len(pdev, 0);
102+
if (!hv->fb_base) {
103+
drm_err(dev, "Resource not available\n");
104+
ret = -ENODEV;
105+
goto error;
106+
}
107+
108+
hv->fb_size = min(hv->fb_size,
109+
(unsigned long)(hv->mmio_megabytes * 1024 * 1024));
110+
hv->vram = devm_ioremap(&pdev->dev, hv->fb_base, hv->fb_size);
111+
if (!hv->vram) {
112+
drm_err(dev, "Failed to map vram\n");
113+
ret = -ENOMEM;
114+
}
115+
116+
error:
117+
pci_dev_put(pdev);
118+
return ret;
119+
}
120+
121+
static int hyperv_setup_gen2(struct hyperv_drm_device *hv,
122+
struct hv_device *hdev)
123+
{
124+
struct drm_device *dev = &hv->dev;
125+
int ret;
126+
127+
drm_aperture_remove_conflicting_framebuffers(screen_info.lfb_base,
128+
screen_info.lfb_size,
129+
false,
130+
"hypervdrmfb");
131+
132+
hv->fb_size = (unsigned long)hv->mmio_megabytes * 1024 * 1024;
133+
134+
ret = vmbus_allocate_mmio(&hv->mem, hdev, 0, -1, hv->fb_size, 0x100000,
135+
true);
136+
if (ret) {
137+
drm_err(dev, "Failed to allocate mmio\n");
138+
return -ENOMEM;
139+
}
140+
141+
/*
142+
* Map the VRAM cacheable for performance. This is also required for VM
143+
* connect to display properly for ARM64 Linux VM, as the host also maps
144+
* the VRAM cacheable.
145+
*/
146+
hv->vram = ioremap_cache(hv->mem->start, hv->fb_size);
147+
if (!hv->vram) {
148+
drm_err(dev, "Failed to map vram\n");
149+
ret = -ENOMEM;
150+
goto error;
151+
}
152+
153+
hv->fb_base = hv->mem->start;
154+
return 0;
155+
156+
error:
157+
vmbus_free_mmio(hv->mem->start, hv->fb_size);
158+
return ret;
159+
}
160+
161+
static int hyperv_vmbus_probe(struct hv_device *hdev,
162+
const struct hv_vmbus_device_id *dev_id)
163+
{
164+
struct hyperv_drm_device *hv;
165+
struct drm_device *dev;
166+
int ret;
167+
168+
hv = devm_drm_dev_alloc(&hdev->device, &hyperv_driver,
169+
struct hyperv_drm_device, dev);
170+
if (IS_ERR(hv))
171+
return PTR_ERR(hv);
172+
173+
dev = &hv->dev;
174+
init_completion(&hv->wait);
175+
hv_set_drvdata(hdev, hv);
176+
hv->hdev = hdev;
177+
178+
ret = hyperv_connect_vsp(hdev);
179+
if (ret) {
180+
drm_err(dev, "Failed to connect to vmbus.\n");
181+
goto err_hv_set_drv_data;
182+
}
183+
184+
if (efi_enabled(EFI_BOOT))
185+
ret = hyperv_setup_gen2(hv, hdev);
186+
else
187+
ret = hyperv_setup_gen1(hv);
188+
189+
if (ret)
190+
goto err_vmbus_close;
191+
192+
/*
193+
* Should be done only once during init and resume. Failing to update
194+
* vram location is not fatal. Device will update dirty area till
195+
* preferred resolution only.
196+
*/
197+
ret = hyperv_update_vram_location(hdev, hv->fb_base);
198+
if (ret)
199+
drm_warn(dev, "Failed to update vram location.\n");
200+
201+
ret = hyperv_mode_config_init(hv);
202+
if (ret)
203+
goto err_vmbus_close;
204+
205+
ret = drm_dev_register(dev, 0);
206+
if (ret) {
207+
drm_err(dev, "Failed to register drm driver.\n");
208+
goto err_vmbus_close;
209+
}
210+
211+
drm_fbdev_generic_setup(dev, 0);
212+
213+
return 0;
214+
215+
err_vmbus_close:
216+
vmbus_close(hdev->channel);
217+
err_hv_set_drv_data:
218+
hv_set_drvdata(hdev, NULL);
219+
return ret;
220+
}
221+
222+
static int hyperv_vmbus_remove(struct hv_device *hdev)
223+
{
224+
struct drm_device *dev = hv_get_drvdata(hdev);
225+
struct hyperv_drm_device *hv = to_hv(dev);
226+
227+
drm_dev_unplug(dev);
228+
drm_atomic_helper_shutdown(dev);
229+
vmbus_close(hdev->channel);
230+
hv_set_drvdata(hdev, NULL);
231+
vmbus_free_mmio(hv->mem->start, hv->fb_size);
232+
233+
return 0;
234+
}
235+
236+
static int hyperv_vmbus_suspend(struct hv_device *hdev)
237+
{
238+
struct drm_device *dev = hv_get_drvdata(hdev);
239+
int ret;
240+
241+
ret = drm_mode_config_helper_suspend(dev);
242+
if (ret)
243+
return ret;
244+
245+
vmbus_close(hdev->channel);
246+
247+
return 0;
248+
}
249+
250+
static int hyperv_vmbus_resume(struct hv_device *hdev)
251+
{
252+
struct drm_device *dev = hv_get_drvdata(hdev);
253+
struct hyperv_drm_device *hv = to_hv(dev);
254+
int ret;
255+
256+
ret = hyperv_connect_vsp(hdev);
257+
if (ret)
258+
return ret;
259+
260+
ret = hyperv_update_vram_location(hdev, hv->fb_base);
261+
if (ret)
262+
return ret;
263+
264+
return drm_mode_config_helper_resume(dev);
265+
}
266+
267+
static const struct hv_vmbus_device_id hyperv_vmbus_tbl[] = {
268+
/* Synthetic Video Device GUID */
269+
{HV_SYNTHVID_GUID},
270+
{}
271+
};
272+
273+
static struct hv_driver hyperv_hv_driver = {
274+
.name = KBUILD_MODNAME,
275+
.id_table = hyperv_vmbus_tbl,
276+
.probe = hyperv_vmbus_probe,
277+
.remove = hyperv_vmbus_remove,
278+
.suspend = hyperv_vmbus_suspend,
279+
.resume = hyperv_vmbus_resume,
280+
.driver = {
281+
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
282+
},
283+
};
284+
285+
static int __init hyperv_init(void)
286+
{
287+
int ret;
288+
289+
ret = pci_register_driver(&hyperv_pci_driver);
290+
if (ret != 0)
291+
return ret;
292+
293+
return vmbus_driver_register(&hyperv_hv_driver);
294+
}
295+
296+
static void __exit hyperv_exit(void)
297+
{
298+
vmbus_driver_unregister(&hyperv_hv_driver);
299+
pci_unregister_driver(&hyperv_pci_driver);
300+
}
301+
302+
module_init(hyperv_init);
303+
module_exit(hyperv_exit);
304+
305+
MODULE_DEVICE_TABLE(pci, hyperv_pci_tbl);
306+
MODULE_DEVICE_TABLE(vmbus, hyperv_vmbus_tbl);
307+
MODULE_LICENSE("GPL");
308+
MODULE_AUTHOR("Deepak Rawat <[email protected]>");
309+
MODULE_DESCRIPTION("DRM driver for Hyper-V synthetic video device");

0 commit comments

Comments
 (0)