From nobody Fri Apr 3 01:25:56 2026 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3BB641CAA79 for ; Thu, 26 Mar 2026 01:17:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774487838; cv=none; b=M6aop0p+vmAAvu5j5eoUspN+mrEO8Adf5gbdYWlzs5QZNmDNt80Ndx5eqhR6MrDIao8s3yN7n7zN54j0nfJ+AHCjnRPueszUlBKBX/Jw52ElomaNx3k/SsU9ZSz5W6RcJ6K3deSxYmW5v3nnt4qpTpGjl2tNVQDsEoYHdVROABw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774487838; c=relaxed/simple; bh=FqOlEAwOfCbP23aG6bMYtRghAfx1YBK01HwsP1a1RWI=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type; b=l9se/O9ME5W0yD7Gj/un38j02Lmctp0R0it6MCeXuUBt9qeqkTUb+m8ldKp8zvzFJy3olxoY9rpRMVXQrDw7Dx7OVwA37oU78gY7DrmA5q7gbLJuTP25EbQWeAzcyV7lpx8Uanu6lnMScgPlYcBFHIp/OL7NSpGn7kJmsottXIM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=reject dis=none) header.from=canonical.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=m/odJqfM; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=reject dis=none) header.from=canonical.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="m/odJqfM" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-4870206f73bso2469825e9.3 for ; Wed, 25 Mar 2026 18:17:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1774487835; x=1775092635; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:sender:from:to:cc:subject:date:message-id:reply-to; bh=tmwvz4ZJv9HEj3L/hWGz1xzYb0Wd5vrxJOQo6Gk1qm4=; b=m/odJqfM39kZbkPwYqeVJlI7gUjv/PManZNjI5FzMUeGSMP4qz+zthNno6G0nsXWJS 3VBUcYu9ZjD9CCk/XNYB8rAPQuyJUSmSppSV3k42id9IYMLVom2+NLQEF7udHjfNGgdv MPT4mTnbRvmlQFhU5/8uBKIKJWsO1nofZOweyFdrDjem62Tr6W9f1E3sTo0cuHluofMi NIhx7Y6Z+X8nrX4SsugUpHRiu6KKp0PX/u6y0Kv0SvgcoDrZxnyv/TL+vR686QE2y8b6 3xEeSvwXx6MpQBQ5LITrTIiwutQEHc2K5WIQzOFn7NIVNW6AOv9vwY4sB01Tz3pEYBcQ qrjA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774487835; x=1775092635; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:sender:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=tmwvz4ZJv9HEj3L/hWGz1xzYb0Wd5vrxJOQo6Gk1qm4=; b=TxPcCqkUScCJg0qe6+0mi2yyS70doWo263TL8zXsqldsw9BiveiKCsm9+226HU2jI+ KpuvOMOOOxPVvFOG4o8ErypvNU4RB3Of/ubXH3g/Grf4hajL39BCvdKsQGIgtGt/G69j v7AB17kx63UiNg2PrxUgvtGDn92MqK6Ng4epfWe7T1AQvoQqq/8S+8iPUNEcZx/SacPM YIuYf9cwx2dtx9ziF9MCpV4Ef4H6k4VG/V/Ts2j8/QJMpH5dyYg6yD5lHQvxONiTdSsz eZaF91+2BMzJ1aVnf8DPqmRqqxAjsAJWTn/RgPqAgxZg6Zu+phCGRl6P9cdEJWMN8yPW iPvA== X-Forwarded-Encrypted: i=1; AJvYcCVcpypMzqHoqBOFyyx0HnI19MuPmCAyDjkO/slnV6N1mKTMaDWLefGsxB5FPYGgAFE6qYmUg8JoHPvpnYw=@vger.kernel.org X-Gm-Message-State: AOJu0YwNfaY1G64aixgABoKrwj982Sg0xu0HehAhh+3aREa9shd6J8Vh S/xTIqCjtMqqDWZp9eytT0MWcn3PJLso8mEFr1gRATw6DdBBUnHRsYqf X-Gm-Gg: ATEYQzyavSABM1akU5HHepGhdXYjadydBoQ0l+mRWLnrDiLG0jsfCw0y6Gu/2tHEXAp YbTrMpL5gtxi/UhX+wMDUX7XQ5a6oZl22nsr6GU5+IYmfgVZI82AG17WfZh220+yAFq6z4ejbhM MZbFH/VTQT1u332E1/dVhbfdenUJvNGiTvl/FEzQEGsUMQD8s9IasdG0yZiHjwdgOUejVF1Zhq3 2plf8qrM01EvH9wBukZklIeuV0HFT0t+ZVnfITzYKxAfvcX7fAo+h3UG7BTC9S4Icc9Gbj759Xs blPQnYCOFTgOE98boi+AShYMqVUgKIJ7IFtE2g/gXRgdi7jvrxFS9SANS9uypVv0Ox6FDKaLgYD dvu7432K527prHtRQ4JXzsnAMp8jwnEZT5MX1c20pKOv/MwK2ZqRXG4LAxcsu2sJyrY+aQLrm22 2cwNgn2Q== X-Received: by 2002:a05:600c:3b12:b0:47e:e2eb:bc22 with SMTP id 5b1f17b1804b1-48715fc3677mr84780155e9.5.1774487834167; Wed, 25 Mar 2026 18:17:14 -0700 (PDT) Received: from localhost ([2001:67c:1562:8007::aac:4468]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4871fbd2a9fsm5664235e9.12.2026.03.25.18.17.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 25 Mar 2026 18:17:13 -0700 (PDT) Sender: AceLan Kao From: "Chia-Lin Kao (AceLan)" To: Greg Kroah-Hartman , Mathias Nyman , Alan Stern , Kuen-Han Tsai , Ingo Molnar , Thomas Gleixner , "Chia-Lin Kao (AceLan)" , Thorsten Blum , Kees Cook , Mika Westerberg , Heikki Krogerus , Chenyuan Yang Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH] USB: hub: call ACPI _PRR reset during port power-cycle on enumeration failure Date: Thu, 26 Mar 2026 09:17:08 +0800 Message-ID: <20260326011708.1128840-1-acelan.kao@canonical.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Some USB-connected devices (e.g. MT7925 Bluetooth on Dell laptops) expose their hardware reset line via an ACPI Power Resource for Reset (_PRR) rather than relying solely on VBUS cycling for recovery. When the reset GPIO gets stuck low the device stops responding on USB; a VBUS power-cycle alone cannot recover it because the chip remains in reset regardless of VBUS state. Add usb_acpi_port_prr_reset() in usb-acpi.c that, given a hub device and one-based port number, looks up the port's ACPI companion handle, evaluates _PRR to obtain the power-resource reference, and then calls _RST on that reference to toggle the reset line. The function is a no-op if the port has no ACPI handle or no _PRR method, so it is safe to call unconditionally for every port. Wire it into hub_port_connect() during the mid-retry VBUS power-cycle (at (PORT_INIT_TRIES-1)/2 iterations), calling usb_acpi_port_prr_reset() *after* VBUS goes off and *before* VBUS comes back on. The ordering is critical: on the tested hardware the ACPI _RST method (MBTR._RST) drives BT_RST low for 200 ms then high again. If _RST is called after VBUS is already restored the GPIO pulse races with device enumeration starting on the live bus; the device begins asserting USB signals while still held in reset and enumeration fails. Performing the reset while the port is de-powered ensures the GPIO pulse completes fully before the device is given power and time to initialise. After VBUS is restored, add an msleep(100) conditional on _PRR._RST having succeeded. USB 2.0 spec =C2=A77.1.7.3 (Fig. 7-29) mandates a minimum of 10= 0 ms between VBUS power-on and the first reset signalling for power settling. On root hubs, hub_power_on_good_delay() returns bPwrOn2PwrGood * 2 with no minimum floor; on the tested xHCI root hub bPwrOn2PwrGood =3D 10, yieldi= ng only 20 ms =E2=80=94 well below the spec minimum. (External hubs already e= nforce a 100 ms minimum via hub_power_on_good_delay().) When _PRR._RST has been exercised the device must also complete its full power-on sequence (GPIO de-assertion, internal oscillator start, firmware load) before USB enumeration begins. The 100 ms sleep enforces the spec minimum and gives the device adequate settling time. Tested on a Dell laptop with MT7925 Bluetooth (idVendor=3D0489, idProduct=3De139) whose BT_RST GPIO was stuck low. With this fix the device recovers autonomously at boot without requiring a G3 (mechanical power-off) cycle. The relevant dmesg sequence: [ 1.448491] usb 3-10: new high-speed USB device number 4 using xhci_hcd [ 6.813942] usb 3-10: device descriptor read/64, error -110 [ 22.685978] usb 3-10: device descriptor read/64, error -110 [ 22.901715] usb 3-10: new high-speed USB device number 5 using xhci_hcd [ 28.317963] usb 3-10: device descriptor read/64, error -110 [ 44.189949] usb 3-10: device descriptor read/64, error -110 [ 44.294065] usb usb3-port10: attempt power cycle [ 44.872709] usb 3-10: new high-speed USB device number 6 using xhci_hcd [ 44.888293] usb 3-10: New USB device found, idVendor=3D0489, idProduct= =3De139, bcdDevice=3D 1.00 [ 44.888318] usb 3-10: Manufacturer: MediaTek Inc. Signed-off-by: Chia-Lin Kao (AceLan) --- drivers/usb/core/hub.c | 14 ++++++++ drivers/usb/core/usb-acpi.c | 68 +++++++++++++++++++++++++++++++++++++ drivers/usb/core/usb.h | 3 ++ 3 files changed, 85 insertions(+) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 24960ba9caa91..1740e96f73cc6 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5603,11 +5603,25 @@ static void hub_port_connect(struct usb_hub *hub, i= nt port1, u16 portstatus, =20 /* When halfway through our retry count, power-cycle the port */ if (i =3D=3D (PORT_INIT_TRIES - 1) / 2) { + int prr_reset; + dev_info(&port_dev->dev, "attempt power cycle\n"); usb_hub_set_port_power(hdev, hub, port1, false); msleep(2 * hub_power_on_good_delay(hub)); + prr_reset =3D usb_acpi_port_prr_reset(hdev, port1); usb_hub_set_port_power(hdev, hub, port1, true); msleep(hub_power_on_good_delay(hub)); + /* + * USB 2.0 spec =C2=A77.1.7.3 requires at least 100 ms + * between VBUS power-on and the first reset for power + * settling. hub_power_on_good_delay() on an xHCI root + * hub returns bPwrOn2PwrGood * 2 with no minimum floor, + * which can be as little as 20 ms. When _PRR _RST was + * also exercised the device must complete its power-on + * sequence before enumeration; enforce the spec minimum. + */ + if (prr_reset =3D=3D 0) + msleep(100); } } if (hub->hdev->parent || diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 489dbdc96f94a..ee62e3fd8e3a1 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -142,6 +142,74 @@ int usb_acpi_set_power_state(struct usb_device *hdev, = int index, bool enable) } EXPORT_SYMBOL_GPL(usb_acpi_set_power_state); =20 +/** + * usb_acpi_port_prr_reset - issue an ACPI _PRR reset on a hub port + * @hdev: USB device belonging to the usb hub + * @port1: port number (one-based) + * + * Some devices expose their hardware reset line via an ACPI Power Resourc= e for + * Reset (_PRR). When such a device fails to enumerate (e.g. because the = reset + * GPIO is stuck low), the USB power-cycle alone is not enough; the firmwa= re + * reset path must also be exercised. + * + * This function evaluates _PRR on the port's ACPI companion to obtain the + * power-resource reference and then calls _RST on that resource to toggle= the + * reset line. It is intended to be called alongside the mid-retry VBUS + * power-cycle already performed by hub_port_connect(). + * + * Returns 0 on success, -ENODEV if the port has no ACPI handle or no _PRR + * method, or a negative error code on failure. + */ +int usb_acpi_port_prr_reset(struct usb_device *hdev, int port1) +{ + acpi_handle port_handle; + struct acpi_buffer buffer =3D { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *pkg, *ref; + acpi_status status; + int ret =3D 0; + + port_handle =3D usb_get_hub_port_acpi_handle(hdev, port1); + if (!port_handle) + return -ENODEV; + + if (!acpi_has_method(port_handle, "_PRR")) + return -ENODEV; + + status =3D acpi_evaluate_object(port_handle, "_PRR", NULL, &buffer); + if (ACPI_FAILURE(status)) { + dev_dbg(&hdev->dev, "port%d: _PRR evaluation failed: %s\n", + port1, acpi_format_exception(status)); + return -ENODEV; + } + + pkg =3D buffer.pointer; + if (!pkg || pkg->type !=3D ACPI_TYPE_PACKAGE || pkg->package.count < 1) { + dev_dbg(&hdev->dev, "port%d: _PRR returned unexpected object\n", + port1); + ret =3D -EINVAL; + goto out; + } + + ref =3D &pkg->package.elements[0]; + if (ref->type !=3D ACPI_TYPE_LOCAL_REFERENCE || !ref->reference.handle) { + dev_dbg(&hdev->dev, "port%d: _PRR element is not a reference\n", + port1); + ret =3D -EINVAL; + goto out; + } + + status =3D acpi_evaluate_object(ref->reference.handle, "_RST", NULL, NULL= ); + if (ACPI_FAILURE(status)) { + dev_dbg(&hdev->dev, "port%d: _RST evaluation failed: %s\n", + port1, acpi_format_exception(status)); + ret =3D -EIO; + } + +out: + kfree(buffer.pointer); + return ret; +} + /** * usb_acpi_add_usb4_devlink - add device link to USB4 Host Interface for = tunneled USB3 devices * diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index a9b37aeb515be..4d3dc3bd881b2 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -211,7 +211,10 @@ extern int usb_acpi_register(void); extern void usb_acpi_unregister(void); extern acpi_handle usb_get_hub_port_acpi_handle(struct usb_device *hdev, int port1); +extern int usb_acpi_port_prr_reset(struct usb_device *hdev, int port1); #else static inline int usb_acpi_register(void) { return 0; }; static inline void usb_acpi_unregister(void) { }; +static inline int usb_acpi_port_prr_reset(struct usb_device *hdev, + int port1) { return -ENODEV; } #endif --=20 2.53.0