net: systemport: Add support for WAKE_FILTER
authorFlorian Fainelli <f.fainelli@gmail.com>
Tue, 7 Aug 2018 17:50:23 +0000 (10:50 -0700)
committerDavid S. Miller <davem@davemloft.net>
Tue, 7 Aug 2018 19:15:03 +0000 (12:15 -0700)
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 <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/broadcom/bcmsysport.c
drivers/net/ethernet/broadcom/bcmsysport.h

index 284581c..ca47309 100644 (file)
@@ -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");
 
index cf440b9..046c6c1 100644 (file)
@@ -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;