[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