162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
262306a36Sopenharmony_ci/* Copyright (C) 2018 Netronome Systems, Inc. */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/bitfield.h>
562306a36Sopenharmony_ci#include <net/pkt_cls.h>
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include "../nfpcore/nfp_cpp.h"
862306a36Sopenharmony_ci#include "../nfp_app.h"
962306a36Sopenharmony_ci#include "../nfp_net_repr.h"
1062306a36Sopenharmony_ci#include "main.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistruct nfp_abm_u32_match {
1362306a36Sopenharmony_ci	u32 handle;
1462306a36Sopenharmony_ci	u32 band;
1562306a36Sopenharmony_ci	u8 mask;
1662306a36Sopenharmony_ci	u8 val;
1762306a36Sopenharmony_ci	struct list_head list;
1862306a36Sopenharmony_ci};
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic bool
2162306a36Sopenharmony_cinfp_abm_u32_check_knode(struct nfp_abm *abm, struct tc_cls_u32_knode *knode,
2262306a36Sopenharmony_ci			__be16 proto, struct netlink_ext_ack *extack)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	struct tc_u32_key *k;
2562306a36Sopenharmony_ci	unsigned int tos_off;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	if (knode->exts && tcf_exts_has_actions(knode->exts)) {
2862306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "action offload not supported");
2962306a36Sopenharmony_ci		return false;
3062306a36Sopenharmony_ci	}
3162306a36Sopenharmony_ci	if (knode->link_handle) {
3262306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "linking not supported");
3362306a36Sopenharmony_ci		return false;
3462306a36Sopenharmony_ci	}
3562306a36Sopenharmony_ci	if (knode->sel->flags != TC_U32_TERMINAL) {
3662306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
3762306a36Sopenharmony_ci				   "flags must be equal to TC_U32_TERMINAL");
3862306a36Sopenharmony_ci		return false;
3962306a36Sopenharmony_ci	}
4062306a36Sopenharmony_ci	if (knode->sel->off || knode->sel->offshift || knode->sel->offmask ||
4162306a36Sopenharmony_ci	    knode->sel->offoff || knode->fshift) {
4262306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "variable offsetting not supported");
4362306a36Sopenharmony_ci		return false;
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci	if (knode->sel->hoff || knode->sel->hmask) {
4662306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "hashing not supported");
4762306a36Sopenharmony_ci		return false;
4862306a36Sopenharmony_ci	}
4962306a36Sopenharmony_ci	if (knode->val || knode->mask) {
5062306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "matching on mark not supported");
5162306a36Sopenharmony_ci		return false;
5262306a36Sopenharmony_ci	}
5362306a36Sopenharmony_ci	if (knode->res && knode->res->class) {
5462306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "setting non-0 class not supported");
5562306a36Sopenharmony_ci		return false;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci	if (knode->res && knode->res->classid >= abm->num_bands) {
5862306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
5962306a36Sopenharmony_ci				   "classid higher than number of bands");
6062306a36Sopenharmony_ci		return false;
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci	if (knode->sel->nkeys != 1) {
6362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "exactly one key required");
6462306a36Sopenharmony_ci		return false;
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	switch (proto) {
6862306a36Sopenharmony_ci	case htons(ETH_P_IP):
6962306a36Sopenharmony_ci		tos_off = 16;
7062306a36Sopenharmony_ci		break;
7162306a36Sopenharmony_ci	case htons(ETH_P_IPV6):
7262306a36Sopenharmony_ci		tos_off = 20;
7362306a36Sopenharmony_ci		break;
7462306a36Sopenharmony_ci	default:
7562306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "only IP and IPv6 supported as filter protocol");
7662306a36Sopenharmony_ci		return false;
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	k = &knode->sel->keys[0];
8062306a36Sopenharmony_ci	if (k->offmask) {
8162306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "offset mask - variable offsetting not supported");
8262306a36Sopenharmony_ci		return false;
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci	if (k->off) {
8562306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "only DSCP fields can be matched");
8662306a36Sopenharmony_ci		return false;
8762306a36Sopenharmony_ci	}
8862306a36Sopenharmony_ci	if (k->val & ~k->mask) {
8962306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "mask does not cover the key");
9062306a36Sopenharmony_ci		return false;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci	if (be32_to_cpu(k->mask) >> tos_off & ~abm->dscp_mask) {
9362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "only high DSCP class selector bits can be used");
9462306a36Sopenharmony_ci		nfp_err(abm->app->cpp,
9562306a36Sopenharmony_ci			"u32 offload: requested mask %x FW can support only %x\n",
9662306a36Sopenharmony_ci			be32_to_cpu(k->mask) >> tos_off, abm->dscp_mask);
9762306a36Sopenharmony_ci		return false;
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	return true;
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci/* This filter list -> map conversion is O(n * m), we expect single digit or
10462306a36Sopenharmony_ci * low double digit number of prios and likewise for the filters.  Also u32
10562306a36Sopenharmony_ci * doesn't report stats, so it's really only setup time cost.
10662306a36Sopenharmony_ci */
10762306a36Sopenharmony_cistatic unsigned int
10862306a36Sopenharmony_cinfp_abm_find_band_for_prio(struct nfp_abm_link *alink, unsigned int prio)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct nfp_abm_u32_match *iter;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	list_for_each_entry(iter, &alink->dscp_map, list)
11362306a36Sopenharmony_ci		if ((prio & iter->mask) == iter->val)
11462306a36Sopenharmony_ci			return iter->band;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	return alink->def_band;
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic int nfp_abm_update_band_map(struct nfp_abm_link *alink)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	unsigned int i, bits_per_prio, prios_per_word, base_shift;
12262306a36Sopenharmony_ci	struct nfp_abm *abm = alink->abm;
12362306a36Sopenharmony_ci	u32 field_mask;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	alink->has_prio = !list_empty(&alink->dscp_map);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	bits_per_prio = roundup_pow_of_two(order_base_2(abm->num_bands));
12862306a36Sopenharmony_ci	field_mask = (1 << bits_per_prio) - 1;
12962306a36Sopenharmony_ci	prios_per_word = sizeof(u32) * BITS_PER_BYTE / bits_per_prio;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	/* FW mask applies from top bits */
13262306a36Sopenharmony_ci	base_shift = 8 - order_base_2(abm->num_prios);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	for (i = 0; i < abm->num_prios; i++) {
13562306a36Sopenharmony_ci		unsigned int offset;
13662306a36Sopenharmony_ci		u32 *word;
13762306a36Sopenharmony_ci		u8 band;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci		word = &alink->prio_map[i / prios_per_word];
14062306a36Sopenharmony_ci		offset = (i % prios_per_word) * bits_per_prio;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		band = nfp_abm_find_band_for_prio(alink, i << base_shift);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci		*word &= ~(field_mask << offset);
14562306a36Sopenharmony_ci		*word |= band << offset;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/* Qdisc offload status may change if has_prio changed */
14962306a36Sopenharmony_ci	nfp_abm_qdisc_offload_update(alink);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return nfp_abm_ctrl_prio_map_update(alink, alink->prio_map);
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic void
15562306a36Sopenharmony_cinfp_abm_u32_knode_delete(struct nfp_abm_link *alink,
15662306a36Sopenharmony_ci			 struct tc_cls_u32_knode *knode)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	struct nfp_abm_u32_match *iter;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	list_for_each_entry(iter, &alink->dscp_map, list)
16162306a36Sopenharmony_ci		if (iter->handle == knode->handle) {
16262306a36Sopenharmony_ci			list_del(&iter->list);
16362306a36Sopenharmony_ci			kfree(iter);
16462306a36Sopenharmony_ci			nfp_abm_update_band_map(alink);
16562306a36Sopenharmony_ci			return;
16662306a36Sopenharmony_ci		}
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic int
17062306a36Sopenharmony_cinfp_abm_u32_knode_replace(struct nfp_abm_link *alink,
17162306a36Sopenharmony_ci			  struct tc_cls_u32_knode *knode,
17262306a36Sopenharmony_ci			  __be16 proto, struct netlink_ext_ack *extack)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	struct nfp_abm_u32_match *match = NULL, *iter;
17562306a36Sopenharmony_ci	unsigned int tos_off;
17662306a36Sopenharmony_ci	u8 mask, val;
17762306a36Sopenharmony_ci	int err;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (!nfp_abm_u32_check_knode(alink->abm, knode, proto, extack))
18062306a36Sopenharmony_ci		goto err_delete;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	tos_off = proto == htons(ETH_P_IP) ? 16 : 20;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	/* Extract the DSCP Class Selector bits */
18562306a36Sopenharmony_ci	val = be32_to_cpu(knode->sel->keys[0].val) >> tos_off & 0xff;
18662306a36Sopenharmony_ci	mask = be32_to_cpu(knode->sel->keys[0].mask) >> tos_off & 0xff;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	/* Check if there is no conflicting mapping and find match by handle */
18962306a36Sopenharmony_ci	list_for_each_entry(iter, &alink->dscp_map, list) {
19062306a36Sopenharmony_ci		u32 cmask;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci		if (iter->handle == knode->handle) {
19362306a36Sopenharmony_ci			match = iter;
19462306a36Sopenharmony_ci			continue;
19562306a36Sopenharmony_ci		}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci		cmask = iter->mask & mask;
19862306a36Sopenharmony_ci		if ((iter->val & cmask) == (val & cmask) &&
19962306a36Sopenharmony_ci		    iter->band != knode->res->classid) {
20062306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "conflict with already offloaded filter");
20162306a36Sopenharmony_ci			goto err_delete;
20262306a36Sopenharmony_ci		}
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	if (!match) {
20662306a36Sopenharmony_ci		match = kzalloc(sizeof(*match), GFP_KERNEL);
20762306a36Sopenharmony_ci		if (!match)
20862306a36Sopenharmony_ci			return -ENOMEM;
20962306a36Sopenharmony_ci		list_add(&match->list, &alink->dscp_map);
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci	match->handle = knode->handle;
21262306a36Sopenharmony_ci	match->band = knode->res->classid;
21362306a36Sopenharmony_ci	match->mask = mask;
21462306a36Sopenharmony_ci	match->val = val;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	err = nfp_abm_update_band_map(alink);
21762306a36Sopenharmony_ci	if (err)
21862306a36Sopenharmony_ci		goto err_delete;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cierr_delete:
22362306a36Sopenharmony_ci	nfp_abm_u32_knode_delete(alink, knode);
22462306a36Sopenharmony_ci	return -EOPNOTSUPP;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic int nfp_abm_setup_tc_block_cb(enum tc_setup_type type,
22862306a36Sopenharmony_ci				     void *type_data, void *cb_priv)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	struct tc_cls_u32_offload *cls_u32 = type_data;
23162306a36Sopenharmony_ci	struct nfp_repr *repr = cb_priv;
23262306a36Sopenharmony_ci	struct nfp_abm_link *alink;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	alink = repr->app_priv;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	if (type != TC_SETUP_CLSU32) {
23762306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
23862306a36Sopenharmony_ci				   "only offload of u32 classifier supported");
23962306a36Sopenharmony_ci		return -EOPNOTSUPP;
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci	if (!tc_cls_can_offload_and_chain0(repr->netdev, &cls_u32->common))
24262306a36Sopenharmony_ci		return -EOPNOTSUPP;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	if (cls_u32->common.protocol != htons(ETH_P_IP) &&
24562306a36Sopenharmony_ci	    cls_u32->common.protocol != htons(ETH_P_IPV6)) {
24662306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
24762306a36Sopenharmony_ci				   "only IP and IPv6 supported as filter protocol");
24862306a36Sopenharmony_ci		return -EOPNOTSUPP;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	switch (cls_u32->command) {
25262306a36Sopenharmony_ci	case TC_CLSU32_NEW_KNODE:
25362306a36Sopenharmony_ci	case TC_CLSU32_REPLACE_KNODE:
25462306a36Sopenharmony_ci		return nfp_abm_u32_knode_replace(alink, &cls_u32->knode,
25562306a36Sopenharmony_ci						 cls_u32->common.protocol,
25662306a36Sopenharmony_ci						 cls_u32->common.extack);
25762306a36Sopenharmony_ci	case TC_CLSU32_DELETE_KNODE:
25862306a36Sopenharmony_ci		nfp_abm_u32_knode_delete(alink, &cls_u32->knode);
25962306a36Sopenharmony_ci		return 0;
26062306a36Sopenharmony_ci	default:
26162306a36Sopenharmony_ci		return -EOPNOTSUPP;
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_cistatic LIST_HEAD(nfp_abm_block_cb_list);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ciint nfp_abm_setup_cls_block(struct net_device *netdev, struct nfp_repr *repr,
26862306a36Sopenharmony_ci			    struct flow_block_offload *f)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	return flow_block_cb_setup_simple(f, &nfp_abm_block_cb_list,
27162306a36Sopenharmony_ci					  nfp_abm_setup_tc_block_cb,
27262306a36Sopenharmony_ci					  repr, repr, true);
27362306a36Sopenharmony_ci}
274