Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 50ee42b9 authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'WoL-filters'



Florian Fainelli says:

====================
net: Support Wake-on-LAN using filters

This is technically a v2, but this patch series builds on your feedback
and defines the following:

- a new WAKE_* bit: WAKE_FILTER which can be enabled alongside other type
  of Wake-on-LAN to support waking up on a programmed filter (match + action)
- a new RX_CLS_FLOW_WAKE flow action which can be specified by an user when
  inserting a flow using ethtool::rxnfc, similar to the existing RX_CLS_FLOW_DISC

The bcm_sf2 and bcmsysport drivers are updated accordingly to work in concert to
allow matching packets at the switch level, identified by their filter location
to be used as a match by the SYSTEM PORT (CPU/management controller) during
Wake-on-LAN.

Let me know if this looks better than the previous incarnation of the patch
series.

Attached is also the ethtool patch that I would be submitting once the uapi
changes are committed.

Thank you!

Changes in v2:

- bail out earlier in bcm_sf2_cfp's get_rxnfc if an error is
  encountered (Andrew)
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 1ba98280 bb9051a2
Loading
Loading
Loading
Loading
+40 −3
Original line number Diff line number Diff line
@@ -732,6 +732,8 @@ static int bcm_sf2_cfp_rule_set(struct dsa_switch *ds, int port,
				struct ethtool_rx_flow_spec *fs)
{
	struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds);
	s8 cpu_port = ds->ports[port].cpu_dp->index;
	__u64 ring_cookie = fs->ring_cookie;
	unsigned int queue_num, port_num;
	int ret = -EINVAL;

@@ -748,13 +750,19 @@ static int bcm_sf2_cfp_rule_set(struct dsa_switch *ds, int port,
	    fs->location > bcm_sf2_cfp_rule_size(priv))
		return -EINVAL;

	/* This rule is a Wake-on-LAN filter and we must specifically
	 * target the CPU port in order for it to be working.
	 */
	if (ring_cookie == RX_CLS_FLOW_WAKE)
		ring_cookie = cpu_port * SF2_NUM_EGRESS_QUEUES;

	/* We do not support discarding packets, check that the
	 * destination port is enabled and that we are within the
	 * number of ports supported by the switch
	 */
	port_num = fs->ring_cookie / SF2_NUM_EGRESS_QUEUES;
	port_num = ring_cookie / SF2_NUM_EGRESS_QUEUES;

	if (fs->ring_cookie == RX_CLS_FLOW_DISC ||
	if (ring_cookie == RX_CLS_FLOW_DISC ||
	    !(dsa_is_user_port(ds, port_num) ||
	      dsa_is_cpu_port(ds, port_num)) ||
	    port_num >= priv->hw_params.num_ports)
@@ -763,7 +771,7 @@ static int bcm_sf2_cfp_rule_set(struct dsa_switch *ds, int port,
	 * We have a small oddity where Port 6 just does not have a
	 * valid bit here (so we substract by one).
	 */
	queue_num = fs->ring_cookie % SF2_NUM_EGRESS_QUEUES;
	queue_num = ring_cookie % SF2_NUM_EGRESS_QUEUES;
	if (port_num >= 7)
		port_num -= 1;

@@ -1188,6 +1196,7 @@ static int bcm_sf2_cfp_rule_get_all(struct bcm_sf2_priv *priv,
int bcm_sf2_get_rxnfc(struct dsa_switch *ds, int port,
		      struct ethtool_rxnfc *nfc, u32 *rule_locs)
{
	struct net_device *p = ds->ports[port].cpu_dp->master;
	struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds);
	int ret = 0;

@@ -1214,12 +1223,23 @@ int bcm_sf2_get_rxnfc(struct dsa_switch *ds, int port,

	mutex_unlock(&priv->cfp.lock);

	if (ret)
		return ret;

	/* Pass up the commands to the attached master network device */
	if (p->ethtool_ops->get_rxnfc) {
		ret = p->ethtool_ops->get_rxnfc(p, nfc, rule_locs);
		if (ret == -EOPNOTSUPP)
			ret = 0;
	}

	return ret;
}

int bcm_sf2_set_rxnfc(struct dsa_switch *ds, int port,
		      struct ethtool_rxnfc *nfc)
{
	struct net_device *p = ds->ports[port].cpu_dp->master;
	struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds);
	int ret = 0;

@@ -1240,6 +1260,23 @@ int bcm_sf2_set_rxnfc(struct dsa_switch *ds, int port,

	mutex_unlock(&priv->cfp.lock);

	if (ret)
		return ret;

	/* Pass up the commands to the attached master network device.
	 * This can fail, so rollback the operation if we need to.
	 */
	if (p->ethtool_ops->set_rxnfc) {
		ret = p->ethtool_ops->set_rxnfc(p, nfc);
		if (ret && ret != -EOPNOTSUPP) {
			mutex_lock(&priv->cfp.lock);
			bcm_sf2_cfp_rule_del(priv, port, nfc->fs.location);
			mutex_unlock(&priv->cfp.lock);
		} else {
			ret = 0;
		}
	}

	return ret;
}

+186 −7
Original line number Diff line number Diff line
@@ -521,7 +521,7 @@ static void bcm_sysport_get_wol(struct net_device *dev,
	struct bcm_sysport_priv *priv = netdev_priv(dev);
	u32 reg;

	wol->supported = WAKE_MAGIC | WAKE_MAGICSECURE;
	wol->supported = WAKE_MAGIC | WAKE_MAGICSECURE | WAKE_FILTER;
	wol->wolopts = priv->wolopts;

	if (!(priv->wolopts & WAKE_MAGICSECURE))
@@ -539,7 +539,7 @@ static int bcm_sysport_set_wol(struct net_device *dev,
{
	struct bcm_sysport_priv *priv = netdev_priv(dev);
	struct device *kdev = &priv->pdev->dev;
	u32 supported = WAKE_MAGIC | WAKE_MAGICSECURE;
	u32 supported = WAKE_MAGIC | WAKE_MAGICSECURE | WAKE_FILTER;

	if (!device_can_wakeup(kdev))
		return -ENOTSUPP;
@@ -1043,7 +1043,7 @@ static int bcm_sysport_poll(struct napi_struct *napi, int budget)

static void mpd_enable_set(struct bcm_sysport_priv *priv, bool enable)
{
	u32 reg;
	u32 reg, bit;

	reg = umac_readl(priv, UMAC_MPD_CTRL);
	if (enable)
@@ -1051,12 +1051,32 @@ static void mpd_enable_set(struct bcm_sysport_priv *priv, bool enable)
	else
		reg &= ~MPD_EN;
	umac_writel(priv, reg, UMAC_MPD_CTRL);

	if (priv->is_lite)
		bit = RBUF_ACPI_EN_LITE;
	else
		bit = RBUF_ACPI_EN;

	reg = rbuf_readl(priv, RBUF_CONTROL);
	if (enable)
		reg |= bit;
	else
		reg &= ~bit;
	rbuf_writel(priv, reg, RBUF_CONTROL);
}

static void bcm_sysport_resume_from_wol(struct bcm_sysport_priv *priv)
{
	u32 reg;

	/* Stop monitoring MPD interrupt */
	intrl2_0_mask_set(priv, INTRL2_0_MPD);
	intrl2_0_mask_set(priv, INTRL2_0_MPD | INTRL2_0_BRCM_MATCH_TAG);

	/* Disable RXCHK, active filters and Broadcom tag matching */
	reg = rxchk_readl(priv, RXCHK_CONTROL);
	reg &= ~(RXCHK_BRCM_TAG_MATCH_MASK <<
		 RXCHK_BRCM_TAG_MATCH_SHIFT | RXCHK_EN | RXCHK_BRCM_TAG_EN);
	rxchk_writel(priv, reg, RXCHK_CONTROL);

	/* Clear the MagicPacket detection logic */
	mpd_enable_set(priv, false);
@@ -1085,6 +1105,7 @@ static irqreturn_t bcm_sysport_rx_isr(int irq, void *dev_id)
	struct bcm_sysport_priv *priv = netdev_priv(dev);
	struct bcm_sysport_tx_ring *txr;
	unsigned int ring, ring_bit;
	u32 reg;

	priv->irq0_stat = intrl2_0_readl(priv, INTRL2_CPU_STATUS) &
			  ~intrl2_0_readl(priv, INTRL2_CPU_MASK_STATUS);
@@ -1111,7 +1132,14 @@ static irqreturn_t bcm_sysport_rx_isr(int irq, void *dev_id)
		bcm_sysport_tx_reclaim_all(priv);

	if (priv->irq0_stat & INTRL2_0_MPD)
		netdev_info(priv->netdev, "Wake-on-LAN interrupt!\n");
		netdev_info(priv->netdev, "Wake-on-LAN (MPD) interrupt!\n");

	if (priv->irq0_stat & INTRL2_0_BRCM_MATCH_TAG) {
		reg = rxchk_readl(priv, RXCHK_BRCM_TAG_MATCH_STATUS) &
				  RXCHK_BRCM_TAG_MATCH_MASK;
		netdev_info(priv->netdev,
			    "Wake-on-LAN (filters 0x%02x) interrupt!\n", reg);
	}

	if (!priv->is_lite)
		goto out;
@@ -2096,6 +2124,132 @@ static int bcm_sysport_stop(struct net_device *dev)
	return 0;
}

static int bcm_sysport_rule_find(struct bcm_sysport_priv *priv,
				 u64 location)
{
	unsigned int index;
	u32 reg;

	for_each_set_bit(index, priv->filters, RXCHK_BRCM_TAG_MAX) {
		reg = rxchk_readl(priv, RXCHK_BRCM_TAG(index));
		reg >>= RXCHK_BRCM_TAG_CID_SHIFT;
		reg &= RXCHK_BRCM_TAG_CID_MASK;
		if (reg == location)
			return index;
	}

	return -EINVAL;
}

static int bcm_sysport_rule_get(struct bcm_sysport_priv *priv,
				struct ethtool_rxnfc *nfc)
{
	int index;

	/* This is not a rule that we know about */
	index = bcm_sysport_rule_find(priv, nfc->fs.location);
	if (index < 0)
		return -EOPNOTSUPP;

	nfc->fs.ring_cookie = RX_CLS_FLOW_WAKE;

	return 0;
}

static int bcm_sysport_rule_set(struct bcm_sysport_priv *priv,
				struct ethtool_rxnfc *nfc)
{
	unsigned int index;
	u32 reg;

	/* We cannot match locations greater than what the classification ID
	 * permits (256 entries)
	 */
	if (nfc->fs.location > RXCHK_BRCM_TAG_CID_MASK)
		return -E2BIG;

	/* We cannot support flows that are not destined for a wake-up */
	if (nfc->fs.ring_cookie != RX_CLS_FLOW_WAKE)
		return -EOPNOTSUPP;

	/* All filters are already in use, we cannot match more rules */
	if (bitmap_weight(priv->filters, RXCHK_BRCM_TAG_MAX) ==
	    RXCHK_BRCM_TAG_MAX)
		return -ENOSPC;

	index = find_first_zero_bit(priv->filters, RXCHK_BRCM_TAG_MAX);
	if (index > RXCHK_BRCM_TAG_MAX)
		return -ENOSPC;

	/* Location is the classification ID, and index is the position
	 * within one of our 8 possible filters to be programmed
	 */
	reg = rxchk_readl(priv, RXCHK_BRCM_TAG(index));
	reg &= ~(RXCHK_BRCM_TAG_CID_MASK << RXCHK_BRCM_TAG_CID_SHIFT);
	reg |= nfc->fs.location << RXCHK_BRCM_TAG_CID_SHIFT;
	rxchk_writel(priv, reg, RXCHK_BRCM_TAG(index));
	rxchk_writel(priv, 0xff00ffff, RXCHK_BRCM_TAG_MASK(index));

	set_bit(index, priv->filters);

	return 0;
}

static int bcm_sysport_rule_del(struct bcm_sysport_priv *priv,
				u64 location)
{
	int index;

	/* This is not a rule that we know about */
	index = bcm_sysport_rule_find(priv, location);
	if (index < 0)
		return -EOPNOTSUPP;

	/* No need to disable this filter if it was enabled, this will
	 * be taken care of during suspend time by bcm_sysport_suspend_to_wol
	 */
	clear_bit(index, priv->filters);

	return 0;
}

static int bcm_sysport_get_rxnfc(struct net_device *dev,
				 struct ethtool_rxnfc *nfc, u32 *rule_locs)
{
	struct bcm_sysport_priv *priv = netdev_priv(dev);
	int ret = -EOPNOTSUPP;

	switch (nfc->cmd) {
	case ETHTOOL_GRXCLSRULE:
		ret = bcm_sysport_rule_get(priv, nfc);
		break;
	default:
		break;
	}

	return ret;
}

static int bcm_sysport_set_rxnfc(struct net_device *dev,
				 struct ethtool_rxnfc *nfc)
{
	struct bcm_sysport_priv *priv = netdev_priv(dev);
	int ret = -EOPNOTSUPP;

	switch (nfc->cmd) {
	case ETHTOOL_SRXCLSRLINS:
		ret = bcm_sysport_rule_set(priv, nfc);
		break;
	case ETHTOOL_SRXCLSRLDEL:
		ret = bcm_sysport_rule_del(priv, nfc->fs.location);
		break;
	default:
		break;
	}

	return ret;
}

static const struct ethtool_ops bcm_sysport_ethtool_ops = {
	.get_drvinfo		= bcm_sysport_get_drvinfo,
	.get_msglevel		= bcm_sysport_get_msglvl,
@@ -2110,6 +2264,8 @@ static const struct ethtool_ops bcm_sysport_ethtool_ops = {
	.set_coalesce		= bcm_sysport_set_coalesce,
	.get_link_ksettings     = phy_ethtool_get_link_ksettings,
	.set_link_ksettings     = phy_ethtool_set_link_ksettings,
	.get_rxnfc		= bcm_sysport_get_rxnfc,
	.set_rxnfc		= bcm_sysport_set_rxnfc,
};

static u16 bcm_sysport_select_queue(struct net_device *dev, struct sk_buff *skb,
@@ -2434,16 +2590,39 @@ static int bcm_sysport_suspend_to_wol(struct bcm_sysport_priv *priv)
{
	struct net_device *ndev = priv->netdev;
	unsigned int timeout = 1000;
	unsigned int index, i = 0;
	u32 reg;

	/* Password has already been programmed */
	reg = umac_readl(priv, UMAC_MPD_CTRL);
	if (priv->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE))
		reg |= MPD_EN;
	reg &= ~PSW_EN;
	if (priv->wolopts & WAKE_MAGICSECURE)
		reg |= PSW_EN;
	umac_writel(priv, reg, UMAC_MPD_CTRL);

	if (priv->wolopts & WAKE_FILTER) {
		/* Turn on ACPI matching to steal packets from RBUF */
		reg = rbuf_readl(priv, RBUF_CONTROL);
		if (priv->is_lite)
			reg |= RBUF_ACPI_EN_LITE;
		else
			reg |= RBUF_ACPI_EN;
		rbuf_writel(priv, reg, RBUF_CONTROL);

		/* Enable RXCHK, active filters and Broadcom tag matching */
		reg = rxchk_readl(priv, RXCHK_CONTROL);
		reg &= ~(RXCHK_BRCM_TAG_MATCH_MASK <<
			 RXCHK_BRCM_TAG_MATCH_SHIFT);
		for_each_set_bit(index, priv->filters, RXCHK_BRCM_TAG_MAX) {
			reg |= BIT(RXCHK_BRCM_TAG_MATCH_SHIFT + i);
			i++;
		}
		reg |= RXCHK_EN | RXCHK_BRCM_TAG_EN;
		rxchk_writel(priv, reg, RXCHK_CONTROL);
	}

	/* Make sure RBUF entered WoL mode as result */
	do {
		reg = rbuf_readl(priv, RBUF_STATUS);
@@ -2464,7 +2643,7 @@ static int bcm_sysport_suspend_to_wol(struct bcm_sysport_priv *priv)
	umac_enable_set(priv, CMD_RX_EN, 1);

	/* Enable the interrupt wake-up source */
	intrl2_0_mask_clear(priv, INTRL2_0_MPD);
	intrl2_0_mask_clear(priv, INTRL2_0_MPD | INTRL2_0_BRCM_MATCH_TAG);

	netif_dbg(priv, wol, ndev, "entered WOL mode\n");

+9 −2
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
#ifndef __BCM_SYSPORT_H
#define __BCM_SYSPORT_H

#include <linux/bitmap.h>
#include <linux/if_vlan.h>
#include <linux/net_dim.h>

@@ -155,14 +156,18 @@ struct bcm_rsb {
#define  RXCHK_PARSE_AUTH		(1 << 22)

#define RXCHK_BRCM_TAG0			0x04
#define RXCHK_BRCM_TAG(i)		((i) * RXCHK_BRCM_TAG0)
#define RXCHK_BRCM_TAG(i)		((i) * 0x4 + RXCHK_BRCM_TAG0)
#define RXCHK_BRCM_TAG0_MASK		0x24
#define RXCHK_BRCM_TAG_MASK(i)		((i) * RXCHK_BRCM_TAG0_MASK)
#define RXCHK_BRCM_TAG_MASK(i)		((i) * 0x4 + RXCHK_BRCM_TAG0_MASK)
#define RXCHK_BRCM_TAG_MATCH_STATUS	0x44
#define RXCHK_ETHERTYPE			0x48
#define RXCHK_BAD_CSUM_CNTR		0x4C
#define RXCHK_OTHER_DISC_CNTR		0x50

#define RXCHK_BRCM_TAG_MAX		8
#define RXCHK_BRCM_TAG_CID_SHIFT	16
#define RXCHK_BRCM_TAG_CID_MASK		0xff

/* TXCHCK offsets and defines */
#define SYS_PORT_TXCHK_OFFSET		0x380
#define TXCHK_PKT_RDY_THRESH		0x00
@@ -185,6 +190,7 @@ struct bcm_rsb {
#define  RBUF_RSB_SWAP0			(1 << 22)
#define  RBUF_RSB_SWAP1			(1 << 23)
#define  RBUF_ACPI_EN			(1 << 23)
#define  RBUF_ACPI_EN_LITE		(1 << 24)

#define RBUF_PKT_RDY_THRESH		0x04

@@ -777,6 +783,7 @@ struct bcm_sysport_priv {

	/* Ethtool */
	u32			msg_enable;
	DECLARE_BITMAP(filters, RXCHK_BRCM_TAG_MAX);

	struct bcm_sysport_stats64	stats64;

+4 −1
Original line number Diff line number Diff line
@@ -870,7 +870,8 @@ struct ethtool_flow_ext {
 *	includes the %FLOW_EXT or %FLOW_MAC_EXT flag
 *	(see &struct ethtool_flow_ext description).
 * @ring_cookie: RX ring/queue index to deliver to, or %RX_CLS_FLOW_DISC
 *	if packets should be discarded
 *	if packets should be discarded, or %RX_CLS_FLOW_WAKE if the
 *	packets should be used for Wake-on-LAN with %WAKE_FILTER
 * @location: Location of rule in the table.  Locations must be
 *	numbered such that a flow matching multiple rules will be
 *	classified according to the first (lowest numbered) rule.
@@ -1634,6 +1635,7 @@ static inline int ethtool_validate_duplex(__u8 duplex)
#define WAKE_ARP		(1 << 4)
#define WAKE_MAGIC		(1 << 5)
#define WAKE_MAGICSECURE	(1 << 6) /* only meaningful if WAKE_MAGIC */
#define WAKE_FILTER		(1 << 7)

/* L2-L4 network traffic flow types */
#define	TCP_V4_FLOW	0x01	/* hash or spec (tcp_ip4_spec) */
@@ -1671,6 +1673,7 @@ static inline int ethtool_validate_duplex(__u8 duplex)
#define	RXH_DISCARD	(1 << 31)

#define	RX_CLS_FLOW_DISC	0xffffffffffffffffULL
#define RX_CLS_FLOW_WAKE	0xfffffffffffffffeULL

/* Special RX classification rule insert location values */
#define RX_CLS_LOC_SPECIAL	0x80000000	/* flag */