Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

vga_switcheroo: Add support for switching only the DDC

Originally by Seth Forshee <seth.forshee@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@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@wunner.de>, 2015-04 - 2015-10:
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.

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 mux_hw_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.

Also acquire mux_hw_lock in stage2 to avoid race condition where
reading the EDID and switching happens simultaneously. Likewise on
MIGD / MDIS commands and on runtime suspend.

v2.1: Overhaul locking, squash commits (Daniel Vetter)

v2.2: Readability improvements (Thierry Reding)

v2.3: Overhaul locking once more

v2.4: Retain semantics of ->switchto handler callback to switch all
pins, including DDC (Daniel Vetter)

v5: Rename ddc_lock to mux_hw_lock: Since we acquire this both
when calling ->switch_ddc and ->switchto, it protects not just
access to the DDC lines but to the mux in general. This is in
line with the DRM convention to use low-level locks to avoid
concurrent hw access (e.g. i2c, dp_aux) which are often called
hw_lock (Daniel Vetter)

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=88861
Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=61115
Tested-by: Lukas Wunner <lukas@wunner.de>
[MBP 9,1 2012 intel IVB + nvidia GK107 pre-retina 15"]
Cc: Seth Forshee <seth.forshee@canonical.com>
Cc: Dave Airlie <airlied@gmail.com>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/e81ae9722b84c5ed591805fee3ea6dbf5dc6c4b3.1452525860.git.lukas@wunner.de

authored by

Lukas Wunner and committed by
Daniel Vetter
e4cb81d7 156d7d41

+103 -2
+95 -2
drivers/gpu/vga/vga_switcheroo.c
··· 74 74 * there can thus be up to three clients: Two vga clients (GPUs) and one audio 75 75 * client (on the discrete GPU). The code is mostly prepared to support 76 76 * machines with more than two GPUs should they become available. 77 + * 77 78 * The GPU to which the outputs are currently switched is called the 78 79 * active client in vga_switcheroo parlance. The GPU not in use is the 79 - * inactive client. 80 + * inactive client. When the inactive client's DRM driver is loaded, 81 + * it will be unable to probe the panel's EDID and hence depends on 82 + * VBIOS to provide its display modes. If the VBIOS modes are bogus or 83 + * if there is no VBIOS at all (which is common on the MacBook Pro), 84 + * a client may alternatively request that the DDC lines are temporarily 85 + * switched to it, provided that the handler supports this. Switching 86 + * only the DDC lines and not the entire output avoids unnecessary 87 + * flickering. 80 88 */ 81 89 82 90 /** ··· 135 127 * @clients: list of registered clients 136 128 * @handler: registered handler 137 129 * @handler_flags: flags of registered handler 130 + * @mux_hw_lock: protects mux state 131 + * (in particular while DDC lines are temporarily switched) 132 + * @old_ddc_owner: client to which DDC lines will be switched back on unlock 138 133 * 139 134 * vga_switcheroo private data. Currently only one vga_switcheroo instance 140 135 * per system is supported. ··· 155 144 156 145 const struct vga_switcheroo_handler *handler; 157 146 enum vga_switcheroo_handler_flags_t handler_flags; 147 + struct mutex mux_hw_lock; 148 + int old_ddc_owner; 158 149 }; 159 150 160 151 #define ID_BIT_AUDIO 0x100 ··· 171 158 /* only one switcheroo per system */ 172 159 static struct vgasr_priv vgasr_priv = { 173 160 .clients = LIST_HEAD_INIT(vgasr_priv.clients), 161 + .mux_hw_lock = __MUTEX_INITIALIZER(vgasr_priv.mux_hw_lock), 174 162 }; 175 163 176 164 static bool vga_switcheroo_ready(void) ··· 241 227 void vga_switcheroo_unregister_handler(void) 242 228 { 243 229 mutex_lock(&vgasr_mutex); 230 + mutex_lock(&vgasr_priv.mux_hw_lock); 244 231 vgasr_priv.handler_flags = 0; 245 232 vgasr_priv.handler = NULL; 246 233 if (vgasr_priv.active) { ··· 249 234 vga_switcheroo_debugfs_fini(&vgasr_priv); 250 235 vgasr_priv.active = false; 251 236 } 237 + mutex_unlock(&vgasr_priv.mux_hw_lock); 252 238 mutex_unlock(&vgasr_mutex); 253 239 } 254 240 EXPORT_SYMBOL(vga_switcheroo_unregister_handler); ··· 449 433 EXPORT_SYMBOL(vga_switcheroo_client_fb_set); 450 434 451 435 /** 436 + * vga_switcheroo_lock_ddc() - temporarily switch DDC lines to a given client 437 + * @pdev: client pci device 438 + * 439 + * Temporarily switch DDC lines to the client identified by @pdev 440 + * (but leave the outputs otherwise switched to where they are). 441 + * This allows the inactive client to probe EDID. The DDC lines must 442 + * afterwards be switched back by calling vga_switcheroo_unlock_ddc(), 443 + * even if this function returns an error. 444 + * 445 + * Return: Previous DDC owner on success or a negative int on error. 446 + * Specifically, %-ENODEV if no handler has registered or if the handler 447 + * does not support switching the DDC lines. Also, a negative value 448 + * returned by the handler is propagated back to the caller. 449 + * The return value has merely an informational purpose for any caller 450 + * which might be interested in it. It is acceptable to ignore the return 451 + * value and simply rely on the result of the subsequent EDID probe, 452 + * which will be %NULL if DDC switching failed. 453 + */ 454 + int vga_switcheroo_lock_ddc(struct pci_dev *pdev) 455 + { 456 + enum vga_switcheroo_client_id id; 457 + 458 + mutex_lock(&vgasr_priv.mux_hw_lock); 459 + if (!vgasr_priv.handler || !vgasr_priv.handler->switch_ddc) { 460 + vgasr_priv.old_ddc_owner = -ENODEV; 461 + return -ENODEV; 462 + } 463 + 464 + id = vgasr_priv.handler->get_client_id(pdev); 465 + vgasr_priv.old_ddc_owner = vgasr_priv.handler->switch_ddc(id); 466 + return vgasr_priv.old_ddc_owner; 467 + } 468 + EXPORT_SYMBOL(vga_switcheroo_lock_ddc); 469 + 470 + /** 471 + * vga_switcheroo_unlock_ddc() - switch DDC lines back to previous owner 472 + * @pdev: client pci device 473 + * 474 + * Switch DDC lines back to the previous owner after calling 475 + * vga_switcheroo_lock_ddc(). This must be called even if 476 + * vga_switcheroo_lock_ddc() returned an error. 477 + * 478 + * Return: Previous DDC owner on success (i.e. the client identifier of @pdev) 479 + * or a negative int on error. 480 + * Specifically, %-ENODEV if no handler has registered or if the handler 481 + * does not support switching the DDC lines. Also, a negative value 482 + * returned by the handler is propagated back to the caller. 483 + * Finally, invoking this function without calling vga_switcheroo_lock_ddc() 484 + * first is not allowed and will result in %-EINVAL. 485 + */ 486 + int vga_switcheroo_unlock_ddc(struct pci_dev *pdev) 487 + { 488 + enum vga_switcheroo_client_id id; 489 + int ret = vgasr_priv.old_ddc_owner; 490 + 491 + if (WARN_ON_ONCE(!mutex_is_locked(&vgasr_priv.mux_hw_lock))) 492 + return -EINVAL; 493 + 494 + if (vgasr_priv.old_ddc_owner >= 0) { 495 + id = vgasr_priv.handler->get_client_id(pdev); 496 + if (vgasr_priv.old_ddc_owner != id) 497 + ret = vgasr_priv.handler->switch_ddc( 498 + vgasr_priv.old_ddc_owner); 499 + } 500 + mutex_unlock(&vgasr_priv.mux_hw_lock); 501 + return ret; 502 + } 503 + EXPORT_SYMBOL(vga_switcheroo_unlock_ddc); 504 + 505 + /** 452 506 * DOC: Manual switching and manual power control 453 507 * 454 508 * In this mode of use, the file /sys/kernel/debug/vgaswitcheroo/switch ··· 655 569 console_unlock(); 656 570 } 657 571 572 + mutex_lock(&vgasr_priv.mux_hw_lock); 658 573 ret = vgasr_priv.handler->switchto(new_client->id); 574 + mutex_unlock(&vgasr_priv.mux_hw_lock); 659 575 if (ret) 660 576 return ret; 661 577 ··· 772 684 vgasr_priv.delayed_switch_active = false; 773 685 774 686 if (just_mux) { 687 + mutex_lock(&vgasr_priv.mux_hw_lock); 775 688 ret = vgasr_priv.handler->switchto(client_id); 689 + mutex_unlock(&vgasr_priv.mux_hw_lock); 776 690 goto out; 777 691 } 778 692 ··· 986 896 if (ret) 987 897 return ret; 988 898 mutex_lock(&vgasr_mutex); 989 - if (vgasr_priv.handler->switchto) 899 + if (vgasr_priv.handler->switchto) { 900 + mutex_lock(&vgasr_priv.mux_hw_lock); 990 901 vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD); 902 + mutex_unlock(&vgasr_priv.mux_hw_lock); 903 + } 991 904 vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF); 992 905 mutex_unlock(&vgasr_mutex); 993 906 return 0;
+8
include/linux/vga_switcheroo.h
··· 102 102 * Mandatory. For muxless machines this should be a no-op. Returning 0 103 103 * denotes success, anything else failure (in which case the switch is 104 104 * aborted) 105 + * @switch_ddc: switch DDC lines to given client. 106 + * Optional. Should return the previous DDC owner on success or a 107 + * negative int on failure 105 108 * @power_state: cut or reinstate power of given client. 106 109 * Optional. The return value is ignored 107 110 * @get_client_id: determine if given pci device is integrated or discrete GPU. ··· 116 113 struct vga_switcheroo_handler { 117 114 int (*init)(void); 118 115 int (*switchto)(enum vga_switcheroo_client_id id); 116 + int (*switch_ddc)(enum vga_switcheroo_client_id id); 119 117 int (*power_state)(enum vga_switcheroo_client_id id, 120 118 enum vga_switcheroo_state state); 121 119 enum vga_switcheroo_client_id (*get_client_id)(struct pci_dev *pdev); ··· 160 156 enum vga_switcheroo_handler_flags_t handler_flags); 161 157 void vga_switcheroo_unregister_handler(void); 162 158 enum vga_switcheroo_handler_flags_t vga_switcheroo_handler_flags(void); 159 + int vga_switcheroo_lock_ddc(struct pci_dev *pdev); 160 + int vga_switcheroo_unlock_ddc(struct pci_dev *pdev); 163 161 164 162 int vga_switcheroo_process_delayed_switch(void); 165 163 ··· 185 179 enum vga_switcheroo_client_id id) { return 0; } 186 180 static inline void vga_switcheroo_unregister_handler(void) {} 187 181 static inline enum vga_switcheroo_handler_flags_t vga_switcheroo_handler_flags(void) { return 0; } 182 + static inline int vga_switcheroo_lock_ddc(struct pci_dev *pdev) { return -ENODEV; } 183 + static inline int vga_switcheroo_unlock_ddc(struct pci_dev *pdev) { return -ENODEV; } 188 184 static inline int vga_switcheroo_process_delayed_switch(void) { return 0; } 189 185 static inline enum vga_switcheroo_state vga_switcheroo_get_client_state(struct pci_dev *dev) { return VGA_SWITCHEROO_ON; } 190 186