Commit 3b454b63 authored by Oleksij Rempel's avatar Oleksij Rempel Committed by Jakub Kicinski
Browse files

net: dsa: microchip: ksz9477: Add Wake on Magic Packet support



Introduce Wake on Magic Packet (WoL) functionality to the ksz9477
driver.

Major changes include:

1. Extending the `ksz9477_handle_wake_reason` function to identify Magic
   Packet wake events alongside existing wake reasons.

2. Updating the `ksz9477_get_wol` and `ksz9477_set_wol` functions to
   handle WAKE_MAGIC alongside the existing WAKE_PHY option, and to
   program the switch's MAC address register accordingly when Magic
   Packet wake-up is enabled. This change will prevent WAKE_MAGIC
   activation if the related port has a different MAC address compared
   to a MAC address already used by HSR or an already active WAKE_MAGIC
   on another port.

3. Adding a restriction in `ksz_port_set_mac_address` to prevent MAC
   address changes on ports with active Wake on Magic Packet, as the
   switch's MAC address register is utilized for this feature.

Signed-off-by: default avatarOleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: default avatarFlorian Fainelli <florian.fainelli@broadcom.com>
Reviewed-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Link: https://lore.kernel.org/r/20231026051051.2316937-2-o.rempel@pengutronix.de


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 3a04927f
Loading
Loading
Loading
Loading
+53 −4
Original line number Diff line number Diff line
@@ -81,7 +81,8 @@ static int ksz9477_handle_wake_reason(struct ksz_device *dev, int port)
	if (!pme_status)
		return 0;

	dev_dbg(dev->dev, "Wake event on port %d due to:%s%s\n", port,
	dev_dbg(dev->dev, "Wake event on port %d due to:%s%s%s\n", port,
		pme_status & PME_WOL_MAGICPKT ? " \"Magic Packet\"" : "",
		pme_status & PME_WOL_LINKUP ? " \"Link Up\"" : "",
		pme_status & PME_WOL_ENERGY ? " \"Enery detect\"" : "");

@@ -109,10 +110,19 @@ void ksz9477_get_wol(struct ksz_device *dev, int port,

	wol->supported = WAKE_PHY;

	/* Check if the current MAC address on this port can be set
	 * as global for WAKE_MAGIC support. The result may vary
	 * dynamically based on other ports configurations.
	 */
	if (ksz_is_port_mac_global_usable(dev->ds, port))
		wol->supported |= WAKE_MAGIC;

	ret = ksz_pread8(dev, port, REG_PORT_PME_CTRL, &pme_ctrl);
	if (ret)
		return;

	if (pme_ctrl & PME_WOL_MAGICPKT)
		wol->wolopts |= WAKE_MAGIC;
	if (pme_ctrl & (PME_WOL_LINKUP | PME_WOL_ENERGY))
		wol->wolopts |= WAKE_PHY;
}
@@ -134,10 +144,12 @@ void ksz9477_get_wol(struct ksz_device *dev, int port,
int ksz9477_set_wol(struct ksz_device *dev, int port,
		    struct ethtool_wolinfo *wol)
{
	u8 pme_ctrl = 0;
	u8 pme_ctrl = 0, pme_ctrl_old = 0;
	bool magic_switched_off;
	bool magic_switched_on;
	int ret;

	if (wol->wolopts & ~WAKE_PHY)
	if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC))
		return -EINVAL;

	if (!dev->wakeup_source)
@@ -147,10 +159,42 @@ int ksz9477_set_wol(struct ksz_device *dev, int port,
	if (ret)
		return ret;

	if (wol->wolopts & WAKE_MAGIC)
		pme_ctrl |= PME_WOL_MAGICPKT;
	if (wol->wolopts & WAKE_PHY)
		pme_ctrl |= PME_WOL_LINKUP | PME_WOL_ENERGY;

	return ksz_pwrite8(dev, port, REG_PORT_PME_CTRL, pme_ctrl);
	ret = ksz_pread8(dev, port, REG_PORT_PME_CTRL, &pme_ctrl_old);
	if (ret)
		return ret;

	if (pme_ctrl_old == pme_ctrl)
		return 0;

	magic_switched_off = (pme_ctrl_old & PME_WOL_MAGICPKT) &&
			    !(pme_ctrl & PME_WOL_MAGICPKT);
	magic_switched_on = !(pme_ctrl_old & PME_WOL_MAGICPKT) &&
			    (pme_ctrl & PME_WOL_MAGICPKT);

	/* To keep reference count of MAC address, we should do this
	 * operation only on change of WOL settings.
	 */
	if (magic_switched_on) {
		ret = ksz_switch_macaddr_get(dev->ds, port, NULL);
		if (ret)
			return ret;
	} else if (magic_switched_off) {
		ksz_switch_macaddr_put(dev->ds);
	}

	ret = ksz_pwrite8(dev, port, REG_PORT_PME_CTRL, pme_ctrl);
	if (ret) {
		if (magic_switched_on)
			ksz_switch_macaddr_put(dev->ds);
		return ret;
	}

	return 0;
}

static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev)
@@ -1106,6 +1150,11 @@ void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port)

	/* clear pending wake flags */
	ksz9477_handle_wake_reason(dev, port);

	/* Disable all WoL options by default. Otherwise
	 * ksz_switch_macaddr_get/put logic will not work properly.
	 */
	ksz_pwrite8(dev, port, REG_PORT_PME_CTRL, 0);
}

void ksz9477_config_cpu_port(struct dsa_switch *ds)
+40 −3
Original line number Diff line number Diff line
@@ -3569,6 +3569,7 @@ static int ksz_port_set_mac_address(struct dsa_switch *ds, int port,
				    const unsigned char *addr)
{
	struct dsa_port *dp = dsa_to_port(ds, port);
	struct ethtool_wolinfo wol;

	if (dp->hsr_dev) {
		dev_err(ds->dev,
@@ -3577,9 +3578,45 @@ static int ksz_port_set_mac_address(struct dsa_switch *ds, int port,
		return -EBUSY;
	}

	ksz_get_wol(ds, dp->index, &wol);
	if (wol.wolopts & WAKE_MAGIC) {
		dev_err(ds->dev,
			"Cannot change MAC address on port %d with active Wake on Magic Packet\n",
			port);
		return -EBUSY;
	}

	return 0;
}

/**
 * ksz_is_port_mac_global_usable - Check if the MAC address on a given port
 *                                 can be used as a global address.
 * @ds: Pointer to the DSA switch structure.
 * @port: The port number on which the MAC address is to be checked.
 *
 * This function examines the MAC address set on the specified port and
 * determines if it can be used as a global address for the switch.
 *
 * Return: true if the port's MAC address can be used as a global address, false
 * otherwise.
 */
bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port)
{
	struct net_device *user = dsa_to_port(ds, port)->user;
	const unsigned char *addr = user->dev_addr;
	struct ksz_switch_macaddr *switch_macaddr;
	struct ksz_device *dev = ds->priv;

	ASSERT_RTNL();

	switch_macaddr = dev->switch_macaddr;
	if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr))
		return false;

	return true;
}

/* Program the switch's MAC address register with the MAC address of the
 * requesting user port. This single address is used by the switch for multiple
 * features, like HSR self-address filtering and WoL. Other user ports are
@@ -3587,7 +3624,7 @@ static int ksz_port_set_mac_address(struct dsa_switch *ds, int port,
 * the same. The user ports' MAC addresses must not change while they have
 * ownership of the switch MAC address.
 */
static int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
			   struct netlink_ext_ack *extack)
{
	struct net_device *user = dsa_to_port(ds, port)->user;
@@ -3628,7 +3665,7 @@ static int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
	return 0;
}

static void ksz_switch_macaddr_put(struct dsa_switch *ds)
void ksz_switch_macaddr_put(struct dsa_switch *ds)
{
	struct ksz_switch_macaddr *switch_macaddr;
	struct ksz_device *dev = ds->priv;
+4 −0
Original line number Diff line number Diff line
@@ -390,12 +390,16 @@ int ksz_switch_register(struct ksz_device *dev);
void ksz_switch_remove(struct ksz_device *dev);

void ksz_init_mib_timer(struct ksz_device *dev);
bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port);
void ksz_r_mib_stats64(struct ksz_device *dev, int port);
void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port);
void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
bool ksz_get_gbit(struct ksz_device *dev, int port);
phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit);
extern const struct ksz_chip_data ksz_switch_chips[];
int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
			   struct netlink_ext_ack *extack);
void ksz_switch_macaddr_put(struct dsa_switch *ds);

/* Common register access functions */
static inline struct regmap *ksz_regmap_8(struct ksz_device *dev)