net: dsa: bcm_sf2: Propagate ethtool::rxnfc to CPU port
authorFlorian Fainelli <f.fainelli@gmail.com>
Tue, 7 Aug 2018 17:50:22 +0000 (10:50 -0700)
committerDavid S. Miller <davem@davemloft.net>
Tue, 7 Aug 2018 19:15:03 +0000 (12:15 -0700)
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 <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/bcm_sf2_cfp.c

index 1e37b65aab931a39d97cffbcfa49f541d748d260..47c5f272a084dffe9b8340514449b5aa5c5bcd40 100644 (file)
@@ -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;
 }