Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: usb: asix: hold PM usage ref to avoid PM/MDIO + RTNL deadlock

Prevent USB runtime PM (autosuspend) for AX88772* in bind.

usbnet enables runtime PM (autosuspend) by default, so disabling it via
the usb_driver flag is ineffective. On AX88772B, autosuspend shows no
measurable power saving with current driver (no link partner, admin
up/down). The ~0.453 W -> ~0.248 W drop on v6.1 comes from phylib powering
the PHY off on admin-down, not from USB autosuspend.

The real hazard is that with runtime PM enabled, ndo_open() (under RTNL)
may synchronously trigger autoresume (usb_autopm_get_interface()) into
asix_resume() while the USB PM lock is held. Resume paths then invoke
phylink/phylib and MDIO, which also expect RTNL, leading to possible
deadlocks or PM lock vs MDIO wake issues.

To avoid this, keep the device runtime-PM active by taking a usage
reference in ax88772_bind() and dropping it in unbind(). A non-zero PM
usage count blocks runtime suspend regardless of userspace policy
(.../power/control - pm_runtime_allow/forbid), making this approach
robust against sysfs overrides.

Holding a runtime-PM usage ref does not affect system-wide suspend;
system sleep/resume callbacks continue to run as before.

Fixes: 4a2c7217cd5a ("net: usb: asix: ax88772: manage PHY PM from MAC")
Reported-by: Hubert Wiśniewski <hubert.wisniewski.25632@gmail.com>
Closes: https://lore.kernel.org/all/DCGHG5UJT9G3.2K1GHFZ3H87T0@gmail.com
Tested-by: Hubert Wiśniewski <hubert.wisniewski.25632@gmail.com>
Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
Closes: https://lore.kernel.org/all/b5ea8296-f981-445d-a09a-2f389d7f6fdd@samsung.com
Cc: stable@vger.kernel.org
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Link: https://patch.msgid.link/20251005081203.3067982-1-o.rempel@pengutronix.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>

authored by

Oleksij Rempel and committed by
Paolo Abeni
3d3c4cd5 b615879d

+29
+29
drivers/net/usb/asix_devices.c
··· 625 625 asix_read_medium_status(dev, 1)); 626 626 } 627 627 628 + /* Notes on PM callbacks and locking context: 629 + * 630 + * - asix_suspend()/asix_resume() are invoked for both runtime PM and 631 + * system-wide suspend/resume. For struct usb_driver the ->resume() 632 + * callback does not receive pm_message_t, so the resume type cannot 633 + * be distinguished here. 634 + * 635 + * - The MAC driver must hold RTNL when calling phylink interfaces such as 636 + * phylink_suspend()/resume(). Those calls will also perform MDIO I/O. 637 + * 638 + * - Taking RTNL and doing MDIO from a runtime-PM resume callback (while 639 + * the USB PM lock is held) is fragile. Since autosuspend brings no 640 + * measurable power saving here, we block it by holding a PM usage 641 + * reference in ax88772_bind(). 642 + */ 628 643 static int asix_suspend(struct usb_interface *intf, pm_message_t message) 629 644 { 630 645 struct usbnet *dev = usb_get_intfdata(intf); ··· 934 919 if (ret) 935 920 goto initphy_err; 936 921 922 + /* Keep this interface runtime-PM active by taking a usage ref. 923 + * Prevents runtime suspend while bound and avoids resume paths 924 + * that could deadlock (autoresume under RTNL while USB PM lock 925 + * is held, phylink/MDIO wants RTNL). 926 + */ 927 + pm_runtime_get_noresume(&intf->dev); 928 + 937 929 return 0; 938 930 939 931 initphy_err: ··· 970 948 phylink_destroy(priv->phylink); 971 949 ax88772_mdio_unregister(priv); 972 950 asix_rx_fixup_common_free(dev->driver_priv); 951 + /* Drop the PM usage ref taken in bind() */ 952 + pm_runtime_put(&intf->dev); 973 953 } 974 954 975 955 static void ax88178_unbind(struct usbnet *dev, struct usb_interface *intf) ··· 1624 1600 .resume = asix_resume, 1625 1601 .reset_resume = asix_resume, 1626 1602 .disconnect = usbnet_disconnect, 1603 + /* usbnet enables autosuspend by default (supports_autosuspend=1). 1604 + * We keep runtime-PM active for AX88772* by taking a PM usage 1605 + * reference in ax88772_bind() (pm_runtime_get_noresume()) and 1606 + * dropping it in unbind(), which effectively blocks autosuspend. 1607 + */ 1627 1608 .supports_autosuspend = 1, 1628 1609 .disable_hub_initiated_lpm = 1, 1629 1610 };