[PATCH RFC v3] vga_switcheroo: restore mux and power state upon resume from hibernation

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

 



Restore state of mux switch and power-off state of graphics cards upon
resume from hibernation as the following problems can occur:

If the mux is set to the discrete card prior to hibernation it is reset
to the integrated card by the system upon boot. Thus it stays at the
wrong state after resume which results in a black screen.
Solve this by saving the current state of the mux and restoring it upon
resume from hibernation.

If a graphics card is powered off prior to hibernation it is powered up
by the system upon boot. Thus it stays still active after resume and
there is an inconsistence between the power state the kernel thinks the
graphic card is in and the actual power state. In case of the discrete
card this also results in a noticeable increase in power consumption.
Solve this by powering off such cards upon resume.

Signed-off-by: Stefan Demharter <stefan.demharter@xxxxxxx>
---
 drivers/gpu/vga/vga_switcheroo.c | 123 +++++++++++++++++++++++++++++++++++----
 1 file changed, 112 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c
index 37ac7b5..67a82f1 100644
--- a/drivers/gpu/vga/vga_switcheroo.c
+++ b/drivers/gpu/vga/vga_switcheroo.c
@@ -28,6 +28,7 @@
 #include <linux/console.h>
 #include <linux/vga_switcheroo.h>
 #include <linux/pm_runtime.h>
+#include <linux/suspend.h>
 
 #include <linux/vgaarb.h>
 
@@ -72,6 +73,89 @@ static struct vgasr_priv vgasr_priv = {
 	.clients = LIST_HEAD_INIT(vgasr_priv.clients),
 };
 
+static int vga_switchoff(struct vga_switcheroo_client *client);
+
+/* Assume that the initial state of the mux is set to IGD at boot time */
+#define MUX_INITIAL_STATE VGA_SWITCHEROO_IGD
+
+/*
+ * The next variable stores the state of the mux
+ * so that it can be restored upon resume from hibernation.
+ *
+ * This should also handle muxless systems quite well
+ * since there mux_state is never altered and the function
+ * restore_mux will end up doing nothing
+ */
+static int mux_state = MUX_INITIAL_STATE;
+
+#define DIS_IGD_STR(id) ((id) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD")
+
+static int vga_switchon(struct vga_switcheroo_client *client);
+static int vga_switchoff(struct vga_switcheroo_client *client);
+
+/* Restore saved mux state */
+static void vga_restore_mux(void)
+{
+	if (vgasr_priv.handler->switchto &&
+		mux_state != MUX_INITIAL_STATE) {
+		int ret;
+
+		pr_info("vga_switcheroo: restoring mux state to %s\n",
+			DIS_IGD_STR(mux_state));
+		ret = vgasr_priv.handler->switchto(mux_state);
+
+		if (ret)
+			pr_warn("vga_switcheroo: failed (%d)\n", ret);
+	}
+}
+
+/*
+ * Power off devices from which the system thinks they are powered-off.
+ * If a device is powered-off before hibernation the system may
+ * power it on upon boot. This results in a mismatch between what
+ * the driver thinks of the power state and the actual power state.
+ * This function resolves this mismatch by powering-off corresponding devices.
+ */
+static void vga_restore_power_off_state(void)
+{
+	struct vga_switcheroo_client *client;
+
+	list_for_each_entry(client, &vgasr_priv.clients, list) {
+		if (client_is_audio(client))
+			continue;
+
+		if (client->pwr_state == VGA_SWITCHEROO_OFF) {
+			pr_info("vga_switcheroo: Restoring power-off state for %s\n",
+				DIS_IGD_STR(client->id));
+			vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF);
+		}
+	}
+}
+
+/*
+ * Callback to handle resume from hibernation.
+ * Restore mux state and power states for switcheroo controlled GPUs.
+ */
+static int vga_switcheroo_resume(struct notifier_block *nb,
+		unsigned long action, void *unused)
+{
+	if (action == PM_POST_HIBERNATION) {
+		mutex_lock(&vgasr_mutex);
+
+		vga_restore_mux();
+		vga_restore_power_off_state();
+
+		mutex_unlock(&vgasr_mutex);
+	}
+
+	return 0;
+}
+
+static struct notifier_block vga_pm_nb = {
+	.notifier_call = vga_switcheroo_resume,
+	.priority = 0,
+};
+
 static bool vga_switcheroo_ready(void)
 {
 	/* we're ready if we get two clients + handler */
@@ -83,6 +167,7 @@ static void vga_switcheroo_enable(void)
 {
 	int ret;
 	struct vga_switcheroo_client *client;
+	register_pm_notifier(&vga_pm_nb);
 
 	/* call the handler to init */
 	if (vgasr_priv.handler->init)
@@ -119,14 +204,20 @@ int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler)
 }
 EXPORT_SYMBOL(vga_switcheroo_register_handler);
 
+void vga_switcheroo_disable(void)
+{
+	unregister_pm_notifier(&vga_pm_nb);
+	pr_info("vga_switcheroo: disabled\n");
+	vga_switcheroo_debugfs_fini(&vgasr_priv);
+	vgasr_priv.active = false;
+}
+
 void vga_switcheroo_unregister_handler(void)
 {
 	mutex_lock(&vgasr_mutex);
 	vgasr_priv.handler = NULL;
 	if (vgasr_priv.active) {
-		pr_info("vga_switcheroo: disabled\n");
-		vga_switcheroo_debugfs_fini(&vgasr_priv);
-		vgasr_priv.active = false;
+		vga_switcheroo_disable();
 	}
 	mutex_unlock(&vgasr_mutex);
 }
@@ -235,9 +326,7 @@ void vga_switcheroo_unregister_client(struct pci_dev *pdev)
 		kfree(client);
 	}
 	if (vgasr_priv.active && vgasr_priv.registered_clients < 2) {
-		printk(KERN_INFO "vga_switcheroo: disabled\n");
-		vga_switcheroo_debugfs_fini(&vgasr_priv);
-		vgasr_priv.active = false;
+		vga_switcheroo_disable();
 	}
 	mutex_unlock(&vgasr_mutex);
 }
@@ -262,12 +351,13 @@ static int vga_switcheroo_show(struct seq_file *m, void *v)
 	int i = 0;
 	mutex_lock(&vgasr_mutex);
 	list_for_each_entry(client, &vgasr_priv.clients, list) {
-		seq_printf(m, "%d:%s%s:%c:%s%s:%s\n", i,
-			   client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD",
+		seq_printf(m, "%d:%s%s:%c:%s%s:%s:%s\n", i,
+			   DIS_IGD_STR(client_id(client)),
 			   client_is_vga(client) ? "" : "-Audio",
 			   client->active ? '+' : ' ',
 			   client->driver_power_control ? "Dyn" : "",
 			   client->pwr_state ? "Pwr" : "Off",
+			   client_id(client) == mux_state ? "mux" : "",
 			   pci_name(client->pdev));
 		i++;
 	}
@@ -304,6 +394,16 @@ static int vga_switchoff(struct vga_switcheroo_client *client)
 	return 0;
 }
 
+static int vga_switch_mux_to(int client_id)
+{
+	int ret = vgasr_priv.handler->switchto(client_id);
+
+	if (ret == 0)
+		mux_state = client_id;
+
+	return ret;
+}
+
 static void set_audio_state(int id, int state)
 {
 	struct vga_switcheroo_client *client;
@@ -353,7 +453,8 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
 		console_unlock();
 	}
 
-	ret = vgasr_priv.handler->switchto(new_client->id);
+	ret = vga_switch_mux_to(new_client->id);
+
 	if (ret)
 		return ret;
 
@@ -468,7 +569,7 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
 	vgasr_priv.delayed_switch_active = false;
 
 	if (just_mux) {
-		ret = vgasr_priv.handler->switchto(client_id);
+		ret = vga_switch_mux_to(client_id);
 		goto out;
 	}
 
@@ -624,7 +725,7 @@ static int vga_switcheroo_runtime_suspend(struct device *dev)
 	if (ret)
 		return ret;
 	if (vgasr_priv.handler->switchto)
-		vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD);
+		vga_switch_mux_to(VGA_SWITCHEROO_IGD);
 	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF);
 	return 0;
 }
-- 
2.1.1

_______________________________________________
dri-devel mailing list
dri-devel@xxxxxxxxxxxxxxxxxxxxx
http://lists.freedesktop.org/mailman/listinfo/dri-devel




[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux