ACPI: PCI: take native PME control on Apple machines
PATCH 1/1 ACPI: PCI: take native PME control on Apple machines
Since commit 7bc5a2bad0b8 ("ACPI: Support _OSI("Darwin") correctly") negotiate_os_control() skips the _OSC handshake on Apple machines and hardcodes the set of features the OS takes over, because those firmwares return failure once _OSI("Darwin") has been acknowledged. The hardcoded set is ~OSC_PCI_EXPRESS_PME_CONTROL, i.e. everything except PME. No reason was given for leaving PME out.
The result is that host_bridge->native_pme stays 0, so portdrv never registers the PME service for any root port. A PCIe device that runtime suspends still arms PME# (the PCI core enables it because pci_dev_run_wake() only looks at capability), asserts it when it needs attention, and is never resumed: no service handles the root port interrupt, and the polling fallback in pci_pme_list_scan() skips the device whenever the bridge above it is runtime suspended, which is exactly when the device itself is suspended.
On a MacBookPro14,3 this silently breaks USB-C hotplug. Both xHCI controllers sit inside the Thunderbolt (Alpine Ridge) switches and runtime suspend to D3hot when idle, together with their parent bridges. Plugging a SuperSpeed device in is then never noticed:
0000:7d:00.0 suspended, PMCSR 0x8103 (D3hot, PME_En=1, PME_Status=1) ... no kernel message at all, for minutes ...
The device only appears once something else resumes the controller, which makes it look intermittent. The firmware on this machine does not handle PME on behalf of the OS either: XHC2/XHC3 declare _PRW returning GPE 0x6D, that GPE is enabled and has never fired, and its _L6D handler notifies PWRB, XHC1 and HDEF only - never XHC2 or XHC3.
With PME control taken, the root port PME interrupt is serviced and the controller is resumed as soon as something is plugged in.
Use calculate_control() for the assumed set rather than open coding it:
that is by definition the set that would have been requested, it drops a magic constant, and on a kernel built without some of those features it no longer claims control of services the OS cannot provide.
Note this also makes pme_is_native() true for pciehp (drivers/pci/ hotplug/pciehp_core.c), so hotplug ports now disable their interrupt across suspend and clear spurious events on resume, as they do on every non-Apple machine. x86_apple_machine covers Macs back to 2006, so this reaches models that were never tested with native PME.
Tested on a MacBookPro14,3, 6.18.49, with a USB 3.1 SATA enclosure:
- before: plugged in at 09:20 with 0000:7d:00.0 runtime suspended, still invisible at 09:21 with PME_Status latched at 1; only appeared when 0000:7d:00.0 was forced to power/control=on.
- with pcie_ports=native (which reaches the same code path in get_port_device_capability()): pcie_pme bound to 5 root ports, and the same enclosure enumerated in the same second it was plugged in, 66 s after the controller had suspended, with nothing forced.
- with this patch, nothing forced: pcie_pme bound to 5 root ports and the _OSC line now reads "OS assumes control of [PCIeHotplug SHPCHotplug PME AER PCIeCapability LTR DPC]". The enclosure was unplugged at 20:42:13, 0000:7d:00.0 runtime suspended to D3hot, and on plugging it back in at 20:43:06 (53 s later) the root port took a PME interrupt and the device enumerated in the same second.
- wifi, internal keyboard, touchbar and audio all came up; 66 warnings and 45 errors during boot, against 65/43 on the distribution kernel.
One rough edge, reproducible on both boots: the port driver logs "PME: Spurious native interrupt!" right before the device is resumed. pcie_pme_handle_request() falls back to pcie_pme_walk_bus() when the root port reports its own requester ID without PCI_PM_CTRL_PME_STATUS set, and that walk skips PCIe devices, so the xHCI is never attributed. The device is still resumed and the wakeup works, but attributing PMEs from PCIe devices behind a switch looks like a separate shortcoming; it is not addressed here.
Boot with pcie_ports=native also showed no new AER/DPC activity and slightly fewer warnings than a boot without it, so taking the remaining control bit does not appear to disturb anything else on this machine.
---
drivers/acpi/pci_root.c +6 -1
@@ -568,9 +568,14 @@* Apple always return failure on _OSC calls when _OSI("Darwin") has * been called successfully. We know the feature set supported by the * platform, so avoid calling _OSC at all +* +* Assume control of the same features that would have been requested, +* PCIe PME included. Leaving PME out disables the PME service for +* every root port, and nothing takes over: a device that runtime +* suspends asserts PME# and is never resumed. */ if (x86_apple_machine) { - root->osc_control_set = ~OSC_PCI_EXPRESS_PME_CONTROL ; + root->osc_control_set =calculate_control ();decode_osc_control (root,"OS assumes control of" , root->osc_control_set);return ;
[email protected]
AI reviewSashiko has reviewed this patch and found no issues. It looks great!
Quoted 19 lines
Since commit
7bc5a2bad0b8("ACPI: Support_OSI("Darwin")correctly")negotiate_os_control()skips the_OSChandshake on Apple machines and hardcodes the set of features the OS takes over, because those firmwares return failure once_OSI("Darwin")has been acknowledged. The hardcoded set is ~OSC_PCI_EXPRESS_PME_CONTROL, i.e. everything except PME. No reason was given for leaving PME out.The result is that
host_bridge->native_pmestays 0, so portdrv never registers the PME service for any root port. A PCIe device that runtime suspends still arms PME# (the PCI core enables it becausepci_dev_run_wake()only looks at capability), asserts it when it needs attention, and is never resumed: no service handles the root port interrupt, and the polling fallback inpci_pme_list_scan()skips the device whenever the bridge above it is runtime suspended, which is exactly when the device itself is suspended.
[ ... ]
slightly fewer warnings than a boot without it, so taking the remaining control bit does not appear to disturb anything else on this machine.
Quoted 2 lines
Fixes 7bc5a2bad0b8 ("ACPI: Support _OSI("Darwin") correctly")Signed-off-by Francisco Beltrán Millalén <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Quoted 1 line · Francisco Beltrán Millalén
Since commit
7bc5a2bad0b8("ACPI: Support_OSI("Darwin")correctly")
[...]
Tested on a second MacBookPro14,3 on 7.2.5 (Omarchy's linux-omarchy 7.2.5-3). Your PCI/PM v2 series and the Alpine Ridge quirk were applied as well. The full report is in reply to the series:
https://lore.kernel.org/all/[email protected]/
Stock 7.2.5: with both Thunderbolt xHCIs (8086:15d4 at 07:00.0 and 7d:00.0) runtime-suspended, plugging a USB 3 stick into any USB-C port produced no kernel messages at all. With power/control=on, it enumerated at SuperSpeed immediately.
With this patch, _OSC reads "OS assumes control of [PCIeHotplug SHPCHotplug PME AER PCIeCapability LTR DPC]". With both controllers left at power/control=auto, the same stick enumerated at SuperSpeed within about 1 s, and 7d:00.0 runtime-resumed on its own. A later run gave the same result: about 1 s when plugged in before a lid-close S3, and about 3 s for a replug about 2 min after resume, still with power/control=auto.
The patches were tested together, not bisected. This is the result that maps most directly onto this one patch.
[+cc Matthew (author of 7bc5a2bad0b8), Darrell, Andreas, Mika, Yehezkel, Lukas, linux-usb]
Quoted 6 lines · Francisco Beltrán Millalén
Since commit
7bc5a2bad0b8("ACPI: Support_OSI("Darwin")correctly")negotiate_os_control()skips the_OSChandshake on Apple machines and hardcodes the set of features the OS takes over, because those firmwares return failure once_OSI("Darwin")has been acknowledged. The hardcoded set is ~OSC_PCI_EXPRESS_PME_CONTROL, i.e. everything except PME. No reason was given for leaving PME out.
It's clear there *was* a reason, but I don't know either. Maybe Matthew will remember?
Quoted 64 lines
The result is that
host_bridge->native_pmestays 0, so portdrv never registers the PME service for any root port. A PCIe device that runtime suspends still arms PME# (the PCI core enables it becausepci_dev_run_wake()only looks at capability), asserts it when it needs attention, and is never resumed: no service handles the root port interrupt, and the polling fallback inpci_pme_list_scan()skips the device whenever the bridge above it is runtime suspended, which is exactly when the device itself is suspended.On a MacBookPro14,3 this silently breaks USB-C hotplug. Both xHCI controllers sit inside the Thunderbolt (Alpine Ridge) switches and runtime suspend to D3hot when idle, together with their parent bridges. Plugging a SuperSpeed device in is then never noticed:
0000:7d:00.0 suspended, PMCSR
0x8103(D3hot, PME_En=1, PME_Status=1) ... no kernel message at all, for minutes ...The device only appears once something else resumes the controller, which makes it look intermittent. The firmware on this machine does not handle PME on behalf of the OS either: XHC2/XHC3 declare
_PRWreturning GPE0x6D, that GPE is enabled and has never fired, and its_L6Dhandler notifies PWRB, XHC1 and HDEF only - never XHC2 or XHC3.With PME control taken, the root port PME interrupt is serviced and the controller is resumed as soon as something is plugged in.
Use
calculate_control()for the assumed set rather than open coding it:
that is by definition the set that would have been requested, it drops a magic constant, and on a kernel built without some of those features it no longer claims control of services the OS cannot provide.Note this also makes
pme_is_native()true for pciehp (drivers/pci/hotplug/pciehp_core.c), so hotplug ports now disable their interrupt across suspend and clear spurious events on resume, as they do on every non-Apple machine.x86_apple_machinecovers Macs back to 2006, so this reaches models that were never tested with native PME.Tested on a MacBookPro14,3, 6.18.49, with a USB 3.1 SATA enclosure:
- before: plugged in at 09:20 with 0000:7d:00.0 runtime suspended, still invisible at 09:21 with
PME_Statuslatched at 1; only appeared when 0000:7d:00.0 was forced to power/control=on.
- with pcie_ports=native (which reaches the same code path inget_port_device_capability()):pcie_pmebound to 5 root ports, and the same enclosure enumerated in the same second it was plugged in, 66 s after the controller had suspended, with nothing forced.
- with this patch, nothing forced:pcie_pmebound to 5 root ports and the_OSCline now reads "OS assumes control of [PCIeHotplug SHPCHotplug PME AER PCIeCapability LTR DPC]". The enclosure was unplugged at 20:42:13, 0000:7d:00.0 runtime suspended to D3hot, and on plugging it back in at 20:43:06 (53 s later) the root port took a PME interrupt and the device enumerated in the same second.
- wifi, internal keyboard, touchbar and audio all came up; 66 warnings and 45 errors during boot, against 65/43 on the distribution kernel.One rough edge, reproducible on both boots: the port driver logs "PME: Spurious native interrupt!" right before the device is resumed.
pcie_pme_handle_request()falls back topcie_pme_walk_bus()when the root port reports its own requester ID withoutPCI_PM_CTRL_PME_STATUSset, and that walk skips PCIe devices, so the xHCI is never attributed. The device is still resumed and the wakeup works, but attributing PMEs from PCIe devices behind a switch looks like a separate shortcoming; it is not addressed here.
Quoted 6 lines
Boot with pcie_ports=native also showed no new AER/DPC activity and slightly fewer warnings than a boot without it, so taking the remaining control bit does not appear to disturb anything else on this machine.
Fixes 7bc5a2bad0b8 ("ACPI: Support _OSI("Darwin") correctly")Signed-off-by Francisco Beltrán Millalén <[email protected]>
I don't object from a PCI standpoint, but Rafael generally takes care of acpi/pci_root.c. I would again suggest putting some of the commit log detail below "---" and relying on the https://patch.msgid.link/
tag Rafael adds.
Quoted 19 lines
---
drivers/acpi/pci_root.c +6
-1@@ -568,9 +568,14 @@* Apple always return failure on _OSC calls when _OSI("Darwin") has * been called successfully. We know the feature set supported by the * platform, so avoid calling _OSC at all +* +* Assume control of the same features that would have been requested, +* PCIe PME included. Leaving PME out disables the PME service for +* every root port, and nothing takes over: a device that runtime +* suspends asserts PME# and is never resumed. */ if (x86_apple_machine) { - root->osc_control_set = ~OSC_PCI_EXPRESS_PME_CONTROL ; + root->osc_control_set =calculate_control ();decode_osc_control (root,"OS assumes control of" , root->osc_control_set);return ;
Quoted 11 lines · Bjorn Helgaas
[+cc Matthew (author of
7bc5a2bad0b8), Darrell, Andreas, Mika, Yehezkel, Lukas, linux-usb]Quoted 6 lines · Francisco Beltrán Millalén
Since commit
7bc5a2bad0b8("ACPI: Support_OSI("Darwin")correctly")negotiate_os_control()skips the_OSChandshake on Apple machines and hardcodes the set of features the OS takes over, because those firmwares return failure once_OSI("Darwin")has been acknowledged. The hardcoded set is ~OSC_PCI_EXPRESS_PME_CONTROL, i.e. everything except PME. No reason was given for leaving PME out.
Quoted 2 lines
It's clear there *was* a reason, but I don't know either. Maybe Matthew will remember?
I /think/ this was the set of parameters that my machine would hand over via _OSC if we hadn't called _OSI("Darwin") - but it was over a decade ago so I can't promise that's the case. I think this probably needs to be tested on some older machines as well just to ensure there's no weird breakage as a result, but if PME is actually hooked up and there's no SMM managing it instead I think that's fine.
Hi Matthew,
Thanks for going back that far, and for the explanation.
Quoted 6 lines · Matthew Garrett
I /think/ this was the set of parameters that my machine would hand over via
_OSCif we hadn't called_OSI("Darwin")- but it was over a decade ago so I can't promise that's the case. I think this probably needs to be tested on some older machines as well just to ensure there's no weird breakage as a result, but if PME is actually hooked up and there's no SMM managing it instead I think that's fine.
On the MacBookPro14,3 both conditions seem to hold. With the patch, the root port takes the PME interrupt as soon as a device is plugged in, and the controller is resumed in the same second. Without it, nothing picks the event up: the controller stays suspended for over a minute with PME_Status set, the GPE the firmware declares for those two xHCI controllers never fires, and its handler doesn't notify them anyway. I can't look at SMM directly, but if the firmware were handling the event itself, the controller would have been woken without the patch, and it never was. Darrell saw the same fix on his MacBookPro14,3.
You're right that older Macs should be tested, and I don't have any: the patch has only run on my machine and Darrell's, both MacBookPro14,3. Two things make me think the risk is small. Handling PME natively doesn't turn off the firmware's own wake path, so if an older Mac does use a GPE for this, that keeps working. The only side effect I would expect is the PME driver logging "Spurious native interrupt". And on these machines the kernel already takes over hotplug and AER without asking the firmware, so PME would be treated the same way as the rest. If anyone on Cc has an older Mac and can try it, I'd be glad to hear how it goes.
Thanks again,
Francisco
Rafael J. Wysocki (Intel)
maintainerQuoted 83 lines · Bjorn Helgaas
[+cc Matthew (author of
7bc5a2bad0b8), Darrell, Andreas, Mika, Yehezkel, Lukas, linux-usb]Quoted 6 lines · Francisco Beltrán Millalén
Since commit
7bc5a2bad0b8("ACPI: Support_OSI("Darwin")correctly")negotiate_os_control()skips the_OSChandshake on Apple machines and hardcodes the set of features the OS takes over, because those firmwares return failure once_OSI("Darwin")has been acknowledged. The hardcoded set is ~OSC_PCI_EXPRESS_PME_CONTROL, i.e. everything except PME. No reason was given for leaving PME out.It's clear there *was* a reason, but I don't know either. Maybe Matthew will remember?
Quoted 68 lines
The result is that
host_bridge->native_pmestays 0, so portdrv never registers the PME service for any root port. A PCIe device that runtime suspends still arms PME# (the PCI core enables it becausepci_dev_run_wake()only looks at capability), asserts it when it needs attention, and is never resumed: no service handles the root port interrupt, and the polling fallback inpci_pme_list_scan()skips the device whenever the bridge above it is runtime suspended, which is exactly when the device itself is suspended.On a MacBookPro14,3 this silently breaks USB-C hotplug. Both xHCI controllers sit inside the Thunderbolt (Alpine Ridge) switches and runtime suspend to D3hot when idle, together with their parent bridges. Plugging a SuperSpeed device in is then never noticed:
0000:7d:00.0 suspended, PMCSR
0x8103(D3hot, PME_En=1, PME_Status=1) ... no kernel message at all, for minutes ...The device only appears once something else resumes the controller, which makes it look intermittent. The firmware on this machine does not handle PME on behalf of the OS either: XHC2/XHC3 declare
_PRWreturning GPE0x6D, that GPE is enabled and has never fired, and its_L6Dhandler notifies PWRB, XHC1 and HDEF only - never XHC2 or XHC3.With PME control taken, the root port PME interrupt is serviced and the controller is resumed as soon as something is plugged in.
Use
calculate_control()for the assumed set rather than open coding it:
that is by definition the set that would have been requested, it drops a magic constant, and on a kernel built without some of those features it no longer claims control of services the OS cannot provide.Note this also makes
pme_is_native()true for pciehp (drivers/pci/hotplug/pciehp_core.c), so hotplug ports now disable their interrupt across suspend and clear spurious events on resume, as they do on every non-Apple machine.x86_apple_machinecovers Macs back to 2006, so this reaches models that were never tested with native PME.Tested on a MacBookPro14,3, 6.18.49, with a USB 3.1 SATA enclosure:
- before: plugged in at 09:20 with 0000:7d:00.0 runtime suspended, still invisible at 09:21 with
PME_Statuslatched at 1; only appeared when 0000:7d:00.0 was forced to power/control=on.
- with pcie_ports=native (which reaches the same code path inget_port_device_capability()):pcie_pmebound to 5 root ports, and the same enclosure enumerated in the same second it was plugged in, 66 s after the controller had suspended, with nothing forced.
- with this patch, nothing forced:pcie_pmebound to 5 root ports and the_OSCline now reads "OS assumes control of [PCIeHotplug SHPCHotplug PME AER PCIeCapability LTR DPC]". The enclosure was unplugged at 20:42:13, 0000:7d:00.0 runtime suspended to D3hot, and on plugging it back in at 20:43:06 (53 s later) the root port took a PME interrupt and the device enumerated in the same second.
- wifi, internal keyboard, touchbar and audio all came up; 66 warnings and 45 errors during boot, against 65/43 on the distribution kernel.One rough edge, reproducible on both boots: the port driver logs "PME: Spurious native interrupt!" right before the device is resumed.
pcie_pme_handle_request()falls back topcie_pme_walk_bus()when the root port reports its own requester ID withoutPCI_PM_CTRL_PME_STATUSset, and that walk skips PCIe devices, so the xHCI is never attributed. The device is still resumed and the wakeup works, but attributing PMEs from PCIe devices behind a switch looks like a separate shortcoming; it is not addressed here.Boot with pcie_ports=native also showed no new AER/DPC activity and slightly fewer warnings than a boot without it, so taking the remaining control bit does not appear to disturb anything else on this machine.
Quoted 7 lines
Quoted 2 lines
Fixes 7bc5a2bad0b8 ("ACPI: Support _OSI("Darwin") correctly")Signed-off-by Francisco Beltrán Millalén <[email protected]>I don't object from a PCI standpoint, but Rafael generally takes care of
acpi/pci_root.c. I would again suggest putting some of the commit log detail below "---" and relying on the https://patch.msgid.link/
tag Rafael adds.
So I trimmed the changelog a bit and applied the patch as 7.5 material.
Thanks!
Quoted 20 lines
Quoted 20 lines
---
drivers/acpi/pci_root.c +6
-1@@ -568,9 +568,14 @@* Apple always return failure on _OSC calls when _OSI("Darwin") has * been called successfully. We know the feature set supported by the * platform, so avoid calling _OSC at all +* +* Assume control of the same features that would have been requested, +* PCIe PME included. Leaving PME out disables the PME service for +* every root port, and nothing takes over: a device that runtime +* suspends asserts PME# and is never resumed. */ if (x86_apple_machine) { - root->osc_control_set = ~OSC_PCI_EXPRESS_PME_CONTROL ; + root->osc_control_set =calculate_control ();decode_osc_control (root,"OS assumes control of" , root->osc_control_set);return ;