sfc: bare bones TC offload on EF100
authorEdward Cree <ecree.xilinx@gmail.com>
Mon, 26 Sep 2022 18:57:36 +0000 (19:57 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 28 Sep 2022 08:43:22 +0000 (09:43 +0100)
This is the absolute minimum viable TC implementation to get traffic to
 VFs and allow them to be tested; it supports no match fields besides
 ingress port, no actions besides mirred and drop, and no stats.
Example usage:
    tc filter add dev $PF parent ffff: flower skip_sw \
        action mirred egress mirror dev $VFREP
    tc filter add dev $VFREP parent ffff: flower skip_sw \
        action mirred egress redirect dev $PF
 gives a VF unfiltered access to the network out the physical port ($PF
 acts here as a physical port representor).
More matches, actions, and counters will be added in subsequent patches.

Signed-off-by: Edward Cree <ecree.xilinx@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/sfc/mae.c
drivers/net/ethernet/sfc/mae.h
drivers/net/ethernet/sfc/mcdi.h
drivers/net/ethernet/sfc/tc.c
drivers/net/ethernet/sfc/tc.h

index 19138b2d2f5c72a04edeefc1705f5166c6a2d590..874c765b2465105d3e9d41ad192b826529ba1cb8 100644 (file)
@@ -168,6 +168,111 @@ int efx_mae_get_caps(struct efx_nic *efx, struct mae_caps *caps)
                                       caps->action_rule_fields);
 }
 
+/* Bit twiddling:
+ * Prefix: 1...110...0
+ *      ~: 0...001...1
+ *    + 1: 0...010...0 is power of two
+ * so (~x) & ((~x) + 1) == 0.  Converse holds also.
+ */
+#define is_prefix_byte(_x)     !(((_x) ^ 0xff) & (((_x) ^ 0xff) + 1))
+
+enum mask_type { MASK_ONES, MASK_ZEROES, MASK_PREFIX, MASK_OTHER };
+
+static const char *mask_type_name(enum mask_type typ)
+{
+       switch (typ) {
+       case MASK_ONES:
+               return "all-1s";
+       case MASK_ZEROES:
+               return "all-0s";
+       case MASK_PREFIX:
+               return "prefix";
+       case MASK_OTHER:
+               return "arbitrary";
+       default: /* can't happen */
+               return "unknown";
+       }
+}
+
+/* Checks a (big-endian) bytestring is a bit prefix */
+static enum mask_type classify_mask(const u8 *mask, size_t len)
+{
+       bool zeroes = true; /* All bits seen so far are zeroes */
+       bool ones = true; /* All bits seen so far are ones */
+       bool prefix = true; /* Valid prefix so far */
+       size_t i;
+
+       for (i = 0; i < len; i++) {
+               if (ones) {
+                       if (!is_prefix_byte(mask[i]))
+                               prefix = false;
+               } else if (mask[i]) {
+                       prefix = false;
+               }
+               if (mask[i] != 0xff)
+                       ones = false;
+               if (mask[i])
+                       zeroes = false;
+       }
+       if (ones)
+               return MASK_ONES;
+       if (zeroes)
+               return MASK_ZEROES;
+       if (prefix)
+               return MASK_PREFIX;
+       return MASK_OTHER;
+}
+
+static int efx_mae_match_check_cap_typ(u8 support, enum mask_type typ)
+{
+       switch (support) {
+       case MAE_FIELD_UNSUPPORTED:
+       case MAE_FIELD_SUPPORTED_MATCH_NEVER:
+               if (typ == MASK_ZEROES)
+                       return 0;
+               return -EOPNOTSUPP;
+       case MAE_FIELD_SUPPORTED_MATCH_OPTIONAL:
+               if (typ == MASK_ZEROES)
+                       return 0;
+               fallthrough;
+       case MAE_FIELD_SUPPORTED_MATCH_ALWAYS:
+               if (typ == MASK_ONES)
+                       return 0;
+               return -EINVAL;
+       case MAE_FIELD_SUPPORTED_MATCH_PREFIX:
+               if (typ == MASK_OTHER)
+                       return -EOPNOTSUPP;
+               return 0;
+       case MAE_FIELD_SUPPORTED_MATCH_MASK:
+               return 0;
+       default:
+               return -EIO;
+       }
+}
+
+int efx_mae_match_check_caps(struct efx_nic *efx,
+                            const struct efx_tc_match_fields *mask,
+                            struct netlink_ext_ack *extack)
+{
+       const u8 *supported_fields = efx->tc->caps->action_rule_fields;
+       __be32 ingress_port = cpu_to_be32(mask->ingress_port);
+       enum mask_type ingress_port_mask_type;
+       int rc;
+
+       /* Check for _PREFIX assumes big-endian, so we need to convert */
+       ingress_port_mask_type = classify_mask((const u8 *)&ingress_port,
+                                              sizeof(ingress_port));
+       rc = efx_mae_match_check_cap_typ(supported_fields[MAE_FIELD_INGRESS_PORT],
+                                        ingress_port_mask_type);
+       if (rc) {
+               efx_tc_err(efx, "No support for %s mask in field ingress_port\n",
+                          mask_type_name(ingress_port_mask_type));
+               NL_SET_ERR_MSG_MOD(extack, "Unsupported mask type for ingress_port");
+               return rc;
+       }
+       return 0;
+}
+
 static bool efx_mae_asl_id(u32 id)
 {
        return !!(id & BIT(31));
@@ -335,6 +440,10 @@ static int efx_mae_populate_match_criteria(MCDI_DECLARE_STRUCT_PTR(match_crit),
        }
        MCDI_STRUCT_SET_DWORD(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_INGRESS_MPORT_SELECTOR_MASK,
                              match->mask.ingress_port);
+       MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_RECIRC_ID,
+                            match->value.recirc_id);
+       MCDI_STRUCT_SET_BYTE(match_crit, MAE_FIELD_MASK_VALUE_PAIRS_V2_RECIRC_ID_MASK,
+                            match->mask.recirc_id);
        return 0;
 }
 
index 2b49a88b303c755675d1bbc5075351dbc7caa61c..3e0cd238d523e0af85d7abe5a3785bd94dac4582 100644 (file)
@@ -37,6 +37,10 @@ struct mae_caps {
 
 int efx_mae_get_caps(struct efx_nic *efx, struct mae_caps *caps);
 
+int efx_mae_match_check_caps(struct efx_nic *efx,
+                            const struct efx_tc_match_fields *mask,
+                            struct netlink_ext_ack *extack);
+
 int efx_mae_alloc_action_set(struct efx_nic *efx, struct efx_tc_action_set *act);
 int efx_mae_free_action_set(struct efx_nic *efx, u32 fw_id);
 
index 26bc69f768016ad8d0c6689178656a103b34cc36..1f18e9dc62e8d00e6bac063d0e8dc9b4a7868046 100644 (file)
@@ -201,6 +201,12 @@ void efx_mcdi_sensor_event(struct efx_nic *efx, efx_qword_t *ev);
        ((u8 *)(_buf) + (_offset))
 #define MCDI_PTR(_buf, _field)                                         \
        _MCDI_PTR(_buf, MC_CMD_ ## _field ## _OFST)
+/* Use MCDI_STRUCT_ functions to access members of MCDI structuredefs.
+ * _buf should point to the start of the structure, typically obtained with
+ * MCDI_DECLARE_STRUCT_PTR(structure) = _MCDI_DWORD(mcdi_buf, FIELD_WHICH_IS_STRUCT);
+ */
+#define MCDI_STRUCT_PTR(_buf, _field)                                  \
+       _MCDI_PTR(_buf, _field ## _OFST)
 #define _MCDI_CHECK_ALIGN(_ofst, _align)                               \
        ((_ofst) + BUILD_BUG_ON_ZERO((_ofst) & (_align - 1)))
 #define _MCDI_DWORD(_buf, _field)                                      \
@@ -208,6 +214,10 @@ void efx_mcdi_sensor_event(struct efx_nic *efx, efx_qword_t *ev);
 #define _MCDI_STRUCT_DWORD(_buf, _field)                               \
        ((_buf) + (_MCDI_CHECK_ALIGN(_field ## _OFST, 4) >> 2))
 
+#define MCDI_STRUCT_SET_BYTE(_buf, _field, _value) do {                        \
+       BUILD_BUG_ON(_field ## _LEN != 1);                              \
+       *(u8 *)MCDI_STRUCT_PTR(_buf, _field) = _value;                  \
+       } while (0)
 #define MCDI_BYTE(_buf, _field)                                                \
        ((void)BUILD_BUG_ON_ZERO(MC_CMD_ ## _field ## _LEN != 1),       \
         *MCDI_PTR(_buf, _field))
index 2b2d45b973050f8edf2cb7c963062f436fb0df38..3478860d40232c07997b4699513534b6a0ed952a 100644 (file)
@@ -9,6 +9,7 @@
  * by the Free Software Foundation, incorporated herein by reference.
  */
 
+#include <net/pkt_cls.h>
 #include "tc.h"
 #include "tc_bindings.h"
 #include "mae.h"
@@ -42,6 +43,20 @@ static struct efx_rep *efx_tc_flower_lookup_efv(struct efx_nic *efx,
        return efv;
 }
 
+/* Convert a driver-internal vport ID into an external device (wire or VF) */
+static s64 efx_tc_flower_external_mport(struct efx_nic *efx, struct efx_rep *efv)
+{
+       u32 mport;
+
+       if (IS_ERR(efv))
+               return PTR_ERR(efv);
+       if (!efv) /* device is PF (us) */
+               efx_mae_mport_wire(efx, &mport);
+       else /* device is repr */
+               efx_mae_mport_mport(efx, efv->mport, &mport);
+       return mport;
+}
+
 static const struct rhashtable_params efx_tc_match_action_ht_params = {
        .key_len        = sizeof(unsigned long),
        .key_offset     = offsetof(struct efx_tc_flow_rule, cookie),
@@ -109,6 +124,260 @@ static void efx_tc_flow_free(void *ptr, void *arg)
        kfree(rule);
 }
 
+static int efx_tc_flower_parse_match(struct efx_nic *efx,
+                                    struct flow_rule *rule,
+                                    struct efx_tc_match *match,
+                                    struct netlink_ext_ack *extack)
+{
+       struct flow_dissector *dissector = rule->match.dissector;
+
+       if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
+               struct flow_match_control fm;
+
+               flow_rule_match_control(rule, &fm);
+
+               if (fm.mask->flags) {
+                       efx_tc_err(efx, "Unsupported match on control.flags %#x\n",
+                                  fm.mask->flags);
+                       NL_SET_ERR_MSG_MOD(extack, "Unsupported match on control.flags");
+                       return -EOPNOTSUPP;
+               }
+       }
+       if (dissector->used_keys &
+           ~(BIT(FLOW_DISSECTOR_KEY_CONTROL) |
+             BIT(FLOW_DISSECTOR_KEY_BASIC))) {
+               efx_tc_err(efx, "Unsupported flower keys %#x\n", dissector->used_keys);
+               NL_SET_ERR_MSG_MOD(extack, "Unsupported flower keys encountered");
+               return -EOPNOTSUPP;
+       }
+
+       if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
+               struct flow_match_basic fm;
+
+               flow_rule_match_basic(rule, &fm);
+               if (fm.mask->n_proto) {
+                       EFX_TC_ERR_MSG(efx, extack, "Unsupported eth_proto match\n");
+                       return -EOPNOTSUPP;
+               }
+               if (fm.mask->ip_proto) {
+                       EFX_TC_ERR_MSG(efx, extack, "Unsupported ip_proto match\n");
+                       return -EOPNOTSUPP;
+               }
+       }
+
+       return 0;
+}
+
+static int efx_tc_flower_replace(struct efx_nic *efx,
+                                struct net_device *net_dev,
+                                struct flow_cls_offload *tc,
+                                struct efx_rep *efv)
+{
+       struct flow_rule *fr = flow_cls_offload_flow_rule(tc);
+       struct netlink_ext_ack *extack = tc->common.extack;
+       struct efx_tc_flow_rule *rule = NULL, *old;
+       struct efx_tc_action_set *act = NULL;
+       const struct flow_action_entry *fa;
+       struct efx_rep *from_efv, *to_efv;
+       struct efx_tc_match match;
+       s64 rc;
+       int i;
+
+       if (!tc_can_offload_extack(efx->net_dev, extack))
+               return -EOPNOTSUPP;
+       if (WARN_ON(!efx->tc))
+               return -ENETDOWN;
+       if (WARN_ON(!efx->tc->up))
+               return -ENETDOWN;
+
+       from_efv = efx_tc_flower_lookup_efv(efx, net_dev);
+       if (IS_ERR(from_efv)) {
+               /* Might be a tunnel decap rule from an indirect block.
+                * Support for those not implemented yet.
+                */
+               return -EOPNOTSUPP;
+       }
+
+       if (efv != from_efv) {
+               /* can't happen */
+               efx_tc_err(efx, "for %s efv is %snull but from_efv is %snull\n",
+                          netdev_name(net_dev), efv ? "non-" : "",
+                          from_efv ? "non-" : "");
+               if (efv)
+                       NL_SET_ERR_MSG_MOD(extack, "vfrep filter has PF net_dev (can't happen)");
+               else
+                       NL_SET_ERR_MSG_MOD(extack, "PF filter has vfrep net_dev (can't happen)");
+               return -EINVAL;
+       }
+
+       /* Parse match */
+       memset(&match, 0, sizeof(match));
+       rc = efx_tc_flower_external_mport(efx, from_efv);
+       if (rc < 0) {
+               EFX_TC_ERR_MSG(efx, extack, "Failed to identify ingress m-port");
+               return rc;
+       }
+       match.value.ingress_port = rc;
+       match.mask.ingress_port = ~0;
+       rc = efx_tc_flower_parse_match(efx, fr, &match, extack);
+       if (rc)
+               return rc;
+
+       if (tc->common.chain_index) {
+               EFX_TC_ERR_MSG(efx, extack, "No support for nonzero chain_index");
+               return -EOPNOTSUPP;
+       }
+       match.mask.recirc_id = 0xff;
+
+       rc = efx_mae_match_check_caps(efx, &match.mask, extack);
+       if (rc)
+               return rc;
+
+       rule = kzalloc(sizeof(*rule), GFP_USER);
+       if (!rule)
+               return -ENOMEM;
+       INIT_LIST_HEAD(&rule->acts.list);
+       rule->cookie = tc->cookie;
+       old = rhashtable_lookup_get_insert_fast(&efx->tc->match_action_ht,
+                                               &rule->linkage,
+                                               efx_tc_match_action_ht_params);
+       if (old) {
+               netif_dbg(efx, drv, efx->net_dev,
+                         "Already offloaded rule (cookie %lx)\n", tc->cookie);
+               rc = -EEXIST;
+               NL_SET_ERR_MSG_MOD(extack, "Rule already offloaded");
+               goto release;
+       }
+
+       /* Parse actions */
+       act = kzalloc(sizeof(*act), GFP_USER);
+       if (!act) {
+               rc = -ENOMEM;
+               goto release;
+       }
+
+       flow_action_for_each(i, fa, &fr->action) {
+               struct efx_tc_action_set save;
+
+               if (!act) {
+                       /* more actions after a non-pipe action */
+                       EFX_TC_ERR_MSG(efx, extack, "Action follows non-pipe action");
+                       rc = -EINVAL;
+                       goto release;
+               }
+
+               switch (fa->id) {
+               case FLOW_ACTION_DROP:
+                       rc = efx_mae_alloc_action_set(efx, act);
+                       if (rc) {
+                               EFX_TC_ERR_MSG(efx, extack, "Failed to write action set to hw (drop)");
+                               goto release;
+                       }
+                       list_add_tail(&act->list, &rule->acts.list);
+                       act = NULL; /* end of the line */
+                       break;
+               case FLOW_ACTION_REDIRECT:
+               case FLOW_ACTION_MIRRED:
+                       save = *act;
+                       to_efv = efx_tc_flower_lookup_efv(efx, fa->dev);
+                       if (IS_ERR(to_efv)) {
+                               EFX_TC_ERR_MSG(efx, extack, "Mirred egress device not on switch");
+                               rc = PTR_ERR(to_efv);
+                               goto release;
+                       }
+                       rc = efx_tc_flower_external_mport(efx, to_efv);
+                       if (rc < 0) {
+                               EFX_TC_ERR_MSG(efx, extack, "Failed to identify egress m-port");
+                               goto release;
+                       }
+                       act->dest_mport = rc;
+                       act->deliver = 1;
+                       rc = efx_mae_alloc_action_set(efx, act);
+                       if (rc) {
+                               EFX_TC_ERR_MSG(efx, extack, "Failed to write action set to hw (mirred)");
+                               goto release;
+                       }
+                       list_add_tail(&act->list, &rule->acts.list);
+                       act = NULL;
+                       if (fa->id == FLOW_ACTION_REDIRECT)
+                               break; /* end of the line */
+                       /* Mirror, so continue on with saved act */
+                       act = kzalloc(sizeof(*act), GFP_USER);
+                       if (!act) {
+                               rc = -ENOMEM;
+                               goto release;
+                       }
+                       *act = save;
+                       break;
+               default:
+                       efx_tc_err(efx, "Unhandled action %u\n", fa->id);
+                       rc = -EOPNOTSUPP;
+                       NL_SET_ERR_MSG_MOD(extack, "Unsupported action");
+                       goto release;
+               }
+       }
+
+       if (act) {
+               /* Not shot/redirected, so deliver to default dest */
+               if (from_efv == EFX_EFV_PF)
+                       /* Rule applies to traffic from the wire,
+                        * and default dest is thus the PF
+                        */
+                       efx_mae_mport_uplink(efx, &act->dest_mport);
+               else
+                       /* Representor, so rule applies to traffic from
+                        * representee, and default dest is thus the rep.
+                        * All reps use the same mport for delivery
+                        */
+                       efx_mae_mport_mport(efx, efx->tc->reps_mport_id,
+                                           &act->dest_mport);
+               act->deliver = 1;
+               rc = efx_mae_alloc_action_set(efx, act);
+               if (rc) {
+                       EFX_TC_ERR_MSG(efx, extack, "Failed to write action set to hw (deliver)");
+                       goto release;
+               }
+               list_add_tail(&act->list, &rule->acts.list);
+               act = NULL; /* Prevent double-free in error path */
+       }
+
+       netif_dbg(efx, drv, efx->net_dev,
+                 "Successfully parsed filter (cookie %lx)\n",
+                 tc->cookie);
+
+       rule->match = match;
+
+       rc = efx_mae_alloc_action_set_list(efx, &rule->acts);
+       if (rc) {
+               EFX_TC_ERR_MSG(efx, extack, "Failed to write action set list to hw");
+               goto release;
+       }
+       rc = efx_mae_insert_rule(efx, &rule->match, EFX_TC_PRIO_TC,
+                                rule->acts.fw_id, &rule->fw_id);
+       if (rc) {
+               EFX_TC_ERR_MSG(efx, extack, "Failed to insert rule in hw");
+               goto release_acts;
+       }
+       return 0;
+
+release_acts:
+       efx_mae_free_action_set_list(efx, &rule->acts);
+release:
+       /* We failed to insert the rule, so free up any entries we created in
+        * subsidiary tables.
+        */
+       if (act)
+               efx_tc_free_action_set(efx, act, false);
+       if (rule) {
+               rhashtable_remove_fast(&efx->tc->match_action_ht,
+                                      &rule->linkage,
+                                      efx_tc_match_action_ht_params);
+               efx_tc_free_action_set_list(efx, &rule->acts, false);
+       }
+       kfree(rule);
+       return rc;
+}
+
 static int efx_tc_flower_destroy(struct efx_nic *efx,
                                 struct net_device *net_dev,
                                 struct flow_cls_offload *tc)
@@ -151,6 +420,9 @@ int efx_tc_flower(struct efx_nic *efx, struct net_device *net_dev,
 
        mutex_lock(&efx->tc->mutex);
        switch (tc->command) {
+       case FLOW_CLS_REPLACE:
+               rc = efx_tc_flower_replace(efx, net_dev, tc, efv);
+               break;
        case FLOW_CLS_DESTROY:
                rc = efx_tc_flower_destroy(efx, net_dev, tc);
                break;
index baf1e67b58a536233ea637a3129e3a88a43f60ef..196fd74ed97338d5a4e13ff188411d97d86475ae 100644 (file)
@@ -43,6 +43,7 @@ struct efx_tc_action_set {
 struct efx_tc_match_fields {
        /* L1 */
        u32 ingress_port;
+       u8 recirc_id;
 };
 
 struct efx_tc_match {
@@ -64,6 +65,7 @@ struct efx_tc_flow_rule {
 };
 
 enum efx_tc_rule_prios {
+       EFX_TC_PRIO_TC, /* Rule inserted by TC */
        EFX_TC_PRIO_DFLT, /* Default switch rule; one of efx_tc_default_rules */
        EFX_TC_PRIO__NUM
 };