Merge branch 'net-dsa-microchip-provide-wake-on-lan-support-part-2'

Oleksij Rempel says:

====================
net: dsa: microchip: provide Wake on LAN support (part 2)

This patch series introduces extensive Wake on LAN (WoL) support for the
Microchip KSZ9477 family of switches, coupled with some code refactoring
and error handling enhancements. The principal aim is to enable and
manage Wake on Magic Packet and other PHY event triggers for waking up
the system, whilst ensuring that the switch isn't reset during a
shutdown if WoL is active.

The Wake on LAN functionality is optional and is particularly beneficial
if the PME pins are connected to the SoC as a wake source or to a PMIC
that can enable or wake the SoC.
====================

Link: https://lore.kernel.org/r/20231026051051.2316937-1-o.rempel@pengutronix.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2023-10-27 14:43:55 -07:00
commit dfaed0e9f1
6 changed files with 200 additions and 24 deletions

View file

@ -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,82 @@ 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;
}
/**
* ksz9477_wol_pre_shutdown - Prepares the switch device for shutdown while
* considering Wake-on-LAN (WoL) settings.
* @dev: The switch device structure.
* @wol_enabled: Pointer to a boolean which will be set to true if WoL is
* enabled on any port.
*
* This function prepares the switch device for a safe shutdown while taking
* into account the Wake-on-LAN (WoL) settings on the user ports. It updates
* the wol_enabled flag accordingly to reflect whether WoL is active on any
* port.
*/
void ksz9477_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled)
{
struct dsa_port *dp;
int ret;
*wol_enabled = false;
if (!dev->wakeup_source)
return;
dsa_switch_for_each_user_port(dp, dev->ds) {
u8 pme_ctrl = 0;
ret = ksz_pread8(dev, dp->index, REG_PORT_PME_CTRL, &pme_ctrl);
if (!ret && pme_ctrl)
*wol_enabled = true;
/* make sure there are no pending wake events which would
* prevent the device from going to sleep/shutdown.
*/
ksz9477_handle_wake_reason(dev, dp->index);
}
/* Now we are save to enable PME pin. */
if (*wol_enabled)
ksz_write8(dev, REG_SW_PME_CTRL, PME_ENABLE);
}
static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev)
@ -1106,6 +1190,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)
@ -1228,6 +1317,12 @@ int ksz9477_setup(struct dsa_switch *ds)
/* enable global MIB counter freeze function */
ksz_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true);
/* Make sure PME (WoL) is not enabled. If requested, it will be
* enabled by ksz9477_wol_pre_shutdown(). Otherwise, some PMICs do not
* like PME events changes before shutdown.
*/
ksz_write8(dev, REG_SW_PME_CTRL, 0);
return 0;
}

View file

@ -62,6 +62,7 @@ void ksz9477_get_wol(struct ksz_device *dev, int port,
struct ethtool_wolinfo *wol);
int ksz9477_set_wol(struct ksz_device *dev, int port,
struct ethtool_wolinfo *wol);
void ksz9477_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled);
int ksz9477_port_acl_init(struct ksz_device *dev, int port);
void ksz9477_port_acl_free(struct ksz_device *dev, int port);

View file

@ -66,10 +66,7 @@ static void ksz9477_i2c_shutdown(struct i2c_client *i2c)
if (!dev)
return;
if (dev->dev_ops->reset)
dev->dev_ops->reset(dev);
dsa_switch_shutdown(dev->ds);
ksz_switch_shutdown(dev);
i2c_set_clientdata(i2c, NULL);
}

View file

@ -321,6 +321,7 @@ static const struct ksz_dev_ops ksz9477_dev_ops = {
.phylink_mac_link_up = ksz9477_phylink_mac_link_up,
.get_wol = ksz9477_get_wol,
.set_wol = ksz9477_set_wol,
.wol_pre_shutdown = ksz9477_wol_pre_shutdown,
.config_cpu_port = ksz9477_config_cpu_port,
.tc_cbs_set_cinc = ksz9477_tc_cbs_set_cinc,
.enable_stp_addr = ksz9477_enable_stp_addr,
@ -3569,6 +3570,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,25 +3579,69 @@ 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;
}
/* 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
* allowed to share ownership of this address as long as their MAC address is
* the same. The user ports' MAC addresses must not change while they have
* ownership of the switch MAC address.
/**
* 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.
*/
static int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
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;
}
/**
* ksz_switch_macaddr_get - Program the switch's MAC address register.
* @ds: DSA switch instance.
* @port: Port number.
* @extack: Netlink extended acknowledgment.
*
* This function programs 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
* can share ownership of this address as long as their MAC address is the same.
* The MAC addresses of user ports must not change while they have ownership of
* the switch MAC address.
*
* Return: 0 on success, or other error codes on failure.
*/
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;
const unsigned char *addr = user->dev_addr;
struct ksz_switch_macaddr *switch_macaddr;
struct ksz_device *dev = ds->priv;
const u16 *regs = dev->info->regs;
int i;
int i, ret;
/* Make sure concurrent MAC address changes are blocked */
ASSERT_RTNL();
@ -3622,13 +3668,23 @@ static int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
dev->switch_macaddr = switch_macaddr;
/* Program the switch MAC address to hardware */
for (i = 0; i < ETH_ALEN; i++)
ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, addr[i]);
for (i = 0; i < ETH_ALEN; i++) {
ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, addr[i]);
if (ret)
goto macaddr_drop;
}
return 0;
macaddr_drop:
dev->switch_macaddr = NULL;
refcount_set(&switch_macaddr->refcount, 0);
kfree(switch_macaddr);
return ret;
}
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;
@ -3790,6 +3846,30 @@ struct ksz_device *ksz_switch_alloc(struct device *base, void *priv)
}
EXPORT_SYMBOL(ksz_switch_alloc);
/**
* ksz_switch_shutdown - Shutdown routine for the switch device.
* @dev: The switch device structure.
*
* This function is responsible for initiating a shutdown sequence for the
* switch device. It invokes the reset operation defined in the device
* operations, if available, to reset the switch. Subsequently, it calls the
* DSA framework's shutdown function to ensure a proper shutdown of the DSA
* switch.
*/
void ksz_switch_shutdown(struct ksz_device *dev)
{
bool wol_enabled = false;
if (dev->dev_ops->wol_pre_shutdown)
dev->dev_ops->wol_pre_shutdown(dev, &wol_enabled);
if (dev->dev_ops->reset && !wol_enabled)
dev->dev_ops->reset(dev);
dsa_switch_shutdown(dev->ds);
}
EXPORT_SYMBOL(ksz_switch_shutdown);
static void ksz_parse_rgmii_delay(struct ksz_device *dev, int port_num,
struct device_node *port_dn)
{

View file

@ -378,6 +378,7 @@ struct ksz_dev_ops {
struct ethtool_wolinfo *wol);
int (*set_wol)(struct ksz_device *dev, int port,
struct ethtool_wolinfo *wol);
void (*wol_pre_shutdown)(struct ksz_device *dev, bool *wol_enabled);
void (*config_cpu_port)(struct dsa_switch *ds);
int (*enable_stp_addr)(struct ksz_device *dev);
int (*reset)(struct ksz_device *dev);
@ -390,12 +391,17 @@ 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);
void ksz_switch_shutdown(struct ksz_device *dev);
/* Common register access functions */
static inline struct regmap *ksz_regmap_8(struct ksz_device *dev)

View file

@ -114,10 +114,7 @@ static void ksz_spi_shutdown(struct spi_device *spi)
if (!dev)
return;
if (dev->dev_ops->reset)
dev->dev_ops->reset(dev);
dsa_switch_shutdown(dev->ds);
ksz_switch_shutdown(dev);
spi_set_drvdata(spi, NULL);
}