[PATCH for v4.9 LTS 066/111] drm/nouveau: Intercept ACPI_VIDEO_NOTIFY_PROBE

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Hans de Goede <hdegoede@xxxxxxxxxx>

[ Upstream commit 3a6536c51d5db3adf58dcd466a3aee6233b58544 ]

Various notebooks with nvidia GPUs generate an ACPI_VIDEO_NOTIFY_PROBE
acpi-video event when an external device gets plugged in (and again on
modesets on that connector), the default behavior in the acpi-video
driver for this is to send a KEY_SWITCHVIDEOMODE evdev event, which
causes e.g. gnome-settings-daemon to ask us to rescan the connectors
(good), but also causes g-s-d to switch to mirror mode on a newly plugged
monitor rather then using the monitor to extend the desktop (bad)
as KEY_SWITCHVIDEOMODE is supposed to switch between extend the desktop
vs mirror mode.

More troublesome are the repeated ACPI_VIDEO_NOTIFY_PROBE events on
changing the mode on the connector, which cause g-s-d to switch
between mirror/extend mode, which causes a new ACPI_VIDEO_NOTIFY_PROBE
event and we end up with an endless loop.

This commit fixes this by adding an acpi notifier block handler to
nouveau_display.c to intercept ACPI_VIDEO_NOTIFY_PROBE and:

1) Wake-up runtime suspended GPUs and call drm_helper_hpd_irq_event()
   on them, this is necessary in some cases for the GPU to detect connector
   hotplug events while runtime suspended
2) Return NOTIFY_BAD to stop acpi-video from emitting a bogus
   KEY_SWITCHVIDEOMODE key-press event

There already is another acpi notifier block handler registered in
drivers/gpu/drm/nouveau/nvkm/engine/device/acpi.c, but that is not
suitable since that one gets unregistered on runtime suspend, and
we also want to intercept ACPI_VIDEO_NOTIFY_PROBE when runtime suspended.

Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx>
Signed-off-by: Sasha Levin <alexander.levin@xxxxxxxxxxx>
---
 drivers/gpu/drm/nouveau/nouveau_display.c | 59 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/nouveau/nouveau_drv.h     |  6 ++++
 2 files changed, 65 insertions(+)

diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index afbf557b23d4..a0be029886d0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -24,6 +24,7 @@
  *
  */
 
+#include <acpi/video.h>
 #include <drm/drmP.h>
 #include <drm/drm_crtc_helper.h>
 
@@ -358,6 +359,55 @@ static struct nouveau_drm_prop_enum_list dither_depth[] = {
 	}                                                                      \
 } while(0)
 
+#ifdef CONFIG_ACPI
+
+/*
+ * Hans de Goede: This define belongs in acpi/video.h, I've submitted a patch
+ * to the acpi subsys to move it there from drivers/acpi/acpi_video.c .
+ * This should be dropped once that is merged.
+ */
+#ifndef ACPI_VIDEO_NOTIFY_PROBE
+#define ACPI_VIDEO_NOTIFY_PROBE			0x81
+#endif
+
+static void
+nouveau_display_acpi_work(struct work_struct *work)
+{
+	struct nouveau_drm *drm = container_of(work, typeof(*drm), acpi_work);
+
+	pm_runtime_get_sync(drm->dev->dev);
+
+	drm_helper_hpd_irq_event(drm->dev);
+
+	pm_runtime_mark_last_busy(drm->dev->dev);
+	pm_runtime_put_sync(drm->dev->dev);
+}
+
+static int
+nouveau_display_acpi_ntfy(struct notifier_block *nb, unsigned long val,
+			  void *data)
+{
+	struct nouveau_drm *drm = container_of(nb, typeof(*drm), acpi_nb);
+	struct acpi_bus_event *info = data;
+
+	if (!strcmp(info->device_class, ACPI_VIDEO_CLASS)) {
+		if (info->type == ACPI_VIDEO_NOTIFY_PROBE) {
+			/*
+			 * This may be the only indication we receive of a
+			 * connector hotplug on a runtime suspended GPU,
+			 * schedule acpi_work to check.
+			 */
+			schedule_work(&drm->acpi_work);
+
+			/* acpi-video should not generate keypresses for this */
+			return NOTIFY_BAD;
+		}
+	}
+
+	return NOTIFY_DONE;
+}
+#endif
+
 int
 nouveau_display_init(struct drm_device *dev)
 {
@@ -537,6 +587,12 @@ nouveau_display_create(struct drm_device *dev)
 	}
 
 	nouveau_backlight_init(dev);
+#ifdef CONFIG_ACPI
+	INIT_WORK(&drm->acpi_work, nouveau_display_acpi_work);
+	drm->acpi_nb.notifier_call = nouveau_display_acpi_ntfy;
+	register_acpi_notifier(&drm->acpi_nb);
+#endif
+
 	return 0;
 
 vblank_err:
@@ -552,6 +608,9 @@ nouveau_display_destroy(struct drm_device *dev)
 {
 	struct nouveau_display *disp = nouveau_display(dev);
 
+#ifdef CONFIG_ACPI
+	unregister_acpi_notifier(&nouveau_drm(dev)->acpi_nb);
+#endif
 	nouveau_backlight_exit(dev);
 	nouveau_display_vblank_fini(dev);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 822a0212cd48..71d45324bd78 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -37,6 +37,8 @@
  *      - implemented limited ABI16/NVIF interop
  */
 
+#include <linux/notifier.h>
+
 #include <nvif/client.h>
 #include <nvif/device.h>
 #include <nvif/ioctl.h>
@@ -161,6 +163,10 @@ struct nouveau_drm {
 	struct nvbios vbios;
 	struct nouveau_display *display;
 	struct backlight_device *backlight;
+#ifdef CONFIG_ACPI
+	struct notifier_block acpi_nb;
+	struct work_struct acpi_work;
+#endif
 
 	/* power management */
 	struct nouveau_hwmon *hwmon;
-- 
2.11.0




[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]