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_fbdev_generic.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_DRM_GEM_FOPS(hv_fops); |
27 | |
28 | static struct drm_driver hyperv_driver = { |
29 | .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, |
30 | |
31 | .name = DRIVER_NAME, |
32 | .desc = DRIVER_DESC, |
33 | .date = DRIVER_DATE, |
34 | .major = DRIVER_MAJOR, |
35 | .minor = DRIVER_MINOR, |
36 | |
37 | .fops = &hv_fops, |
38 | DRM_GEM_SHMEM_DRIVER_OPS, |
39 | }; |
40 | |
41 | static int hyperv_pci_probe(struct pci_dev *pdev, |
42 | const struct pci_device_id *ent) |
43 | { |
44 | return 0; |
45 | } |
46 | |
47 | static void hyperv_pci_remove(struct pci_dev *pdev) |
48 | { |
49 | } |
50 | |
51 | static const struct pci_device_id hyperv_pci_tbl[] = { |
52 | { |
53 | .vendor = PCI_VENDOR_ID_MICROSOFT, |
54 | .device = PCI_DEVICE_ID_HYPERV_VIDEO, |
55 | }, |
56 | { /* end of list */ } |
57 | }; |
58 | |
59 | /* |
60 | * PCI stub to support gen1 VM. |
61 | */ |
62 | static struct pci_driver hyperv_pci_driver = { |
63 | .name = KBUILD_MODNAME, |
64 | .id_table = hyperv_pci_tbl, |
65 | .probe = hyperv_pci_probe, |
66 | .remove = hyperv_pci_remove, |
67 | }; |
68 | |
69 | static int hyperv_setup_vram(struct hyperv_drm_device *hv, |
70 | struct hv_device *hdev) |
71 | { |
72 | struct drm_device *dev = &hv->dev; |
73 | int ret; |
74 | |
75 | hv->fb_size = (unsigned long)hv->mmio_megabytes * 1024 * 1024; |
76 | |
77 | ret = vmbus_allocate_mmio(new: &hv->mem, device_obj: hdev, min: 0, max: -1, size: hv->fb_size, align: 0x100000, |
78 | fb_overlap_ok: true); |
79 | if (ret) { |
80 | drm_err(dev, "Failed to allocate mmio\n" ); |
81 | return -ENOMEM; |
82 | } |
83 | |
84 | /* |
85 | * Map the VRAM cacheable for performance. This is also required for VM |
86 | * connect to display properly for ARM64 Linux VM, as the host also maps |
87 | * the VRAM cacheable. |
88 | */ |
89 | hv->vram = ioremap_cache(offset: hv->mem->start, size: hv->fb_size); |
90 | if (!hv->vram) { |
91 | drm_err(dev, "Failed to map vram\n" ); |
92 | ret = -ENOMEM; |
93 | goto error; |
94 | } |
95 | |
96 | hv->fb_base = hv->mem->start; |
97 | return 0; |
98 | |
99 | error: |
100 | vmbus_free_mmio(start: hv->mem->start, size: hv->fb_size); |
101 | return ret; |
102 | } |
103 | |
104 | static int hyperv_vmbus_probe(struct hv_device *hdev, |
105 | const struct hv_vmbus_device_id *dev_id) |
106 | { |
107 | struct hyperv_drm_device *hv; |
108 | struct drm_device *dev; |
109 | int ret; |
110 | |
111 | hv = devm_drm_dev_alloc(&hdev->device, &hyperv_driver, |
112 | struct hyperv_drm_device, dev); |
113 | if (IS_ERR(ptr: hv)) |
114 | return PTR_ERR(ptr: hv); |
115 | |
116 | dev = &hv->dev; |
117 | init_completion(x: &hv->wait); |
118 | hv_set_drvdata(dev: hdev, data: hv); |
119 | hv->hdev = hdev; |
120 | |
121 | ret = hyperv_connect_vsp(hdev); |
122 | if (ret) { |
123 | drm_err(dev, "Failed to connect to vmbus.\n" ); |
124 | goto err_hv_set_drv_data; |
125 | } |
126 | |
127 | drm_aperture_remove_framebuffers(req_driver: &hyperv_driver); |
128 | |
129 | ret = hyperv_setup_vram(hv, hdev); |
130 | if (ret) |
131 | goto err_vmbus_close; |
132 | |
133 | /* |
134 | * Should be done only once during init and resume. Failing to update |
135 | * vram location is not fatal. Device will update dirty area till |
136 | * preferred resolution only. |
137 | */ |
138 | ret = hyperv_update_vram_location(hdev, vram_pp: hv->fb_base); |
139 | if (ret) |
140 | drm_warn(dev, "Failed to update vram location.\n" ); |
141 | |
142 | ret = hyperv_mode_config_init(hv); |
143 | if (ret) |
144 | goto err_free_mmio; |
145 | |
146 | ret = drm_dev_register(dev, flags: 0); |
147 | if (ret) { |
148 | drm_err(dev, "Failed to register drm driver.\n" ); |
149 | goto err_free_mmio; |
150 | } |
151 | |
152 | drm_fbdev_generic_setup(dev, preferred_bpp: 0); |
153 | |
154 | return 0; |
155 | |
156 | err_free_mmio: |
157 | vmbus_free_mmio(start: hv->mem->start, size: hv->fb_size); |
158 | err_vmbus_close: |
159 | vmbus_close(channel: hdev->channel); |
160 | err_hv_set_drv_data: |
161 | hv_set_drvdata(dev: hdev, NULL); |
162 | return ret; |
163 | } |
164 | |
165 | static void hyperv_vmbus_remove(struct hv_device *hdev) |
166 | { |
167 | struct drm_device *dev = hv_get_drvdata(dev: hdev); |
168 | struct hyperv_drm_device *hv = to_hv(dev); |
169 | |
170 | drm_dev_unplug(dev); |
171 | drm_atomic_helper_shutdown(dev); |
172 | vmbus_close(channel: hdev->channel); |
173 | hv_set_drvdata(dev: hdev, NULL); |
174 | |
175 | vmbus_free_mmio(start: hv->mem->start, size: hv->fb_size); |
176 | } |
177 | |
178 | static void hyperv_vmbus_shutdown(struct hv_device *hdev) |
179 | { |
180 | drm_atomic_helper_shutdown(dev: hv_get_drvdata(dev: hdev)); |
181 | } |
182 | |
183 | static int hyperv_vmbus_suspend(struct hv_device *hdev) |
184 | { |
185 | struct drm_device *dev = hv_get_drvdata(dev: hdev); |
186 | int ret; |
187 | |
188 | ret = drm_mode_config_helper_suspend(dev); |
189 | if (ret) |
190 | return ret; |
191 | |
192 | vmbus_close(channel: hdev->channel); |
193 | |
194 | return 0; |
195 | } |
196 | |
197 | static int hyperv_vmbus_resume(struct hv_device *hdev) |
198 | { |
199 | struct drm_device *dev = hv_get_drvdata(dev: hdev); |
200 | struct hyperv_drm_device *hv = to_hv(dev); |
201 | int ret; |
202 | |
203 | ret = hyperv_connect_vsp(hdev); |
204 | if (ret) |
205 | return ret; |
206 | |
207 | ret = hyperv_update_vram_location(hdev, vram_pp: hv->fb_base); |
208 | if (ret) |
209 | return ret; |
210 | |
211 | return drm_mode_config_helper_resume(dev); |
212 | } |
213 | |
214 | static const struct hv_vmbus_device_id hyperv_vmbus_tbl[] = { |
215 | /* Synthetic Video Device GUID */ |
216 | {HV_SYNTHVID_GUID}, |
217 | {} |
218 | }; |
219 | |
220 | static struct hv_driver hyperv_hv_driver = { |
221 | .name = KBUILD_MODNAME, |
222 | .id_table = hyperv_vmbus_tbl, |
223 | .probe = hyperv_vmbus_probe, |
224 | .remove = hyperv_vmbus_remove, |
225 | .shutdown = hyperv_vmbus_shutdown, |
226 | .suspend = hyperv_vmbus_suspend, |
227 | .resume = hyperv_vmbus_resume, |
228 | .driver = { |
229 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
230 | }, |
231 | }; |
232 | |
233 | static int __init hyperv_init(void) |
234 | { |
235 | int ret; |
236 | |
237 | if (drm_firmware_drivers_only()) |
238 | return -ENODEV; |
239 | |
240 | ret = pci_register_driver(&hyperv_pci_driver); |
241 | if (ret != 0) |
242 | return ret; |
243 | |
244 | return vmbus_driver_register(&hyperv_hv_driver); |
245 | } |
246 | |
247 | static void __exit hyperv_exit(void) |
248 | { |
249 | vmbus_driver_unregister(hv_driver: &hyperv_hv_driver); |
250 | pci_unregister_driver(dev: &hyperv_pci_driver); |
251 | } |
252 | |
253 | module_init(hyperv_init); |
254 | module_exit(hyperv_exit); |
255 | |
256 | MODULE_DEVICE_TABLE(pci, hyperv_pci_tbl); |
257 | MODULE_DEVICE_TABLE(vmbus, hyperv_vmbus_tbl); |
258 | MODULE_LICENSE("GPL" ); |
259 | MODULE_AUTHOR("Deepak Rawat <drawat.floss@gmail.com>" ); |
260 | MODULE_DESCRIPTION("DRM driver for Hyper-V synthetic video device" ); |
261 | |