From 6cfef793b558eee47bac720574aff0d36b89d20a Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 7 Aug 2018 10:50:20 -0700 Subject: [PATCH 1/3] ethtool: Add WAKE_FILTER and RX_CLS_FLOW_WAKE Add the ability to specify through ethtool::rxnfc that a rule location is special and will be used to participate in Wake-on-LAN, by e.g: having a specific pattern be matched. When this is the case, fs->ring_cookie must be set to the special value RX_CLS_FLOW_WAKE. We also define an additional ethtool::wolinfo flag: WAKE_FILTER which can be used to configure an Ethernet adapter to allow Wake-on-LAN using previously programmed filters. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- include/uapi/linux/ethtool.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index 813282cc8af6..dc69391d2bba 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -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 */ From 8a75f4f2acd78adfaf4b96939322d8a1de97d378 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 7 Aug 2018 10:50:22 -0700 Subject: [PATCH 2/3] net: dsa: bcm_sf2: Propagate ethtool::rxnfc to CPU port Allow propagating ethtool::rxnfc programming to the CPU/management port such that it is possible for such a CPU to perform e.g: Wake-on-LAN using filters configured by the switch. We need a tiny bit of cooperation between the switch drivers which is able to do the full flow matching, whereas the CPU/management port might not. The CPU/management driver needs to return -EOPNOTSUPP to indicate an non critical error, any other error code otherwise. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/dsa/bcm_sf2_cfp.c | 43 ++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/drivers/net/dsa/bcm_sf2_cfp.c b/drivers/net/dsa/bcm_sf2_cfp.c index 1e37b65aab93..47c5f272a084 100644 --- a/drivers/net/dsa/bcm_sf2_cfp.c +++ b/drivers/net/dsa/bcm_sf2_cfp.c @@ -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; } From bb9051a2b23005a18826a2c33c98e5d977332352 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 7 Aug 2018 10:50:23 -0700 Subject: [PATCH 3/3] net: systemport: Add support for WAKE_FILTER The SYSTEMPORT MAC allows up to 8 filters to be programmed to wake-up from LAN. Verify that we have up to 8 filters and program them to the appropriate RXCHK entries to be matched (along with their masks). We need to update the entry and exit to Wake-on-LAN mode to keep the RXCHK engine running to match during suspend, but this is otherwise fairly similar to Magic Packet detection. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/ethernet/broadcom/bcmsysport.c | 193 ++++++++++++++++++++- drivers/net/ethernet/broadcom/bcmsysport.h | 11 +- 2 files changed, 195 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index 284581c9680e..ca47309d4494 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -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); - reg |= MPD_EN; + 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"); diff --git a/drivers/net/ethernet/broadcom/bcmsysport.h b/drivers/net/ethernet/broadcom/bcmsysport.h index cf440b91fd04..046c6c1d97fd 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.h +++ b/drivers/net/ethernet/broadcom/bcmsysport.h @@ -11,6 +11,7 @@ #ifndef __BCM_SYSPORT_H #define __BCM_SYSPORT_H +#include #include #include @@ -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;