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