All of lore.kernel.org
 help / color / mirror / Atom feed
From: Lukas Wunner <lukas@wunner.de>
To: dri-devel@lists.freedesktop.org
Cc: Andreas Heider <andreas@meetr.de>,
	Paul Hordiienko <pvt.gord@gmail.com>,
	William Brown <william@blackhats.net.au>,
	Bruno Bierbaumer <bruno@bierbaumer.net>,
	Matthew Garrett <mjg59@coreos.com>,
	Dave Airlie <airlied@redhat.com>
Subject: [PATCH v2 06/22] vga_switcheroo: Lock/unlock DDC lines harder
Date: Fri, 27 Mar 2015 12:29:40 +0100	[thread overview]
Message-ID: <ec399430bb382373e8b4edaa5f773563ebc6aaa9.1439288957.git.lukas@wunner.de> (raw)
In-Reply-To: <dc9642f97141bea03ec9f34f721cd0545c841d8c.1439288957.git.lukas@wunner.de>

Unlock DDC lines if drm_probe_ddc() fails.

If the inactive client registers before the active client then
old_ddc_owner cannot be determined with find_active_client()
(null pointer dereference). Therefore change semantics of the
->switch_ddc handler callback to return old_ddc_owner on success
or a negative int on failure.

Use mutex_trylock(&vgasr_mutex) in vga_switcheroo_unlock_ddc() to
avoid locking inversion when one client locks vgasr_mutex on entering
vga_switcheroo_lock_ddc() and tries to acquire ddc_lock while another
client is holding ddc_lock and tries to acquire vgasr_mutex on entering
vga_switcheroo_unlock_ddc().

Lock ddc_lock in stage2 to avoid race condition where reading the
EDID and switching happens simultaneously.

Switch DDC lines on MIGD / MDIS commands and on runtime suspend.

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=88861
Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=61115
Tested-by: Paul Hordiienko <pvt.gord@gmail.com>
    [MBP  6,2 2010  intel ILK + nvidia GT216  pre-retina]
Tested-by: William Brown <william@blackhats.net.au>
    [MBP  8,2 2011  intel SNB + amd turks     pre-retina]
Tested-by: Lukas Wunner <lukas@wunner.de>
    [MBP  9,1 2012  intel IVB + nvidia GK107  pre-retina]
Tested-by: Bruno Bierbaumer <bruno@bierbaumer.net>
    [MBP 11,3 2013  intel HSW + nvidia GK107  retina -- work in progress]

Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
 drivers/gpu/drm/drm_edid.c        |   9 ++--
 drivers/gpu/vga/vga_switcheroo.c  | 110 ++++++++++++++++++++++----------------
 drivers/platform/x86/apple-gmux.c |  15 +++++-
 include/linux/vga_switcheroo.h    |   3 +-
 4 files changed, 87 insertions(+), 50 deletions(-)

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 505eed1..cdb2fa1 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -1378,17 +1378,20 @@ struct edid *drm_get_edid(struct drm_connector *connector,
 			  struct i2c_adapter *adapter)
 {
 	struct edid *edid;
+	struct pci_dev *pdev = connector->dev->pdev;
 
-	vga_switcheroo_lock_ddc(connector->dev->pdev);
+	vga_switcheroo_lock_ddc(pdev);
 
-	if (!drm_probe_ddc(adapter))
+	if (!drm_probe_ddc(adapter)) {
+		vga_switcheroo_unlock_ddc(pdev);
 		return NULL;
+	}
 
 	edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
 	if (edid)
 		drm_get_displayid(connector, edid);
 
-	vga_switcheroo_unlock_ddc(connector->dev->pdev);
+	vga_switcheroo_unlock_ddc(pdev);
 
 	return edid;
 }
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c
index 0223020..f02e7fc 100644
--- a/drivers/gpu/vga/vga_switcheroo.c
+++ b/drivers/gpu/vga/vga_switcheroo.c
@@ -9,12 +9,13 @@
 
  Switcher interface - methods require for ATPX and DCM
  - switchto - this throws the output MUX switch
- - discrete_set_power - sets the power state for the discrete card
+ - switch_ddc - switch only DDC lines, return old DDC owner (or < 0 on failure)
+ - power_state - sets the power state for either GPU
 
  GPU driver interface
  - set_gpu_state - this should do the equiv of s/r for the card
 		  - this should *not* set the discrete power state
- - switch_check  - check if the device is in a position to switch now
+ - can_switch - check if the device is in a position to switch now
  */
 
 #include <linux/module.h>
@@ -59,7 +60,7 @@ struct vgasr_priv {
 	struct vga_switcheroo_handler *handler;
 
 	struct mutex ddc_lock;
-	struct pci_dev *old_ddc_owner;
+	enum vga_switcheroo_client_id old_ddc_owner;
 };
 
 #define ID_BIT_AUDIO		0x100
@@ -276,33 +277,24 @@ EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
 
 int vga_switcheroo_lock_ddc(struct pci_dev *pdev)
 {
-	struct vga_switcheroo_client *client;
-	int ret = 0;
-	int id;
+	int ret, id;
 
 	mutex_lock(&vgasr_mutex);
 
-	if (!vgasr_priv.handler) {
+	if (!vgasr_priv.handler || !vgasr_priv.handler->switch_ddc) {
 		ret = -ENODEV;
 		goto out;
 	}
 
-	if (vgasr_priv.handler->switch_ddc) {
-		mutex_lock(&vgasr_priv.ddc_lock);
+	mutex_lock(&vgasr_priv.ddc_lock);
+	id = vgasr_priv.handler->get_client_id(pdev);
+	ret = vgasr_priv.handler->switch_ddc(id);
 
-		client = find_active_client(&vgasr_priv.clients);
-		if (!client) {
-			mutex_unlock(&vgasr_priv.ddc_lock);
-			ret = -ENODEV;
-			goto out;
-		}
-		vgasr_priv.old_ddc_owner = client->pdev;
-		if (client->pdev == pdev)
-			goto out;
-
-		id = vgasr_priv.handler->get_client_id(pdev);
-		ret = vgasr_priv.handler->switch_ddc(id);
-	}
+	if (ret < 0) {
+		pr_err("vga_switcheroo: failed to switch DDC lines\n");
+		mutex_unlock(&vgasr_priv.ddc_lock);
+	} else
+		vgasr_priv.old_ddc_owner = ret;
 
 out:
 	mutex_unlock(&vgasr_mutex);
@@ -312,25 +304,33 @@ EXPORT_SYMBOL(vga_switcheroo_lock_ddc);
 
 int vga_switcheroo_unlock_ddc(struct pci_dev *pdev)
 {
-	int ret = 0;
-	int id;
-	mutex_lock(&vgasr_mutex);
+	int ret, id;
+	bool vgasr_mutex_acquired = mutex_trylock(&vgasr_mutex);
 
-	if (!vgasr_priv.handler) {
-		ret = -ENODEV;
+	if (!mutex_is_locked(&vgasr_priv.ddc_lock)) {
+		ret = -EINVAL;
 		goto out;
 	}
 
-	if (vgasr_priv.handler->switch_ddc) {
-		if (vgasr_priv.old_ddc_owner != pdev) {
-			id = vgasr_priv.handler->get_client_id(vgasr_priv.old_ddc_owner);
-			ret = vgasr_priv.handler->switch_ddc(id);
-		}
-		vgasr_priv.old_ddc_owner = NULL;
+	if (!vgasr_priv.handler || !vgasr_priv.handler->switch_ddc) {
+		pr_err("vga_switcheroo: handler disappeared\n");
 		mutex_unlock(&vgasr_priv.ddc_lock);
+		ret = -ENODEV;
+		goto out;
 	}
+
+	id = vgasr_priv.handler->get_client_id(pdev);
+	if (vgasr_priv.old_ddc_owner != id) {
+		ret = vgasr_priv.handler->switch_ddc(vgasr_priv.old_ddc_owner);
+		if (ret < 0)
+			pr_err("vga_switcheroo: failed to switch DDC lines\n");
+	} else
+		ret = vgasr_priv.old_ddc_owner;
+	mutex_unlock(&vgasr_priv.ddc_lock);
+
 out:
-	mutex_unlock(&vgasr_mutex);
+	if (vgasr_mutex_acquired)
+		mutex_unlock(&vgasr_mutex);
 	return ret;
 }
 EXPORT_SYMBOL(vga_switcheroo_unlock_ddc);
@@ -433,14 +433,26 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
 	}
 
 	if (vgasr_priv.handler->switch_ddc) {
+		mutex_lock(&vgasr_priv.ddc_lock);
 		ret = vgasr_priv.handler->switch_ddc(new_client->id);
-		if (ret)
+		mutex_unlock(&vgasr_priv.ddc_lock);
+		if (ret < 0) {
+			pr_err("vga_switcheroo: failed to switch DDC lines\n");
 			return ret;
+		}
 	}
 
 	ret = vgasr_priv.handler->switchto(new_client->id);
-	if (ret)
-		goto restore_ddc;
+	if (ret) {
+		pr_err("vga_switcheroo: failed to switch to client %d\n",
+		       new_client->id);
+		active->active = true;
+		/* restore DDC lines */
+		mutex_lock(&vgasr_priv.ddc_lock);
+		vgasr_priv.handler->switch_ddc(active->id);
+		mutex_unlock(&vgasr_priv.ddc_lock);
+		return ret;
+	}
 
 	if (new_client->ops->reprobe)
 		new_client->ops->reprobe(new_client->pdev);
@@ -452,14 +464,6 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
 
 	new_client->active = true;
 	return 0;
-
-restore_ddc:
-	if (vgasr_priv.handler->switch_ddc) {
-		int id = (new_client->id == VGA_SWITCHEROO_IGD) ?
-				VGA_SWITCHEROO_DIS : VGA_SWITCHEROO_IGD;
-		ret = vgasr_priv.handler->switch_ddc(id);
-	}
-	return ret;
 }
 
 static bool check_can_switch(void)
@@ -561,6 +565,15 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
 	vgasr_priv.delayed_switch_active = false;
 
 	if (just_mux) {
+		if (vgasr_priv.handler->switch_ddc) {
+			mutex_lock(&vgasr_priv.ddc_lock);
+			ret = vgasr_priv.handler->switch_ddc(client_id);
+			mutex_unlock(&vgasr_priv.ddc_lock);
+			if (ret < 0) {
+				pr_err("vga_switcheroo: failed to switch DDC lines\n");
+				goto out;
+			}
+		}
 		ret = vgasr_priv.handler->switchto(client_id);
 		goto out;
 	}
@@ -716,6 +729,13 @@ static int vga_switcheroo_runtime_suspend(struct device *dev)
 	ret = dev->bus->pm->runtime_suspend(dev);
 	if (ret)
 		return ret;
+	if (vgasr_priv.handler->switch_ddc) {
+		mutex_lock(&vgasr_priv.ddc_lock);
+		ret = vgasr_priv.handler->switch_ddc(VGA_SWITCHEROO_IGD);
+		mutex_unlock(&vgasr_priv.ddc_lock);
+		if (ret < 0)
+			pr_err("vga_switcheroo: failed to switch DDC lines\n");
+	}
 	if (vgasr_priv.handler->switchto)
 		vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD);
 	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF);
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c
index f0a55a4..08bdf1e 100644
--- a/drivers/platform/x86/apple-gmux.c
+++ b/drivers/platform/x86/apple-gmux.c
@@ -275,11 +275,24 @@ static const struct backlight_ops gmux_bl_ops = {
 
 static int gmux_switch_ddc(enum vga_switcheroo_client_id id)
 {
+	enum vga_switcheroo_client_id old_ddc_owner;
+
+	if (gmux_read8(apple_gmux_data, GMUX_PORT_SWITCH_DDC) == 1)
+		old_ddc_owner = VGA_SWITCHEROO_IGD;
+	else
+		old_ddc_owner = VGA_SWITCHEROO_DIS;
+
+	pr_debug("Switching gmux DDC from %d to %d\n", old_ddc_owner, id);
+
+	if (id == old_ddc_owner)
+		return old_ddc_owner;
+
 	if (id == VGA_SWITCHEROO_IGD)
 		gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 1);
 	else
 		gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 2);
-	return 0;
+
+	return old_ddc_owner;
 }
 
 static int gmux_switchto(enum vga_switcheroo_client_id id)
diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h
index 352bef3..39b25b0 100644
--- a/include/linux/vga_switcheroo.h
+++ b/include/linux/vga_switcheroo.h
@@ -77,7 +77,8 @@ static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {}
 static inline int vga_switcheroo_register_client(struct pci_dev *dev,
 		const struct vga_switcheroo_client_ops *ops, bool driver_power_control) { return 0; }
 static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {}
-static inline void vga_switcheroo_switch_ddc(struct pci_dev *pdev) { return NULL; }
+static inline int vga_switcheroo_lock_ddc(struct pci_dev *pdev) { return -ENODEV; }
+static inline int vga_switcheroo_unlock_ddc(struct pci_dev *pdev) { return -ENODEV; }
 static inline int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) { return 0; }
 static inline int vga_switcheroo_register_audio_client(struct pci_dev *pdev,
 	const struct vga_switcheroo_client_ops *ops,
-- 
1.8.5.2 (Apple Git-48)

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

  reply	other threads:[~2015-08-12 11:39 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-08-11 10:29 [PATCH v2 00/22] Enable gpu switching on the MacBook Pro Lukas Wunner
2012-09-07 15:22 ` [PATCH v2 01/22] vga_switcheroo: Add support for switching only the DDC Lukas Wunner
2012-09-07 15:22   ` [PATCH v2 02/22] vga_switcheroo: Add helper function to get the active client Lukas Wunner
2012-09-07 15:22     ` [PATCH v2 03/22] apple-gmux: Add switch_ddc support Lukas Wunner
2012-09-07 15:22       ` [PATCH v2 04/22] drm/edid: Switch DDC when reading the EDID Lukas Wunner
2012-12-22  2:52         ` [PATCH v2 05/22] vga_switcheroo: Lock/unlock DDC lines Lukas Wunner
2015-03-27 11:29           ` Lukas Wunner [this message]
2015-04-21  8:39             ` [PATCH v2 07/22] Revert "vga_switcheroo: Add helper function to get the active client" Lukas Wunner
2015-08-02  9:06               ` [PATCH v2 08/22] Revert "vga_switcheroo: add reprobe hook for fbcon to recheck connected outputs." Lukas Wunner
2015-05-09 15:20                 ` [PATCH v2 09/22] drm/nouveau: Lock/unlock DDC lines on probe Lukas Wunner
2014-03-05 22:34                   ` [PATCH v2 10/22] apple-gmux: Assign apple_gmux_data before registering Lukas Wunner
2015-04-20 10:08                     ` [PATCH v2 11/22] vga_switcheroo: Generate hotplug event on handler and proxy registration Lukas Wunner
2015-07-15 11:57                       ` [PATCH v2 12/22] drm/i915: Preserve SSC earlier Lukas Wunner
2015-04-19 15:01                         ` [PATCH v2 13/22] drm/i915: Reprobe eDP and LVDS connectors on hotplug event Lukas Wunner
2015-06-30  9:06                           ` [PATCH v2 14/22 RESEND] drm/i915: Fix failure paths around initial fbdev allocation Lukas Wunner
2015-07-04  9:50                             ` [PATCH v2 15/22 RESEND] drm/i915: On fb alloc failure, unref gem object where it gets refed Lukas Wunner
2015-05-25 13:15                               ` [PATCH v2 16/22] drm: Create new fb and replace default 1024x768 fb on hotplug event Lukas Wunner
     [not found]                                 ` <afe73d5a7382f85c9bdbfc46197a52c4278c99c7.1439288957.git.lukas-JFq808J9C/izQB+pC5nmwQ@public.gmane.org>
2015-07-23 10:59                                   ` [PATCH v2 17/22] drm/nouveau/timer: Fall back to kernel timer if GPU timer read failed Lukas Wunner
2015-07-29 19:23                                     ` [PATCH v2 18/22 EXPERIMENTAL] vga_switcheroo: Allow using active client as proxy when reading DDC/AUX Lukas Wunner
2015-05-13 19:50                                       ` [PATCH v2 19/22 EXPERIMENTAL] drm: Amend struct drm_dp_aux with connector attribute Lukas Wunner
2015-05-06 12:06                                         ` [PATCH v2 20/22 EXPERIMENTAL] drm: Use vga_switcheroo active client as proxy when reading DDC/AUX Lukas Wunner
2015-07-30 11:31                                           ` [PATCH v2 21/22 EXPERIMENTAL] drm/nouveau/i2c: " Lukas Wunner
2015-06-07  9:20                                             ` [PATCH v2 22/22 EXPERIMENTAL] drm/nouveau: Use vga_switcheroo active client as proxy when probing DDC on LVDS Lukas Wunner
2015-08-31 20:23                         ` [PATCH v2 12/22] drm/i915: Preserve SSC earlier Jesse Barnes
2015-09-01  6:46                           ` Jani Nikula
2015-08-12 14:25               ` [PATCH v2 07/22] Revert "vga_switcheroo: Add helper function to get the active client" Daniel Vetter
2015-08-12 17:34                 ` Lukas Wunner
2015-08-12 21:10                   ` Daniel Vetter
2015-08-12 14:23             ` [PATCH v2 06/22] vga_switcheroo: Lock/unlock DDC lines harder Daniel Vetter
     [not found] ` <cover.1439288957.git.lukas-JFq808J9C/izQB+pC5nmwQ@public.gmane.org>
2015-08-12 14:16   ` [Intel-gfx] [PATCH v2 00/22] Enable gpu switching on the MacBook Pro Daniel Vetter
2015-08-12 23:37     ` Lukas Wunner
     [not found]       ` <20150812233711.GA6002-JFq808J9C/izQB+pC5nmwQ@public.gmane.org>
2015-08-13  6:50         ` [Intel-gfx] " Daniel Vetter
2015-08-16 19:10           ` Lukas Wunner
2015-08-25  7:36 ` Lukas Wunner
2015-08-25  8:21   ` Daniel Vetter
2015-08-26 14:01     ` Lukas Wunner
2015-08-29 14:15 ` Lukas Wunner
2015-08-31 19:15   ` Jani Nikula
2015-09-01  6:48     ` Jani Nikula
2015-09-04 14:00     ` Lukas Wunner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ec399430bb382373e8b4edaa5f773563ebc6aaa9.1439288957.git.lukas@wunner.de \
    --to=lukas@wunner.de \
    --cc=airlied@redhat.com \
    --cc=andreas@meetr.de \
    --cc=bruno@bierbaumer.net \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=mjg59@coreos.com \
    --cc=pvt.gord@gmail.com \
    --cc=william@blackhats.net.au \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.