162306a36Sopenharmony_ci// SPDX-License-Identifier: MIT 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2016-2017 Oracle Corporation 462306a36Sopenharmony_ci * This file is based on qxl_irq.c 562306a36Sopenharmony_ci * Copyright 2013 Red Hat Inc. 662306a36Sopenharmony_ci * Authors: Dave Airlie 762306a36Sopenharmony_ci * Alon Levy 862306a36Sopenharmony_ci * Michael Thayer <michael.thayer@oracle.com, 962306a36Sopenharmony_ci * Hans de Goede <hdegoede@redhat.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/pci.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <drm/drm_drv.h> 1562306a36Sopenharmony_ci#include <drm/drm_probe_helper.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "vbox_drv.h" 1862306a36Sopenharmony_ci#include "vboxvideo.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic void vbox_clear_irq(void) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci outl((u32)~0, VGA_PORT_HGSMI_HOST); 2362306a36Sopenharmony_ci} 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic u32 vbox_get_flags(struct vbox_private *vbox) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci return readl(vbox->guest_heap + HOST_FLAGS_OFFSET); 2862306a36Sopenharmony_ci} 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_civoid vbox_report_hotplug(struct vbox_private *vbox) 3162306a36Sopenharmony_ci{ 3262306a36Sopenharmony_ci schedule_work(&vbox->hotplug_work); 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic irqreturn_t vbox_irq_handler(int irq, void *arg) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci struct drm_device *dev = (struct drm_device *)arg; 3862306a36Sopenharmony_ci struct vbox_private *vbox = to_vbox_dev(dev); 3962306a36Sopenharmony_ci u32 host_flags = vbox_get_flags(vbox); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci if (!(host_flags & HGSMIHOSTFLAGS_IRQ)) 4262306a36Sopenharmony_ci return IRQ_NONE; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci /* 4562306a36Sopenharmony_ci * Due to a bug in the initial host implementation of hot-plug irqs, 4662306a36Sopenharmony_ci * the hot-plug and cursor capability flags were never cleared. 4762306a36Sopenharmony_ci * Fortunately we can tell when they would have been set by checking 4862306a36Sopenharmony_ci * that the VSYNC flag is not set. 4962306a36Sopenharmony_ci */ 5062306a36Sopenharmony_ci if (host_flags & 5162306a36Sopenharmony_ci (HGSMIHOSTFLAGS_HOTPLUG | HGSMIHOSTFLAGS_CURSOR_CAPABILITIES) && 5262306a36Sopenharmony_ci !(host_flags & HGSMIHOSTFLAGS_VSYNC)) 5362306a36Sopenharmony_ci vbox_report_hotplug(vbox); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci vbox_clear_irq(); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci return IRQ_HANDLED; 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci/* 6162306a36Sopenharmony_ci * Check that the position hints provided by the host are suitable for GNOME 6262306a36Sopenharmony_ci * shell (i.e. all screens disjoint and hints for all enabled screens) and if 6362306a36Sopenharmony_ci * not replace them with default ones. Providing valid hints improves the 6462306a36Sopenharmony_ci * chances that we will get a known screen layout for pointer mapping. 6562306a36Sopenharmony_ci */ 6662306a36Sopenharmony_cistatic void validate_or_set_position_hints(struct vbox_private *vbox) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci struct vbva_modehint *hintsi, *hintsj; 6962306a36Sopenharmony_ci bool valid = true; 7062306a36Sopenharmony_ci u16 currentx = 0; 7162306a36Sopenharmony_ci int i, j; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci for (i = 0; i < vbox->num_crtcs; ++i) { 7462306a36Sopenharmony_ci for (j = 0; j < i; ++j) { 7562306a36Sopenharmony_ci hintsi = &vbox->last_mode_hints[i]; 7662306a36Sopenharmony_ci hintsj = &vbox->last_mode_hints[j]; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci if (hintsi->enabled && hintsj->enabled) { 7962306a36Sopenharmony_ci if (hintsi->dx >= 0xffff || 8062306a36Sopenharmony_ci hintsi->dy >= 0xffff || 8162306a36Sopenharmony_ci hintsj->dx >= 0xffff || 8262306a36Sopenharmony_ci hintsj->dy >= 0xffff || 8362306a36Sopenharmony_ci (hintsi->dx < 8462306a36Sopenharmony_ci hintsj->dx + (hintsj->cx & 0x8fff) && 8562306a36Sopenharmony_ci hintsi->dx + (hintsi->cx & 0x8fff) > 8662306a36Sopenharmony_ci hintsj->dx) || 8762306a36Sopenharmony_ci (hintsi->dy < 8862306a36Sopenharmony_ci hintsj->dy + (hintsj->cy & 0x8fff) && 8962306a36Sopenharmony_ci hintsi->dy + (hintsi->cy & 0x8fff) > 9062306a36Sopenharmony_ci hintsj->dy)) 9162306a36Sopenharmony_ci valid = false; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci if (!valid) 9662306a36Sopenharmony_ci for (i = 0; i < vbox->num_crtcs; ++i) { 9762306a36Sopenharmony_ci if (vbox->last_mode_hints[i].enabled) { 9862306a36Sopenharmony_ci vbox->last_mode_hints[i].dx = currentx; 9962306a36Sopenharmony_ci vbox->last_mode_hints[i].dy = 0; 10062306a36Sopenharmony_ci currentx += 10162306a36Sopenharmony_ci vbox->last_mode_hints[i].cx & 0x8fff; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci/* Query the host for the most recent video mode hints. */ 10762306a36Sopenharmony_cistatic void vbox_update_mode_hints(struct vbox_private *vbox) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct drm_connector_list_iter conn_iter; 11062306a36Sopenharmony_ci struct drm_device *dev = &vbox->ddev; 11162306a36Sopenharmony_ci struct drm_connector *connector; 11262306a36Sopenharmony_ci struct vbox_connector *vbox_conn; 11362306a36Sopenharmony_ci struct vbva_modehint *hints; 11462306a36Sopenharmony_ci u16 flags; 11562306a36Sopenharmony_ci bool disconnected; 11662306a36Sopenharmony_ci unsigned int crtc_id; 11762306a36Sopenharmony_ci int ret; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci ret = hgsmi_get_mode_hints(vbox->guest_pool, vbox->num_crtcs, 12062306a36Sopenharmony_ci vbox->last_mode_hints); 12162306a36Sopenharmony_ci if (ret) { 12262306a36Sopenharmony_ci DRM_ERROR("vboxvideo: hgsmi_get_mode_hints failed: %d\n", ret); 12362306a36Sopenharmony_ci return; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci validate_or_set_position_hints(vbox); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); 12962306a36Sopenharmony_ci drm_connector_list_iter_begin(dev, &conn_iter); 13062306a36Sopenharmony_ci drm_for_each_connector_iter(connector, &conn_iter) { 13162306a36Sopenharmony_ci vbox_conn = to_vbox_connector(connector); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci hints = &vbox->last_mode_hints[vbox_conn->vbox_crtc->crtc_id]; 13462306a36Sopenharmony_ci if (hints->magic != VBVAMODEHINT_MAGIC) 13562306a36Sopenharmony_ci continue; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci disconnected = !(hints->enabled); 13862306a36Sopenharmony_ci crtc_id = vbox_conn->vbox_crtc->crtc_id; 13962306a36Sopenharmony_ci vbox_conn->mode_hint.width = hints->cx; 14062306a36Sopenharmony_ci vbox_conn->mode_hint.height = hints->cy; 14162306a36Sopenharmony_ci vbox_conn->vbox_crtc->x_hint = hints->dx; 14262306a36Sopenharmony_ci vbox_conn->vbox_crtc->y_hint = hints->dy; 14362306a36Sopenharmony_ci vbox_conn->mode_hint.disconnected = disconnected; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci if (vbox_conn->vbox_crtc->disconnected == disconnected) 14662306a36Sopenharmony_ci continue; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci if (disconnected) 14962306a36Sopenharmony_ci flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_DISABLED; 15062306a36Sopenharmony_ci else 15162306a36Sopenharmony_ci flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_BLANK; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci hgsmi_process_display_info(vbox->guest_pool, crtc_id, 0, 0, 0, 15462306a36Sopenharmony_ci hints->cx * 4, hints->cx, 15562306a36Sopenharmony_ci hints->cy, 0, flags); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci vbox_conn->vbox_crtc->disconnected = disconnected; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci drm_connector_list_iter_end(&conn_iter); 16062306a36Sopenharmony_ci drm_modeset_unlock(&dev->mode_config.connection_mutex); 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic void vbox_hotplug_worker(struct work_struct *work) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct vbox_private *vbox = container_of(work, struct vbox_private, 16662306a36Sopenharmony_ci hotplug_work); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci vbox_update_mode_hints(vbox); 16962306a36Sopenharmony_ci drm_kms_helper_hotplug_event(&vbox->ddev); 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ciint vbox_irq_init(struct vbox_private *vbox) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci struct drm_device *dev = &vbox->ddev; 17562306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(dev->dev); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci INIT_WORK(&vbox->hotplug_work, vbox_hotplug_worker); 17862306a36Sopenharmony_ci vbox_update_mode_hints(vbox); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci /* PCI devices require shared interrupts. */ 18162306a36Sopenharmony_ci return request_irq(pdev->irq, vbox_irq_handler, IRQF_SHARED, dev->driver->name, dev); 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_civoid vbox_irq_fini(struct vbox_private *vbox) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct drm_device *dev = &vbox->ddev; 18762306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(dev->dev); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci free_irq(pdev->irq, dev); 19062306a36Sopenharmony_ci flush_work(&vbox->hotplug_work); 19162306a36Sopenharmony_ci} 192