[PATCH v3 1/6] vga_switcheroo: Add support for switching only the DDC

Daniel Vetter daniel at ffwll.ch
Tue Oct 6 00:27:24 PDT 2015


On Fri, Aug 14, 2015 at 06:50:15PM +0200, Lukas Wunner wrote:
> Originally by Seth Forshee <seth.forshee at canonical.com>, 2012-10-04:
>     During graphics driver initialization it's useful to be able to mux
>     only the DDC to the inactive client in order to read the EDID. Add
>     a switch_ddc callback to allow capable handlers to provide this
>     functionality, and add vga_switcheroo_switch_ddc() to allow DRM
>     to mux only the DDC.
> 
> Modified by Dave Airlie <airlied at gmail.com>, 2012-12-22:
>     I can't figure out why I didn't like this, but I rewrote this [...]
>     to lock/unlock the ddc lines [...]. I think I'd prefer something
>     like that otherwise the interface got really ugly.
> 
> Modified by Lukas Wunner <lukas at wunner.de>, 2015-08-14:
>     Don't lock vgasr_mutex in _lock_ddc() / _unlock_ddc(), it can cause
>     deadlocks because (a) during switch (with vgasr_mutex already held),
>     GPU is woken and probes its outputs, tries to re-acquire vgasr_mutex
>     to lock DDC lines; (b) Likewise during switch, GPU is suspended and
>     calls cancel_delayed_work_sync() to stop output polling, if poll
>     task is running at this moment we may wait forever for it to finish.
> 
>     Instead, lock ddc_lock when unregistering the handler because the
>     only reason why we'd want to lock vgasr_mutex in _lock_ddc() /
>     _unlock_ddc() is to block the handler from disappearing while DDC
>     lines are switched.

Shouldn't we also take the new look in register_handler, for consistency?
I know that if you look the mux provider too late it's fairly hopeless ...

Also while reading the patch I realized that the new lock really protects
hw state (instead of our software-side tracking and driver resume/suspend
code). And at least myself I have no idea what vgasr means, so what about
renaming it to hw_mutex? We have this pattern of low-level locks to avoid
concurrent hw access in a lot of places like i2c, dp_aux, and it's very
often called hw_lock or similar.

Otherwise patch looks good.

Wrt the overall patch series, can you pls haggle driver-maintainers (gmux,
radeon, nouveau) for acks/reviews? scripts/get_maintainers.pl should help.
Also, the revised docbook patch seems to be missing from this iteration,
please follow up with that one.

Thanks, Daniel

>     Also lock ddc_lock in stage2 to avoid race condition where reading
>     the EDID and switching happens simultaneously. Likewise on MIGD /
>     MDIS commands and on runtime suspend.
> 
>     Retain semantics of ->switchto handler callback to switch all pins,
>     including DDC. Change semantics of ->switch_ddc handler callback to
>     return previous DDC owner. Original version tried to determine
>     previous DDC owner with find_active_client() but this fails if the
>     inactive client registers before the active client.
> 
> v2.1: Overhaul locking, squash commits
>     (requested by Daniel Vetter <daniel.vetter at ffwll.ch>)
> 
> v2.2: Readability improvements
>     (requested by Thierry Reding <thierry.reding at gmail.com>)
> 
> v2.3: Overhaul locking once more
> 
> v2.4: Retain semantics of ->switchto handler callback to switch all pins
>     (requested by Daniel Vetter <daniel.vetter at ffwll.ch>)
> 
> Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=88861
> Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=61115
> Tested-by: Pierre Moreau <pierre.morrow at free.fr>
>     [MBP  5,3 2009  nvidia MCP79 + G96        pre-retina  15"]
> Tested-by: William Brown <william at blackhats.net.au>
>     [MBP  8,2 2011  intel SNB + amd turks     pre-retina  15"]
> Tested-by: Lukas Wunner <lukas at wunner.de>
>     [MBP  9,1 2012  intel IVB + nvidia GK107  pre-retina  15"]
> 
> Cc: Seth Forshee <seth.forshee at canonical.com>
> Cc: Dave Airlie <airlied at gmail.com>
> Signed-off-by: Lukas Wunner <lukas at wunner.de>
> ---
>  drivers/gpu/vga/vga_switcheroo.c | 98 +++++++++++++++++++++++++++++++++++++++-
>  include/linux/vga_switcheroo.h   |  9 ++++
>  2 files changed, 105 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c
> index aa077aa..9b6946e 100644
> --- a/drivers/gpu/vga/vga_switcheroo.c
> +++ b/drivers/gpu/vga/vga_switcheroo.c
> @@ -73,9 +73,19 @@
>   * there can thus be up to three clients: Two vga clients (GPUs) and one audio
>   * client (on the discrete GPU). The code is mostly prepared to support
>   * machines with more than two GPUs should they become available.
> + *
>   * The GPU to which the outputs are currently switched is called the
>   * active client in vga_switcheroo parlance. The GPU not in use is the
> - * inactive client.
> + * inactive client. When the inactive client's DRM driver is loaded,
> + * it will be unable to probe the panel's EDID and hence depends on
> + * VBIOS to provide its display modes. If the VBIOS modes are bogus or
> + * if there is no VBIOS at all (which is common on the MacBook Pro),
> + * a client may alternatively request that the DDC lines are temporarily
> + * switched to it, provided that the handler supports this. Switching
> + * only the DDC lines and not the entire output avoids unnecessary
> + * flickering. On machines which are able to mux external connectors,
> + * VBIOS cannot know of attached displays so switching the DDC lines
> + * is the only option to retrieve the displays' EDID.
>   */
>  
>  /**
> @@ -125,6 +135,8 @@ static DEFINE_MUTEX(vgasr_mutex);
>   * 	(counting only vga clients, not audio clients)
>   * @clients: list of registered clients
>   * @handler: registered handler
> + * @ddc_lock: protects access to DDC lines while they are temporarily switched
> + * @old_ddc_owner: client to which DDC lines will be switched back on unlock
>   *
>   * vga_switcheroo private data. Currently only one vga_switcheroo instance
>   * per system is supported.
> @@ -141,6 +153,8 @@ struct vgasr_priv {
>  	struct list_head clients;
>  
>  	struct vga_switcheroo_handler *handler;
> +	struct mutex ddc_lock;
> +	int old_ddc_owner;
>  };
>  
>  #define ID_BIT_AUDIO		0x100
> @@ -155,6 +169,7 @@ static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv);
>  /* only one switcheroo per system */
>  static struct vgasr_priv vgasr_priv = {
>  	.clients = LIST_HEAD_INIT(vgasr_priv.clients),
> +	.ddc_lock = __MUTEX_INITIALIZER(vgasr_priv.ddc_lock),
>  };
>  
>  static bool vga_switcheroo_ready(void)
> @@ -221,12 +236,14 @@ EXPORT_SYMBOL(vga_switcheroo_register_handler);
>  void vga_switcheroo_unregister_handler(void)
>  {
>  	mutex_lock(&vgasr_mutex);
> +	mutex_lock(&vgasr_priv.ddc_lock);
>  	vgasr_priv.handler = NULL;
>  	if (vgasr_priv.active) {
>  		pr_info("disabled\n");
>  		vga_switcheroo_debugfs_fini(&vgasr_priv);
>  		vgasr_priv.active = false;
>  	}
> +	mutex_unlock(&vgasr_priv.ddc_lock);
>  	mutex_unlock(&vgasr_mutex);
>  }
>  EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
> @@ -412,6 +429,76 @@ void vga_switcheroo_client_fb_set(struct pci_dev *pdev,
>  EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
>  
>  /**
> + * vga_switcheroo_lock_ddc() - temporarily switch DDC lines to a given client
> + * @pdev: client pci device
> + *
> + * Temporarily switch DDC lines to the client identified by @pdev
> + * (but leave the outputs otherwise switched to where they are).
> + * This allows the inactive client to probe EDID. The DDC lines must
> + * afterwards be switched back by calling vga_switcheroo_unlock_ddc(),
> + * even if this function returns an error.
> + *
> + * Return: Previous DDC owner on success or a negative int on error.
> + * Specifically, -ENODEV if no handler has registered or if the handler
> + * does not support switching the DDC lines. Also, a negative value
> + * returned by the handler is propagated back to the caller.
> + * The return value has merely an informational purpose for any caller
> + * which might be interested in it. It is acceptable to ignore the return
> + * value and simply rely on the result of the subsequent EDID probe,
> + * which will be NULL if DDC switching failed.
> + */
> +int vga_switcheroo_lock_ddc(struct pci_dev *pdev)
> +{
> +	enum vga_switcheroo_client_id id;
> +
> +	mutex_lock(&vgasr_priv.ddc_lock);
> +	if (!vgasr_priv.handler || !vgasr_priv.handler->switch_ddc) {
> +		vgasr_priv.old_ddc_owner = -ENODEV;
> +		return -ENODEV;
> +	}
> +
> +	id = vgasr_priv.handler->get_client_id(pdev);
> +	vgasr_priv.old_ddc_owner = vgasr_priv.handler->switch_ddc(id);
> +	return vgasr_priv.old_ddc_owner;
> +}
> +EXPORT_SYMBOL(vga_switcheroo_lock_ddc);
> +
> +/**
> + * vga_switcheroo_unlock_ddc() - switch DDC lines back to previous owner
> + * @pdev: client pci device
> + *
> + * Switch DDC lines back to the previous owner after calling
> + * vga_switcheroo_lock_ddc(). This must be called even if
> + * vga_switcheroo_lock_ddc() returned an error.
> + *
> + * Return: Previous DDC owner on success (i.e. the client identifier of @pdev)
> + * or a negative int on error.
> + * Specifically, -ENODEV if no handler has registered or if the handler
> + * does not support switching the DDC lines. Also, a negative value
> + * returned by the handler is propagated back to the caller.
> + * Finally, invoking this function without calling vga_switcheroo_lock_ddc()
> + * first is not allowed and will result in -EINVAL.
> + */
> +int vga_switcheroo_unlock_ddc(struct pci_dev *pdev)
> +{
> +	enum vga_switcheroo_client_id id;
> +	int ret = vgasr_priv.old_ddc_owner;
> +
> +	if (WARN_ON_ONCE(!mutex_is_locked(&vgasr_priv.ddc_lock)))
> +		return -EINVAL;
> +
> +	if (vgasr_priv.old_ddc_owner >= 0) {
> +		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);
> +	}
> +	mutex_unlock(&vgasr_priv.ddc_lock);
> +	return ret;
> +}
> +EXPORT_SYMBOL(vga_switcheroo_unlock_ddc);
> +
> +/**
>   * DOC: Manual switching and manual power control
>   *
>   * In this mode of use, the file /sys/kernel/debug/vgaswitcheroo/switch
> @@ -548,7 +635,9 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
>  		console_unlock();
>  	}
>  
> +	mutex_lock(&vgasr_priv.ddc_lock);
>  	ret = vgasr_priv.handler->switchto(new_client->id);
> +	mutex_unlock(&vgasr_priv.ddc_lock);
>  	if (ret)
>  		return ret;
>  
> @@ -663,7 +752,9 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
>  	vgasr_priv.delayed_switch_active = false;
>  
>  	if (just_mux) {
> +		mutex_lock(&vgasr_priv.ddc_lock);
>  		ret = vgasr_priv.handler->switchto(client_id);
> +		mutex_unlock(&vgasr_priv.ddc_lock);
>  		goto out;
>  	}
>  
> @@ -875,8 +966,11 @@ static int vga_switcheroo_runtime_suspend(struct device *dev)
>  	if (ret)
>  		return ret;
>  	mutex_lock(&vgasr_mutex);
> -	if (vgasr_priv.handler->switchto)
> +	if (vgasr_priv.handler->switchto) {
> +		mutex_lock(&vgasr_priv.ddc_lock);
>  		vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD);
> +		mutex_unlock(&vgasr_priv.ddc_lock);
> +	}
>  	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF);
>  	mutex_unlock(&vgasr_mutex);
>  	return 0;
> diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h
> index b2a3dda..6edaacc 100644
> --- a/include/linux/vga_switcheroo.h
> +++ b/include/linux/vga_switcheroo.h
> @@ -82,6 +82,9 @@ enum vga_switcheroo_client_id {
>   * 	Mandatory. For muxless machines this should be a no-op. Returning 0
>   * 	denotes success, anything else failure (in which case the switch is
>   * 	aborted)
> + * @switch_ddc: switch DDC lines to given client.
> + * 	Optional. Should return the previous DDC owner on success or a
> + * 	negative int on failure
>   * @power_state: cut or reinstate power of given client.
>   * 	Optional. The return value is ignored
>   * @get_client_id: determine if given pci device is integrated or discrete GPU.
> @@ -93,6 +96,7 @@ enum vga_switcheroo_client_id {
>  struct vga_switcheroo_handler {
>  	int (*init)(void);
>  	int (*switchto)(enum vga_switcheroo_client_id id);
> +	int (*switch_ddc)(enum vga_switcheroo_client_id id);
>  	int (*power_state)(enum vga_switcheroo_client_id id,
>  			   enum vga_switcheroo_state state);
>  	enum vga_switcheroo_client_id (*get_client_id)(struct pci_dev *pdev);
> @@ -132,6 +136,9 @@ int vga_switcheroo_register_audio_client(struct pci_dev *pdev,
>  void vga_switcheroo_client_fb_set(struct pci_dev *dev,
>  				  struct fb_info *info);
>  
> +int vga_switcheroo_lock_ddc(struct pci_dev *pdev);
> +int vga_switcheroo_unlock_ddc(struct pci_dev *pdev);
> +
>  int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler);
>  void vga_switcheroo_unregister_handler(void);
>  
> @@ -150,6 +157,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 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 at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch


More information about the dri-devel mailing list