Skip to content

dwc2: Soft disconnect pullup failure (-EIO) on Kernel 6.18 (bcm2835-usb / Pi Zero 2 W, & PI Zero w) #7549

Description

@bradcar

Describe the bug

It seems the fundamental issue is that the Pi's dwc2 driver under Linux Kernel 6.18 (Trixie) never drives the USB 2.0 D+/D- signal lines upon boot/cable connection, causing the MacBook M2 to wait out its 28-second timeout before shutting down the port's USB 2.0 data layer.

I've spent time trying to remove the possibilities of cable, Pi micro-USB, and macOS issues.

Description

Under Kernel 6.18, binding a USB gadget (either via legacy g_ether or libcomposite ConfigFS) instantiates the usb0 network interface in software, but the dwc2 driver fails to activate the physical D+ pull-up resistor on the USB Device Controller (UDC).

The UDC hardware state remains stuck at not attached, usb0 stays in <NO-CARRIER>, and attempting to manually trigger a soft re-connect via sysfs returns an I/O error (-EIO):

Steps to reproduce the behaviour

$ sudo sh -c 'echo 1 > /sys/class/udc/20980000.usb/soft_connect'
sh: echo: I/O error

Hardware & Cable Verification
To isolate hardware faults before reporting. I have a pi-hole (RP 4).

When I use the micro-USB to USB-A, Both Pi Zero 2 W and Pi Zero W were connected without SD cards to a Pi 4 host via lsusb; both enumerated instantly in bootloader recovery mode (0a5c:2763 and 0a5c:2764).

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 003: ID 0a5c:2763 Broadcom Corp. BCM2708 Boot
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
$

When I use the micro-USB to USB-A, connecting the Pi Zero 2 W (without SD card) to the RP 4, I get the following:.

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 005: ID 0a5c:2764 Broadcom Corp. BCM2710 Boot
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
$ 

This implies the micro-USB to USB-A cable and the data ports on both the Pi Zero W, and Pi Zero 2 W are working.

I also checked both cable setups by transferring data between Sony Camera and Mac M2, with:

  1. micro-USB to USB-A to Apple USB-A to USB-C dongle by transferring data between Mac M2 and camera.
  2. micro-USB to USB-C by transferring data between Mac M2 and camera.
    Using these cables both show High-Speed (480 Mb/s).

Detailed Diagnostics & Log Output

  1. Software Binds cleanly, but UDC State remains not attached
    Running sudo modprobe g_ether executes with return code 0, creating usb0, but the underlying UDC state is never asserted:
$ sudo modprobe g_ether
$ cat /sys/class/udc/20980000.usb/state
not attached
$ ip link show usb0
2: usb0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN mode DEFAULT group default qlen 1000
    link/ether 12:34:56:78:9a:bc brd ff:ff:ff:ff:ff:ff
  1. dmesg confirms the gadget bound to 20980000.usb:
[   7.986338] dwc2 20980000.usb: supply vusb_d not found, using dummy regulator
[   7.986679] dwc2 20980000.usb: supply vusb_a not found, using dummy regulator
[   8.089287] dwc2 20980000.usb: EPs: 8, dedicated fifos, 4080 entries in SPRAM
[  21.188659] g_ether gadget.0: HOST MAC 12:34:56:78:9a:bd
[  21.188699] g_ether gadget.0: MAC 12:34:56:78:9a:bc
[  21.189010] g_ether gadget.0: Ethernet Gadget, version: Memorial Day 2008
[  21.189040] g_ether gadget.0: g_ether ready
[  21.189183] dwc2 20980000.usb: bound driver g_ether
  1. Manual Soft-Connect Control Fails with -EIOAttempting to toggle soft-connect fails at the register level:
$ sudo sh -c 'echo 0 > /sys/class/udc/20980000.usb/soft_connect'
sleep 1
$ sudo sh -c 'echo 1 > /sys/class/udc/20980000.usb/soft_connect'
sh: echo: I/O error
  1. macOS Host Hardware Sampling Log (IOAccessoryManager)
    Live kernel logging on the macOS host shows the Mac detects Type-C CC line insertion and keeps the USB 2.0 port powered listening for signaling, but times out after receiving 0 Hz clock frequency from the Pi:
[Port-USB-C@3: CC] active: YES (transportType: 1 [CC])
AppleUSBHostPort::cableChangeOccurred: powering on
[Port-USB-C@3: USB2] Setting nominal signaling frequencies... (nominalSignalingFrequenciesHz: NO, nominalSignalingFrequenciesHz.count: 0)
[Port-USB-C@3: USB2] active: NO

Code-Level Analysis (drivers/usb/dwc2/gadget.c)
Writing to /sys/class/udc/20980000.usb/soft_connect routes through the Linux UDC core to dwc2_hsotg_pullup() in drivers/usb/dwc2/gadget.c:

static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on)
{
	struct dwc2_hsotg *hsotg = to_hsotg(gadget);
	unsigned long flags;
	int ret = 0;

	/* Unusual Early-return?  **** catch-22?  */
	if (!hsotg->enabled)
		return -EIO;

	spin_lock_irqsave(&hsotg->lock, flags);

	hsotg->gadget_connected = is_on;

	if (is_on) {
		dwc2_hsotg_core_start(hsotg);

		if (hsotg->enabled)
			ret = dwc2_hsotg_wait_bit_clear(hsotg, DCTL, DCTL_SFTDIS, 100000);
	} else {
		dwc2_sftdis(hsotg, true);
	}

	spin_unlock_irqrestore(&hsotg->lock, flags);

	return ret;
}

The State-Machine Catch-22 and throwing EIO
hsotg->enabled seems to show whether the internal controller clocks and power domains are active (1), rather than module presence!

If power domain is gated/disabled at boot (or put to sleep?), hsotg->enabled is 0.
When userspace or udc-core invokes pullup(is_on = 1) to connect the USB gadget, dwc2_hsotg_pullup() checks if (!hsotg->enabled) before calling dwc2_hsotg_core_start(hsotg).

It immediately returns -EIO to userspace without ever executing dwc2_hsotg_core_start() to wake the controller clock.
...or...
If dwc2_hsotg_core_start() is called but fails to clear DCTL_SFTDIS on the Broadcom BCM2837 SoC, dwc2_hsotg_wait_bit_clear() times out waiting for the MMIO register bit to acknowledge, which also returns -EIO.

This doesn't make sense to me.

Device (s)

Raspberry Pi Zero 2 W

System

Environment

  • Pi: Raspberry Pi Zero 2 W (reproduced on Pi Zero W)
  • OS: Raspberry Pi OS (Trixie / Debian 13)
  • Kernel: 6.18.x (6.18.34+rpt-rpi-v6)
  • Host: Apple MacBook Pro M2 (macOS Tahoe)
  • Connection: Direct Micro-USB to USB-C data cable (no hub/adapter)

Logs

Below is the complete macOS Host Hardware Sampling Log (IOAccessoryManager)

$ sudo log stream --predicate 'process == "kernel" AND eventMessage CONTAINS[c] "USB"' --style compact

While that was running, on the Pi Zero W (Micro-USB to USB-A to apple Dongle to USB-C to M2) I did this:

$ sudo rpi-usb-gadget on
Turning on USB Gadget mode
Reboot to apply changes
$ sudo reboot

Here is the output from the Mac:

$ sudo log stream --predicate 'process == "kernel" AND eventMessage CONTAINS[c] "USB"' --style compact
Password:
Filtering the log data using "process == "kernel" AND composedMessage CONTAINS[c] "USB""
Timestamp               Ty Process[PID:TID]
2026-08-10 23:49:10.353 Df kernel[0:1567d8] (IOAccessoryManager) IOPortTransportState::setNominalSignalingFrequencies(): [Port-USB-C@3: USB2] Setting nominal signaling frequencies... (nominalSignalingFrequenciesHz: NO, nominalSignalingFrequenciesHz.count: 0)
2026-08-10 23:49:10.353 Df kernel[0:1567d8] (IOAccessoryManager) IOPortTransportStateUSB2::setActive(): [Port-USB-C@3: USB2] active: NO
2026-08-10 23:49:10.353 Df kernel[0:1567d8] (IOAccessoryManager) IOPortTransportState::setActive(): [Port-USB-C@3: USB2] active: NO (transportType: 2 [USB2])

I then unplugged the Pi Zero W, plugged in the microUSB to USB-C cable and saw the output below where the macOS IOAccessoryManager detects physical Type-C CC line insertion and powers on Port-USB-C@3. It waits 28 seconds for USB 2.0 High-Speed signaling before timing out.

2026-08-10 23:58:52.586 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::setActive(): [Port-USB-C@3: CC] active: YES (transportType: 1 [CC])
2026-08-10 23:58:52.586 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: CC] Handling state change...
2026-08-10 23:58:52.587 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [@: IOPortTransportStateUSB3] Handling state change...
2026-08-10 23:58:52.587 Df kernel[0:2ea] (IOAccessoryManager) IOPort::addTransport(): [Port-USB-C@3] Adding transport... (transportType: 3 [USB3], index: 0)
2026-08-10 23:58:52.589 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::start(): [Port-USB-C@3: USB2] m_parentPort->isFeatureEnabled(kIOPortFeatureTRM): YES, this->isTRMSupported(): YES, (transportType: 2, parentPortType: 2, parentPortNumber: 3)
2026-08-10 23:58:52.589 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::start(): [Port-USB-C@3: USB2] Found ACM service during startup! (transportType: 2, parentPortType: 2, parentPortNumber: 3)
2026-08-10 23:58:52.589 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::_updatePreDefinedMetadataProperties(): [Port-USB-C@3: USB2] Updating pre-defined metadata properties...
2026-08-10 23:58:52.589 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::setAuthenticationTimeout(): [Port-USB-C@3: USB2] authTimeout: 0 seconds
2026-08-10 23:58:52.589 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::setDriverStatus(): [Port-USB-C@3: USB2] driverStatus: 1 [Not Ready]
2026-08-10 23:58:52.589 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::registerService(): [Port-USB-C@3: USB2] Registering service USB2@(null)... (transportType: 2, provider: Port-USB-C@3)
2026-08-10 23:58:52.589 Df kernel[0:2ea] (IOAccessoryManager) IOPort::addTransport_block_invoke(): [Port-USB-C@3] Added transport! (transport: Port-USB-C@3/USB2)
2026-08-10 23:58:52.589 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:58:52.593 Df kernel[0:150f33] (IOAccessoryManager) IOPortTransportState::_handleNotification_block_invoke(): [Port-USB-C@3: USB3] m_trmInfo.acmState.state: 0 -> 2 [Unknown -> Limited], m_trmInfo.acmState.gracePeriodReason: 0 -> 4 [Unknown -> Device Unlocked], m_trmInfo.acmState.profile: 0 -> 2 [Unknown -> Ask for New Accessories], m_trmInfo.acmState.deviceLocked: YES -> NO, m_trmInfo.acmState.cacheMiss: NO -> NO, m_trmInfo.acmState.relaxedPeriod: NO -> YES
2026-08-10 23:58:52.593 Df kernel[0:150f33] (IOAccessoryManager) IOPortTransportState::_updateTRMState(): [Port-USB-C@3: USB3] Updating TRM state...
2026-08-10 23:58:52.593 Df kernel[0:150f33] (IOAccessoryManager) IOPortTransportStateUSB::calculateAuthorizationState(): [Port-USB-C@3: USB3] Calculating authorization state...
2026-08-10 23:58:52.593 Df kernel[0:150f33] (IOAccessoryManager) IOPortTransportState::calculateAuthorizationState(): [Port-USB-C@3: USB3] Calculating authorization state...
2026-08-10 23:58:52.593 Df kernel[0:150f33] (IOAccessoryManager) IOPortTransportState::calculateAuthorizationState_block_invoke(): [Port-USB-C@3: USB3] *** LTNART ***
2026-08-10 23:58:52.593 Df kernel[0:150f33] (IOAccessoryManager) IOPortTransportState::_updateTRMState_block_invoke(): [Port-USB-C@3: USB3] m_trmInfo.identificationRestricted: [YES -> NO], m_trmInfo.transportRestricted: [YES -> YES], m_trmInfo.state: 2, m_authorizationStatus: 1, m_active: NO
2026-08-10 23:58:52.593 Df kernel[0:150f33] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB3] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:58:52.593 Df kernel[0:15934b] (IOAccessoryManager) IOPortTransportState::_handleNotification_block_invoke(): [Port-USB-C@3: USB2] m_trmInfo.acmState.state: 0 -> 2 [Unknown -> Limited], m_trmInfo.acmState.gracePeriodReason: 0 -> 4 [Unknown -> Device Unlocked], m_trmInfo.acmState.profile: 0 -> 2 [Unknown -> Ask for New Accessories], m_trmInfo.acmState.deviceLocked: YES -> NO, m_trmInfo.acmState.cacheMiss: NO -> NO, m_trmInfo.acmState.relaxedPeriod: NO -> YES
2026-08-10 23:58:52.593 Df kernel[0:15934b] (IOAccessoryManager) IOPortTransportState::_updateTRMState(): [Port-USB-C@3: USB2] Updating TRM state...
2026-08-10 23:58:52.593 Df kernel[0:15934b] (IOAccessoryManager) IOPortTransportStateUSB::calculateAuthorizationState(): [Port-USB-C@3: USB2] Calculating authorization state...
2026-08-10 23:58:52.593 Df kernel[0:15934b] (IOAccessoryManager) IOPortTransportState::calculateAuthorizationState(): [Port-USB-C@3: USB2] Calculating authorization state...
2026-08-10 23:58:52.593 Df kernel[0:15934b] (IOAccessoryManager) IOPortTransportState::calculateAuthorizationState_block_invoke(): [Port-USB-C@3: USB2] *** LTNART ***
2026-08-10 23:58:52.593 Df kernel[0:15934b] (IOAccessoryManager) IOPortTransportState::_updateTRMState_block_invoke(): [Port-USB-C@3: USB2] m_trmInfo.identificationRestricted: [YES -> NO], m_trmInfo.transportRestricted: [YES -> YES], m_trmInfo.state: 2, m_authorizationStatus: 1, m_active: NO
2026-08-10 23:58:52.593 Df kernel[0:15934b] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB2] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:58:52.593 Df kernel[0:150f6f] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3] Sending 1 message(s)... (m_propertyChanged: NO)
2026-08-10 23:58:52.643 Df kernel[0:646] (IOAccessoryManager) IOPortTransportState::setDriverStatus(): [Port-USB-C@3: USB2] driverStatus: 2 [Ready]
2026-08-10 23:58:52.643 Df kernel[0:64a] (IOAccessoryManager) IOPortTransportState::setDriverStatus(): [Port-USB-C@3: USB3] driverStatus: 2 [Ready]
2026-08-10 23:58:52.643 Df kernel[0:64a] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB3] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:58:52.643 Df kernel[0:646] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB2] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:58:52.646 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: USB3] Handling state change...
2026-08-10 23:58:52.646 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB3] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:58:52.646 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: USB2] Handling state change...
2026-08-10 23:58:52.646 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB2] Sending 1 message(s)... (m_propertyChanged: NO)
2026-08-10 23:58:52.654 Df kernel[0:64a] (IOUSBHostFamily) usb-drd2-port-ss@02200000: AppleUSBHostPort::cableChangeOccurred: powering on
2026-08-10 23:58:52.654 Df kernel[0:646] (IOUSBHostFamily) usb-drd2-port-hs@02100000: AppleUSBHostPort::cableChangeOccurred: powering on
2026-08-10 23:58:52.664 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: USB3] Handling state change...
2026-08-10 23:58:52.664 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB3] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:58:52.664 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: USB2] Handling state change...
2026-08-10 23:58:52.664 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB2] Sending 1 message(s)... (m_propertyChanged: NO)
2026-08-10 23:58:52.667 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: USB3] Handling state change...
2026-08-10 23:58:52.667 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB3] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:58:52.667 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: USB2] Handling state change...
2026-08-10 23:58:52.667 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB2] Sending 1 message(s)... (m_propertyChanged: NO)
2026-08-10 23:58:52.667 Df kernel[0:2ea] (AppleHPM) AppleTCController::printConnectedTransports(0x2@0x3b): AppleTCController::printConnectedTransports(@0x3b) - setting USB2 USB3 as DFP |   |  ,   | as connected
2026-08-10 23:58:53.910 Df kernel[0:157396] (IOAccessoryManager) IOPortTransportState::setNominalSignalingFrequencies(): [Port-USB-C@3: USB3] Setting nominal signaling frequencies... (nominalSignalingFrequenciesHz: YES, nominalSignalingFrequenciesHz.count: 1)
2026-08-10 23:58:53.910 Df kernel[0:157396] (IOAccessoryManager) IOPortTransportStateUSB3::setActive(): [Port-USB-C@3: USB3] active: NO
2026-08-10 23:58:53.910 Df kernel[0:157396] (IOAccessoryManager) IOPortTransportState::setActive(): [Port-USB-C@3: USB3] active: NO (transportType: 3 [USB3])
2026-08-10 23:59:01.914 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: USB3] Handling state change...
2026-08-10 23:59:01.914 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB3] Sending 2 message(s)... (m_propertyChanged: YES)
2026-08-10 23:59:01.914 Df kernel[0:2ea] (IOAccessoryManager) IOPortTransportState::handleStateChange(): [Port-USB-C@3: USB2] Handling state change...
2026-08-10 23:59:01.914 Df kernel[0:2ea] (IOAccessoryManager) IOServiceNotificationManager::sendMessages(): [Port-USB-C@3/USB2] Sending 1 message(s)... (m_propertyChanged: NO)
2026-08-10 23:59:20.543 Df kernel[0:157396] (IOAccessoryManager) IOPortTransportState::setNominalSignalingFrequencies(): [Port-USB-C@3: USB2] Setting nominal signaling frequencies... (nominalSignalingFrequenciesHz: NO, nominalSignalingFrequenciesHz.count: 0)
2026-08-10 23:59:20.543 Df kernel[0:157396] (IOAccessoryManager) IOPortTransportStateUSB2::setActive(): [Port-USB-C@3: USB2] active: NO
2026-08-10 23:59:20.543 Df kernel[0:157396] (IOAccessoryManager) IOPortTransportState::setActive(): [Port-USB-C@3: USB2] active: NO (transportType: 2 [USB2])
2026-08-10 23:59:22.606 Df kernel[0:135829] (IOAccessoryManager) IOPortFamily::registerService(): [Port-USB-C@3] Registering service... (providerPath: IOService:/AppleARMPE/arm-io/AppleT602xIO/i2c0@9B040000/AppleS5L8940XI2CController/hpmBusManager@6B/AppleHPMBusController/hpm2/AppleHPMARMI2C/AppleHPMDevice@3B, options: 8)
2026-08-10 23:59:22.606 Df kernel[0:135829] (IOAccessoryManager) Port-USB-C::configureAccessoryPowerInternal: _powerState=3, _powerStateInternal=1, _sleeping=0, _accessoryPowerModeExtOffRequested 0
2026-08-10 23:59:22.606 Df kernel[0:135829] (IOAccessoryManager) Port-USB-C::configureAccessoryPowerInternal: !getAccessoryPowerReady(); set PowerModeOff?
2026-08-10 23:59:22.606 Df kernel[0:135829] (IOAccessoryManager) Port-USB-C::configureAccessoryPowerInternal: handleAccessoryPowerWillChange, confirm PowerModeOff

Additional context

my full journey is logged at:
raspberrypi/rpi-usb-gadget#29

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions