Contribute
Register

USB-C Hotplug Questions

Status
Not open for further replies.
Joined
Dec 7, 2010
Messages
25
Motherboard
Mac Mini
CPU
Core i7
Graphics
Integrated
Mac
  1. 0
Classic Mac
  1. 0
Mobile Phone
  1. 0
I have a Dell XPS 15 9550 but this may be applicable to other laptops as well. Apologies if this has already been fixed or discussed, but I could find very little information on it.

The 9550's USB-C USB 3.1/TB3 port looks to be powered by a similar chip to the new Macbook Pros (the 15" has two of them). Unlike the MBP, on the 9550 this chip only appears to be powered on when a device is inserted into the USB-C port. Under Windows, the USB 3.1 controller appears or disappears in Device Manager depending on what's in the port, so this appears to be a hardware design choice.

On the 9550, when a USB 3.1 device is inserted, it appears in the ACPI chain as RP15-> PXSX (which lights up as a PCI-to-PCI bridge) -> the third of four PCI busses on the chip -> the USB 3.1 controller -> first of two USB 3.1 port devices. On the MBP, the PCI-to-PCI bridge is called UPSB, the four PCI buses are DSB0-3, and the USB 3.1 controller is XHC2. The Thunderbolt controller, called NHI0 on the MBP, is on the chip's first PCI bus.

I've gotten a USB 3.1 device to hotplug (briefly) by overriding the _RMV method on RP15.PXSX to return 1. This makes OS X think the PCI-to-PCI bridge is an ExpressCard (IOPCIHotplugCapable in ioreg), so it scans for, and loads kexts for the 9550's chip when a device is inserted and it is powered on. There's even a card icon that appears in the menu bar that gives you the option to power off the bus. I've also gotten the 9550 to sleep properly when a USB 3.1 device is present by creating an ACPI device tree all the way down to the USB 3.1 controller (calling it XHC2) and creating a _PRW method on it.

The problem is, when you unplug the device, or use the ExpressCard menu to power off the bus, the USB 3.1 controller being unplugged isn't handled properly. I get the following errors:

Code:
XHC2@00000000: AppleUSBXHCIPCI::hardwareException: controller unplugged (regRead32)
XHC2@00000000: AppleUSBXHCI::hardwareException: Error saving state
XHC2@00000000: AppleUSBHostController::setPowerStateGated: going to state 1 returned 0xe00002e9

I *think* this could be handled by creating _PS0 and _PS3 methods on the XHC2 device, but I have no idea how to go about this. None of the info I've found on the Apple developer's site has been helpful. Looking at the ACPI code from the XHC2 device on the MBP, _PS3 fiddles with bits in the PCI_Config space, but I don't know if the chips are similar enough for this to work. Windows 10 handles this gracefully without any ACPI code somehow. I know it's also possible on OS X since there are USB 3 ExpressCards that work, but I'm assuming they have their own drivers.

I'm hoping someone more knowledgeable about how USB works in OS X (@RehabMan ?) might be able to help. I've attached the SSDT with the ACPI objects I'm using.
 

Attachments

  • SSDT-TB.dsl
    15.2 KB · Views: 623
I have a Dell XPS 15 9550 but this may be applicable to other laptops as well. Apologies if this has already been fixed or discussed, but I could find very little information on it.

The 9550's USB-C USB 3.1/TB3 port looks to be powered by a similar chip to the new Macbook Pros (the 15" has two of them). Unlike the MBP, on the 9550 this chip only appears to be powered on when a device is inserted into the USB-C port. Under Windows, the USB 3.1 controller appears or disappears in Device Manager depending on what's in the port, so this appears to be a hardware design choice.

On the 9550, when a USB 3.1 device is inserted, it appears in the ACPI chain as RP15-> PXSX (which lights up as a PCI-to-PCI bridge) -> the third of four PCI busses on the chip -> the USB 3.1 controller -> first of two USB 3.1 port devices. On the MBP, the PCI-to-PCI bridge is called UPSB, the four PCI buses are DSB0-3, and the USB 3.1 controller is XHC2. The Thunderbolt controller, called NHI0 on the MBP, is on the chip's first PCI bus.

I've gotten a USB 3.1 device to hotplug (briefly) by overriding the _RMV method on RP15.PXSX to return 1. This makes OS X think the PCI-to-PCI bridge is an ExpressCard (IOPCIHotplugCapable in ioreg), so it scans for, and loads kexts for the 9550's chip when a device is inserted and it is powered on. There's even a card icon that appears in the menu bar that gives you the option to power off the bus. I've also gotten the 9550 to sleep properly when a USB 3.1 device is present by creating an ACPI device tree all the way down to the USB 3.1 controller (calling it XHC2) and creating a _PRW method on it.

The problem is, when you unplug the device, or use the ExpressCard menu to power off the bus, the USB 3.1 controller being unplugged isn't handled properly. I get the following errors:

Code:
XHC2@00000000: AppleUSBXHCIPCI::hardwareException: controller unplugged (regRead32)
XHC2@00000000: AppleUSBXHCI::hardwareException: Error saving state
XHC2@00000000: AppleUSBHostController::setPowerStateGated: going to state 1 returned 0xe00002e9

I *think* this could be handled by creating _PS0 and _PS3 methods on the XHC2 device, but I have no idea how to go about this. None of the info I've found on the Apple developer's site has been helpful. Looking at the ACPI code from the XHC2 device on the MBP, _PS3 fiddles with bits in the PCI_Config space, but I don't know if the chips are similar enough for this to work. Windows 10 handles this gracefully without any ACPI code somehow. I know it's also possible on OS X since there are USB 3 ExpressCards that work, but I'm assuming they have their own drivers.

I'm hoping someone more knowledgeable about how USB works in OS X (@RehabMan ?) might be able to help. I've attached the SSDT with the ACPI objects I'm using.

As far as I know, OS X/macOS doesn't support PCI hotplug. And it sounds like you would need it...
 
As far as I know, OS X/macOS doesn't support PCI hotplug. And it sounds like you would need it...

How would ExpressCards with USB controllers on them work?

Or maybe a better question; how does macOS implement power management for USB controllers? Does it call any ACPI methods?
 
How would ExpressCards with USB controllers on them work?

No experience with ExpressCard, but I can't think of any Macs with ExpressCard slots.
And presumably it would work just fine (on a hack) if you left the card plugged in (no hotplug issue if it is always plugged from boot onwards).

Or maybe a better question; how does macOS implement power management for USB controllers? Does it call any ACPI methods?

Power management for the onboard USB controllers is done via ACPI and/or facilities in the chipset. But there is no PCI hotplug going on there... the USB controller remains always connected to the PCI bus.
 
No experience with ExpressCard, but I can't think of any Macs with ExpressCard slots.
And presumably it would work just fine (on a hack) if you left the card plugged in (no hotplug issue if it is always plugged from boot onwards).

2011 Macbook Pros and prior had ExpressCard slots. It looks like the code to support them is still in Sierra, but who knows when or if Apple will remove it. When you add _RMV = 1 to the RP15.PXSX device, it adds an IOPCIHotplug property in the IO registry. When you plug in a USB 3.1 device, everything that appears underneath RP15.PXSX also has an IOPCIEjectable property. I've attached IORegistryExplorer output from before and after a hotplug if you're curious.

I've managed to figure out why I could only hotplug a USB device a couple of times, and it turns out it is not the USB controller errors. I found that when I would hotplug something the first time, I'd get ACPI errors:

Code:
ACPI Error: ACPI Error: [SPRT][SPRT] Namespace lookup failure, AE_ALREADY_EXISTS Namespace lookup failure, AE_ALREADY_EXISTS (20140828/dswload2-19d)
ACPI Exception: AE_ALREADY_EXISTS, ACPI Exception: AE_ALREADY_EXISTS, During name lookup/catalogDuring name lookup/catalog (20140828/psobject-131)
ACPI Error: ACPI Error: Method parse/execution failed Method parse/execution failed [\134_GPE._E42] (Node ffffff80507d8010)[\134_GPE._E42] (Node ffffff80507d8010), AE_ALREADY_EXISTS, AE_ALREADY_EXISTS (20140828/psparse-270)
ACPI Error: ACPI Error: Method parse/execution failed Method parse/execution failed [\134_GPE._E42] (Node ffffff80507d8010)[\134_GPE._E42] (Node ffffff80507d8010), AE_ALREADY_EXISTS, AE_ALREADY_EXISTS (20140828/psparse-270)
ACPI: ACPI: Marking method _E42 as Serialized because of AE_ALREADY_EXISTS errorMarking method _E42 as Serialized because of AE_ALREADY_EXISTS error
ACPI Exception: AE_ALREADY_EXISTS, ACPI Exception: AE_ALREADY_EXISTS, while evaluating GPE method [_E42]while evaluating GPE method [_E42] (20140828/evgpe-2f5)

Trying to figure out what this error meant, it turns out that Linux has had the same issue (see http://marc.info/?l=linux-acpi&m=145929159015853&w=2, which fixes the issue with a kernel patch). The method _E42 is called by GPE interrupt whenever a device is plugged or unplugged from the USB-C port, and it calls itself recursively, causing the error. I fixed the issue by hotpatching the recursive call to instead call a duplicate method called XE42 that I added via an SSDT. I suspect that after the ACPI error _E42 was never called again, and never notifying the OS that there was a hotplug event. I still get the USB controller errors, but I can perform multiple hotplugs with no issue.

While I'd like to figure out how to get rid of the controller errors, the big issue now is that hotplug doesn't work after sleep, and the log is filled with these errors until I unplug the device:

Code:
XHC2@00000000: AppleUSBXHCIPCI::hardwareException: controller unplugged (regRead32)
XHC2@00000000: AppleUSBXHCI::hardwareException: kUSBStatusControllerNotReady did not clear
XHC2@00000000: AppleUSBHostController::setPowerStateGated: going to state 2 failed with 0xe00002e9
SSP1@00100000: AppleUSBXHCIPort::powerOn: deadline passed (PORTSC 0x00000000)

I suspect this won't be a difficult problem to solve, but I'm still working on it.
 

Attachments

  • pre-usbc-hotplug.ioreg
    5.3 MB · Views: 333
  • post-usbc-hotplug.ioreg
    5.4 MB · Views: 309
Trying to figure out what this error meant, it turns out that Linux has had the same issue (see http://marc.info/?l=linux-acpi&m=145929159015853&w=2, which fixes the issue with a kernel patch). The method _E42 is called by GPE interrupt whenever a device is plugged or unplugged from the USB-C port, and it calls itself recursively, causing the error.

Sounds like the code in DSDT is correct, but it exposes a bug in acpica. Apple's ACPI implementation uses Intel's acpica, so it would be affected by the bug as well. Although the bug was fixed in acpica, who knows when it will be making it into macOS's ACPI implementation. Windows is not affected as Windows does not use Intel's acpica. Perhaps you can re-write the _E42 method such that it does what it needs to but without running into the acpica recursion bug (sounds like it has to do with a mutex).
 
Sounds like the code in DSDT is correct, but it exposes a bug in acpica. Apple's ACPI implementation uses Intel's acpica, so it would be affected by the bug as well. Although the bug was fixed in acpica, who knows when it will be making it into macOS's ACPI implementation. Windows is not affected as Windows does not use Intel's acpica. Perhaps you can re-write the _E42 method such that it does what it needs to but without running into the acpica recursion bug (sounds like it has to do with a mutex).

If it only recurses once (which seems to be the case) would duplicating the method and patching the recursive call to call the duplicate not be sufficient? I'm admittedly a bit out of my depth here... the ACPI documentation is easy to follow, but when it comes to Dell's specific implementation I'm guessing as to what a lot of the methods and variables actually do. Your ACPIDebug has been very helpful in trying to figure things out.

I did find that older versions of Dell's BIOS skipped the recursive call and returned zero if the OS was Windows 2013, so I rewrote it to just omit the call, and it seems to work fine... here's the code in question:

Code:
        Method (_E42, 0, NotSerialized)  // _Exx: Edge-Triggered GPE 
        { 
            (...) 

            ADBG ("TBT-HP-Handler") 
            ADBG ("PEG WorkAround") 
            PGWA () 
            Acquire (OSUM, 0xFFFF) 
            Local1 = TBFF () 
            If ((Local1 == One)) 
            { 
                Sleep (0x10) 
                Release (OSUM) 
                ADBG ("OS_Up_Received") 
                If (DPTF == One) 
                { 
                    _E42 ()     // I'm hotpatching this section now to call a method that returns zero.
                } 

                Return (Zero) 
            } 

            (...) 
        }

I'm guessing that DPTF is a bit that is set when Dynamic Power and Thermal Framework is available? But again, just a guess.

Thank you for the help!
 
If it only recurses once (which seems to be the case) would duplicating the method and patching the recursive call to call the duplicate not be sufficient? I'm admittedly a bit out of my depth here... the ACPI documentation is easy to follow, but when it comes to Dell's specific implementation I'm guessing as to what a lot of the methods and variables actually do. Your ACPIDebug has been very helpful in trying to figure things out.

I did find that older versions of Dell's BIOS skipped the recursive call and returned zero if the OS was Windows 2013, so I rewrote it to just omit the call, and it seems to work fine... here's the code in question:

Code:
        Method (_E42, 0, NotSerialized)  // _Exx: Edge-Triggered GPE
        {
            (...)

            ADBG ("TBT-HP-Handler")
            ADBG ("PEG WorkAround")
            PGWA ()
            Acquire (OSUM, 0xFFFF)
            Local1 = TBFF ()
            If ((Local1 == One))
            {
                Sleep (0x10)
                Release (OSUM)
                ADBG ("OS_Up_Received")
                If (DPTF == One)
                {
                    _E42 ()     // I'm hotpatching this section now to call a method that returns zero.
                }

                Return (Zero)
            }

            (...)
        }

I'm guessing that DPTF is a bit that is set when Dynamic Power and Thermal Framework is available? But again, just a guess.

Thank you for the help!

I would have to see the entire method and related code.
 
I would have to see the entire method and related code.

Here's the method. I'll also attach the decompiled DSDT.

Code:
        Method (_E42, 0, NotSerialized)  // _Exx: Edge-Triggered GPE
        {
            ADBG ("_E42")
            If (LEqual (CF2T, One))
            {
                ADBG ("Clear")
                ADBG ("GPI_GPE_STS")
                \_SB.CAGS (CPGN)
            }

            WWAK ()
            WSUB ()
            If (LEqual (TNAT, One))
            {
                Store (RSMI (), Local0)
                If (LNot (Local0))
                {
                    Return (Zero)
                }

                If (DMSI ())
                {
                    Return (Zero)
                }
            }

            If (GNIS ())
            {
                Return (Zero)
            }

            OperationRegion (SPRT, SystemIO, 0xB2, 0x02)
            Field (SPRT, ByteAcc, Lock, Preserve)
            {
                SSMP,   8
            }

            ADBG ("TBT-HP-Handler")
            ADBG ("PEG WorkAround")
            PGWA ()
            Acquire (OSUM, 0xFFFF)
            Store (TBFF (), Local1)
            If (LEqual (Local1, One))
            {
                Sleep (0x10)
                Release (OSUM)
                ADBG ("OS_Up_Received")
                If (LEqual (DPTF, One))
                {
                    _E42 ()
                }

                Return (Zero)
            }

            If (LEqual (Local1, 0x02))
            {
                NTFY ()
                Sleep (0x10)
                Release (OSUM)
                P8XH (Zero, 0x7D)
                ADBG ("Disconnect")
                Return (Zero)
            }

            If (LEqual (SOHP, One))
            {
                ADBG ("TBT SW SMI")
                Store (TBSW, SSMP)
            }

            NTFY ()
            Sleep (0x10)
            Release (OSUM)
            ADBG ("End-of-_E42")
        }
 

Attachments

  • DSDT.dsl
    1 MB · Views: 420
Here's the method. I'll also attach the decompiled DSDT.

Code:
        Method (_E42, 0, NotSerialized)  // _Exx: Edge-Triggered GPE
        {
            ADBG ("_E42")
            If (LEqual (CF2T, One))
            {
                ADBG ("Clear")
                ADBG ("GPI_GPE_STS")
                \_SB.CAGS (CPGN)
            }

            WWAK ()
            WSUB ()
            If (LEqual (TNAT, One))
            {
                Store (RSMI (), Local0)
                If (LNot (Local0))
                {
                    Return (Zero)
                }

                If (DMSI ())
                {
                    Return (Zero)
                }
            }

            If (GNIS ())
            {
                Return (Zero)
            }

            OperationRegion (SPRT, SystemIO, 0xB2, 0x02)
            Field (SPRT, ByteAcc, Lock, Preserve)
            {
                SSMP,   8
            }

            ADBG ("TBT-HP-Handler")
            ADBG ("PEG WorkAround")
            PGWA ()
            Acquire (OSUM, 0xFFFF)
            Store (TBFF (), Local1)
            If (LEqual (Local1, One))
            {
                Sleep (0x10)
                Release (OSUM)
                ADBG ("OS_Up_Received")
                If (LEqual (DPTF, One))
                {
                    _E42 ()
                }

                Return (Zero)
            }

            If (LEqual (Local1, 0x02))
            {
                NTFY ()
                Sleep (0x10)
                Release (OSUM)
                P8XH (Zero, 0x7D)
                ADBG ("Disconnect")
                Return (Zero)
            }

            If (LEqual (SOHP, One))
            {
                ADBG ("TBT SW SMI")
                Store (TBSW, SSMP)
            }

            NTFY ()
            Sleep (0x10)
            Release (OSUM)
            ADBG ("End-of-_E42")
        }

It is a little hard to tell what might be causing the problem. Endless recursion?
You should use ACPIDebug.kext to debug it.
 
Status
Not open for further replies.
Back
Top