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