b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 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 | #include <drm/drm_irq.h> |
| 14 | #include <drm/drm_probe_helper.h> |
| 15 | |
| 16 | #include "vbox_drv.h" |
| 17 | #include "vboxvideo.h" |
| 18 | |
| 19 | static void vbox_clear_irq(void) |
| 20 | { |
| 21 | outl((u32)~0, VGA_PORT_HGSMI_HOST); |
| 22 | } |
| 23 | |
| 24 | static u32 vbox_get_flags(struct vbox_private *vbox) |
| 25 | { |
| 26 | return readl(vbox->guest_heap + HOST_FLAGS_OFFSET); |
| 27 | } |
| 28 | |
| 29 | void vbox_report_hotplug(struct vbox_private *vbox) |
| 30 | { |
| 31 | schedule_work(&vbox->hotplug_work); |
| 32 | } |
| 33 | |
| 34 | irqreturn_t vbox_irq_handler(int irq, void *arg) |
| 35 | { |
| 36 | struct drm_device *dev = (struct drm_device *)arg; |
| 37 | struct vbox_private *vbox = (struct vbox_private *)dev->dev_private; |
| 38 | u32 host_flags = vbox_get_flags(vbox); |
| 39 | |
| 40 | if (!(host_flags & HGSMIHOSTFLAGS_IRQ)) |
| 41 | return IRQ_NONE; |
| 42 | |
| 43 | /* |
| 44 | * Due to a bug in the initial host implementation of hot-plug irqs, |
| 45 | * the hot-plug and cursor capability flags were never cleared. |
| 46 | * Fortunately we can tell when they would have been set by checking |
| 47 | * that the VSYNC flag is not set. |
| 48 | */ |
| 49 | if (host_flags & |
| 50 | (HGSMIHOSTFLAGS_HOTPLUG | HGSMIHOSTFLAGS_CURSOR_CAPABILITIES) && |
| 51 | !(host_flags & HGSMIHOSTFLAGS_VSYNC)) |
| 52 | vbox_report_hotplug(vbox); |
| 53 | |
| 54 | vbox_clear_irq(); |
| 55 | |
| 56 | return IRQ_HANDLED; |
| 57 | } |
| 58 | |
| 59 | /* |
| 60 | * Check that the position hints provided by the host are suitable for GNOME |
| 61 | * shell (i.e. all screens disjoint and hints for all enabled screens) and if |
| 62 | * not replace them with default ones. Providing valid hints improves the |
| 63 | * chances that we will get a known screen layout for pointer mapping. |
| 64 | */ |
| 65 | static void validate_or_set_position_hints(struct vbox_private *vbox) |
| 66 | { |
| 67 | struct vbva_modehint *hintsi, *hintsj; |
| 68 | bool valid = true; |
| 69 | u16 currentx = 0; |
| 70 | int i, j; |
| 71 | |
| 72 | for (i = 0; i < vbox->num_crtcs; ++i) { |
| 73 | for (j = 0; j < i; ++j) { |
| 74 | hintsi = &vbox->last_mode_hints[i]; |
| 75 | hintsj = &vbox->last_mode_hints[j]; |
| 76 | |
| 77 | if (hintsi->enabled && hintsj->enabled) { |
| 78 | if (hintsi->dx >= 0xffff || |
| 79 | hintsi->dy >= 0xffff || |
| 80 | hintsj->dx >= 0xffff || |
| 81 | hintsj->dy >= 0xffff || |
| 82 | (hintsi->dx < |
| 83 | hintsj->dx + (hintsj->cx & 0x8fff) && |
| 84 | hintsi->dx + (hintsi->cx & 0x8fff) > |
| 85 | hintsj->dx) || |
| 86 | (hintsi->dy < |
| 87 | hintsj->dy + (hintsj->cy & 0x8fff) && |
| 88 | hintsi->dy + (hintsi->cy & 0x8fff) > |
| 89 | hintsj->dy)) |
| 90 | valid = false; |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | if (!valid) |
| 95 | for (i = 0; i < vbox->num_crtcs; ++i) { |
| 96 | if (vbox->last_mode_hints[i].enabled) { |
| 97 | vbox->last_mode_hints[i].dx = currentx; |
| 98 | vbox->last_mode_hints[i].dy = 0; |
| 99 | currentx += |
| 100 | vbox->last_mode_hints[i].cx & 0x8fff; |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | /* Query the host for the most recent video mode hints. */ |
| 106 | static void vbox_update_mode_hints(struct vbox_private *vbox) |
| 107 | { |
| 108 | struct drm_connector_list_iter conn_iter; |
| 109 | struct drm_device *dev = &vbox->ddev; |
| 110 | struct drm_connector *connector; |
| 111 | struct vbox_connector *vbox_conn; |
| 112 | struct vbva_modehint *hints; |
| 113 | u16 flags; |
| 114 | bool disconnected; |
| 115 | unsigned int crtc_id; |
| 116 | int ret; |
| 117 | |
| 118 | ret = hgsmi_get_mode_hints(vbox->guest_pool, vbox->num_crtcs, |
| 119 | vbox->last_mode_hints); |
| 120 | if (ret) { |
| 121 | DRM_ERROR("vboxvideo: hgsmi_get_mode_hints failed: %d\n", ret); |
| 122 | return; |
| 123 | } |
| 124 | |
| 125 | validate_or_set_position_hints(vbox); |
| 126 | |
| 127 | drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); |
| 128 | drm_connector_list_iter_begin(dev, &conn_iter); |
| 129 | drm_for_each_connector_iter(connector, &conn_iter) { |
| 130 | vbox_conn = to_vbox_connector(connector); |
| 131 | |
| 132 | hints = &vbox->last_mode_hints[vbox_conn->vbox_crtc->crtc_id]; |
| 133 | if (hints->magic != VBVAMODEHINT_MAGIC) |
| 134 | continue; |
| 135 | |
| 136 | disconnected = !(hints->enabled); |
| 137 | crtc_id = vbox_conn->vbox_crtc->crtc_id; |
| 138 | vbox_conn->mode_hint.width = hints->cx; |
| 139 | vbox_conn->mode_hint.height = hints->cy; |
| 140 | vbox_conn->vbox_crtc->x_hint = hints->dx; |
| 141 | vbox_conn->vbox_crtc->y_hint = hints->dy; |
| 142 | vbox_conn->mode_hint.disconnected = disconnected; |
| 143 | |
| 144 | if (vbox_conn->vbox_crtc->disconnected == disconnected) |
| 145 | continue; |
| 146 | |
| 147 | if (disconnected) |
| 148 | flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_DISABLED; |
| 149 | else |
| 150 | flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_BLANK; |
| 151 | |
| 152 | hgsmi_process_display_info(vbox->guest_pool, crtc_id, 0, 0, 0, |
| 153 | hints->cx * 4, hints->cx, |
| 154 | hints->cy, 0, flags); |
| 155 | |
| 156 | vbox_conn->vbox_crtc->disconnected = disconnected; |
| 157 | } |
| 158 | drm_connector_list_iter_end(&conn_iter); |
| 159 | drm_modeset_unlock(&dev->mode_config.connection_mutex); |
| 160 | } |
| 161 | |
| 162 | static void vbox_hotplug_worker(struct work_struct *work) |
| 163 | { |
| 164 | struct vbox_private *vbox = container_of(work, struct vbox_private, |
| 165 | hotplug_work); |
| 166 | |
| 167 | vbox_update_mode_hints(vbox); |
| 168 | drm_kms_helper_hotplug_event(&vbox->ddev); |
| 169 | } |
| 170 | |
| 171 | int vbox_irq_init(struct vbox_private *vbox) |
| 172 | { |
| 173 | INIT_WORK(&vbox->hotplug_work, vbox_hotplug_worker); |
| 174 | vbox_update_mode_hints(vbox); |
| 175 | |
| 176 | return drm_irq_install(&vbox->ddev, vbox->ddev.pdev->irq); |
| 177 | } |
| 178 | |
| 179 | void vbox_irq_fini(struct vbox_private *vbox) |
| 180 | { |
| 181 | drm_irq_uninstall(&vbox->ddev); |
| 182 | flush_work(&vbox->hotplug_work); |
| 183 | } |