1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * Copyright (C) 2016-2017 Oracle Corporation |
4 | * This file is based on qxl_irq.c |
5 | * Copyright 2013 Red Hat Inc. |
6 | * Authors: Dave Airlie |
7 | * Alon Levy |
8 | * Michael Thayer <michael.thayer@oracle.com, |
9 | * Hans de Goede <hdegoede@redhat.com> |
10 | */ |
11 | |
12 | #include <linux/pci.h> |
13 | |
14 | #include <drm/drm_drv.h> |
15 | #include <drm/drm_probe_helper.h> |
16 | |
17 | #include "vbox_drv.h" |
18 | #include "vboxvideo.h" |
19 | |
20 | static void vbox_clear_irq(void) |
21 | { |
22 | outl(value: (u32)~0, VGA_PORT_HGSMI_HOST); |
23 | } |
24 | |
25 | static u32 vbox_get_flags(struct vbox_private *vbox) |
26 | { |
27 | return readl(addr: vbox->guest_heap + HOST_FLAGS_OFFSET); |
28 | } |
29 | |
30 | void vbox_report_hotplug(struct vbox_private *vbox) |
31 | { |
32 | schedule_work(work: &vbox->hotplug_work); |
33 | } |
34 | |
35 | static irqreturn_t vbox_irq_handler(int irq, void *arg) |
36 | { |
37 | struct drm_device *dev = (struct drm_device *)arg; |
38 | struct vbox_private *vbox = to_vbox_dev(dev); |
39 | u32 host_flags = vbox_get_flags(vbox); |
40 | |
41 | if (!(host_flags & HGSMIHOSTFLAGS_IRQ)) |
42 | return IRQ_NONE; |
43 | |
44 | /* |
45 | * Due to a bug in the initial host implementation of hot-plug irqs, |
46 | * the hot-plug and cursor capability flags were never cleared. |
47 | * Fortunately we can tell when they would have been set by checking |
48 | * that the VSYNC flag is not set. |
49 | */ |
50 | if (host_flags & |
51 | (HGSMIHOSTFLAGS_HOTPLUG | HGSMIHOSTFLAGS_CURSOR_CAPABILITIES) && |
52 | !(host_flags & HGSMIHOSTFLAGS_VSYNC)) |
53 | vbox_report_hotplug(vbox); |
54 | |
55 | vbox_clear_irq(); |
56 | |
57 | return IRQ_HANDLED; |
58 | } |
59 | |
60 | /* |
61 | * Check that the position hints provided by the host are suitable for GNOME |
62 | * shell (i.e. all screens disjoint and hints for all enabled screens) and if |
63 | * not replace them with default ones. Providing valid hints improves the |
64 | * chances that we will get a known screen layout for pointer mapping. |
65 | */ |
66 | static void validate_or_set_position_hints(struct vbox_private *vbox) |
67 | { |
68 | struct vbva_modehint *hintsi, *hintsj; |
69 | bool valid = true; |
70 | u16 currentx = 0; |
71 | int i, j; |
72 | |
73 | for (i = 0; i < vbox->num_crtcs; ++i) { |
74 | for (j = 0; j < i; ++j) { |
75 | hintsi = &vbox->last_mode_hints[i]; |
76 | hintsj = &vbox->last_mode_hints[j]; |
77 | |
78 | if (hintsi->enabled && hintsj->enabled) { |
79 | if (hintsi->dx >= 0xffff || |
80 | hintsi->dy >= 0xffff || |
81 | hintsj->dx >= 0xffff || |
82 | hintsj->dy >= 0xffff || |
83 | (hintsi->dx < |
84 | hintsj->dx + (hintsj->cx & 0x8fff) && |
85 | hintsi->dx + (hintsi->cx & 0x8fff) > |
86 | hintsj->dx) || |
87 | (hintsi->dy < |
88 | hintsj->dy + (hintsj->cy & 0x8fff) && |
89 | hintsi->dy + (hintsi->cy & 0x8fff) > |
90 | hintsj->dy)) |
91 | valid = false; |
92 | } |
93 | } |
94 | } |
95 | if (!valid) |
96 | for (i = 0; i < vbox->num_crtcs; ++i) { |
97 | if (vbox->last_mode_hints[i].enabled) { |
98 | vbox->last_mode_hints[i].dx = currentx; |
99 | vbox->last_mode_hints[i].dy = 0; |
100 | currentx += |
101 | vbox->last_mode_hints[i].cx & 0x8fff; |
102 | } |
103 | } |
104 | } |
105 | |
106 | /* Query the host for the most recent video mode hints. */ |
107 | static void vbox_update_mode_hints(struct vbox_private *vbox) |
108 | { |
109 | struct drm_connector_list_iter conn_iter; |
110 | struct drm_device *dev = &vbox->ddev; |
111 | struct drm_connector *connector; |
112 | struct vbox_connector *vbox_conn; |
113 | struct vbva_modehint *hints; |
114 | u16 flags; |
115 | bool disconnected; |
116 | unsigned int crtc_id; |
117 | int ret; |
118 | |
119 | ret = hgsmi_get_mode_hints(ctx: vbox->guest_pool, screens: vbox->num_crtcs, |
120 | hints: vbox->last_mode_hints); |
121 | if (ret) { |
122 | DRM_ERROR("vboxvideo: hgsmi_get_mode_hints failed: %d\n" , ret); |
123 | return; |
124 | } |
125 | |
126 | validate_or_set_position_hints(vbox); |
127 | |
128 | drm_modeset_lock(lock: &dev->mode_config.connection_mutex, NULL); |
129 | drm_connector_list_iter_begin(dev, iter: &conn_iter); |
130 | drm_for_each_connector_iter(connector, &conn_iter) { |
131 | vbox_conn = to_vbox_connector(connector); |
132 | |
133 | hints = &vbox->last_mode_hints[vbox_conn->vbox_crtc->crtc_id]; |
134 | if (hints->magic != VBVAMODEHINT_MAGIC) |
135 | continue; |
136 | |
137 | disconnected = !(hints->enabled); |
138 | crtc_id = vbox_conn->vbox_crtc->crtc_id; |
139 | vbox_conn->mode_hint.width = hints->cx; |
140 | vbox_conn->mode_hint.height = hints->cy; |
141 | vbox_conn->vbox_crtc->x_hint = hints->dx; |
142 | vbox_conn->vbox_crtc->y_hint = hints->dy; |
143 | vbox_conn->mode_hint.disconnected = disconnected; |
144 | |
145 | if (vbox_conn->vbox_crtc->disconnected == disconnected) |
146 | continue; |
147 | |
148 | if (disconnected) |
149 | flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_DISABLED; |
150 | else |
151 | flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_BLANK; |
152 | |
153 | hgsmi_process_display_info(ctx: vbox->guest_pool, display: crtc_id, origin_x: 0, origin_y: 0, start_offset: 0, |
154 | pitch: hints->cx * 4, width: hints->cx, |
155 | height: hints->cy, bpp: 0, flags); |
156 | |
157 | vbox_conn->vbox_crtc->disconnected = disconnected; |
158 | } |
159 | drm_connector_list_iter_end(iter: &conn_iter); |
160 | drm_modeset_unlock(lock: &dev->mode_config.connection_mutex); |
161 | } |
162 | |
163 | static void vbox_hotplug_worker(struct work_struct *work) |
164 | { |
165 | struct vbox_private *vbox = container_of(work, struct vbox_private, |
166 | hotplug_work); |
167 | |
168 | vbox_update_mode_hints(vbox); |
169 | drm_kms_helper_hotplug_event(dev: &vbox->ddev); |
170 | } |
171 | |
172 | int vbox_irq_init(struct vbox_private *vbox) |
173 | { |
174 | struct drm_device *dev = &vbox->ddev; |
175 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
176 | |
177 | INIT_WORK(&vbox->hotplug_work, vbox_hotplug_worker); |
178 | vbox_update_mode_hints(vbox); |
179 | |
180 | /* PCI devices require shared interrupts. */ |
181 | return request_irq(irq: pdev->irq, handler: vbox_irq_handler, IRQF_SHARED, name: dev->driver->name, dev); |
182 | } |
183 | |
184 | void vbox_irq_fini(struct vbox_private *vbox) |
185 | { |
186 | struct drm_device *dev = &vbox->ddev; |
187 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
188 | |
189 | free_irq(pdev->irq, dev); |
190 | flush_work(work: &vbox->hotplug_work); |
191 | } |
192 | |