lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aJxkgU_28lgh_sdL@shell.armlinux.org.uk>
Date: Wed, 13 Aug 2025 11:10:09 +0100
From: "Russell King (Oracle)" <linux@...linux.org.uk>
To: Andrew Lunn <andrew@...n.ch>, Heiner Kallweit <hkallweit1@...il.com>,
	Daniel Braunwarth <daniel.braunwarth@...a.com>
Cc: "David S. Miller" <davem@...emloft.net>,
	Eric Dumazet <edumazet@...gle.com>,
	Jakub Kicinski <kuba@...nel.org>, Jon Hunter <jonathanh@...dia.com>,
	netdev@...r.kernel.org, Paolo Abeni <pabeni@...hat.com>,
	Thierry Reding <treding@...dia.com>
Subject: Re: [PATCH net-next] net: phy: realtek: fix RTL8211F wake-on-lan
 support

Sorry, I missed adding Daniel Braunwarth.

Please also note that this is essentially a rewrite of Daniels patch
merged during the previous merge window, thus I think it makes sense
for net-next rather than net. It may be appropriate to be backported
to stable at a later date.

On Wed, Aug 13, 2025 at 11:04:45AM +0100, Russell King (Oracle) wrote:
> Implement Wake-on-Lan for RTL8211F correctly. The existing
> implementation has multiple issues:
> 
> 1. It assumes that Wake-on-Lan can always be used, whether or not the
>    interrupt is wired, and whether or not the interrupt is capable of
>    waking the system. This breaks the ability for MAC drivers to detect
>    whether the PHY WoL is functional.
> 2. switching the interrupt pin in the .set_wol() method to PMEB mode
>    immediately silences link-state interrupts, which breaks phylib
>    when interrupts are being used rather than polling mode.
> 3. the code claiming to "reset WOL status" was doing nothing of the
>    sort. Bit 15 in page 0xd8a register 17 controls WoL reset, and
>    needs to be pulsed low to reset the WoL state. This bit was always
>    written as '1', resulting in no reset.
> 4. not resetting WoL state results in the PMEB pin remaining asserted,
>    which in turn leads to an interrupt storm. Only resetting the WoL
>    state in .set_wol() is not sufficient.
> 5. PMEB mode does not allow software detection of the wake-up event as
>    there is no status bit to indicate we received the WoL packet.
> 6. across reboots of at least the Jetson Xavier NX system, the WoL
>    configuration is preserved.
> 
> Fix all of these issues by essentially rewriting the support. We:
> 1. clear the WoL event enable register at probe time.
> 2. detect whether we can support wake-up by having a valid interrupt,
>    and the "wakeup-source" property in DT. If we can, then we mark
>    the MDIO device as wakeup capable, and associate the interrupt
>    with the wakeup source.
> 3. arrange for the get_wol() and set_wol() implementations to handle
>    the case where the MDIO device has not been marked as wakeup
>    capable (thereby returning no WoL support, and refusing to enable
>    WoL support.)
> 4. avoid switching to PMEB mode, instead using INTB mode with the
>    interrupt enable, reconfiguring the interrupt enables at suspend
>    time, and restoring their original state at resume time (we track
>    the state of the interrupt enable register in .config_intr()
>    register.)
> 5. move WoL reset from .set_wol() to the suspend function to ensure
>    that WoL state is cleared prior to suspend. This is necessary
>    after the PME interrupt has been enabled as a second WoL packet
>    will not re-raise a previously cleared PME interrupt.
> 6. when a PME interrupt (for wakeup) is asserted, pass this to the
>    PM wakeup so it knows which device woke the system.
> 
> This fixes WoL support in the Realtek RTL8211F driver when used on the
> nVidia Jetson Xavier NX platform, and needs to be applied before stmmac
> patches which allow these platforms to forward the ethtool WoL commands
> to the Realtek PHY.
> 
> Signed-off-by: Russell King (Oracle) <rmk+kernel@...linux.org.uk>
> ---
>  drivers/net/phy/realtek/realtek_main.c | 172 ++++++++++++++++++++-----
>  1 file changed, 140 insertions(+), 32 deletions(-)
> 
> diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
> index dd0d675149ad..4361529e68fb 100644
> --- a/drivers/net/phy/realtek/realtek_main.c
> +++ b/drivers/net/phy/realtek/realtek_main.c
> @@ -10,6 +10,7 @@
>  #include <linux/bitops.h>
>  #include <linux/of.h>
>  #include <linux/phy.h>
> +#include <linux/pm_wakeirq.h>
>  #include <linux/netdevice.h>
>  #include <linux/module.h>
>  #include <linux/delay.h>
> @@ -31,6 +32,7 @@
>  #define RTL821x_INER				0x12
>  #define RTL8211B_INER_INIT			0x6400
>  #define RTL8211E_INER_LINK_STATUS		BIT(10)
> +#define RTL8211F_INER_PME			BIT(7)
>  #define RTL8211F_INER_LINK_STATUS		BIT(4)
>  
>  #define RTL821x_INSR				0x13
> @@ -96,17 +98,13 @@
>  #define RTL8211F_RXCR				0x15
>  #define RTL8211F_RX_DELAY			BIT(3)
>  
> -/* RTL8211F WOL interrupt configuration */
> -#define RTL8211F_INTBCR_PAGE			0xd40
> -#define RTL8211F_INTBCR				0x16
> -#define RTL8211F_INTBCR_INTB_PMEB		BIT(5)
> -
>  /* RTL8211F WOL settings */
> -#define RTL8211F_WOL_SETTINGS_PAGE		0xd8a
> +#define RTL8211F_WOL_PAGE		0xd8a
>  #define RTL8211F_WOL_SETTINGS_EVENTS		16
>  #define RTL8211F_WOL_EVENT_MAGIC		BIT(12)
> -#define RTL8211F_WOL_SETTINGS_STATUS		17
> -#define RTL8211F_WOL_STATUS_RESET		(BIT(15) | 0x1fff)
> +#define RTL8211F_WOL_RST_RMSQ		17
> +#define RTL8211F_WOL_RG_RSTB			BIT(15)
> +#define RTL8211F_WOL_RMSQ			0x1fff
>  
>  /* RTL8211F Unique phyiscal and multicast address (WOL) */
>  #define RTL8211F_PHYSICAL_ADDR_PAGE		0xd8c
> @@ -172,7 +170,8 @@ struct rtl821x_priv {
>  	u16 phycr2;
>  	bool has_phycr2;
>  	struct clk *clk;
> -	u32 saved_wolopts;
> +	/* rtl8211f */
> +	u16 iner;
>  };
>  
>  static int rtl821x_read_page(struct phy_device *phydev)
> @@ -255,6 +254,34 @@ static int rtl821x_probe(struct phy_device *phydev)
>  	return 0;
>  }
>  
> +static int rtl8211f_probe(struct phy_device *phydev)
> +{
> +	struct device *dev = &phydev->mdio.dev;
> +	int ret;
> +
> +	ret = rtl821x_probe(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Disable all PME events */
> +	ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE,
> +			      RTL8211F_WOL_SETTINGS_EVENTS, 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Mark this PHY as wakeup capable and register the interrupt as a
> +	 * wakeup IRQ if the PHY is marked as a wakeup source in firmware,
> +	 * and the interrupt is valid.
> +	 */
> +	if (device_property_read_bool(dev, "wakeup-source") &&
> +	    phy_interrupt_is_valid(phydev)) {
> +		device_set_wakeup_capable(dev, true);
> +		devm_pm_set_wake_irq(dev, phydev->irq);
> +	}
> +
> +	return ret;
> +}
> +
>  static int rtl8201_ack_interrupt(struct phy_device *phydev)
>  {
>  	int err;
> @@ -352,6 +379,7 @@ static int rtl8211e_config_intr(struct phy_device *phydev)
>  
>  static int rtl8211f_config_intr(struct phy_device *phydev)
>  {
> +	struct rtl821x_priv *priv = phydev->priv;
>  	u16 val;
>  	int err;
>  
> @@ -362,8 +390,10 @@ static int rtl8211f_config_intr(struct phy_device *phydev)
>  
>  		val = RTL8211F_INER_LINK_STATUS;
>  		err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
> +		if (err == 0)
> +			priv->iner = val;
>  	} else {
> -		val = 0;
> +		priv->iner = val = 0;
>  		err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
>  		if (err)
>  			return err;
> @@ -426,21 +456,34 @@ static irqreturn_t rtl8211f_handle_interrupt(struct phy_device *phydev)
>  		return IRQ_NONE;
>  	}
>  
> -	if (!(irq_status & RTL8211F_INER_LINK_STATUS))
> -		return IRQ_NONE;
> +	if (irq_status & RTL8211F_INER_LINK_STATUS) {
> +		phy_trigger_machine(phydev);
> +		return IRQ_HANDLED;
> +	}
>  
> -	phy_trigger_machine(phydev);
> +	if (irq_status & RTL8211F_INER_PME) {
> +		pm_wakeup_event(&phydev->mdio.dev, 0);
> +		return IRQ_HANDLED;
> +	}
>  
> -	return IRQ_HANDLED;
> +	return IRQ_NONE;
>  }
>  
>  static void rtl8211f_get_wol(struct phy_device *dev, struct ethtool_wolinfo *wol)
>  {
>  	int wol_events;
>  
> +	/* If the PHY is not capable of waking the system, then WoL can not
> +	 * be supported.
> +	 */
> +	if (!device_can_wakeup(&dev->mdio.dev)) {
> +		wol->supported = 0;
> +		return;
> +	}
> +
>  	wol->supported = WAKE_MAGIC;
>  
> -	wol_events = phy_read_paged(dev, RTL8211F_WOL_SETTINGS_PAGE, RTL8211F_WOL_SETTINGS_EVENTS);
> +	wol_events = phy_read_paged(dev, RTL8211F_WOL_PAGE, RTL8211F_WOL_SETTINGS_EVENTS);
>  	if (wol_events < 0)
>  		return;
>  
> @@ -453,6 +496,9 @@ static int rtl8211f_set_wol(struct phy_device *dev, struct ethtool_wolinfo *wol)
>  	const u8 *mac_addr = dev->attached_dev->dev_addr;
>  	int oldpage;
>  
> +	if (!device_can_wakeup(&dev->mdio.dev))
> +		return -EOPNOTSUPP;
> +
>  	oldpage = phy_save_page(dev);
>  	if (oldpage < 0)
>  		goto err;
> @@ -464,25 +510,23 @@ static int rtl8211f_set_wol(struct phy_device *dev, struct ethtool_wolinfo *wol)
>  		__phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD1, mac_addr[3] << 8 | (mac_addr[2]));
>  		__phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD2, mac_addr[5] << 8 | (mac_addr[4]));
>  
> -		/* Enable magic packet matching and reset WOL status */
> -		rtl821x_write_page(dev, RTL8211F_WOL_SETTINGS_PAGE);
> +		/* Enable magic packet matching */
> +		rtl821x_write_page(dev, RTL8211F_WOL_PAGE);
>  		__phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, RTL8211F_WOL_EVENT_MAGIC);
> -		__phy_write(dev, RTL8211F_WOL_SETTINGS_STATUS, RTL8211F_WOL_STATUS_RESET);
> -
> -		/* Enable the WOL interrupt */
> -		rtl821x_write_page(dev, RTL8211F_INTBCR_PAGE);
> -		__phy_set_bits(dev, RTL8211F_INTBCR, RTL8211F_INTBCR_INTB_PMEB);
> +		/* Set the maximum packet size, and assert WoL reset */
> +		__phy_write(dev, RTL8211F_WOL_RST_RMSQ, RTL8211F_WOL_RMSQ);
>  	} else {
> -		/* Disable the WOL interrupt */
> -		rtl821x_write_page(dev, RTL8211F_INTBCR_PAGE);
> -		__phy_clear_bits(dev, RTL8211F_INTBCR, RTL8211F_INTBCR_INTB_PMEB);
> -
> -		/* Disable magic packet matching and reset WOL status */
> -		rtl821x_write_page(dev, RTL8211F_WOL_SETTINGS_PAGE);
> +		/* Disable magic packet matching */
> +		rtl821x_write_page(dev, RTL8211F_WOL_PAGE);
>  		__phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, 0);
> -		__phy_write(dev, RTL8211F_WOL_SETTINGS_STATUS, RTL8211F_WOL_STATUS_RESET);
> +
> +		/* Place WoL in reset */
> +		__phy_clear_bits(dev, RTL8211F_WOL_RST_RMSQ,
> +				 RTL8211F_WOL_RG_RSTB);
>  	}
>  
> +	device_set_wakeup_enable(&dev->mdio.dev, !!(wol->wolopts & WAKE_MAGIC));
> +
>  err:
>  	return phy_restore_page(dev, oldpage, 0);
>  }
> @@ -628,6 +672,52 @@ static int rtl821x_suspend(struct phy_device *phydev)
>  	return ret;
>  }
>  
> +static int rtl8211f_suspend(struct phy_device *phydev)
> +{
> +	u16 wol_rst;
> +	int ret;
> +
> +	ret = rtl821x_suspend(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* If a PME event is enabled, then configure the interrupt for
> +	 * PME events only, disabling link interrupt. We avoid switching
> +	 * to PMEB mode as we don't have a status bit for that.
> +	 */
> +	if (device_may_wakeup(&phydev->mdio.dev)) {
> +		ret = phy_write_paged(phydev, 0xa42, RTL821x_INER,
> +				      RTL8211F_INER_PME);
> +		if (ret < 0)
> +			goto err;
> +
> +		/* Read the INSR to clear any pending interrupt */
> +		phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR);
> +
> +		/* Reset the WoL to ensure that an event is picked up.
> +		 * Unless we do this, even if we receive another packet,
> +		 * we may not have a PME interrupt raised.
> +		 */
> +		ret = phy_read_paged(phydev, RTL8211F_WOL_PAGE,
> +				     RTL8211F_WOL_RST_RMSQ);
> +		if (ret < 0)
> +			goto err;
> +
> +		wol_rst = ret & ~RTL8211F_WOL_RG_RSTB;
> +		ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE,
> +				      RTL8211F_WOL_RST_RMSQ, wol_rst);
> +		if (ret < 0)
> +			goto err;
> +
> +		wol_rst |= RTL8211F_WOL_RG_RSTB;
> +		ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE,
> +				      RTL8211F_WOL_RST_RMSQ, wol_rst);
> +	}
> +
> +err:
> +	return ret;
> +}
> +
>  static int rtl821x_resume(struct phy_device *phydev)
>  {
>  	struct rtl821x_priv *priv = phydev->priv;
> @@ -645,6 +735,24 @@ static int rtl821x_resume(struct phy_device *phydev)
>  	return 0;
>  }
>  
> +static int rtl8211f_resume(struct phy_device *phydev)
> +{
> +	struct rtl821x_priv *priv = phydev->priv;
> +	int ret;
> +
> +	ret = rtl821x_resume(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* If the device was programmed for a PME event, restore the interrupt
> +	 * enable so phylib can receive link state interrupts.
> +	 */
> +	if (device_may_wakeup(&phydev->mdio.dev))
> +		ret = phy_write_paged(phydev, 0xa42, RTL821x_INER, priv->iner);
> +
> +	return ret;
> +}
> +
>  static int rtl8211x_led_hw_is_supported(struct phy_device *phydev, u8 index,
>  					unsigned long rules)
>  {
> @@ -1612,15 +1720,15 @@ static struct phy_driver realtek_drvs[] = {
>  	}, {
>  		PHY_ID_MATCH_EXACT(0x001cc916),
>  		.name		= "RTL8211F Gigabit Ethernet",
> -		.probe		= rtl821x_probe,
> +		.probe		= rtl8211f_probe,
>  		.config_init	= &rtl8211f_config_init,
>  		.read_status	= rtlgen_read_status,
>  		.config_intr	= &rtl8211f_config_intr,
>  		.handle_interrupt = rtl8211f_handle_interrupt,
>  		.set_wol	= rtl8211f_set_wol,
>  		.get_wol	= rtl8211f_get_wol,
> -		.suspend	= rtl821x_suspend,
> -		.resume		= rtl821x_resume,
> +		.suspend	= rtl8211f_suspend,
> +		.resume		= rtl8211f_resume,
>  		.read_page	= rtl821x_read_page,
>  		.write_page	= rtl821x_write_page,
>  		.flags		= PHY_ALWAYS_CALL_SUSPEND,
> -- 
> 2.30.2
> 
> 

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ