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: Oleksij Rempel <o.rempel@pengutronix.de> Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com> Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com> Link: https://lore.kernel.org/r/20231026051051.2316937-2-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
3a04927f8d
commit
3b454b6390
3 changed files with 97 additions and 7 deletions
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue