net: bcmgenet: add support for ethtool rxnfc flows
authorDoug Berger <opendmb@gmail.com>
Wed, 29 Apr 2020 20:02:05 +0000 (13:02 -0700)
committerDavid S. Miller <davem@davemloft.net>
Wed, 29 Apr 2020 23:55:22 +0000 (16:55 -0700)
This commit enables driver support for ethtool commands of this form:
ethtool -N|-U|--config-nfc|--config-ntuple devname
    flow-type ether|ip4
    [src xx:yy:zz:aa:bb:cc [m xx:yy:zz:aa:bb:cc]]
    [dst xx:yy:zz:aa:bb:cc [m xx:yy:zz:aa:bb:cc]] [proto N [m N]]
    [src-ip x.x.x.x [m x.x.x.x]] [dst-ip x.x.x.x [m x.x.x.x]] [tos N [m N]]
    [l4proto N [m N]] [src-port N [m N]] [dst-port N [m N]] [spi N [m N]]
    [l4data N [m N]] [vlan-etype N [m N]] [vlan N [m N]]
    [dst-mac xx:yy:zz:aa:bb:cc [m xx:yy:zz:aa:bb:cc]] [action 0] [loc N] |
    delete N

Since there is only one Rx Ring in this implementation action 0 behaves no
differently from not specifying a rule.

The rules can be seen with ethtool commands of this form:
ethtool -n|-u|--show-nfc|--show-ntuple devname [rule N]

Signed-off-by: Doug Berger <opendmb@gmail.com>
Acked-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/broadcom/genet/bcmgenet.c
drivers/net/ethernet/broadcom/genet/bcmgenet.h

index ad41944..5ef1ea7 100644 (file)
@@ -479,6 +479,30 @@ static void bcmgenet_hfb_enable_filter(struct bcmgenet_priv *priv, u32 f_index)
        reg = bcmgenet_hfb_reg_readl(priv, offset);
        reg |= (1 << (f_index % 32));
        bcmgenet_hfb_reg_writel(priv, reg, offset);
+       reg = bcmgenet_hfb_reg_readl(priv, HFB_CTRL);
+       reg |= RBUF_HFB_EN;
+       bcmgenet_hfb_reg_writel(priv, reg, HFB_CTRL);
+}
+
+static void bcmgenet_hfb_disable_filter(struct bcmgenet_priv *priv, u32 f_index)
+{
+       u32 offset, reg, reg1;
+
+       offset = HFB_FLT_ENABLE_V3PLUS;
+       reg = bcmgenet_hfb_reg_readl(priv, offset);
+       reg1 = bcmgenet_hfb_reg_readl(priv, offset + sizeof(u32));
+       if  (f_index < 32) {
+               reg1 &= ~(1 << (f_index % 32));
+               bcmgenet_hfb_reg_writel(priv, reg1, offset + sizeof(u32));
+       } else {
+               reg &= ~(1 << (f_index % 32));
+               bcmgenet_hfb_reg_writel(priv, reg, offset);
+       }
+       if (!reg && !reg1) {
+               reg = bcmgenet_hfb_reg_readl(priv, HFB_CTRL);
+               reg &= ~RBUF_HFB_EN;
+               bcmgenet_hfb_reg_writel(priv, reg, HFB_CTRL);
+       }
 }
 
 static void bcmgenet_hfb_set_filter_rx_queue_mapping(struct bcmgenet_priv *priv,
@@ -513,13 +537,213 @@ static int bcmgenet_hfb_find_unused_filter(struct bcmgenet_priv *priv)
 {
        u32 f_index;
 
-       for (f_index = 0; f_index < priv->hw_params->hfb_filter_cnt; f_index++)
+       /* First MAX_NUM_OF_FS_RULES are reserved for Rx NFC filters */
+       for (f_index = MAX_NUM_OF_FS_RULES;
+            f_index < priv->hw_params->hfb_filter_cnt; f_index++)
                if (!bcmgenet_hfb_is_filter_enabled(priv, f_index))
                        return f_index;
 
        return -ENOMEM;
 }
 
+static int bcmgenet_hfb_validate_mask(void *mask, size_t size)
+{
+       while (size) {
+               switch (*(unsigned char *)mask++) {
+               case 0x00:
+               case 0x0f:
+               case 0xf0:
+               case 0xff:
+                       size--;
+                       continue;
+               default:
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+#define VALIDATE_MASK(x) \
+       bcmgenet_hfb_validate_mask(&(x), sizeof(x))
+
+static int bcmgenet_hfb_insert_data(u32 *f, int offset,
+                                   void *val, void *mask, size_t size)
+{
+       int index;
+       u32 tmp;
+
+       index = offset / 2;
+       tmp = f[index];
+
+       while (size--) {
+               if (offset++ & 1) {
+                       tmp &= ~0x300FF;
+                       tmp |= (*(unsigned char *)val++);
+                       switch ((*(unsigned char *)mask++)) {
+                       case 0xFF:
+                               tmp |= 0x30000;
+                               break;
+                       case 0xF0:
+                               tmp |= 0x20000;
+                               break;
+                       case 0x0F:
+                               tmp |= 0x10000;
+                               break;
+                       }
+                       f[index++] = tmp;
+                       if (size)
+                               tmp = f[index];
+               } else {
+                       tmp &= ~0xCFF00;
+                       tmp |= (*(unsigned char *)val++) << 8;
+                       switch ((*(unsigned char *)mask++)) {
+                       case 0xFF:
+                               tmp |= 0xC0000;
+                               break;
+                       case 0xF0:
+                               tmp |= 0x80000;
+                               break;
+                       case 0x0F:
+                               tmp |= 0x40000;
+                               break;
+                       }
+                       if (!size)
+                               f[index] = tmp;
+               }
+       }
+
+       return 0;
+}
+
+static void bcmgenet_hfb_set_filter(struct bcmgenet_priv *priv, u32 *f_data,
+                                   u32 f_length, u32 rx_queue, int f_index)
+{
+       u32 base = f_index * priv->hw_params->hfb_filter_size;
+       int i;
+
+       for (i = 0; i < f_length; i++)
+               bcmgenet_hfb_writel(priv, f_data[i], (base + i) * sizeof(u32));
+
+       bcmgenet_hfb_set_filter_length(priv, f_index, 2 * f_length);
+       bcmgenet_hfb_set_filter_rx_queue_mapping(priv, f_index, rx_queue);
+}
+
+static int bcmgenet_hfb_create_rxnfc_filter(struct bcmgenet_priv *priv,
+                                           struct bcmgenet_rxnfc_rule *rule)
+{
+       struct ethtool_rx_flow_spec *fs = &rule->fs;
+       int err = 0, offset = 0, f_length = 0;
+       u16 val_16, mask_16;
+       u8 val_8, mask_8;
+       size_t size;
+       u32 *f_data;
+
+       f_data = kcalloc(priv->hw_params->hfb_filter_size, sizeof(u32),
+                        GFP_KERNEL);
+       if (!f_data)
+               return -ENOMEM;
+
+       if (fs->flow_type & FLOW_MAC_EXT) {
+               bcmgenet_hfb_insert_data(f_data, 0,
+                                        &fs->h_ext.h_dest, &fs->m_ext.h_dest,
+                                        sizeof(fs->h_ext.h_dest));
+       }
+
+       if (fs->flow_type & FLOW_EXT) {
+               if (fs->m_ext.vlan_etype ||
+                   fs->m_ext.vlan_tci) {
+                       bcmgenet_hfb_insert_data(f_data, 12,
+                                                &fs->h_ext.vlan_etype,
+                                                &fs->m_ext.vlan_etype,
+                                                sizeof(fs->h_ext.vlan_etype));
+                       bcmgenet_hfb_insert_data(f_data, 14,
+                                                &fs->h_ext.vlan_tci,
+                                                &fs->m_ext.vlan_tci,
+                                                sizeof(fs->h_ext.vlan_tci));
+                       offset += VLAN_HLEN;
+                       f_length += DIV_ROUND_UP(VLAN_HLEN, 2);
+               }
+       }
+
+       switch (fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT)) {
+       case ETHER_FLOW:
+               f_length += DIV_ROUND_UP(ETH_HLEN, 2);
+               bcmgenet_hfb_insert_data(f_data, 0,
+                                        &fs->h_u.ether_spec.h_dest,
+                                        &fs->m_u.ether_spec.h_dest,
+                                        sizeof(fs->h_u.ether_spec.h_dest));
+               bcmgenet_hfb_insert_data(f_data, ETH_ALEN,
+                                        &fs->h_u.ether_spec.h_source,
+                                        &fs->m_u.ether_spec.h_source,
+                                        sizeof(fs->h_u.ether_spec.h_source));
+               bcmgenet_hfb_insert_data(f_data, (2 * ETH_ALEN) + offset,
+                                        &fs->h_u.ether_spec.h_proto,
+                                        &fs->m_u.ether_spec.h_proto,
+                                        sizeof(fs->h_u.ether_spec.h_proto));
+               break;
+       case IP_USER_FLOW:
+               f_length += DIV_ROUND_UP(ETH_HLEN + 20, 2);
+               /* Specify IP Ether Type */
+               val_16 = htons(ETH_P_IP);
+               mask_16 = 0xFFFF;
+               bcmgenet_hfb_insert_data(f_data, (2 * ETH_ALEN) + offset,
+                                        &val_16, &mask_16, sizeof(val_16));
+               bcmgenet_hfb_insert_data(f_data, 15 + offset,
+                                        &fs->h_u.usr_ip4_spec.tos,
+                                        &fs->m_u.usr_ip4_spec.tos,
+                                        sizeof(fs->h_u.usr_ip4_spec.tos));
+               bcmgenet_hfb_insert_data(f_data, 23 + offset,
+                                        &fs->h_u.usr_ip4_spec.proto,
+                                        &fs->m_u.usr_ip4_spec.proto,
+                                        sizeof(fs->h_u.usr_ip4_spec.proto));
+               bcmgenet_hfb_insert_data(f_data, 26 + offset,
+                                        &fs->h_u.usr_ip4_spec.ip4src,
+                                        &fs->m_u.usr_ip4_spec.ip4src,
+                                        sizeof(fs->h_u.usr_ip4_spec.ip4src));
+               bcmgenet_hfb_insert_data(f_data, 30 + offset,
+                                        &fs->h_u.usr_ip4_spec.ip4dst,
+                                        &fs->m_u.usr_ip4_spec.ip4dst,
+                                        sizeof(fs->h_u.usr_ip4_spec.ip4dst));
+               if (!fs->m_u.usr_ip4_spec.l4_4_bytes)
+                       break;
+
+               /* Only supports 20 byte IPv4 header */
+               val_8 = 0x45;
+               mask_8 = 0xFF;
+               bcmgenet_hfb_insert_data(f_data, ETH_HLEN + offset,
+                                        &val_8, &mask_8,
+                                        sizeof(val_8));
+               size = sizeof(fs->h_u.usr_ip4_spec.l4_4_bytes);
+               bcmgenet_hfb_insert_data(f_data,
+                                        ETH_HLEN + 20 + offset,
+                                        &fs->h_u.usr_ip4_spec.l4_4_bytes,
+                                        &fs->m_u.usr_ip4_spec.l4_4_bytes,
+                                        size);
+               f_length += DIV_ROUND_UP(size, 2);
+               break;
+       }
+
+       if (!fs->ring_cookie) {
+               /* Ring 0 flows can be handled by the default Descriptor Ring
+                * We'll map them to ring 0, but don't enable the filter
+                */
+               bcmgenet_hfb_set_filter(priv, f_data, f_length, 0,
+                                       fs->location);
+               rule->state = BCMGENET_RXNFC_STATE_DISABLED;
+       } else {
+               /* Other Rx rings are direct mapped here */
+               bcmgenet_hfb_set_filter(priv, f_data, f_length,
+                                       fs->ring_cookie, fs->location);
+               bcmgenet_hfb_enable_filter(priv, fs->location);
+               rule->state = BCMGENET_RXNFC_STATE_ENABLED;
+       }
+
+       kfree(f_data);
+
+       return err;
+}
+
 /* bcmgenet_hfb_add_filter
  *
  * Add new filter to Hardware Filter Block to match and direct Rx traffic to
@@ -559,7 +783,6 @@ int bcmgenet_hfb_add_filter(struct bcmgenet_priv *priv, u32 *f_data,
                            u32 f_length, u32 rx_queue)
 {
        int f_index;
-       u32 i;
 
        f_index = bcmgenet_hfb_find_unused_filter(priv);
        if (f_index < 0)
@@ -568,15 +791,8 @@ int bcmgenet_hfb_add_filter(struct bcmgenet_priv *priv, u32 *f_data,
        if (f_length > priv->hw_params->hfb_filter_size)
                return -EINVAL;
 
-       for (i = 0; i < f_length; i++)
-               bcmgenet_hfb_writel(priv, f_data[i],
-                       (f_index * priv->hw_params->hfb_filter_size + i) *
-                       sizeof(u32));
-
-       bcmgenet_hfb_set_filter_length(priv, f_index, 2 * f_length);
-       bcmgenet_hfb_set_filter_rx_queue_mapping(priv, f_index, rx_queue);
+       bcmgenet_hfb_set_filter(priv, f_data, f_length, rx_queue, f_index);
        bcmgenet_hfb_enable_filter(priv, f_index);
-       bcmgenet_hfb_reg_writel(priv, 0x1, HFB_CTRL);
 
        return 0;
 }
@@ -607,9 +823,17 @@ static void bcmgenet_hfb_clear(struct bcmgenet_priv *priv)
 
 static void bcmgenet_hfb_init(struct bcmgenet_priv *priv)
 {
+       int i;
+
        if (GENET_IS_V1(priv) || GENET_IS_V2(priv))
                return;
 
+       INIT_LIST_HEAD(&priv->rxnfc_list);
+       for (i = 0; i < MAX_NUM_OF_FS_RULES; i++) {
+               INIT_LIST_HEAD(&priv->rxnfc_rules[i].list);
+               priv->rxnfc_rules[i].state = BCMGENET_RXNFC_STATE_UNUSED;
+       }
+
        bcmgenet_hfb_clear(priv);
 }
 
@@ -1197,6 +1421,228 @@ static int bcmgenet_set_eee(struct net_device *dev, struct ethtool_eee *e)
        return phy_ethtool_set_eee(dev->phydev, e);
 }
 
+static int bcmgenet_validate_flow(struct net_device *dev,
+                                 struct ethtool_rxnfc *cmd)
+{
+       struct ethtool_usrip4_spec *l4_mask;
+       struct ethhdr *eth_mask;
+
+       if (cmd->fs.location >= MAX_NUM_OF_FS_RULES) {
+               netdev_err(dev, "rxnfc: Invalid location (%d)\n",
+                          cmd->fs.location);
+               return -EINVAL;
+       }
+
+       switch (cmd->fs.flow_type & ~(FLOW_EXT | FLOW_MAC_EXT)) {
+       case IP_USER_FLOW:
+               l4_mask = &cmd->fs.m_u.usr_ip4_spec;
+               /* don't allow mask which isn't valid */
+               if (VALIDATE_MASK(l4_mask->ip4src) ||
+                   VALIDATE_MASK(l4_mask->ip4dst) ||
+                   VALIDATE_MASK(l4_mask->l4_4_bytes) ||
+                   VALIDATE_MASK(l4_mask->proto) ||
+                   VALIDATE_MASK(l4_mask->ip_ver) ||
+                   VALIDATE_MASK(l4_mask->tos)) {
+                       netdev_err(dev, "rxnfc: Unsupported mask\n");
+                       return -EINVAL;
+               }
+               break;
+       case ETHER_FLOW:
+               eth_mask = &cmd->fs.m_u.ether_spec;
+               /* don't allow mask which isn't valid */
+               if (VALIDATE_MASK(eth_mask->h_source) ||
+                   VALIDATE_MASK(eth_mask->h_source) ||
+                   VALIDATE_MASK(eth_mask->h_proto)) {
+                       netdev_err(dev, "rxnfc: Unsupported mask\n");
+                       return -EINVAL;
+               }
+               break;
+       default:
+               netdev_err(dev, "rxnfc: Unsupported flow type (0x%x)\n",
+                          cmd->fs.flow_type);
+               return -EINVAL;
+       }
+
+       if ((cmd->fs.flow_type & FLOW_EXT)) {
+               /* don't allow mask which isn't valid */
+               if (VALIDATE_MASK(cmd->fs.m_ext.vlan_etype) ||
+                   VALIDATE_MASK(cmd->fs.m_ext.vlan_tci)) {
+                       netdev_err(dev, "rxnfc: Unsupported mask\n");
+                       return -EINVAL;
+               }
+               if (cmd->fs.m_ext.data[0] || cmd->fs.m_ext.data[1]) {
+                       netdev_err(dev, "rxnfc: user-def not supported\n");
+                       return -EINVAL;
+               }
+       }
+
+       if ((cmd->fs.flow_type & FLOW_MAC_EXT)) {
+               /* don't allow mask which isn't valid */
+               if (VALIDATE_MASK(cmd->fs.m_ext.h_dest)) {
+                       netdev_err(dev, "rxnfc: Unsupported mask\n");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+static int bcmgenet_insert_flow(struct net_device *dev,
+                               struct ethtool_rxnfc *cmd)
+{
+       struct bcmgenet_priv *priv = netdev_priv(dev);
+       struct bcmgenet_rxnfc_rule *loc_rule;
+       int err;
+
+       if (priv->hw_params->hfb_filter_size < 128) {
+               netdev_err(dev, "rxnfc: Not supported by this device\n");
+               return -EINVAL;
+       }
+
+       if (cmd->fs.ring_cookie > priv->hw_params->rx_queues) {
+               netdev_err(dev, "rxnfc: Unsupported action (%llu)\n",
+                          cmd->fs.ring_cookie);
+               return -EINVAL;
+       }
+
+       err = bcmgenet_validate_flow(dev, cmd);
+       if (err)
+               return err;
+
+       loc_rule = &priv->rxnfc_rules[cmd->fs.location];
+       if (loc_rule->state == BCMGENET_RXNFC_STATE_ENABLED)
+               bcmgenet_hfb_disable_filter(priv, cmd->fs.location);
+       if (loc_rule->state != BCMGENET_RXNFC_STATE_UNUSED)
+               list_del(&loc_rule->list);
+       loc_rule->state = BCMGENET_RXNFC_STATE_UNUSED;
+       memcpy(&loc_rule->fs, &cmd->fs,
+              sizeof(struct ethtool_rx_flow_spec));
+
+       err = bcmgenet_hfb_create_rxnfc_filter(priv, loc_rule);
+       if (err) {
+               netdev_err(dev, "rxnfc: Could not install rule (%d)\n",
+                          err);
+               return err;
+       }
+
+       list_add_tail(&loc_rule->list, &priv->rxnfc_list);
+
+       return 0;
+}
+
+static int bcmgenet_delete_flow(struct net_device *dev,
+                               struct ethtool_rxnfc *cmd)
+{
+       struct bcmgenet_priv *priv = netdev_priv(dev);
+       struct bcmgenet_rxnfc_rule *rule;
+       int err = 0;
+
+       if (cmd->fs.location >= MAX_NUM_OF_FS_RULES)
+               return -EINVAL;
+
+       rule = &priv->rxnfc_rules[cmd->fs.location];
+       if (rule->state == BCMGENET_RXNFC_STATE_UNUSED) {
+               err =  -ENOENT;
+               goto out;
+       }
+
+       if (rule->state == BCMGENET_RXNFC_STATE_ENABLED)
+               bcmgenet_hfb_disable_filter(priv, cmd->fs.location);
+       if (rule->state != BCMGENET_RXNFC_STATE_UNUSED)
+               list_del(&rule->list);
+       rule->state = BCMGENET_RXNFC_STATE_UNUSED;
+       memset(&rule->fs, 0, sizeof(struct ethtool_rx_flow_spec));
+
+out:
+       return err;
+}
+
+static int bcmgenet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
+{
+       struct bcmgenet_priv *priv = netdev_priv(dev);
+       int err = 0;
+
+       switch (cmd->cmd) {
+       case ETHTOOL_SRXCLSRLINS:
+               err = bcmgenet_insert_flow(dev, cmd);
+               break;
+       case ETHTOOL_SRXCLSRLDEL:
+               err = bcmgenet_delete_flow(dev, cmd);
+               break;
+       default:
+               netdev_warn(priv->dev, "Unsupported ethtool command. (%d)\n",
+                           cmd->cmd);
+               return -EINVAL;
+       }
+
+       return err;
+}
+
+static int bcmgenet_get_flow(struct net_device *dev, struct ethtool_rxnfc *cmd,
+                            int loc)
+{
+       struct bcmgenet_priv *priv = netdev_priv(dev);
+       struct bcmgenet_rxnfc_rule *rule;
+       int err = 0;
+
+       if (loc < 0 || loc >= MAX_NUM_OF_FS_RULES)
+               return -EINVAL;
+
+       rule = &priv->rxnfc_rules[loc];
+       if (rule->state == BCMGENET_RXNFC_STATE_UNUSED)
+               err = -ENOENT;
+       else
+               memcpy(&cmd->fs, &rule->fs,
+                      sizeof(struct ethtool_rx_flow_spec));
+
+       return err;
+}
+
+static int bcmgenet_get_num_flows(struct bcmgenet_priv *priv)
+{
+       struct list_head *pos;
+       int res = 0;
+
+       list_for_each(pos, &priv->rxnfc_list)
+               res++;
+
+       return res;
+}
+
+static int bcmgenet_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
+                             u32 *rule_locs)
+{
+       struct bcmgenet_priv *priv = netdev_priv(dev);
+       struct bcmgenet_rxnfc_rule *rule;
+       int err = 0;
+       int i = 0;
+
+       switch (cmd->cmd) {
+       case ETHTOOL_GRXRINGS:
+               cmd->data = priv->hw_params->rx_queues ?: 1;
+               break;
+       case ETHTOOL_GRXCLSRLCNT:
+               cmd->rule_cnt = bcmgenet_get_num_flows(priv);
+               cmd->data = MAX_NUM_OF_FS_RULES;
+               break;
+       case ETHTOOL_GRXCLSRULE:
+               err = bcmgenet_get_flow(dev, cmd, cmd->fs.location);
+               break;
+       case ETHTOOL_GRXCLSRLALL:
+               list_for_each_entry(rule, &priv->rxnfc_list, list)
+                       if (i < cmd->rule_cnt)
+                               rule_locs[i++] = rule->fs.location;
+               cmd->rule_cnt = i;
+               cmd->data = MAX_NUM_OF_FS_RULES;
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+       }
+
+       return err;
+}
+
 /* standard ethtool support functions. */
 static const struct ethtool_ops bcmgenet_ethtool_ops = {
        .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS |
@@ -1221,6 +1667,8 @@ static const struct ethtool_ops bcmgenet_ethtool_ops = {
        .get_link_ksettings     = bcmgenet_get_link_ksettings,
        .set_link_ksettings     = bcmgenet_set_link_ksettings,
        .get_ts_info            = ethtool_op_get_ts_info,
+       .get_rxnfc              = bcmgenet_get_rxnfc,
+       .set_rxnfc              = bcmgenet_set_rxnfc,
 };
 
 /* Power down the unimac, based on mode. */
@@ -3730,8 +4178,8 @@ static int bcmgenet_resume(struct device *d)
        struct net_device *dev = dev_get_drvdata(d);
        struct bcmgenet_priv *priv = netdev_priv(dev);
        unsigned long dma_ctrl;
+       u32 offset, reg;
        int ret;
-       u32 reg;
 
        if (!netif_running(dev))
                return 0;
@@ -3766,6 +4214,11 @@ static int bcmgenet_resume(struct device *d)
 
        bcmgenet_set_hw_addr(priv, dev->dev_addr);
 
+       offset = HFB_FLT_ENABLE_V3PLUS;
+       bcmgenet_hfb_reg_writel(priv, priv->hfb_en[1], offset);
+       bcmgenet_hfb_reg_writel(priv, priv->hfb_en[2], offset + sizeof(u32));
+       bcmgenet_hfb_reg_writel(priv, priv->hfb_en[0], HFB_CTRL);
+
        if (priv->internal_phy) {
                reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT);
                reg |= EXT_ENERGY_DET_MASK;
@@ -3809,6 +4262,7 @@ static int bcmgenet_suspend(struct device *d)
        struct net_device *dev = dev_get_drvdata(d);
        struct bcmgenet_priv *priv = netdev_priv(dev);
        int ret = 0;
+       u32 offset;
 
        if (!netif_running(dev))
                return 0;
@@ -3820,6 +4274,13 @@ static int bcmgenet_suspend(struct device *d)
        if (!device_may_wakeup(d))
                phy_suspend(dev->phydev);
 
+       /* Preserve filter state and disable filtering */
+       priv->hfb_en[0] = bcmgenet_hfb_reg_readl(priv, HFB_CTRL);
+       offset = HFB_FLT_ENABLE_V3PLUS;
+       priv->hfb_en[1] = bcmgenet_hfb_reg_readl(priv, offset);
+       priv->hfb_en[2] = bcmgenet_hfb_reg_readl(priv, offset + sizeof(u32));
+       bcmgenet_hfb_reg_writel(priv, 0, HFB_CTRL);
+
        /* Prepare the device for Wake-on-LAN and switch to the slow clock */
        if (device_may_wakeup(d) && priv->wolopts)
                ret = bcmgenet_power_down(priv, GENET_POWER_WOL_MAGIC);
index a858b73..031d91f 100644 (file)
@@ -32,6 +32,7 @@
 #define DMA_MAX_BURST_LENGTH    0x10
 
 /* misc. configuration */
+#define MAX_NUM_OF_FS_RULES            16
 #define CLEAR_ALL_HFB                  0xFF
 #define DMA_FC_THRESH_HI               (TOTAL_DESC >> 4)
 #define DMA_FC_THRESH_LO               5
@@ -609,6 +610,18 @@ struct bcmgenet_rx_ring {
        struct bcmgenet_priv *priv;
 };
 
+enum bcmgenet_rxnfc_state {
+       BCMGENET_RXNFC_STATE_UNUSED = 0,
+       BCMGENET_RXNFC_STATE_DISABLED,
+       BCMGENET_RXNFC_STATE_ENABLED
+};
+
+struct bcmgenet_rxnfc_rule {
+       struct  list_head list;
+       struct ethtool_rx_flow_spec     fs;
+       enum bcmgenet_rxnfc_state state;
+};
+
 /* device context */
 struct bcmgenet_priv {
        void __iomem *base;
@@ -627,6 +640,8 @@ struct bcmgenet_priv {
        struct enet_cb *rx_cbs;
        unsigned int num_rx_bds;
        unsigned int rx_buf_len;
+       struct bcmgenet_rxnfc_rule rxnfc_rules[MAX_NUM_OF_FS_RULES];
+       struct list_head rxnfc_list;
 
        struct bcmgenet_rx_ring rx_rings[DESC_INDEX + 1];
 
@@ -679,6 +694,7 @@ struct bcmgenet_priv {
        u32 wolopts;
        u8 sopass[SOPASS_MAX];
        bool wol_active;
+       u32 hfb_en[3];
 
        struct bcmgenet_mib_counters mib;