0007-surface-hotplug.patch 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. From f255f201f64e16da9651393a9906956d6dcc89d7 Mon Sep 17 00:00:00 2001
  2. From: Maximilian Luz <luzmaximilian@gmail.com>
  3. Date: Sat, 31 Oct 2020 20:46:33 +0100
  4. Subject: [PATCH] PCI: Add sysfs attribute for PCI device power state
  5. While most PCI power-states can be queried from user-space via lspci,
  6. this has some limits. Specifically, lspci fails to provide an accurate
  7. value when the device is in D3cold as it has to resume the device before
  8. it can access its power state via the configuration space, leading to it
  9. reporting D0 or another on-state. Thus lspci can, for example, not be
  10. used to diagnose power-consumption issues for devices that can enter
  11. D3cold or to ensure that devices properly enter D3cold at all.
  12. To alleviate this issue, introduce a new sysfs device attribute for the
  13. PCI power state, showing the current power state as seen by the kernel.
  14. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  15. Patchset: surface-hotplug
  16. ---
  17. Documentation/ABI/testing/sysfs-bus-pci | 9 +++++++++
  18. drivers/pci/pci-sysfs.c | 12 ++++++++++++
  19. 2 files changed, 21 insertions(+)
  20. diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
  21. index 77ad9ec3c801..25c9c39770c6 100644
  22. --- a/Documentation/ABI/testing/sysfs-bus-pci
  23. +++ b/Documentation/ABI/testing/sysfs-bus-pci
  24. @@ -366,3 +366,12 @@ Contact: Heiner Kallweit <hkallweit1@gmail.com>
  25. Description: If ASPM is supported for an endpoint, these files can be
  26. used to disable or enable the individual power management
  27. states. Write y/1/on to enable, n/0/off to disable.
  28. +
  29. +What: /sys/bus/pci/devices/.../power_state
  30. +Date: November 2020
  31. +Contact: Linux PCI developers <linux-pci@vger.kernel.org>
  32. +Description:
  33. + This file contains the current PCI power state of the device.
  34. + The value comes from the PCI kernel device state and can be one
  35. + of: "unknown", "error", "D0", D1", "D2", "D3hot", "D3cold".
  36. + The file is read only.
  37. diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
  38. index d15c881e2e7e..b15f754e6346 100644
  39. --- a/drivers/pci/pci-sysfs.c
  40. +++ b/drivers/pci/pci-sysfs.c
  41. @@ -124,6 +124,17 @@ static ssize_t cpulistaffinity_show(struct device *dev,
  42. }
  43. static DEVICE_ATTR_RO(cpulistaffinity);
  44. +/* PCI power state */
  45. +static ssize_t power_state_show(struct device *dev,
  46. + struct device_attribute *attr, char *buf)
  47. +{
  48. + struct pci_dev *pci_dev = to_pci_dev(dev);
  49. + pci_power_t state = READ_ONCE(pci_dev->current_state);
  50. +
  51. + return sprintf(buf, "%s\n", pci_power_name(state));
  52. +}
  53. +static DEVICE_ATTR_RO(power_state);
  54. +
  55. /* show resources */
  56. static ssize_t resource_show(struct device *dev, struct device_attribute *attr,
  57. char *buf)
  58. @@ -581,6 +592,7 @@ static ssize_t driver_override_show(struct device *dev,
  59. static DEVICE_ATTR_RW(driver_override);
  60. static struct attribute *pci_dev_attrs[] = {
  61. + &dev_attr_power_state.attr,
  62. &dev_attr_resource.attr,
  63. &dev_attr_vendor.attr,
  64. &dev_attr_device.attr,
  65. --
  66. 2.33.0
  67. From cd1bedb6ac1c78f328d474774dcdaabdc385c409 Mon Sep 17 00:00:00 2001
  68. From: Maximilian Luz <luzmaximilian@gmail.com>
  69. Date: Mon, 14 Dec 2020 20:50:59 +0100
  70. Subject: [PATCH] platform/x86: Add Surface Hotplug driver
  71. Add a driver to handle out-of-band hot-plug signaling for the discrete
  72. GPU (dGPU) on Microsoft Surface Book 2 and 3 devices. This driver is
  73. required to properly detect hot-plugging of the dGPU and relay the
  74. appropriate signal to the PCIe hot-plug driver core.
  75. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
  76. Patchset: surface-hotplug
  77. ---
  78. drivers/platform/x86/Kconfig | 20 ++
  79. drivers/platform/x86/Makefile | 1 +
  80. drivers/platform/x86/surface_hotplug.c | 282 +++++++++++++++++++++++++
  81. 3 files changed, 303 insertions(+)
  82. create mode 100644 drivers/platform/x86/surface_hotplug.c
  83. diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
  84. index d44d3fb9ca72..533998040530 100644
  85. --- a/drivers/platform/x86/Kconfig
  86. +++ b/drivers/platform/x86/Kconfig
  87. @@ -1012,6 +1012,26 @@ config SURFACE_GPE
  88. accordingly. It is required on those devices to allow wake-ups from
  89. suspend by opening the lid.
  90. +config SURFACE_HOTPLUG
  91. + tristate "Surface Hot-Plug System Driver"
  92. + depends on ACPI
  93. + default m
  94. + help
  95. + Driver for out-of-band hot-plug event signaling on Microsoft Surface
  96. + devices with hot-pluggable PCIe cards.
  97. +
  98. + This driver is used on Surface Book (2 and 3) devices with a
  99. + hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those
  100. + devices can enter D3cold, which prevents in-band (standard) PCIe
  101. + hot-plug signaling. Thus, without this driver, detaching the base
  102. + containing the dGPU will not correctly update the state of the
  103. + corresponding PCIe device if it is in D3cold. This driver adds support
  104. + for out-of-band hot-plug notifications, ensuring that the device state
  105. + is properly updated even when the device in question is in D3cold.
  106. +
  107. + Select M or Y here, if you want to (fully) support hot-plugging of
  108. + dGPU devices on the Surface Book 2 and/or 3 during D3cold.
  109. +
  110. config SURFACE_BOOK1_DGPU_SWITCH
  111. tristate "Surface Book 1 dGPU Switch Driver"
  112. depends on ACPI && SYSFS
  113. diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
  114. index 2e0a2896c78d..f552cbfb7914 100644
  115. --- a/drivers/platform/x86/Makefile
  116. +++ b/drivers/platform/x86/Makefile
  117. @@ -94,6 +94,7 @@ obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
  118. obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
  119. obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
  120. obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
  121. +obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
  122. obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o
  123. # MSI
  124. diff --git a/drivers/platform/x86/surface_hotplug.c b/drivers/platform/x86/surface_hotplug.c
  125. new file mode 100644
  126. index 000000000000..cfcc15cfbacb
  127. --- /dev/null
  128. +++ b/drivers/platform/x86/surface_hotplug.c
  129. @@ -0,0 +1,282 @@
  130. +// SPDX-License-Identifier: GPL-2.0+
  131. +/*
  132. + * Surface Book (2 and later) hot-plug driver.
  133. + *
  134. + * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This
  135. + * driver is responsible for out-of-band hot-plug event signaling on these
  136. + * devices. It is specifically required when the hot-plug device is in D3cold
  137. + * and can thus not generate PCIe hot-plug events itself.
  138. + *
  139. + * Event signaling is handled via ACPI, which will generate the appropriate
  140. + * device-check notifications to be picked up by the PCIe hot-plug driver.
  141. + *
  142. + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
  143. + */
  144. +
  145. +#include <linux/acpi.h>
  146. +#include <linux/gpio.h>
  147. +#include <linux/interrupt.h>
  148. +#include <linux/kernel.h>
  149. +#include <linux/module.h>
  150. +#include <linux/mutex.h>
  151. +#include <linux/platform_device.h>
  152. +
  153. +static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false };
  154. +static const struct acpi_gpio_params shps_base_presence = { 1, 0, false };
  155. +static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false };
  156. +static const struct acpi_gpio_params shps_device_power = { 3, 0, false };
  157. +static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false };
  158. +static const struct acpi_gpio_params shps_device_presence = { 5, 0, false };
  159. +
  160. +static const struct acpi_gpio_mapping shps_acpi_gpios[] = {
  161. + { "base_presence-int-gpio", &shps_base_presence_int, 1 },
  162. + { "base_presence-gpio", &shps_base_presence, 1 },
  163. + { "device_power-int-gpio", &shps_device_power_int, 1 },
  164. + { "device_power-gpio", &shps_device_power, 1 },
  165. + { "device_presence-int-gpio", &shps_device_presence_int, 1 },
  166. + { "device_presence-gpio", &shps_device_presence, 1 },
  167. + { },
  168. +};
  169. +
  170. +/* 5515a847-ed55-4b27-8352-cd320e10360a */
  171. +static const guid_t shps_dsm_guid =
  172. + GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a);
  173. +
  174. +#define SHPS_DSM_REVISION 1
  175. +
  176. +enum shps_dsm_fn {
  177. + SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01,
  178. + SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02,
  179. + SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03,
  180. + SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04,
  181. + SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05,
  182. +};
  183. +
  184. +enum shps_irq_type {
  185. + /* NOTE: Must be in order of enum shps_dsm_fn above. */
  186. + SHPS_IRQ_TYPE_BASE_PRESENCE = 0,
  187. + SHPS_IRQ_TYPE_DEVICE_POWER = 1,
  188. + SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2,
  189. + SHPS_NUM_IRQS,
  190. +};
  191. +
  192. +static const char *const shps_gpio_names[] = {
  193. + [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence",
  194. + [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power",
  195. + [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence",
  196. +};
  197. +
  198. +struct shps_device {
  199. + struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */
  200. + struct gpio_desc *gpio[SHPS_NUM_IRQS];
  201. + unsigned int irq[SHPS_NUM_IRQS];
  202. +};
  203. +
  204. +#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1)
  205. +
  206. +static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type)
  207. +{
  208. + return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type;
  209. +}
  210. +
  211. +static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type)
  212. +{
  213. + struct shps_device *sdev = platform_get_drvdata(pdev);
  214. + acpi_handle handle = ACPI_HANDLE(&pdev->dev);
  215. + union acpi_object *result;
  216. + union acpi_object param;
  217. + int value;
  218. +
  219. + mutex_lock(&sdev->lock[type]);
  220. +
  221. + value = gpiod_get_value_cansleep(sdev->gpio[type]);
  222. + if (value < 0) {
  223. + mutex_unlock(&sdev->lock[type]);
  224. + dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value);
  225. + return;
  226. + }
  227. +
  228. + dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value);
  229. +
  230. + param.type = ACPI_TYPE_INTEGER;
  231. + param.integer.value = value;
  232. +
  233. + result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION,
  234. + shps_dsm_fn_for_irq(type), &param);
  235. +
  236. + if (!result) {
  237. + dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n",
  238. + type, value);
  239. +
  240. + } else if (result->type != ACPI_TYPE_BUFFER) {
  241. + dev_err(&pdev->dev,
  242. + "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n",
  243. + type, value);
  244. +
  245. + } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) {
  246. + dev_err(&pdev->dev,
  247. + "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n",
  248. + type, value);
  249. + }
  250. +
  251. + mutex_unlock(&sdev->lock[type]);
  252. +
  253. + if (result)
  254. + ACPI_FREE(result);
  255. +}
  256. +
  257. +static irqreturn_t shps_handle_irq(int irq, void *data)
  258. +{
  259. + struct platform_device *pdev = data;
  260. + struct shps_device *sdev = platform_get_drvdata(pdev);
  261. + int type;
  262. +
  263. + /* Figure out which IRQ we're handling. */
  264. + for (type = 0; type < SHPS_NUM_IRQS; type++)
  265. + if (irq == sdev->irq[type])
  266. + break;
  267. +
  268. + /* We should have found our interrupt, if not: this is a bug. */
  269. + if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq))
  270. + return IRQ_HANDLED;
  271. +
  272. + /* Forward interrupt to ACPI via DSM. */
  273. + shps_dsm_notify_irq(pdev, type);
  274. + return IRQ_HANDLED;
  275. +}
  276. +
  277. +static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type)
  278. +{
  279. + unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
  280. + struct shps_device *sdev = platform_get_drvdata(pdev);
  281. + struct gpio_desc *gpiod;
  282. + acpi_handle handle = ACPI_HANDLE(&pdev->dev);
  283. + const char *irq_name;
  284. + const int dsm = shps_dsm_fn_for_irq(type);
  285. + int status, irq;
  286. +
  287. + /*
  288. + * Only set up interrupts that we actually need: The Surface Book 3
  289. + * does not have a DSM for base presence, so don't set up an interrupt
  290. + * for that.
  291. + */
  292. + if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) {
  293. + dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type);
  294. + return 0;
  295. + }
  296. +
  297. + gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS);
  298. + if (IS_ERR(gpiod))
  299. + return PTR_ERR(gpiod);
  300. +
  301. + irq = gpiod_to_irq(gpiod);
  302. + if (irq < 0)
  303. + return irq;
  304. +
  305. + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type);
  306. + if (!irq_name)
  307. + return -ENOMEM;
  308. +
  309. + status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq,
  310. + flags, irq_name, pdev);
  311. + if (status)
  312. + return status;
  313. +
  314. + dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type);
  315. +
  316. + sdev->gpio[type] = gpiod;
  317. + sdev->irq[type] = irq;
  318. +
  319. + return 0;
  320. +}
  321. +
  322. +static int surface_hotplug_remove(struct platform_device *pdev)
  323. +{
  324. + struct shps_device *sdev = platform_get_drvdata(pdev);
  325. + int i;
  326. +
  327. + /* Ensure that IRQs have been fully handled and won't trigger any more. */
  328. + for (i = 0; i < SHPS_NUM_IRQS; i++) {
  329. + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT)
  330. + disable_irq(sdev->irq[i]);
  331. +
  332. + mutex_destroy(&sdev->lock[i]);
  333. + }
  334. +
  335. + return 0;
  336. +}
  337. +
  338. +static int surface_hotplug_probe(struct platform_device *pdev)
  339. +{
  340. + struct shps_device *sdev;
  341. + int status, i;
  342. +
  343. + /*
  344. + * The MSHW0153 device is also present on the Surface Laptop 3,
  345. + * however that doesn't have a hot-pluggable PCIe device. It also
  346. + * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter
  347. + * it out here.
  348. + */
  349. + if (gpiod_count(&pdev->dev, NULL) < 0)
  350. + return -ENODEV;
  351. +
  352. + status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios);
  353. + if (status)
  354. + return status;
  355. +
  356. + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
  357. + if (!sdev)
  358. + return -ENOMEM;
  359. +
  360. + platform_set_drvdata(pdev, sdev);
  361. +
  362. + /*
  363. + * Initialize IRQs so that we can safely call surface_hotplug_remove()
  364. + * on errors.
  365. + */
  366. + for (i = 0; i < SHPS_NUM_IRQS; i++)
  367. + sdev->irq[i] = SHPS_IRQ_NOT_PRESENT;
  368. +
  369. + /* Set up IRQs. */
  370. + for (i = 0; i < SHPS_NUM_IRQS; i++) {
  371. + mutex_init(&sdev->lock[i]);
  372. +
  373. + status = shps_setup_irq(pdev, i);
  374. + if (status) {
  375. + dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status);
  376. + goto err;
  377. + }
  378. + }
  379. +
  380. + /* Ensure everything is up-to-date. */
  381. + for (i = 0; i < SHPS_NUM_IRQS; i++)
  382. + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT)
  383. + shps_dsm_notify_irq(pdev, i);
  384. +
  385. + return 0;
  386. +
  387. +err:
  388. + surface_hotplug_remove(pdev);
  389. + return status;
  390. +}
  391. +
  392. +static const struct acpi_device_id surface_hotplug_acpi_match[] = {
  393. + { "MSHW0153", 0 },
  394. + { },
  395. +};
  396. +MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match);
  397. +
  398. +static struct platform_driver surface_hotplug_driver = {
  399. + .probe = surface_hotplug_probe,
  400. + .remove = surface_hotplug_remove,
  401. + .driver = {
  402. + .name = "surface_hotplug",
  403. + .acpi_match_table = surface_hotplug_acpi_match,
  404. + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
  405. + },
  406. +};
  407. +module_platform_driver(surface_hotplug_driver);
  408. +
  409. +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
  410. +MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices");
  411. +MODULE_LICENSE("GPL");
  412. --
  413. 2.33.0
  414. From 4a19147e3089c339a35adf8babad555cda52cb2e Mon Sep 17 00:00:00 2001
  415. From: Maximilian Luz <luzmaximilian@gmail.com>
  416. Date: Fri, 2 Jul 2021 14:35:43 +0200
  417. Subject: [PATCH] Revert "Revert "PCI: PM: Do not read power state in
  418. pci_enable_device_flags()""
  419. This reverts commit f11f9ff8a7c97b2a3990c7322304627d9b58d362.
  420. Patchset: surface-hotplug
  421. ---
  422. drivers/pci/pci.c | 16 +++-------------
  423. 1 file changed, 3 insertions(+), 13 deletions(-)
  424. diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
  425. index 29f5d699fa06..c73fcdff0c16 100644
  426. --- a/drivers/pci/pci.c
  427. +++ b/drivers/pci/pci.c
  428. @@ -1874,20 +1874,10 @@ static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
  429. int err;
  430. int i, bars = 0;
  431. - /*
  432. - * Power state could be unknown at this point, either due to a fresh
  433. - * boot or a device removal call. So get the current power state
  434. - * so that things like MSI message writing will behave as expected
  435. - * (e.g. if the device really is in D0 at enable time).
  436. - */
  437. - if (dev->pm_cap) {
  438. - u16 pmcsr;
  439. - pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
  440. - dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
  441. - }
  442. -
  443. - if (atomic_inc_return(&dev->enable_cnt) > 1)
  444. + if (atomic_inc_return(&dev->enable_cnt) > 1) {
  445. + pci_update_current_state(dev, dev->current_state);
  446. return 0; /* already enabled */
  447. + }
  448. bridge = pci_upstream_bridge(dev);
  449. if (bridge)
  450. --
  451. 2.33.0