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