summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/sfc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ethernet/sfc')
-rw-r--r--drivers/net/ethernet/sfc/efx_channels.c2
-rw-r--r--drivers/net/ethernet/sfc/mae.c62
-rw-r--r--drivers/net/ethernet/sfc/mcdi.c3
-rw-r--r--drivers/net/ethernet/sfc/ptp.c27
-rw-r--r--drivers/net/ethernet/sfc/siena/efx_channels.c2
-rw-r--r--drivers/net/ethernet/sfc/tc.c337
-rw-r--r--drivers/net/ethernet/sfc/tc.h8
-rw-r--r--drivers/net/ethernet/sfc/tc_conntrack.c91
8 files changed, 506 insertions, 26 deletions
diff --git a/drivers/net/ethernet/sfc/efx_channels.c b/drivers/net/ethernet/sfc/efx_channels.c
index 8d2d7ea2ebef..c9e17a8208a9 100644
--- a/drivers/net/ethernet/sfc/efx_channels.c
+++ b/drivers/net/ethernet/sfc/efx_channels.c
@@ -1260,7 +1260,7 @@ static int efx_poll(struct napi_struct *napi, int budget)
spent = efx_process_channel(channel, budget);
- xdp_do_flush_map();
+ xdp_do_flush();
if (spent < budget) {
if (efx_channel_has_rx_queue(channel) &&
diff --git a/drivers/net/ethernet/sfc/mae.c b/drivers/net/ethernet/sfc/mae.c
index c3e2b4a21d10..10709d828a63 100644
--- a/drivers/net/ethernet/sfc/mae.c
+++ b/drivers/net/ethernet/sfc/mae.c
@@ -1291,10 +1291,11 @@ int efx_mae_alloc_action_set(struct efx_nic *efx, struct efx_tc_action_set *act)
size_t outlen;
int rc;
- MCDI_POPULATE_DWORD_4(inbuf, MAE_ACTION_SET_ALLOC_IN_FLAGS,
+ MCDI_POPULATE_DWORD_5(inbuf, MAE_ACTION_SET_ALLOC_IN_FLAGS,
MAE_ACTION_SET_ALLOC_IN_VLAN_PUSH, act->vlan_push,
MAE_ACTION_SET_ALLOC_IN_VLAN_POP, act->vlan_pop,
MAE_ACTION_SET_ALLOC_IN_DECAP, act->decap,
+ MAE_ACTION_SET_ALLOC_IN_DO_NAT, act->do_nat,
MAE_ACTION_SET_ALLOC_IN_DO_DECR_IP_TTL,
act->do_ttl_dec);
@@ -1705,8 +1706,10 @@ static int efx_mae_insert_lhs_outer_rule(struct efx_nic *efx,
/* action */
act = &rule->lhs_act;
- MCDI_SET_DWORD(inbuf, MAE_OUTER_RULE_INSERT_IN_ENCAP_TYPE,
- MAE_MCDI_ENCAP_TYPE_NONE);
+ rc = efx_mae_encap_type_to_mae_type(act->tun_type);
+ if (rc < 0)
+ return rc;
+ MCDI_SET_DWORD(inbuf, MAE_OUTER_RULE_INSERT_IN_ENCAP_TYPE, rc);
/* We always inhibit CT lookup on TCP_INTERESTING_FLAGS, since the
* SW path needs to process the packet to update the conntrack tables
* on connection establishment (SYN) or termination (FIN, RST).
@@ -1734,9 +1737,60 @@ static int efx_mae_insert_lhs_outer_rule(struct efx_nic *efx,
return 0;
}
+static int efx_mae_populate_match_criteria(MCDI_DECLARE_STRUCT_PTR(match_crit),
+ const struct efx_tc_match *match);
+
+static int efx_mae_insert_lhs_action_rule(struct efx_nic *efx,
+ struct efx_tc_lhs_rule *rule,
+ u32 prio)
+{
+ MCDI_DECLARE_BUF(inbuf, MC_CMD_MAE_ACTION_RULE_INSERT_IN_LEN(MAE_FIELD_MASK_VALUE_PAIRS_V2_LEN));
+ MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_ACTION_RULE_INSERT_OUT_LEN);
+ struct efx_tc_lhs_action *act = &rule->lhs_act;
+ MCDI_DECLARE_STRUCT_PTR(match_crit);
+ MCDI_DECLARE_STRUCT_PTR(response);
+ size_t outlen;
+ int rc;
+
+ match_crit = _MCDI_DWORD(inbuf, MAE_ACTION_RULE_INSERT_IN_MATCH_CRITERIA);
+ response = _MCDI_DWORD(inbuf, MAE_ACTION_RULE_INSERT_IN_RESPONSE);
+ MCDI_STRUCT_SET_DWORD(response, MAE_ACTION_RULE_RESPONSE_ASL_ID,
+ MC_CMD_MAE_ACTION_SET_LIST_ALLOC_OUT_ACTION_SET_LIST_ID_NULL);
+ MCDI_STRUCT_SET_DWORD(response, MAE_ACTION_RULE_RESPONSE_AS_ID,
+ MC_CMD_MAE_ACTION_SET_ALLOC_OUT_ACTION_SET_ID_NULL);
+ EFX_POPULATE_DWORD_5(*_MCDI_STRUCT_DWORD(response, MAE_ACTION_RULE_RESPONSE_LOOKUP_CONTROL),
+ MAE_ACTION_RULE_RESPONSE_DO_CT, !!act->zone,
+ MAE_ACTION_RULE_RESPONSE_DO_RECIRC,
+ act->rid && !act->zone,
+ MAE_ACTION_RULE_RESPONSE_CT_VNI_MODE,
+ MAE_CT_VNI_MODE_ZERO,
+ MAE_ACTION_RULE_RESPONSE_RECIRC_ID,
+ act->rid ? act->rid->fw_id : 0,
+ MAE_ACTION_RULE_RESPONSE_CT_DOMAIN,
+ act->zone ? act->zone->zone : 0);
+ MCDI_STRUCT_SET_DWORD(response, MAE_ACTION_RULE_RESPONSE_COUNTER_ID,
+ act->count ? act->count->cnt->fw_id :
+ MC_CMD_MAE_COUNTER_ALLOC_OUT_COUNTER_ID_NULL);
+ MCDI_SET_DWORD(inbuf, MAE_ACTION_RULE_INSERT_IN_PRIO, prio);
+ rc = efx_mae_populate_match_criteria(match_crit, &rule->match);
+ if (rc)
+ return rc;
+
+ rc = efx_mcdi_rpc(efx, MC_CMD_MAE_ACTION_RULE_INSERT, inbuf, sizeof(inbuf),
+ outbuf, sizeof(outbuf), &outlen);
+ if (rc)
+ return rc;
+ if (outlen < sizeof(outbuf))
+ return -EIO;
+ rule->fw_id = MCDI_DWORD(outbuf, MAE_ACTION_RULE_INSERT_OUT_AR_ID);
+ return 0;
+}
+
int efx_mae_insert_lhs_rule(struct efx_nic *efx, struct efx_tc_lhs_rule *rule,
u32 prio)
{
+ if (rule->is_ar)
+ return efx_mae_insert_lhs_action_rule(efx, rule, prio);
return efx_mae_insert_lhs_outer_rule(efx, rule, prio);
}
@@ -1770,6 +1824,8 @@ static int efx_mae_remove_lhs_outer_rule(struct efx_nic *efx,
int efx_mae_remove_lhs_rule(struct efx_nic *efx, struct efx_tc_lhs_rule *rule)
{
+ if (rule->is_ar)
+ return efx_mae_delete_rule(efx, rule->fw_id);
return efx_mae_remove_lhs_outer_rule(efx, rule);
}
diff --git a/drivers/net/ethernet/sfc/mcdi.c b/drivers/net/ethernet/sfc/mcdi.c
index d23da9627338..76578502226e 100644
--- a/drivers/net/ethernet/sfc/mcdi.c
+++ b/drivers/net/ethernet/sfc/mcdi.c
@@ -2205,10 +2205,9 @@ int efx_mcdi_nvram_metadata(struct efx_nic *efx, unsigned int type,
goto out_free;
}
- strncpy(desc,
+ strscpy(desc,
MCDI_PTR(outbuf, NVRAM_METADATA_OUT_DESCRIPTION),
MC_CMD_NVRAM_METADATA_OUT_DESCRIPTION_NUM(outlen));
- desc[MC_CMD_NVRAM_METADATA_OUT_DESCRIPTION_NUM(outlen)] = '\0';
} else {
desc[0] = '\0';
}
diff --git a/drivers/net/ethernet/sfc/ptp.c b/drivers/net/ethernet/sfc/ptp.c
index f54200f03e15..b04fdbb8aece 100644
--- a/drivers/net/ethernet/sfc/ptp.c
+++ b/drivers/net/ethernet/sfc/ptp.c
@@ -108,11 +108,17 @@
#define PTP_MIN_LENGTH 63
#define PTP_ADDR_IPV4 0xe0000181 /* 224.0.1.129 */
-#define PTP_ADDR_IPV6 {0xff, 0x0e, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
- 0, 0x01, 0x81} /* ff0e::181 */
+
+/* ff0e::181 */
+static const struct in6_addr ptp_addr_ipv6 = { { {
+ 0xff, 0x0e, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x81 } } };
+
+/* 01-1B-19-00-00-00 */
+static const u8 ptp_addr_ether[ETH_ALEN] __aligned(2) = {
+ 0x01, 0x1b, 0x19, 0x00, 0x00, 0x00 };
+
#define PTP_EVENT_PORT 319
#define PTP_GENERAL_PORT 320
-#define PTP_ADDR_ETHER {0x01, 0x1b, 0x19, 0, 0, 0} /* 01-1B-19-00-00-00 */
/* Annoyingly the format of the version numbers are different between
* versions 1 and 2 so it isn't possible to simply look for 1 or 2.
@@ -1296,7 +1302,7 @@ static int efx_ptp_insert_ipv4_filter(struct efx_nic *efx,
static int efx_ptp_insert_ipv6_filter(struct efx_nic *efx,
struct list_head *filter_list,
- struct in6_addr *addr, u16 port,
+ const struct in6_addr *addr, u16 port,
unsigned long expiry)
{
struct efx_filter_spec spec;
@@ -1309,11 +1315,10 @@ static int efx_ptp_insert_ipv6_filter(struct efx_nic *efx,
static int efx_ptp_insert_eth_multicast_filter(struct efx_nic *efx)
{
struct efx_ptp_data *ptp = efx->ptp_data;
- const u8 addr[ETH_ALEN] = PTP_ADDR_ETHER;
struct efx_filter_spec spec;
efx_ptp_init_filter(efx, &spec);
- efx_filter_set_eth_local(&spec, EFX_FILTER_VID_UNSPEC, addr);
+ efx_filter_set_eth_local(&spec, EFX_FILTER_VID_UNSPEC, ptp_addr_ether);
spec.match_flags |= EFX_FILTER_MATCH_ETHER_TYPE;
spec.ether_type = htons(ETH_P_1588);
return efx_ptp_insert_filter(efx, &ptp->rxfilters_mcast, &spec, 0);
@@ -1346,15 +1351,13 @@ static int efx_ptp_insert_multicast_filters(struct efx_nic *efx)
* PTP over IPv6 and Ethernet
*/
if (efx_ptp_use_mac_tx_timestamps(efx)) {
- struct in6_addr ipv6_addr = {{PTP_ADDR_IPV6}};
-
rc = efx_ptp_insert_ipv6_filter(efx, &ptp->rxfilters_mcast,
- &ipv6_addr, PTP_EVENT_PORT, 0);
+ &ptp_addr_ipv6, PTP_EVENT_PORT, 0);
if (rc < 0)
goto fail;
rc = efx_ptp_insert_ipv6_filter(efx, &ptp->rxfilters_mcast,
- &ipv6_addr, PTP_GENERAL_PORT, 0);
+ &ptp_addr_ipv6, PTP_GENERAL_PORT, 0);
if (rc < 0)
goto fail;
@@ -1379,9 +1382,7 @@ static bool efx_ptp_valid_unicast_event_pkt(struct sk_buff *skb)
ip_hdr(skb)->protocol == IPPROTO_UDP &&
udp_hdr(skb)->source == htons(PTP_EVENT_PORT);
} else if (skb->protocol == htons(ETH_P_IPV6)) {
- struct in6_addr mcast_addr = {{PTP_ADDR_IPV6}};
-
- return !ipv6_addr_equal(&ipv6_hdr(skb)->daddr, &mcast_addr) &&
+ return !ipv6_addr_equal(&ipv6_hdr(skb)->daddr, &ptp_addr_ipv6) &&
ipv6_hdr(skb)->nexthdr == IPPROTO_UDP &&
udp_hdr(skb)->source == htons(PTP_EVENT_PORT);
}
diff --git a/drivers/net/ethernet/sfc/siena/efx_channels.c b/drivers/net/ethernet/sfc/siena/efx_channels.c
index 1776f7f8a7a9..a7346e965bfe 100644
--- a/drivers/net/ethernet/sfc/siena/efx_channels.c
+++ b/drivers/net/ethernet/sfc/siena/efx_channels.c
@@ -1285,7 +1285,7 @@ static int efx_poll(struct napi_struct *napi, int budget)
spent = efx_process_channel(channel, budget);
- xdp_do_flush_map();
+ xdp_do_flush();
if (spent < budget) {
if (efx_channel_has_rx_queue(channel) &&
diff --git a/drivers/net/ethernet/sfc/tc.c b/drivers/net/ethernet/sfc/tc.c
index 834f000ba1c4..6db3d7ed3a86 100644
--- a/drivers/net/ethernet/sfc/tc.c
+++ b/drivers/net/ethernet/sfc/tc.c
@@ -642,6 +642,15 @@ static int efx_tc_flower_record_encap_match(struct efx_nic *efx,
return -EEXIST;
}
break;
+ case EFX_TC_EM_PSEUDO_OR:
+ /* old EM corresponds to an OR that has to be unique
+ * (it must not overlap with any other OR, whether
+ * direct-EM or pseudo).
+ */
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "%s encap match conflicts with existing pseudo(OR) entry",
+ em_type ? "Pseudo" : "Direct");
+ return -EEXIST;
default: /* Unrecognised pseudo-type. Just say no */
NL_SET_ERR_MSG_FMT_MOD(extack,
"%s encap match conflicts with existing pseudo(%d) entry",
@@ -872,6 +881,93 @@ static bool efx_tc_rule_is_lhs_rule(struct flow_rule *fr,
return false;
}
+/* A foreign LHS rule has matches on enc_ keys at the TC layer (including an
+ * implied match on enc_ip_proto UDP). Translate these into non-enc_ keys,
+ * so that we can use the same MAE machinery as local LHS rules (and so that
+ * the lhs_rules entries have uniform semantics). It may seem odd to do it
+ * this way round, given that the corresponding fields in the MAE MCDIs are
+ * all ENC_, but (a) we don't have enc_L2 or enc_ip_proto in struct
+ * efx_tc_match_fields and (b) semantically an LHS rule doesn't have inner
+ * fields so it's just matching on *the* header rather than the outer header.
+ * Make sure that the non-enc_ keys were not already being matched on, as that
+ * would imply a rule that needed a triple lookup. (Hardware can do that,
+ * with OR-AR-CT-AR, but it halves packet rate so we avoid it where possible;
+ * see efx_tc_flower_flhs_needs_ar().)
+ */
+static int efx_tc_flower_translate_flhs_match(struct efx_tc_match *match)
+{
+ int rc = 0;
+
+#define COPY_MASK_AND_VALUE(_key, _ekey) ({ \
+ if (match->mask._key) { \
+ rc = -EOPNOTSUPP; \
+ } else { \
+ match->mask._key = match->mask._ekey; \
+ match->mask._ekey = 0; \
+ match->value._key = match->value._ekey; \
+ match->value._ekey = 0; \
+ } \
+ rc; \
+})
+#define COPY_FROM_ENC(_key) COPY_MASK_AND_VALUE(_key, enc_##_key)
+ if (match->mask.ip_proto)
+ return -EOPNOTSUPP;
+ match->mask.ip_proto = ~0;
+ match->value.ip_proto = IPPROTO_UDP;
+ if (COPY_FROM_ENC(src_ip) || COPY_FROM_ENC(dst_ip))
+ return rc;
+#ifdef CONFIG_IPV6
+ if (!ipv6_addr_any(&match->mask.src_ip6))
+ return -EOPNOTSUPP;
+ match->mask.src_ip6 = match->mask.enc_src_ip6;
+ memset(&match->mask.enc_src_ip6, 0, sizeof(struct in6_addr));
+ if (!ipv6_addr_any(&match->mask.dst_ip6))
+ return -EOPNOTSUPP;
+ match->mask.dst_ip6 = match->mask.enc_dst_ip6;
+ memset(&match->mask.enc_dst_ip6, 0, sizeof(struct in6_addr));
+#endif
+ if (COPY_FROM_ENC(ip_tos) || COPY_FROM_ENC(ip_ttl))
+ return rc;
+ /* should really copy enc_ip_frag but we don't have that in
+ * parse_match yet
+ */
+ if (COPY_MASK_AND_VALUE(l4_sport, enc_sport) ||
+ COPY_MASK_AND_VALUE(l4_dport, enc_dport))
+ return rc;
+ return 0;
+#undef COPY_FROM_ENC
+#undef COPY_MASK_AND_VALUE
+}
+
+/* If a foreign LHS rule wants to match on keys that are only available after
+ * encap header identification and parsing, then it can't be done in the Outer
+ * Rule lookup, because that lookup determines the encap type used to parse
+ * beyond the outer headers. Thus, such rules must use the OR-AR-CT-AR lookup
+ * sequence, with an EM (struct efx_tc_encap_match) in the OR step.
+ * Return true iff the passed match requires this.
+ */
+static bool efx_tc_flower_flhs_needs_ar(struct efx_tc_match *match)
+{
+ /* matches on inner-header keys can't be done in OR */
+ return match->mask.eth_proto ||
+ match->mask.vlan_tci[0] || match->mask.vlan_tci[1] ||
+ match->mask.vlan_proto[0] || match->mask.vlan_proto[1] ||
+ memchr_inv(match->mask.eth_saddr, 0, ETH_ALEN) ||
+ memchr_inv(match->mask.eth_daddr, 0, ETH_ALEN) ||
+ match->mask.ip_proto ||
+ match->mask.ip_tos || match->mask.ip_ttl ||
+ match->mask.src_ip || match->mask.dst_ip ||
+#ifdef CONFIG_IPV6
+ !ipv6_addr_any(&match->mask.src_ip6) ||
+ !ipv6_addr_any(&match->mask.dst_ip6) ||
+#endif
+ match->mask.ip_frag || match->mask.ip_firstfrag ||
+ match->mask.l4_sport || match->mask.l4_dport ||
+ match->mask.tcp_flags ||
+ /* nor can VNI */
+ match->mask.enc_keyid;
+}
+
static int efx_tc_flower_handle_lhs_actions(struct efx_nic *efx,
struct flow_cls_offload *tc,
struct flow_rule *fr,
@@ -882,9 +978,12 @@ static int efx_tc_flower_handle_lhs_actions(struct efx_nic *efx,
struct netlink_ext_ack *extack = tc->common.extack;
struct efx_tc_lhs_action *act = &rule->lhs_act;
const struct flow_action_entry *fa;
+ enum efx_tc_counter_type ctype;
bool pipe = true;
int i;
+ ctype = rule->is_ar ? EFX_TC_COUNTER_TYPE_AR : EFX_TC_COUNTER_TYPE_OR;
+
flow_action_for_each(i, fa, &fr->action) {
struct efx_tc_ct_zone *ct_zone;
struct efx_tc_recirc_id *rid;
@@ -917,7 +1016,7 @@ static int efx_tc_flower_handle_lhs_actions(struct efx_nic *efx,
return -EOPNOTSUPP;
}
cnt = efx_tc_flower_get_counter_index(efx, tc->cookie,
- EFX_TC_COUNTER_TYPE_OR);
+ ctype);
if (IS_ERR(cnt)) {
NL_SET_ERR_MSG_MOD(extack, "Failed to obtain a counter");
return PTR_ERR(cnt);
@@ -1354,6 +1453,222 @@ static int efx_tc_incomplete_mangle(struct efx_tc_mangler_state *mung,
return 0;
}
+static int efx_tc_flower_replace_foreign_lhs_ar(struct efx_nic *efx,
+ struct flow_cls_offload *tc,
+ struct flow_rule *fr,
+ struct efx_tc_match *match,
+ struct net_device *net_dev)
+{
+ struct netlink_ext_ack *extack = tc->common.extack;
+ struct efx_tc_lhs_rule *rule, *old;
+ enum efx_encap_type type;
+ int rc;
+
+ type = efx_tc_indr_netdev_type(net_dev);
+ if (type == EFX_ENCAP_TYPE_NONE) {
+ NL_SET_ERR_MSG_MOD(extack, "Egress encap match on unsupported tunnel device");
+ return -EOPNOTSUPP;
+ }
+
+ rc = efx_mae_check_encap_type_supported(efx, type);
+ if (rc) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Firmware reports no support for %s encap match",
+ efx_tc_encap_type_name(type));
+ return rc;
+ }
+ /* This is an Action Rule, so it needs a separate Encap Match in the
+ * Outer Rule table. Insert that now.
+ */
+ rc = efx_tc_flower_record_encap_match(efx, match, type,
+ EFX_TC_EM_DIRECT, 0, 0, extack);
+ if (rc)
+ return rc;
+
+ match->mask.recirc_id = 0xff;
+ if (match->mask.ct_state_trk && match->value.ct_state_trk) {
+ NL_SET_ERR_MSG_MOD(extack, "LHS rule can never match +trk");
+ rc = -EOPNOTSUPP;
+ goto release_encap_match;
+ }
+ /* LHS rules are always -trk, so we don't need to match on that */
+ match->mask.ct_state_trk = 0;
+ match->value.ct_state_trk = 0;
+ /* We must inhibit match on TCP SYN/FIN/RST, so that SW can see
+ * the packet and update the conntrack table.
+ * Outer Rules will do that with CT_TCP_FLAGS_INHIBIT, but Action
+ * Rules don't have that; instead they support matching on
+ * TCP_SYN_FIN_RST (aka TCP_INTERESTING_FLAGS), so use that.
+ * This is only strictly needed if there will be a DO_CT action,
+ * which we don't know yet, but typically there will be and it's
+ * simpler not to bother checking here.
+ */
+ match->mask.tcp_syn_fin_rst = true;
+
+ rc = efx_mae_match_check_caps(efx, &match->mask, extack);
+ if (rc)
+ goto release_encap_match;
+
+ rule = kzalloc(sizeof(*rule), GFP_USER);
+ if (!rule) {
+ rc = -ENOMEM;
+ goto release_encap_match;
+ }
+ rule->cookie = tc->cookie;
+ rule->is_ar = true;
+ old = rhashtable_lookup_get_insert_fast(&efx->tc->lhs_rule_ht,
+ &rule->linkage,
+ efx_tc_lhs_rule_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 */
+ rc = efx_tc_flower_handle_lhs_actions(efx, tc, fr, net_dev, rule);
+ if (rc)
+ goto release;
+
+ rule->match = *match;
+ rule->lhs_act.tun_type = type;
+
+ rc = efx_mae_insert_lhs_rule(efx, rule, EFX_TC_PRIO_TC);
+ if (rc) {
+ NL_SET_ERR_MSG_MOD(extack, "Failed to insert rule in hw");
+ goto release;
+ }
+ netif_dbg(efx, drv, efx->net_dev,
+ "Successfully parsed lhs rule (cookie %lx)\n",
+ tc->cookie);
+ return 0;
+
+release:
+ efx_tc_flower_release_lhs_actions(efx, &rule->lhs_act);
+ if (!old)
+ rhashtable_remove_fast(&efx->tc->lhs_rule_ht, &rule->linkage,
+ efx_tc_lhs_rule_ht_params);
+ kfree(rule);
+release_encap_match:
+ if (match->encap)
+ efx_tc_flower_release_encap_match(efx, match->encap);
+ return rc;
+}
+
+static int efx_tc_flower_replace_foreign_lhs(struct efx_nic *efx,
+ struct flow_cls_offload *tc,
+ struct flow_rule *fr,
+ struct efx_tc_match *match,
+ struct net_device *net_dev)
+{
+ struct netlink_ext_ack *extack = tc->common.extack;
+ struct efx_tc_lhs_rule *rule, *old;
+ enum efx_encap_type type;
+ int rc;
+
+ if (tc->common.chain_index) {
+ NL_SET_ERR_MSG_MOD(extack, "LHS rule only allowed in chain 0");
+ return -EOPNOTSUPP;
+ }
+
+ if (!efx_tc_match_is_encap(&match->mask)) {
+ /* This is not a tunnel decap rule, ignore it */
+ netif_dbg(efx, drv, efx->net_dev, "Ignoring foreign LHS filter without encap match\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (efx_tc_flower_flhs_needs_ar(match))
+ return efx_tc_flower_replace_foreign_lhs_ar(efx, tc, fr, match,
+ net_dev);
+
+ type = efx_tc_indr_netdev_type(net_dev);
+ if (type == EFX_ENCAP_TYPE_NONE) {
+ NL_SET_ERR_MSG_MOD(extack, "Egress encap match on unsupported tunnel device\n");
+ return -EOPNOTSUPP;
+ }
+
+ rc = efx_mae_check_encap_type_supported(efx, type);
+ if (rc) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Firmware reports no support for %s encap match",
+ efx_tc_encap_type_name(type));
+ return rc;
+ }
+ /* Reserve the outer tuple with a pseudo Encap Match */
+ rc = efx_tc_flower_record_encap_match(efx, match, type,
+ EFX_TC_EM_PSEUDO_OR, 0, 0,
+ extack);
+ if (rc)
+ return rc;
+
+ if (match->mask.ct_state_trk && match->value.ct_state_trk) {
+ NL_SET_ERR_MSG_MOD(extack, "LHS rule can never match +trk");
+ rc = -EOPNOTSUPP;
+ goto release_encap_match;
+ }
+ /* LHS rules are always -trk, so we don't need to match on that */
+ match->mask.ct_state_trk = 0;
+ match->value.ct_state_trk = 0;
+
+ rc = efx_tc_flower_translate_flhs_match(match);
+ if (rc) {
+ NL_SET_ERR_MSG_MOD(extack, "LHS rule cannot match on inner fields");
+ goto release_encap_match;
+ }
+
+ rc = efx_mae_match_check_caps_lhs(efx, &match->mask, extack);
+ if (rc)
+ goto release_encap_match;
+
+ rule = kzalloc(sizeof(*rule), GFP_USER);
+ if (!rule) {
+ rc = -ENOMEM;
+ goto release_encap_match;
+ }
+ rule->cookie = tc->cookie;
+ old = rhashtable_lookup_get_insert_fast(&efx->tc->lhs_rule_ht,
+ &rule->linkage,
+ efx_tc_lhs_rule_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 */
+ rc = efx_tc_flower_handle_lhs_actions(efx, tc, fr, net_dev, rule);
+ if (rc)
+ goto release;
+
+ rule->match = *match;
+ rule->lhs_act.tun_type = type;
+
+ rc = efx_mae_insert_lhs_rule(efx, rule, EFX_TC_PRIO_TC);
+ if (rc) {
+ NL_SET_ERR_MSG_MOD(extack, "Failed to insert rule in hw");
+ goto release;
+ }
+ netif_dbg(efx, drv, efx->net_dev,
+ "Successfully parsed lhs rule (cookie %lx)\n",
+ tc->cookie);
+ return 0;
+
+release:
+ efx_tc_flower_release_lhs_actions(efx, &rule->lhs_act);
+ if (!old)
+ rhashtable_remove_fast(&efx->tc->lhs_rule_ht, &rule->linkage,
+ efx_tc_lhs_rule_ht_params);
+ kfree(rule);
+release_encap_match:
+ if (match->encap)
+ efx_tc_flower_release_encap_match(efx, match->encap);
+ return rc;
+}
+
static int efx_tc_flower_replace_foreign(struct efx_nic *efx,
struct net_device *net_dev,
struct flow_cls_offload *tc)
@@ -1371,7 +1686,7 @@ static int efx_tc_flower_replace_foreign(struct efx_nic *efx,
/* Parse match */
memset(&match, 0, sizeof(match));
- rc = efx_tc_flower_parse_match(efx, fr, &match, NULL);
+ rc = efx_tc_flower_parse_match(efx, fr, &match, extack);
if (rc)
return rc;
/* The rule as given to us doesn't specify a source netdevice.
@@ -1387,6 +1702,10 @@ static int efx_tc_flower_replace_foreign(struct efx_nic *efx,
match.value.ingress_port = rc;
match.mask.ingress_port = ~0;
+ if (efx_tc_rule_is_lhs_rule(fr, &match))
+ return efx_tc_flower_replace_foreign_lhs(efx, tc, fr, &match,
+ net_dev);
+
if (tc->common.chain_index) {
struct efx_tc_recirc_id *rid;
@@ -1416,6 +1735,7 @@ static int efx_tc_flower_replace_foreign(struct efx_nic *efx,
if (match.mask.ct_state_est && !match.value.ct_state_est) {
if (match.value.tcp_syn_fin_rst) {
/* Can't offload this combination */
+ NL_SET_ERR_MSG_MOD(extack, "TCP flags and -est conflict for offload");
rc = -EOPNOTSUPP;
goto release;
}
@@ -1442,7 +1762,7 @@ static int efx_tc_flower_replace_foreign(struct efx_nic *efx,
goto release;
}
- rc = efx_mae_match_check_caps(efx, &match.mask, NULL);
+ rc = efx_mae_match_check_caps(efx, &match.mask, extack);
if (rc)
goto release;
@@ -1470,7 +1790,7 @@ static int efx_tc_flower_replace_foreign(struct efx_nic *efx,
extack);
if (rc)
goto release;
- } else {
+ } else if (!tc->common.chain_index) {
/* This is not a tunnel decap rule, ignore it */
netif_dbg(efx, drv, efx->net_dev,
"Ignoring foreign filter without encap match\n");
@@ -1530,6 +1850,7 @@ static int efx_tc_flower_replace_foreign(struct efx_nic *efx,
goto release;
}
if (!efx_tc_flower_action_order_ok(act, EFX_TC_AO_COUNT)) {
+ NL_SET_ERR_MSG_MOD(extack, "Count action violates action order (can't happen)");
rc = -EOPNOTSUPP;
goto release;
}
@@ -2136,6 +2457,14 @@ static int efx_tc_flower_replace(struct efx_nic *efx,
NL_SET_ERR_MSG_MOD(extack, "Cannot offload tunnel decap action without tunnel device");
rc = -EOPNOTSUPP;
goto release;
+ case FLOW_ACTION_CT:
+ if (fa->ct.action != TCA_CT_ACT_NAT) {
+ rc = -EOPNOTSUPP;
+ NL_SET_ERR_MSG_FMT_MOD(extack, "Can only offload CT 'nat' action in RHS rules, not %d", fa->ct.action);
+ goto release;
+ }
+ act->do_nat = 1;
+ break;
default:
NL_SET_ERR_MSG_FMT_MOD(extack, "Unhandled action %u",
fa->id);
diff --git a/drivers/net/ethernet/sfc/tc.h b/drivers/net/ethernet/sfc/tc.h
index 4dd2c378fd9f..7b5190078bee 100644
--- a/drivers/net/ethernet/sfc/tc.h
+++ b/drivers/net/ethernet/sfc/tc.h
@@ -48,6 +48,7 @@ struct efx_tc_encap_action; /* see tc_encap_actions.h */
* @vlan_push: the number of vlan headers to push
* @vlan_pop: the number of vlan headers to pop
* @decap: used to indicate a tunnel header decapsulation should take place
+ * @do_nat: perform NAT/NPT with values returned by conntrack match
* @do_ttl_dec: used to indicate IP TTL / Hop Limit should be decremented
* @deliver: used to indicate a deliver action should take place
* @vlan_tci: tci fields for vlan push actions
@@ -68,6 +69,7 @@ struct efx_tc_action_set {
u16 vlan_push:2;
u16 vlan_pop:2;
u16 decap:1;
+ u16 do_nat:1;
u16 do_ttl_dec:1;
u16 deliver:1;
__be16 vlan_tci[2];
@@ -140,10 +142,14 @@ static inline bool efx_tc_match_is_encap(const struct efx_tc_match_fields *mask)
* The pseudo encap match may be referenced again by an encap match
* with different values for these fields, but all masks must match the
* first (stored in our child_* fields).
+ * @EFX_TC_EM_PSEUDO_OR: registered by an fLHS rule that fits in the OR
+ * table. The &struct efx_tc_lhs_rule already holds the HW OR entry.
+ * Only one reference to this encap match may exist.
*/
enum efx_tc_em_pseudo_type {
EFX_TC_EM_DIRECT,
EFX_TC_EM_PSEUDO_MASK,
+ EFX_TC_EM_PSEUDO_OR,
};
struct efx_tc_encap_match {
@@ -183,6 +189,7 @@ struct efx_tc_action_set_list {
};
struct efx_tc_lhs_action {
+ enum efx_encap_type tun_type;
struct efx_tc_recirc_id *rid;
struct efx_tc_ct_zone *zone;
struct efx_tc_counter_index *count;
@@ -203,6 +210,7 @@ struct efx_tc_lhs_rule {
struct efx_tc_lhs_action lhs_act;
struct rhash_head linkage;
u32 fw_id;
+ bool is_ar; /* Action Rule (for OR-AR-CT-AR sequence) */
};
enum efx_tc_rule_prios {
diff --git a/drivers/net/ethernet/sfc/tc_conntrack.c b/drivers/net/ethernet/sfc/tc_conntrack.c
index 44bb57670340..d90206f27161 100644
--- a/drivers/net/ethernet/sfc/tc_conntrack.c
+++ b/drivers/net/ethernet/sfc/tc_conntrack.c
@@ -276,10 +276,84 @@ static int efx_tc_ct_parse_match(struct efx_nic *efx, struct flow_rule *fr,
return 0;
}
+/**
+ * struct efx_tc_ct_mangler_state - tracks which fields have been pedited
+ *
+ * @ipv4: IP source or destination addr has been set
+ * @tcpudp: TCP/UDP source or destination port has been set
+ */
+struct efx_tc_ct_mangler_state {
+ u8 ipv4:1;
+ u8 tcpudp:1;
+};
+
+static int efx_tc_ct_mangle(struct efx_nic *efx, struct efx_tc_ct_entry *conn,
+ const struct flow_action_entry *fa,
+ struct efx_tc_ct_mangler_state *mung)
+{
+ /* Is this the first mangle we've processed for this rule? */
+ bool first = !(mung->ipv4 || mung->tcpudp);
+ bool dnat = false;
+
+ switch (fa->mangle.htype) {
+ case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
+ switch (fa->mangle.offset) {
+ case offsetof(struct iphdr, daddr):
+ dnat = true;
+ fallthrough;
+ case offsetof(struct iphdr, saddr):
+ if (fa->mangle.mask)
+ return -EOPNOTSUPP;
+ conn->nat_ip = htonl(fa->mangle.val);
+ mung->ipv4 = 1;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
+ case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
+ /* Both struct tcphdr and struct udphdr start with
+ * __be16 source;
+ * __be16 dest;
+ * so we can use the same code for both.
+ */
+ switch (fa->mangle.offset) {
+ case offsetof(struct tcphdr, dest):
+ BUILD_BUG_ON(offsetof(struct tcphdr, dest) !=
+ offsetof(struct udphdr, dest));
+ dnat = true;
+ fallthrough;
+ case offsetof(struct tcphdr, source):
+ BUILD_BUG_ON(offsetof(struct tcphdr, source) !=
+ offsetof(struct udphdr, source));
+ if (~fa->mangle.mask != 0xffff)
+ return -EOPNOTSUPP;
+ conn->l4_natport = htons(fa->mangle.val);
+ mung->tcpudp = 1;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ /* first mangle tells us whether this is SNAT or DNAT;
+ * subsequent mangles must match that
+ */
+ if (first)
+ conn->dnat = dnat;
+ else if (conn->dnat != dnat)
+ return -EOPNOTSUPP;
+ return 0;
+}
+
static int efx_tc_ct_replace(struct efx_tc_ct_zone *ct_zone,
struct flow_cls_offload *tc)
{
struct flow_rule *fr = flow_cls_offload_flow_rule(tc);
+ struct efx_tc_ct_mangler_state mung = {};
struct efx_tc_ct_entry *conn, *old;
struct efx_nic *efx = ct_zone->efx;
const struct flow_action_entry *fa;
@@ -326,6 +400,17 @@ static int efx_tc_ct_replace(struct efx_tc_ct_zone *ct_zone,
goto release;
}
break;
+ case FLOW_ACTION_MANGLE:
+ if (conn->eth_proto != htons(ETH_P_IP)) {
+ netif_dbg(efx, drv, efx->net_dev,
+ "NAT only supported for IPv4\n");
+ rc = -EOPNOTSUPP;
+ goto release;
+ }
+ rc = efx_tc_ct_mangle(efx, conn, fa, &mung);
+ if (rc)
+ goto release;
+ break;
default:
netif_dbg(efx, drv, efx->net_dev,
"Unhandled action %u for conntrack\n", fa->id);
@@ -335,8 +420,10 @@ static int efx_tc_ct_replace(struct efx_tc_ct_zone *ct_zone,
}
/* fill in defaults for unmangled values */
- conn->nat_ip = conn->dnat ? conn->dst_ip : conn->src_ip;
- conn->l4_natport = conn->dnat ? conn->l4_dport : conn->l4_sport;
+ if (!mung.ipv4)
+ conn->nat_ip = conn->dnat ? conn->dst_ip : conn->src_ip;
+ if (!mung.tcpudp)
+ conn->l4_natport = conn->dnat ? conn->l4_dport : conn->l4_sport;
cnt = efx_tc_flower_allocate_counter(efx, EFX_TC_COUNTER_TYPE_CT);
if (IS_ERR(cnt)) {