18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
28c2ecf20Sopenharmony_ci/* Copyright (C) 2018 Netronome Systems, Inc. */
38c2ecf20Sopenharmony_ci
48c2ecf20Sopenharmony_ci#include <linux/bitfield.h>
58c2ecf20Sopenharmony_ci#include <net/pkt_cls.h>
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include "../nfpcore/nfp_cpp.h"
88c2ecf20Sopenharmony_ci#include "../nfp_app.h"
98c2ecf20Sopenharmony_ci#include "../nfp_net_repr.h"
108c2ecf20Sopenharmony_ci#include "main.h"
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_cistruct nfp_abm_u32_match {
138c2ecf20Sopenharmony_ci	u32 handle;
148c2ecf20Sopenharmony_ci	u32 band;
158c2ecf20Sopenharmony_ci	u8 mask;
168c2ecf20Sopenharmony_ci	u8 val;
178c2ecf20Sopenharmony_ci	struct list_head list;
188c2ecf20Sopenharmony_ci};
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistatic bool
218c2ecf20Sopenharmony_cinfp_abm_u32_check_knode(struct nfp_abm *abm, struct tc_cls_u32_knode *knode,
228c2ecf20Sopenharmony_ci			__be16 proto, struct netlink_ext_ack *extack)
238c2ecf20Sopenharmony_ci{
248c2ecf20Sopenharmony_ci	struct tc_u32_key *k;
258c2ecf20Sopenharmony_ci	unsigned int tos_off;
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci	if (knode->exts && tcf_exts_has_actions(knode->exts)) {
288c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "action offload not supported");
298c2ecf20Sopenharmony_ci		return false;
308c2ecf20Sopenharmony_ci	}
318c2ecf20Sopenharmony_ci	if (knode->link_handle) {
328c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "linking not supported");
338c2ecf20Sopenharmony_ci		return false;
348c2ecf20Sopenharmony_ci	}
358c2ecf20Sopenharmony_ci	if (knode->sel->flags != TC_U32_TERMINAL) {
368c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
378c2ecf20Sopenharmony_ci				   "flags must be equal to TC_U32_TERMINAL");
388c2ecf20Sopenharmony_ci		return false;
398c2ecf20Sopenharmony_ci	}
408c2ecf20Sopenharmony_ci	if (knode->sel->off || knode->sel->offshift || knode->sel->offmask ||
418c2ecf20Sopenharmony_ci	    knode->sel->offoff || knode->fshift) {
428c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "variable offsetting not supported");
438c2ecf20Sopenharmony_ci		return false;
448c2ecf20Sopenharmony_ci	}
458c2ecf20Sopenharmony_ci	if (knode->sel->hoff || knode->sel->hmask) {
468c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "hashing not supported");
478c2ecf20Sopenharmony_ci		return false;
488c2ecf20Sopenharmony_ci	}
498c2ecf20Sopenharmony_ci	if (knode->val || knode->mask) {
508c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "matching on mark not supported");
518c2ecf20Sopenharmony_ci		return false;
528c2ecf20Sopenharmony_ci	}
538c2ecf20Sopenharmony_ci	if (knode->res && knode->res->class) {
548c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "setting non-0 class not supported");
558c2ecf20Sopenharmony_ci		return false;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci	if (knode->res && knode->res->classid >= abm->num_bands) {
588c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
598c2ecf20Sopenharmony_ci				   "classid higher than number of bands");
608c2ecf20Sopenharmony_ci		return false;
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci	if (knode->sel->nkeys != 1) {
638c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "exactly one key required");
648c2ecf20Sopenharmony_ci		return false;
658c2ecf20Sopenharmony_ci	}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	switch (proto) {
688c2ecf20Sopenharmony_ci	case htons(ETH_P_IP):
698c2ecf20Sopenharmony_ci		tos_off = 16;
708c2ecf20Sopenharmony_ci		break;
718c2ecf20Sopenharmony_ci	case htons(ETH_P_IPV6):
728c2ecf20Sopenharmony_ci		tos_off = 20;
738c2ecf20Sopenharmony_ci		break;
748c2ecf20Sopenharmony_ci	default:
758c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "only IP and IPv6 supported as filter protocol");
768c2ecf20Sopenharmony_ci		return false;
778c2ecf20Sopenharmony_ci	}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	k = &knode->sel->keys[0];
808c2ecf20Sopenharmony_ci	if (k->offmask) {
818c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "offset mask - variable offsetting not supported");
828c2ecf20Sopenharmony_ci		return false;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci	if (k->off) {
858c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "only DSCP fields can be matched");
868c2ecf20Sopenharmony_ci		return false;
878c2ecf20Sopenharmony_ci	}
888c2ecf20Sopenharmony_ci	if (k->val & ~k->mask) {
898c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "mask does not cover the key");
908c2ecf20Sopenharmony_ci		return false;
918c2ecf20Sopenharmony_ci	}
928c2ecf20Sopenharmony_ci	if (be32_to_cpu(k->mask) >> tos_off & ~abm->dscp_mask) {
938c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "only high DSCP class selector bits can be used");
948c2ecf20Sopenharmony_ci		nfp_err(abm->app->cpp,
958c2ecf20Sopenharmony_ci			"u32 offload: requested mask %x FW can support only %x\n",
968c2ecf20Sopenharmony_ci			be32_to_cpu(k->mask) >> tos_off, abm->dscp_mask);
978c2ecf20Sopenharmony_ci		return false;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	return true;
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/* This filter list -> map conversion is O(n * m), we expect single digit or
1048c2ecf20Sopenharmony_ci * low double digit number of prios and likewise for the filters.  Also u32
1058c2ecf20Sopenharmony_ci * doesn't report stats, so it's really only setup time cost.
1068c2ecf20Sopenharmony_ci */
1078c2ecf20Sopenharmony_cistatic unsigned int
1088c2ecf20Sopenharmony_cinfp_abm_find_band_for_prio(struct nfp_abm_link *alink, unsigned int prio)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	struct nfp_abm_u32_match *iter;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	list_for_each_entry(iter, &alink->dscp_map, list)
1138c2ecf20Sopenharmony_ci		if ((prio & iter->mask) == iter->val)
1148c2ecf20Sopenharmony_ci			return iter->band;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	return alink->def_band;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic int nfp_abm_update_band_map(struct nfp_abm_link *alink)
1208c2ecf20Sopenharmony_ci{
1218c2ecf20Sopenharmony_ci	unsigned int i, bits_per_prio, prios_per_word, base_shift;
1228c2ecf20Sopenharmony_ci	struct nfp_abm *abm = alink->abm;
1238c2ecf20Sopenharmony_ci	u32 field_mask;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	alink->has_prio = !list_empty(&alink->dscp_map);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	bits_per_prio = roundup_pow_of_two(order_base_2(abm->num_bands));
1288c2ecf20Sopenharmony_ci	field_mask = (1 << bits_per_prio) - 1;
1298c2ecf20Sopenharmony_ci	prios_per_word = sizeof(u32) * BITS_PER_BYTE / bits_per_prio;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/* FW mask applies from top bits */
1328c2ecf20Sopenharmony_ci	base_shift = 8 - order_base_2(abm->num_prios);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	for (i = 0; i < abm->num_prios; i++) {
1358c2ecf20Sopenharmony_ci		unsigned int offset;
1368c2ecf20Sopenharmony_ci		u32 *word;
1378c2ecf20Sopenharmony_ci		u8 band;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci		word = &alink->prio_map[i / prios_per_word];
1408c2ecf20Sopenharmony_ci		offset = (i % prios_per_word) * bits_per_prio;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci		band = nfp_abm_find_band_for_prio(alink, i << base_shift);
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci		*word &= ~(field_mask << offset);
1458c2ecf20Sopenharmony_ci		*word |= band << offset;
1468c2ecf20Sopenharmony_ci	}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	/* Qdisc offload status may change if has_prio changed */
1498c2ecf20Sopenharmony_ci	nfp_abm_qdisc_offload_update(alink);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	return nfp_abm_ctrl_prio_map_update(alink, alink->prio_map);
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic void
1558c2ecf20Sopenharmony_cinfp_abm_u32_knode_delete(struct nfp_abm_link *alink,
1568c2ecf20Sopenharmony_ci			 struct tc_cls_u32_knode *knode)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	struct nfp_abm_u32_match *iter;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	list_for_each_entry(iter, &alink->dscp_map, list)
1618c2ecf20Sopenharmony_ci		if (iter->handle == knode->handle) {
1628c2ecf20Sopenharmony_ci			list_del(&iter->list);
1638c2ecf20Sopenharmony_ci			kfree(iter);
1648c2ecf20Sopenharmony_ci			nfp_abm_update_band_map(alink);
1658c2ecf20Sopenharmony_ci			return;
1668c2ecf20Sopenharmony_ci		}
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_cistatic int
1708c2ecf20Sopenharmony_cinfp_abm_u32_knode_replace(struct nfp_abm_link *alink,
1718c2ecf20Sopenharmony_ci			  struct tc_cls_u32_knode *knode,
1728c2ecf20Sopenharmony_ci			  __be16 proto, struct netlink_ext_ack *extack)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	struct nfp_abm_u32_match *match = NULL, *iter;
1758c2ecf20Sopenharmony_ci	unsigned int tos_off;
1768c2ecf20Sopenharmony_ci	u8 mask, val;
1778c2ecf20Sopenharmony_ci	int err;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	if (!nfp_abm_u32_check_knode(alink->abm, knode, proto, extack))
1808c2ecf20Sopenharmony_ci		goto err_delete;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	tos_off = proto == htons(ETH_P_IP) ? 16 : 20;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	/* Extract the DSCP Class Selector bits */
1858c2ecf20Sopenharmony_ci	val = be32_to_cpu(knode->sel->keys[0].val) >> tos_off & 0xff;
1868c2ecf20Sopenharmony_ci	mask = be32_to_cpu(knode->sel->keys[0].mask) >> tos_off & 0xff;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	/* Check if there is no conflicting mapping and find match by handle */
1898c2ecf20Sopenharmony_ci	list_for_each_entry(iter, &alink->dscp_map, list) {
1908c2ecf20Sopenharmony_ci		u32 cmask;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci		if (iter->handle == knode->handle) {
1938c2ecf20Sopenharmony_ci			match = iter;
1948c2ecf20Sopenharmony_ci			continue;
1958c2ecf20Sopenharmony_ci		}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci		cmask = iter->mask & mask;
1988c2ecf20Sopenharmony_ci		if ((iter->val & cmask) == (val & cmask) &&
1998c2ecf20Sopenharmony_ci		    iter->band != knode->res->classid) {
2008c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "conflict with already offloaded filter");
2018c2ecf20Sopenharmony_ci			goto err_delete;
2028c2ecf20Sopenharmony_ci		}
2038c2ecf20Sopenharmony_ci	}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	if (!match) {
2068c2ecf20Sopenharmony_ci		match = kzalloc(sizeof(*match), GFP_KERNEL);
2078c2ecf20Sopenharmony_ci		if (!match)
2088c2ecf20Sopenharmony_ci			return -ENOMEM;
2098c2ecf20Sopenharmony_ci		list_add(&match->list, &alink->dscp_map);
2108c2ecf20Sopenharmony_ci	}
2118c2ecf20Sopenharmony_ci	match->handle = knode->handle;
2128c2ecf20Sopenharmony_ci	match->band = knode->res->classid;
2138c2ecf20Sopenharmony_ci	match->mask = mask;
2148c2ecf20Sopenharmony_ci	match->val = val;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	err = nfp_abm_update_band_map(alink);
2178c2ecf20Sopenharmony_ci	if (err)
2188c2ecf20Sopenharmony_ci		goto err_delete;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	return 0;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cierr_delete:
2238c2ecf20Sopenharmony_ci	nfp_abm_u32_knode_delete(alink, knode);
2248c2ecf20Sopenharmony_ci	return -EOPNOTSUPP;
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cistatic int nfp_abm_setup_tc_block_cb(enum tc_setup_type type,
2288c2ecf20Sopenharmony_ci				     void *type_data, void *cb_priv)
2298c2ecf20Sopenharmony_ci{
2308c2ecf20Sopenharmony_ci	struct tc_cls_u32_offload *cls_u32 = type_data;
2318c2ecf20Sopenharmony_ci	struct nfp_repr *repr = cb_priv;
2328c2ecf20Sopenharmony_ci	struct nfp_abm_link *alink;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	alink = repr->app_priv;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	if (type != TC_SETUP_CLSU32) {
2378c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
2388c2ecf20Sopenharmony_ci				   "only offload of u32 classifier supported");
2398c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2408c2ecf20Sopenharmony_ci	}
2418c2ecf20Sopenharmony_ci	if (!tc_cls_can_offload_and_chain0(repr->netdev, &cls_u32->common))
2428c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	if (cls_u32->common.protocol != htons(ETH_P_IP) &&
2458c2ecf20Sopenharmony_ci	    cls_u32->common.protocol != htons(ETH_P_IPV6)) {
2468c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
2478c2ecf20Sopenharmony_ci				   "only IP and IPv6 supported as filter protocol");
2488c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2498c2ecf20Sopenharmony_ci	}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	switch (cls_u32->command) {
2528c2ecf20Sopenharmony_ci	case TC_CLSU32_NEW_KNODE:
2538c2ecf20Sopenharmony_ci	case TC_CLSU32_REPLACE_KNODE:
2548c2ecf20Sopenharmony_ci		return nfp_abm_u32_knode_replace(alink, &cls_u32->knode,
2558c2ecf20Sopenharmony_ci						 cls_u32->common.protocol,
2568c2ecf20Sopenharmony_ci						 cls_u32->common.extack);
2578c2ecf20Sopenharmony_ci	case TC_CLSU32_DELETE_KNODE:
2588c2ecf20Sopenharmony_ci		nfp_abm_u32_knode_delete(alink, &cls_u32->knode);
2598c2ecf20Sopenharmony_ci		return 0;
2608c2ecf20Sopenharmony_ci	default:
2618c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cistatic LIST_HEAD(nfp_abm_block_cb_list);
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ciint nfp_abm_setup_cls_block(struct net_device *netdev, struct nfp_repr *repr,
2688c2ecf20Sopenharmony_ci			    struct flow_block_offload *f)
2698c2ecf20Sopenharmony_ci{
2708c2ecf20Sopenharmony_ci	return flow_block_cb_setup_simple(f, &nfp_abm_block_cb_list,
2718c2ecf20Sopenharmony_ci					  nfp_abm_setup_tc_block_cb,
2728c2ecf20Sopenharmony_ci					  repr, repr, true);
2738c2ecf20Sopenharmony_ci}
274