xref: /linux/drivers/net/ethernet/sfc/tc_conntrack.c (revision 5cce7814)
1c3bb5c6aSEdward Cree // SPDX-License-Identifier: GPL-2.0-only
2c3bb5c6aSEdward Cree /****************************************************************************
3c3bb5c6aSEdward Cree  * Driver for Solarflare network controllers and boards
4c3bb5c6aSEdward Cree  * Copyright 2023, Advanced Micro Devices, Inc.
5c3bb5c6aSEdward Cree  *
6c3bb5c6aSEdward Cree  * This program is free software; you can redistribute it and/or modify it
7c3bb5c6aSEdward Cree  * under the terms of the GNU General Public License version 2 as published
8c3bb5c6aSEdward Cree  * by the Free Software Foundation, incorporated herein by reference.
9c3bb5c6aSEdward Cree  */
10c3bb5c6aSEdward Cree 
11c3bb5c6aSEdward Cree #include "tc_conntrack.h"
12c3bb5c6aSEdward Cree #include "tc.h"
13c3bb5c6aSEdward Cree #include "mae.h"
14c3bb5c6aSEdward Cree 
15c3bb5c6aSEdward Cree static int efx_tc_flow_block(enum tc_setup_type type, void *type_data,
16c3bb5c6aSEdward Cree 			     void *cb_priv);
17c3bb5c6aSEdward Cree 
18c3bb5c6aSEdward Cree static const struct rhashtable_params efx_tc_ct_zone_ht_params = {
19c3bb5c6aSEdward Cree 	.key_len	= offsetof(struct efx_tc_ct_zone, linkage),
20c3bb5c6aSEdward Cree 	.key_offset	= 0,
21c3bb5c6aSEdward Cree 	.head_offset	= offsetof(struct efx_tc_ct_zone, linkage),
22c3bb5c6aSEdward Cree };
23c3bb5c6aSEdward Cree 
241909387fSEdward Cree static const struct rhashtable_params efx_tc_ct_ht_params = {
251909387fSEdward Cree 	.key_len	= offsetof(struct efx_tc_ct_entry, linkage),
261909387fSEdward Cree 	.key_offset	= 0,
271909387fSEdward Cree 	.head_offset	= offsetof(struct efx_tc_ct_entry, linkage),
281909387fSEdward Cree };
291909387fSEdward Cree 
30c3bb5c6aSEdward Cree static void efx_tc_ct_zone_free(void *ptr, void *arg)
31c3bb5c6aSEdward Cree {
32c3bb5c6aSEdward Cree 	struct efx_tc_ct_zone *zone = ptr;
33c3bb5c6aSEdward Cree 	struct efx_nic *efx = zone->efx;
34c3bb5c6aSEdward Cree 
35c3bb5c6aSEdward Cree 	netif_err(efx, drv, efx->net_dev,
36c3bb5c6aSEdward Cree 		  "tc ct_zone %u still present at teardown, removing\n",
37c3bb5c6aSEdward Cree 		  zone->zone);
38c3bb5c6aSEdward Cree 
39c3bb5c6aSEdward Cree 	nf_flow_table_offload_del_cb(zone->nf_ft, efx_tc_flow_block, zone);
40c3bb5c6aSEdward Cree 	kfree(zone);
41c3bb5c6aSEdward Cree }
42c3bb5c6aSEdward Cree 
431909387fSEdward Cree static void efx_tc_ct_free(void *ptr, void *arg)
441909387fSEdward Cree {
451909387fSEdward Cree 	struct efx_tc_ct_entry *conn = ptr;
461909387fSEdward Cree 	struct efx_nic *efx = arg;
471909387fSEdward Cree 
481909387fSEdward Cree 	netif_err(efx, drv, efx->net_dev,
491909387fSEdward Cree 		  "tc ct_entry %lx still present at teardown\n",
501909387fSEdward Cree 		  conn->cookie);
511909387fSEdward Cree 
521909387fSEdward Cree 	/* We can release the counter, but we can't remove the CT itself
531909387fSEdward Cree 	 * from hardware because the table meta is already gone.
541909387fSEdward Cree 	 */
551909387fSEdward Cree 	efx_tc_flower_release_counter(efx, conn->cnt);
561909387fSEdward Cree 	kfree(conn);
571909387fSEdward Cree }
581909387fSEdward Cree 
59c3bb5c6aSEdward Cree int efx_tc_init_conntrack(struct efx_nic *efx)
60c3bb5c6aSEdward Cree {
61c3bb5c6aSEdward Cree 	int rc;
62c3bb5c6aSEdward Cree 
63c3bb5c6aSEdward Cree 	rc = rhashtable_init(&efx->tc->ct_zone_ht, &efx_tc_ct_zone_ht_params);
64c3bb5c6aSEdward Cree 	if (rc < 0)
651909387fSEdward Cree 		goto fail_ct_zone_ht;
661909387fSEdward Cree 	rc = rhashtable_init(&efx->tc->ct_ht, &efx_tc_ct_ht_params);
671909387fSEdward Cree 	if (rc < 0)
681909387fSEdward Cree 		goto fail_ct_ht;
69c3bb5c6aSEdward Cree 	return 0;
701909387fSEdward Cree fail_ct_ht:
711909387fSEdward Cree 	rhashtable_destroy(&efx->tc->ct_zone_ht);
721909387fSEdward Cree fail_ct_zone_ht:
731909387fSEdward Cree 	return rc;
74c3bb5c6aSEdward Cree }
75c3bb5c6aSEdward Cree 
7629416025SEdward Cree /* Only call this in init failure teardown.
7729416025SEdward Cree  * Normal exit should fini instead as there may be entries in the table.
7829416025SEdward Cree  */
7929416025SEdward Cree void efx_tc_destroy_conntrack(struct efx_nic *efx)
8029416025SEdward Cree {
8129416025SEdward Cree 	rhashtable_destroy(&efx->tc->ct_ht);
8229416025SEdward Cree 	rhashtable_destroy(&efx->tc->ct_zone_ht);
8329416025SEdward Cree }
8429416025SEdward Cree 
85c3bb5c6aSEdward Cree void efx_tc_fini_conntrack(struct efx_nic *efx)
86c3bb5c6aSEdward Cree {
87c3bb5c6aSEdward Cree 	rhashtable_free_and_destroy(&efx->tc->ct_zone_ht, efx_tc_ct_zone_free, NULL);
881909387fSEdward Cree 	rhashtable_free_and_destroy(&efx->tc->ct_ht, efx_tc_ct_free, efx);
891909387fSEdward Cree }
901909387fSEdward Cree 
911909387fSEdward Cree #define EFX_NF_TCP_FLAG(flg)	cpu_to_be16(be32_to_cpu(TCP_FLAG_##flg) >> 16)
921909387fSEdward Cree 
931909387fSEdward Cree static int efx_tc_ct_parse_match(struct efx_nic *efx, struct flow_rule *fr,
941909387fSEdward Cree 				 struct efx_tc_ct_entry *conn)
951909387fSEdward Cree {
961909387fSEdward Cree 	struct flow_dissector *dissector = fr->match.dissector;
971909387fSEdward Cree 	unsigned char ipv = 0;
981909387fSEdward Cree 	bool tcp = false;
991909387fSEdward Cree 
1001909387fSEdward Cree 	if (flow_rule_match_key(fr, FLOW_DISSECTOR_KEY_CONTROL)) {
1011909387fSEdward Cree 		struct flow_match_control fm;
1021909387fSEdward Cree 
1031909387fSEdward Cree 		flow_rule_match_control(fr, &fm);
1041909387fSEdward Cree 		if (IS_ALL_ONES(fm.mask->addr_type))
1051909387fSEdward Cree 			switch (fm.key->addr_type) {
1061909387fSEdward Cree 			case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
1071909387fSEdward Cree 				ipv = 4;
1081909387fSEdward Cree 				break;
1091909387fSEdward Cree 			case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
1101909387fSEdward Cree 				ipv = 6;
1111909387fSEdward Cree 				break;
1121909387fSEdward Cree 			default:
1131909387fSEdward Cree 				break;
1141909387fSEdward Cree 			}
1151909387fSEdward Cree 	}
1161909387fSEdward Cree 
1171909387fSEdward Cree 	if (!ipv) {
1181909387fSEdward Cree 		netif_dbg(efx, drv, efx->net_dev,
1191909387fSEdward Cree 			  "Conntrack missing ipv specification\n");
1201909387fSEdward Cree 		return -EOPNOTSUPP;
1211909387fSEdward Cree 	}
1221909387fSEdward Cree 
1231909387fSEdward Cree 	if (dissector->used_keys &
1241909387fSEdward Cree 	    ~(BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
1251909387fSEdward Cree 	      BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
1261909387fSEdward Cree 	      BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
1271909387fSEdward Cree 	      BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
1281909387fSEdward Cree 	      BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) |
1291909387fSEdward Cree 	      BIT_ULL(FLOW_DISSECTOR_KEY_TCP) |
1301909387fSEdward Cree 	      BIT_ULL(FLOW_DISSECTOR_KEY_META))) {
1311909387fSEdward Cree 		netif_dbg(efx, drv, efx->net_dev,
1321909387fSEdward Cree 			  "Unsupported conntrack keys %#llx\n",
1331909387fSEdward Cree 			  dissector->used_keys);
1341909387fSEdward Cree 		return -EOPNOTSUPP;
1351909387fSEdward Cree 	}
1361909387fSEdward Cree 
1371909387fSEdward Cree 	if (flow_rule_match_key(fr, FLOW_DISSECTOR_KEY_BASIC)) {
1381909387fSEdward Cree 		struct flow_match_basic fm;
1391909387fSEdward Cree 
1401909387fSEdward Cree 		flow_rule_match_basic(fr, &fm);
1411909387fSEdward Cree 		if (!IS_ALL_ONES(fm.mask->n_proto)) {
1421909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
1431909387fSEdward Cree 				  "Conntrack eth_proto is not exact-match; mask %04x\n",
1441909387fSEdward Cree 				   ntohs(fm.mask->n_proto));
1451909387fSEdward Cree 			return -EOPNOTSUPP;
1461909387fSEdward Cree 		}
1471909387fSEdward Cree 		conn->eth_proto = fm.key->n_proto;
1481909387fSEdward Cree 		if (conn->eth_proto != (ipv == 4 ? htons(ETH_P_IP)
1491909387fSEdward Cree 						 : htons(ETH_P_IPV6))) {
1501909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
1511909387fSEdward Cree 				  "Conntrack eth_proto is not IPv%u, is %04x\n",
1521909387fSEdward Cree 				   ipv, ntohs(conn->eth_proto));
1531909387fSEdward Cree 			return -EOPNOTSUPP;
1541909387fSEdward Cree 		}
1551909387fSEdward Cree 		if (!IS_ALL_ONES(fm.mask->ip_proto)) {
1561909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
1571909387fSEdward Cree 				  "Conntrack ip_proto is not exact-match; mask %02x\n",
1581909387fSEdward Cree 				   fm.mask->ip_proto);
1591909387fSEdward Cree 			return -EOPNOTSUPP;
1601909387fSEdward Cree 		}
1611909387fSEdward Cree 		conn->ip_proto = fm.key->ip_proto;
1621909387fSEdward Cree 		switch (conn->ip_proto) {
1631909387fSEdward Cree 		case IPPROTO_TCP:
1641909387fSEdward Cree 			tcp = true;
1651909387fSEdward Cree 			break;
1661909387fSEdward Cree 		case IPPROTO_UDP:
1671909387fSEdward Cree 			break;
1681909387fSEdward Cree 		default:
1691909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
1701909387fSEdward Cree 				  "Conntrack ip_proto not TCP or UDP, is %02x\n",
1711909387fSEdward Cree 				   conn->ip_proto);
1721909387fSEdward Cree 			return -EOPNOTSUPP;
1731909387fSEdward Cree 		}
1741909387fSEdward Cree 	} else {
1751909387fSEdward Cree 		netif_dbg(efx, drv, efx->net_dev,
1761909387fSEdward Cree 			  "Conntrack missing eth_proto, ip_proto\n");
1771909387fSEdward Cree 		return -EOPNOTSUPP;
1781909387fSEdward Cree 	}
1791909387fSEdward Cree 
1801909387fSEdward Cree 	if (ipv == 4 && flow_rule_match_key(fr, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
1811909387fSEdward Cree 		struct flow_match_ipv4_addrs fm;
1821909387fSEdward Cree 
1831909387fSEdward Cree 		flow_rule_match_ipv4_addrs(fr, &fm);
1841909387fSEdward Cree 		if (!IS_ALL_ONES(fm.mask->src)) {
1851909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
1861909387fSEdward Cree 				  "Conntrack ipv4.src is not exact-match; mask %08x\n",
1871909387fSEdward Cree 				   ntohl(fm.mask->src));
1881909387fSEdward Cree 			return -EOPNOTSUPP;
1891909387fSEdward Cree 		}
1901909387fSEdward Cree 		conn->src_ip = fm.key->src;
1911909387fSEdward Cree 		if (!IS_ALL_ONES(fm.mask->dst)) {
1921909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
1931909387fSEdward Cree 				  "Conntrack ipv4.dst is not exact-match; mask %08x\n",
1941909387fSEdward Cree 				   ntohl(fm.mask->dst));
1951909387fSEdward Cree 			return -EOPNOTSUPP;
1961909387fSEdward Cree 		}
1971909387fSEdward Cree 		conn->dst_ip = fm.key->dst;
1981909387fSEdward Cree 	} else if (ipv == 6 && flow_rule_match_key(fr, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) {
1991909387fSEdward Cree 		struct flow_match_ipv6_addrs fm;
2001909387fSEdward Cree 
2011909387fSEdward Cree 		flow_rule_match_ipv6_addrs(fr, &fm);
2021909387fSEdward Cree 		if (!efx_ipv6_addr_all_ones(&fm.mask->src)) {
2031909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
2041909387fSEdward Cree 				  "Conntrack ipv6.src is not exact-match; mask %pI6\n",
2051909387fSEdward Cree 				   &fm.mask->src);
2061909387fSEdward Cree 			return -EOPNOTSUPP;
2071909387fSEdward Cree 		}
2081909387fSEdward Cree 		conn->src_ip6 = fm.key->src;
2091909387fSEdward Cree 		if (!efx_ipv6_addr_all_ones(&fm.mask->dst)) {
2101909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
2111909387fSEdward Cree 				  "Conntrack ipv6.dst is not exact-match; mask %pI6\n",
2121909387fSEdward Cree 				   &fm.mask->dst);
2131909387fSEdward Cree 			return -EOPNOTSUPP;
2141909387fSEdward Cree 		}
2151909387fSEdward Cree 		conn->dst_ip6 = fm.key->dst;
2161909387fSEdward Cree 	} else {
2171909387fSEdward Cree 		netif_dbg(efx, drv, efx->net_dev,
2181909387fSEdward Cree 			  "Conntrack missing IPv%u addrs\n", ipv);
2191909387fSEdward Cree 		return -EOPNOTSUPP;
2201909387fSEdward Cree 	}
2211909387fSEdward Cree 
2221909387fSEdward Cree 	if (flow_rule_match_key(fr, FLOW_DISSECTOR_KEY_PORTS)) {
2231909387fSEdward Cree 		struct flow_match_ports fm;
2241909387fSEdward Cree 
2251909387fSEdward Cree 		flow_rule_match_ports(fr, &fm);
2261909387fSEdward Cree 		if (!IS_ALL_ONES(fm.mask->src)) {
2271909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
2281909387fSEdward Cree 				  "Conntrack ports.src is not exact-match; mask %04x\n",
2291909387fSEdward Cree 				   ntohs(fm.mask->src));
2301909387fSEdward Cree 			return -EOPNOTSUPP;
2311909387fSEdward Cree 		}
2321909387fSEdward Cree 		conn->l4_sport = fm.key->src;
2331909387fSEdward Cree 		if (!IS_ALL_ONES(fm.mask->dst)) {
2341909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
2351909387fSEdward Cree 				  "Conntrack ports.dst is not exact-match; mask %04x\n",
2361909387fSEdward Cree 				   ntohs(fm.mask->dst));
2371909387fSEdward Cree 			return -EOPNOTSUPP;
2381909387fSEdward Cree 		}
2391909387fSEdward Cree 		conn->l4_dport = fm.key->dst;
2401909387fSEdward Cree 	} else {
2411909387fSEdward Cree 		netif_dbg(efx, drv, efx->net_dev, "Conntrack missing L4 ports\n");
2421909387fSEdward Cree 		return -EOPNOTSUPP;
2431909387fSEdward Cree 	}
2441909387fSEdward Cree 
2451909387fSEdward Cree 	if (flow_rule_match_key(fr, FLOW_DISSECTOR_KEY_TCP)) {
2461909387fSEdward Cree 		__be16 tcp_interesting_flags;
2471909387fSEdward Cree 		struct flow_match_tcp fm;
2481909387fSEdward Cree 
2491909387fSEdward Cree 		if (!tcp) {
2501909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
2511909387fSEdward Cree 				  "Conntrack matching on TCP keys but ipproto is not tcp\n");
2521909387fSEdward Cree 			return -EOPNOTSUPP;
2531909387fSEdward Cree 		}
2541909387fSEdward Cree 		flow_rule_match_tcp(fr, &fm);
2551909387fSEdward Cree 		tcp_interesting_flags = EFX_NF_TCP_FLAG(SYN) |
2561909387fSEdward Cree 					EFX_NF_TCP_FLAG(RST) |
2571909387fSEdward Cree 					EFX_NF_TCP_FLAG(FIN);
2581909387fSEdward Cree 		/* If any of the tcp_interesting_flags is set, we always
2591909387fSEdward Cree 		 * inhibit CT lookup in LHS (so SW can update CT table).
2601909387fSEdward Cree 		 */
2611909387fSEdward Cree 		if (fm.key->flags & tcp_interesting_flags) {
2621909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
2631909387fSEdward Cree 				  "Unsupported conntrack tcp.flags %04x/%04x\n",
2641909387fSEdward Cree 				   ntohs(fm.key->flags), ntohs(fm.mask->flags));
2651909387fSEdward Cree 			return -EOPNOTSUPP;
2661909387fSEdward Cree 		}
2671909387fSEdward Cree 		/* Other TCP flags cannot be filtered at CT */
2681909387fSEdward Cree 		if (fm.mask->flags & ~tcp_interesting_flags) {
2691909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
2701909387fSEdward Cree 				  "Unsupported conntrack tcp.flags %04x/%04x\n",
2711909387fSEdward Cree 				   ntohs(fm.key->flags), ntohs(fm.mask->flags));
2721909387fSEdward Cree 			return -EOPNOTSUPP;
2731909387fSEdward Cree 		}
2741909387fSEdward Cree 	}
2751909387fSEdward Cree 
2761909387fSEdward Cree 	return 0;
2771909387fSEdward Cree }
2781909387fSEdward Cree 
2791909387fSEdward Cree static int efx_tc_ct_replace(struct efx_tc_ct_zone *ct_zone,
2801909387fSEdward Cree 			     struct flow_cls_offload *tc)
2811909387fSEdward Cree {
2821909387fSEdward Cree 	struct flow_rule *fr = flow_cls_offload_flow_rule(tc);
2831909387fSEdward Cree 	struct efx_tc_ct_entry *conn, *old;
2841909387fSEdward Cree 	struct efx_nic *efx = ct_zone->efx;
2851909387fSEdward Cree 	const struct flow_action_entry *fa;
2861909387fSEdward Cree 	struct efx_tc_counter *cnt;
2871909387fSEdward Cree 	int rc, i;
2881909387fSEdward Cree 
2891909387fSEdward Cree 	if (WARN_ON(!efx->tc))
2901909387fSEdward Cree 		return -ENETDOWN;
2911909387fSEdward Cree 	if (WARN_ON(!efx->tc->up))
2921909387fSEdward Cree 		return -ENETDOWN;
2931909387fSEdward Cree 
2941909387fSEdward Cree 	conn = kzalloc(sizeof(*conn), GFP_USER);
2951909387fSEdward Cree 	if (!conn)
2961909387fSEdward Cree 		return -ENOMEM;
2971909387fSEdward Cree 	conn->cookie = tc->cookie;
2981909387fSEdward Cree 	old = rhashtable_lookup_get_insert_fast(&efx->tc->ct_ht,
2991909387fSEdward Cree 						&conn->linkage,
3001909387fSEdward Cree 						efx_tc_ct_ht_params);
3011909387fSEdward Cree 	if (old) {
3021909387fSEdward Cree 		netif_dbg(efx, drv, efx->net_dev,
3031909387fSEdward Cree 			  "Already offloaded conntrack (cookie %lx)\n", tc->cookie);
3041909387fSEdward Cree 		rc = -EEXIST;
3051909387fSEdward Cree 		goto release;
3061909387fSEdward Cree 	}
3071909387fSEdward Cree 
3081909387fSEdward Cree 	/* Parse match */
3091909387fSEdward Cree 	conn->zone = ct_zone;
3101909387fSEdward Cree 	rc = efx_tc_ct_parse_match(efx, fr, conn);
3111909387fSEdward Cree 	if (rc)
3121909387fSEdward Cree 		goto release;
3131909387fSEdward Cree 
3141909387fSEdward Cree 	/* Parse actions */
3151909387fSEdward Cree 	flow_action_for_each(i, fa, &fr->action) {
3161909387fSEdward Cree 		switch (fa->id) {
3171909387fSEdward Cree 		case FLOW_ACTION_CT_METADATA:
3181909387fSEdward Cree 			conn->mark = fa->ct_metadata.mark;
3191909387fSEdward Cree 			if (memchr_inv(fa->ct_metadata.labels, 0, sizeof(fa->ct_metadata.labels))) {
3201909387fSEdward Cree 				netif_dbg(efx, drv, efx->net_dev,
3211909387fSEdward Cree 					  "Setting CT label not supported\n");
3221909387fSEdward Cree 				rc = -EOPNOTSUPP;
3231909387fSEdward Cree 				goto release;
3241909387fSEdward Cree 			}
3251909387fSEdward Cree 			break;
3261909387fSEdward Cree 		default:
3271909387fSEdward Cree 			netif_dbg(efx, drv, efx->net_dev,
3281909387fSEdward Cree 				  "Unhandled action %u for conntrack\n", fa->id);
3291909387fSEdward Cree 			rc = -EOPNOTSUPP;
3301909387fSEdward Cree 			goto release;
3311909387fSEdward Cree 		}
3321909387fSEdward Cree 	}
3331909387fSEdward Cree 
3341909387fSEdward Cree 	/* fill in defaults for unmangled values */
3351909387fSEdward Cree 	conn->nat_ip = conn->dnat ? conn->dst_ip : conn->src_ip;
3361909387fSEdward Cree 	conn->l4_natport = conn->dnat ? conn->l4_dport : conn->l4_sport;
3371909387fSEdward Cree 
3381909387fSEdward Cree 	cnt = efx_tc_flower_allocate_counter(efx, EFX_TC_COUNTER_TYPE_CT);
3391909387fSEdward Cree 	if (IS_ERR(cnt)) {
3401909387fSEdward Cree 		rc = PTR_ERR(cnt);
3411909387fSEdward Cree 		goto release;
3421909387fSEdward Cree 	}
3431909387fSEdward Cree 	conn->cnt = cnt;
3441909387fSEdward Cree 
3451909387fSEdward Cree 	rc = efx_mae_insert_ct(efx, conn);
3461909387fSEdward Cree 	if (rc) {
3471909387fSEdward Cree 		netif_dbg(efx, drv, efx->net_dev,
3481909387fSEdward Cree 			  "Failed to insert conntrack, %d\n", rc);
3491909387fSEdward Cree 		goto release;
3501909387fSEdward Cree 	}
3511909387fSEdward Cree 	mutex_lock(&ct_zone->mutex);
3521909387fSEdward Cree 	list_add_tail(&conn->list, &ct_zone->cts);
3531909387fSEdward Cree 	mutex_unlock(&ct_zone->mutex);
3541909387fSEdward Cree 	return 0;
3551909387fSEdward Cree release:
3561909387fSEdward Cree 	if (conn->cnt)
3571909387fSEdward Cree 		efx_tc_flower_release_counter(efx, conn->cnt);
3581909387fSEdward Cree 	if (!old)
3591909387fSEdward Cree 		rhashtable_remove_fast(&efx->tc->ct_ht, &conn->linkage,
3601909387fSEdward Cree 				       efx_tc_ct_ht_params);
3611909387fSEdward Cree 	kfree(conn);
3621909387fSEdward Cree 	return rc;
3631909387fSEdward Cree }
3641909387fSEdward Cree 
3651909387fSEdward Cree /* Caller must follow with efx_tc_ct_remove_finish() after RCU grace period! */
3661909387fSEdward Cree static void efx_tc_ct_remove(struct efx_nic *efx, struct efx_tc_ct_entry *conn)
3671909387fSEdward Cree {
3681909387fSEdward Cree 	int rc;
3691909387fSEdward Cree 
3701909387fSEdward Cree 	/* Remove it from HW */
3711909387fSEdward Cree 	rc = efx_mae_remove_ct(efx, conn);
3721909387fSEdward Cree 	/* Delete it from SW */
3731909387fSEdward Cree 	rhashtable_remove_fast(&efx->tc->ct_ht, &conn->linkage,
3741909387fSEdward Cree 			       efx_tc_ct_ht_params);
3751909387fSEdward Cree 	if (rc) {
3761909387fSEdward Cree 		netif_err(efx, drv, efx->net_dev,
3771909387fSEdward Cree 			  "Failed to remove conntrack %lx from hw, rc %d\n",
3781909387fSEdward Cree 			  conn->cookie, rc);
3791909387fSEdward Cree 	} else {
3801909387fSEdward Cree 		netif_dbg(efx, drv, efx->net_dev, "Removed conntrack %lx\n",
3811909387fSEdward Cree 			  conn->cookie);
3821909387fSEdward Cree 	}
3831909387fSEdward Cree }
3841909387fSEdward Cree 
3851909387fSEdward Cree static void efx_tc_ct_remove_finish(struct efx_nic *efx, struct efx_tc_ct_entry *conn)
3861909387fSEdward Cree {
3871909387fSEdward Cree 	/* Remove related CT counter.  This is delayed after the conn object we
3881909387fSEdward Cree 	 * are working with has been successfully removed.  This protects the
3891909387fSEdward Cree 	 * counter from being used-after-free inside efx_tc_ct_stats.
3901909387fSEdward Cree 	 */
3911909387fSEdward Cree 	efx_tc_flower_release_counter(efx, conn->cnt);
3921909387fSEdward Cree 	kfree(conn);
3931909387fSEdward Cree }
3941909387fSEdward Cree 
3951909387fSEdward Cree static int efx_tc_ct_destroy(struct efx_tc_ct_zone *ct_zone,
3961909387fSEdward Cree 			     struct flow_cls_offload *tc)
3971909387fSEdward Cree {
3981909387fSEdward Cree 	struct efx_nic *efx = ct_zone->efx;
3991909387fSEdward Cree 	struct efx_tc_ct_entry *conn;
4001909387fSEdward Cree 
4011909387fSEdward Cree 	conn = rhashtable_lookup_fast(&efx->tc->ct_ht, &tc->cookie,
4021909387fSEdward Cree 				      efx_tc_ct_ht_params);
4031909387fSEdward Cree 	if (!conn) {
4041909387fSEdward Cree 		netif_warn(efx, drv, efx->net_dev,
4051909387fSEdward Cree 			   "Conntrack %lx not found to remove\n", tc->cookie);
4061909387fSEdward Cree 		return -ENOENT;
4071909387fSEdward Cree 	}
4081909387fSEdward Cree 
4091909387fSEdward Cree 	mutex_lock(&ct_zone->mutex);
4101909387fSEdward Cree 	list_del(&conn->list);
4111909387fSEdward Cree 	efx_tc_ct_remove(efx, conn);
4121909387fSEdward Cree 	mutex_unlock(&ct_zone->mutex);
4131909387fSEdward Cree 	synchronize_rcu();
4141909387fSEdward Cree 	efx_tc_ct_remove_finish(efx, conn);
4151909387fSEdward Cree 	return 0;
4161909387fSEdward Cree }
4171909387fSEdward Cree 
4181909387fSEdward Cree static int efx_tc_ct_stats(struct efx_tc_ct_zone *ct_zone,
4191909387fSEdward Cree 			   struct flow_cls_offload *tc)
4201909387fSEdward Cree {
4211909387fSEdward Cree 	struct efx_nic *efx = ct_zone->efx;
4221909387fSEdward Cree 	struct efx_tc_ct_entry *conn;
4231909387fSEdward Cree 	struct efx_tc_counter *cnt;
4241909387fSEdward Cree 
4251909387fSEdward Cree 	rcu_read_lock();
4261909387fSEdward Cree 	conn = rhashtable_lookup_fast(&efx->tc->ct_ht, &tc->cookie,
4271909387fSEdward Cree 				      efx_tc_ct_ht_params);
4281909387fSEdward Cree 	if (!conn) {
4291909387fSEdward Cree 		netif_warn(efx, drv, efx->net_dev,
4301909387fSEdward Cree 			   "Conntrack %lx not found for stats\n", tc->cookie);
4311909387fSEdward Cree 		rcu_read_unlock();
4321909387fSEdward Cree 		return -ENOENT;
4331909387fSEdward Cree 	}
4341909387fSEdward Cree 
4351909387fSEdward Cree 	cnt = conn->cnt;
4361909387fSEdward Cree 	spin_lock_bh(&cnt->lock);
4371909387fSEdward Cree 	/* Report only last use */
4381909387fSEdward Cree 	flow_stats_update(&tc->stats, 0, 0, 0, cnt->touched,
4391909387fSEdward Cree 			  FLOW_ACTION_HW_STATS_DELAYED);
4401909387fSEdward Cree 	spin_unlock_bh(&cnt->lock);
4411909387fSEdward Cree 	rcu_read_unlock();
4421909387fSEdward Cree 
4431909387fSEdward Cree 	return 0;
444c3bb5c6aSEdward Cree }
445c3bb5c6aSEdward Cree 
446c3bb5c6aSEdward Cree static int efx_tc_flow_block(enum tc_setup_type type, void *type_data,
447c3bb5c6aSEdward Cree 			     void *cb_priv)
448c3bb5c6aSEdward Cree {
4491909387fSEdward Cree 	struct flow_cls_offload *tcb = type_data;
4501909387fSEdward Cree 	struct efx_tc_ct_zone *ct_zone = cb_priv;
4511909387fSEdward Cree 
4521909387fSEdward Cree 	if (type != TC_SETUP_CLSFLOWER)
4531909387fSEdward Cree 		return -EOPNOTSUPP;
4541909387fSEdward Cree 
4551909387fSEdward Cree 	switch (tcb->command) {
4561909387fSEdward Cree 	case FLOW_CLS_REPLACE:
4571909387fSEdward Cree 		return efx_tc_ct_replace(ct_zone, tcb);
4581909387fSEdward Cree 	case FLOW_CLS_DESTROY:
4591909387fSEdward Cree 		return efx_tc_ct_destroy(ct_zone, tcb);
4601909387fSEdward Cree 	case FLOW_CLS_STATS:
4611909387fSEdward Cree 		return efx_tc_ct_stats(ct_zone, tcb);
4621909387fSEdward Cree 	default:
4631909387fSEdward Cree 		break;
464*5cce7814SYang Li 	}
4651909387fSEdward Cree 
466c3bb5c6aSEdward Cree 	return -EOPNOTSUPP;
467c3bb5c6aSEdward Cree }
468c3bb5c6aSEdward Cree 
469c3bb5c6aSEdward Cree struct efx_tc_ct_zone *efx_tc_ct_register_zone(struct efx_nic *efx, u16 zone,
470c3bb5c6aSEdward Cree 					       struct nf_flowtable *ct_ft)
471c3bb5c6aSEdward Cree {
472c3bb5c6aSEdward Cree 	struct efx_tc_ct_zone *ct_zone, *old;
473c3bb5c6aSEdward Cree 	int rc;
474c3bb5c6aSEdward Cree 
475c3bb5c6aSEdward Cree 	ct_zone = kzalloc(sizeof(*ct_zone), GFP_USER);
476c3bb5c6aSEdward Cree 	if (!ct_zone)
477c3bb5c6aSEdward Cree 		return ERR_PTR(-ENOMEM);
478c3bb5c6aSEdward Cree 	ct_zone->zone = zone;
479c3bb5c6aSEdward Cree 	old = rhashtable_lookup_get_insert_fast(&efx->tc->ct_zone_ht,
480c3bb5c6aSEdward Cree 						&ct_zone->linkage,
481c3bb5c6aSEdward Cree 						efx_tc_ct_zone_ht_params);
482c3bb5c6aSEdward Cree 	if (old) {
483c3bb5c6aSEdward Cree 		/* don't need our new entry */
484c3bb5c6aSEdward Cree 		kfree(ct_zone);
485c3bb5c6aSEdward Cree 		if (!refcount_inc_not_zero(&old->ref))
486c3bb5c6aSEdward Cree 			return ERR_PTR(-EAGAIN);
487c3bb5c6aSEdward Cree 		/* existing entry found */
488c3bb5c6aSEdward Cree 		WARN_ON_ONCE(old->nf_ft != ct_ft);
489c3bb5c6aSEdward Cree 		netif_dbg(efx, drv, efx->net_dev,
490c3bb5c6aSEdward Cree 			  "Found existing ct_zone for %u\n", zone);
491c3bb5c6aSEdward Cree 		return old;
492c3bb5c6aSEdward Cree 	}
493c3bb5c6aSEdward Cree 	ct_zone->nf_ft = ct_ft;
494c3bb5c6aSEdward Cree 	ct_zone->efx = efx;
4951909387fSEdward Cree 	INIT_LIST_HEAD(&ct_zone->cts);
4961909387fSEdward Cree 	mutex_init(&ct_zone->mutex);
497c3bb5c6aSEdward Cree 	rc = nf_flow_table_offload_add_cb(ct_ft, efx_tc_flow_block, ct_zone);
498c3bb5c6aSEdward Cree 	netif_dbg(efx, drv, efx->net_dev, "Adding new ct_zone for %u, rc %d\n",
499c3bb5c6aSEdward Cree 		  zone, rc);
500c3bb5c6aSEdward Cree 	if (rc < 0)
501c3bb5c6aSEdward Cree 		goto fail;
502c3bb5c6aSEdward Cree 	refcount_set(&ct_zone->ref, 1);
503c3bb5c6aSEdward Cree 	return ct_zone;
504c3bb5c6aSEdward Cree fail:
505c3bb5c6aSEdward Cree 	rhashtable_remove_fast(&efx->tc->ct_zone_ht, &ct_zone->linkage,
506c3bb5c6aSEdward Cree 			       efx_tc_ct_zone_ht_params);
507c3bb5c6aSEdward Cree 	kfree(ct_zone);
508c3bb5c6aSEdward Cree 	return ERR_PTR(rc);
509c3bb5c6aSEdward Cree }
510c3bb5c6aSEdward Cree 
511c3bb5c6aSEdward Cree void efx_tc_ct_unregister_zone(struct efx_nic *efx,
512c3bb5c6aSEdward Cree 			       struct efx_tc_ct_zone *ct_zone)
513c3bb5c6aSEdward Cree {
5141909387fSEdward Cree 	struct efx_tc_ct_entry *conn, *next;
5151909387fSEdward Cree 
516c3bb5c6aSEdward Cree 	if (!refcount_dec_and_test(&ct_zone->ref))
517c3bb5c6aSEdward Cree 		return; /* still in use */
518c3bb5c6aSEdward Cree 	nf_flow_table_offload_del_cb(ct_zone->nf_ft, efx_tc_flow_block, ct_zone);
519c3bb5c6aSEdward Cree 	rhashtable_remove_fast(&efx->tc->ct_zone_ht, &ct_zone->linkage,
520c3bb5c6aSEdward Cree 			       efx_tc_ct_zone_ht_params);
5211909387fSEdward Cree 	mutex_lock(&ct_zone->mutex);
5221909387fSEdward Cree 	list_for_each_entry(conn, &ct_zone->cts, list)
5231909387fSEdward Cree 		efx_tc_ct_remove(efx, conn);
5241909387fSEdward Cree 	synchronize_rcu();
5251909387fSEdward Cree 	/* need to use _safe because efx_tc_ct_remove_finish() frees conn */
5261909387fSEdward Cree 	list_for_each_entry_safe(conn, next, &ct_zone->cts, list)
5271909387fSEdward Cree 		efx_tc_ct_remove_finish(efx, conn);
5281909387fSEdward Cree 	mutex_unlock(&ct_zone->mutex);
5291909387fSEdward Cree 	mutex_destroy(&ct_zone->mutex);
530c3bb5c6aSEdward Cree 	netif_dbg(efx, drv, efx->net_dev, "Removed ct_zone for %u\n",
531c3bb5c6aSEdward Cree 		  ct_zone->zone);
532c3bb5c6aSEdward Cree 	kfree(ct_zone);
533c3bb5c6aSEdward Cree }
534