From patchwork Fri Aug 14 16:50:15 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lukas Wunner X-Patchwork-Id: 7378251 Return-Path: X-Original-To: patchwork-dri-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 57DE89F37F for ; Mon, 12 Oct 2015 18:29:13 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 1D4B0208DE for ; Mon, 12 Oct 2015 18:29:12 +0000 (UTC) Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by mail.kernel.org (Postfix) with ESMTP id E1C8B208C4 for ; Mon, 12 Oct 2015 18:29:10 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id DEA976E08E; Mon, 12 Oct 2015 11:29:08 -0700 (PDT) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mailout2.hostsharing.net (mailout2.hostsharing.net [83.223.90.233]) by gabe.freedesktop.org (Postfix) with ESMTPS id 29B336E08E for ; Mon, 12 Oct 2015 11:29:07 -0700 (PDT) Received: from h08.hostsharing.net (h08.hostsharing.net [83.223.95.28]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mailout2.hostsharing.net (Postfix) with ESMTPS id BAB3F10341C2B; Mon, 12 Oct 2015 20:29:04 +0200 (CEST) Received: from localhost (6-38-90-81.adsl.cmo.de [81.90.38.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA (128/128 bits)) (No client certificate requested) by h08.hostsharing.net (Postfix) with ESMTPSA id 22952603E04D; Mon, 12 Oct 2015 20:29:01 +0200 (CEST) X-Mailbox-Line: From bfb07f467cbe382fb5f8930a328c07186eeab4d3 Mon Sep 17 00:00:00 2001 Message-Id: In-Reply-To: References: <20151006182744.GA15532@wunner.de> From: Lukas Wunner Date: Fri, 14 Aug 2015 18:50:15 +0200 Subject: [PATCH v4 1/6] vga_switcheroo: Add support for switching only the DDC To: Daniel Vetter , DRI Development X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Spam-Status: No, score=-0.8 required=5.0 tests=BAYES_00, DATE_IN_PAST_96_XX, RCVD_IN_DNSWL_MED, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Originally by Seth Forshee , 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 , 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 , 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. Don't switch DDC lines in stage2, it's pointless because if DDC is accessed, _lock_ddc() needs to be called anyway. And for consistency we'd also have to switch DDC on MIGD / MDIS commands and on runtime suspend, needlessly inflating the code. For the same reason it's unnecessary to switch DDC back to the previous owner on unlock. v2.1: Overhaul locking, squash commits (requested by Daniel Vetter ) v2.2: Readability improvements (requested by Thierry Reding ) v2.3: Overhaul locking once more v2.4: Retain semantics of ->switchto handler callback to switch all pins (requested by Daniel Vetter ) v4: Drop DDC switch in stage2, MIGD / MDIS and runtime suspend Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=88861 Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=61115 Tested-by: Lukas Wunner [MBP 9,1 2012 intel IVB + nvidia GK107 pre-retina 15"] Cc: Seth Forshee Cc: Dave Airlie Signed-off-by: Lukas Wunner --- drivers/gpu/vga/vga_switcheroo.c | 66 +++++++++++++++++++++++++++++++++++++++- include/linux/vga_switcheroo.h | 15 +++++++-- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index af0d372..7aaaa57 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,7 @@ 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 * * vga_switcheroo private data. Currently only one vga_switcheroo instance * per system is supported. @@ -141,6 +152,7 @@ struct vgasr_priv { struct list_head clients; struct vga_switcheroo_handler *handler; + struct mutex ddc_lock; }; #define ID_BIT_AUDIO 0x100 @@ -155,6 +167,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 +234,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); @@ -414,6 +429,55 @@ 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 released by calling vga_switcheroo_unlock_ddc(), + * even if this function returns an error. + * + * Return: 0 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) + return -ENODEV; + + id = vgasr_priv.handler->get_client_id(pdev); + return vgasr_priv.handler->switch_ddc(id); +} +EXPORT_SYMBOL(vga_switcheroo_lock_ddc); + +/** + * vga_switcheroo_unlock_ddc() - release DDC lines after a temporary switch + * + * Release DDC lines after having temporarily switched them to a client. + * This must be called even if vga_switcheroo_lock_ddc() returned an error. + * Invoking this function without calling vga_switcheroo_lock_ddc() first + * is not allowed. + */ +void vga_switcheroo_unlock_ddc(void) +{ + if (WARN_ON_ONCE(!mutex_is_locked(&vgasr_priv.ddc_lock))) + return; + + mutex_unlock(&vgasr_priv.ddc_lock); +} +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 diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index c557511..f5899b6 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -84,9 +84,12 @@ enum vga_switcheroo_client_id { * vga clients. Currently only the radeon and amdgpu drivers use this. * The return value is ignored * @switchto: switch outputs to given client. - * Mandatory. For muxless machines this should be a no-op. Returning 0 - * denotes success, anything else failure (in which case the switch is - * aborted) + * Mandatory. For muxless machines this should be a no-op. If the mux + * is able to switch DDC separately, this should switch all pins except + * for the DDC lines. 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 0 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. @@ -98,6 +101,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); @@ -140,6 +144,9 @@ void vga_switcheroo_client_fb_set(struct pci_dev *dev, int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler); void vga_switcheroo_unregister_handler(void); +int vga_switcheroo_lock_ddc(struct pci_dev *pdev); +void vga_switcheroo_unlock_ddc(void); + int vga_switcheroo_process_delayed_switch(void); enum vga_switcheroo_state vga_switcheroo_get_client_state(struct pci_dev *dev); @@ -160,6 +167,8 @@ static inline int vga_switcheroo_register_audio_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, enum vga_switcheroo_client_id id) { return 0; } static inline void vga_switcheroo_unregister_handler(void) {} +static inline int vga_switcheroo_lock_ddc(struct pci_dev *pdev) { return 0; } +static inline void vga_switcheroo_unlock_ddc(void) {} static inline int vga_switcheroo_process_delayed_switch(void) { return 0; } static inline enum vga_switcheroo_state vga_switcheroo_get_client_state(struct pci_dev *dev) { return VGA_SWITCHEROO_ON; }