blob: 55f255a3c9db69b92d5743bd42c34cbaba46a0a8 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/* Microchip VCAP API
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#include <net/tc_act/tc_gate.h>
#include <net/tcp.h>
#include "sparx5_tc.h"
#include "vcap_api.h"
#include "vcap_api_client.h"
#include "vcap_tc.h"
#include "sparx5_main.h"
#include "sparx5_vcap_impl.h"
#define SPX5_MAX_RULE_SIZE 13 /* allows X1, X2, X4, X6 and X12 rules */
/* Collect keysets and type ids for multiple rules per size */
struct sparx5_wildcard_rule {
bool selected;
u8 value;
u8 mask;
enum vcap_keyfield_set keyset;
};
struct sparx5_multiple_rules {
struct sparx5_wildcard_rule rule[SPX5_MAX_RULE_SIZE];
};
struct sparx5_tc_flower_template {
struct list_head list; /* for insertion in the list of templates */
int cid; /* chain id */
enum vcap_keyfield_set orig; /* keyset used before the template */
enum vcap_keyfield_set keyset; /* new keyset used by template */
u16 l3_proto; /* protocol specified in the template */
};
/* SparX-5 VCAP fragment types:
* 0 = no fragment, 1 = initial fragment,
* 2 = suspicious fragment, 3 = valid follow-up fragment
*/
enum { /* key / mask */
FRAG_NOT = 0x03, /* 0 / 3 */
FRAG_SOME = 0x11, /* 1 / 1 */
FRAG_FIRST = 0x13, /* 1 / 3 */
FRAG_LATER = 0x33, /* 3 / 3 */
FRAG_INVAL = 0xff, /* invalid */
};
/* Flower fragment flag to VCAP fragment type mapping */
static const u8 sparx5_vcap_frag_map[4][4] = { /* is_frag */
{ FRAG_INVAL, FRAG_INVAL, FRAG_INVAL, FRAG_FIRST }, /* 0/0 */
{ FRAG_NOT, FRAG_NOT, FRAG_INVAL, FRAG_INVAL }, /* 0/1 */
{ FRAG_INVAL, FRAG_INVAL, FRAG_INVAL, FRAG_INVAL }, /* 1/0 */
{ FRAG_SOME, FRAG_LATER, FRAG_INVAL, FRAG_FIRST } /* 1/1 */
/* 0/0 0/1 1/0 1/1 <-- first_frag */
};
static int
sparx5_tc_flower_es0_tpid(struct vcap_tc_flower_parse_usage *st)
{
int err = 0;
switch (st->tpid) {
case ETH_P_8021Q:
err = vcap_rule_add_key_u32(st->vrule,
VCAP_KF_8021Q_TPID,
SPX5_TPID_SEL_8100, ~0);
break;
case ETH_P_8021AD:
err = vcap_rule_add_key_u32(st->vrule,
VCAP_KF_8021Q_TPID,
SPX5_TPID_SEL_88A8, ~0);
break;
default:
NL_SET_ERR_MSG_MOD(st->fco->common.extack,
"Invalid vlan proto");
err = -EINVAL;
break;
}
return err;
}
static int
sparx5_tc_flower_handler_basic_usage(struct vcap_tc_flower_parse_usage *st)
{
struct flow_match_basic mt;
int err = 0;
flow_rule_match_basic(st->frule, &mt);
if (mt.mask->n_proto) {
st->l3_proto = be16_to_cpu(mt.key->n_proto);
if (!sparx5_vcap_is_known_etype(st->admin, st->l3_proto)) {
err = vcap_rule_add_key_u32(st->vrule, VCAP_KF_ETYPE,
st->l3_proto, ~0);
if (err)
goto out;
} else if (st->l3_proto == ETH_P_IP) {
err = vcap_rule_add_key_bit(st->vrule, VCAP_KF_IP4_IS,
VCAP_BIT_1);
if (err)
goto out;
} else if (st->l3_proto == ETH_P_IPV6) {
err = vcap_rule_add_key_bit(st->vrule, VCAP_KF_IP4_IS,
VCAP_BIT_0);
if (err)
goto out;
if (st->admin->vtype == VCAP_TYPE_IS0) {
err = vcap_rule_add_key_bit(st->vrule,
VCAP_KF_IP_SNAP_IS,
VCAP_BIT_1);
if (err)
goto out;
}
}
}
if (mt.mask->ip_proto) {
st->l4_proto = mt.key->ip_proto;
if (st->l4_proto == IPPROTO_TCP) {
err = vcap_rule_add_key_bit(st->vrule,
VCAP_KF_TCP_IS,
VCAP_BIT_1);
if (err)
goto out;
} else if (st->l4_proto == IPPROTO_UDP) {
err = vcap_rule_add_key_bit(st->vrule,
VCAP_KF_TCP_IS,
VCAP_BIT_0);
if (err)
goto out;
if (st->admin->vtype == VCAP_TYPE_IS0) {
err = vcap_rule_add_key_bit(st->vrule,
VCAP_KF_TCP_UDP_IS,
VCAP_BIT_1);
if (err)
goto out;
}
} else {
err = vcap_rule_add_key_u32(st->vrule,
VCAP_KF_L3_IP_PROTO,
st->l4_proto, ~0);
if (err)
goto out;
}
}
st->used_keys |= BIT_ULL(FLOW_DISSECTOR_KEY_BASIC);
return err;
out:
NL_SET_ERR_MSG_MOD(st->fco->common.extack, "ip_proto parse error");
return err;
}
static int
sparx5_tc_flower_handler_control_usage(struct vcap_tc_flower_parse_usage *st)
{
struct flow_match_control mt;
u32 value, mask;
int err = 0;
flow_rule_match_control(st->frule, &mt);
if (mt.mask->flags) {
u8 is_frag_key = !!(mt.key->flags & FLOW_DIS_IS_FRAGMENT);
u8 is_frag_mask = !!(mt.mask->flags & FLOW_DIS_IS_FRAGMENT);
u8 is_frag_idx = (is_frag_key << 1) | is_frag_mask;
u8 first_frag_key = !!(mt.key->flags & FLOW_DIS_FIRST_FRAG);
u8 first_frag_mask = !!(mt.mask->flags & FLOW_DIS_FIRST_FRAG);
u8 first_frag_idx = (first_frag_key << 1) | first_frag_mask;
/* Lookup verdict based on the 2 + 2 input bits */
u8 vdt = sparx5_vcap_frag_map[is_frag_idx][first_frag_idx];
if (vdt == FRAG_INVAL) {
NL_SET_ERR_MSG_MOD(st->fco->common.extack,
"Match on invalid fragment flag combination");
return -EINVAL;
}
/* Extract VCAP fragment key and mask from verdict */
value = (vdt >> 4) & 0x3;
mask = vdt & 0x3;
err = vcap_rule_add_key_u32(st->vrule,
VCAP_KF_L3_FRAGMENT_TYPE,
value, mask);
if (err)
goto out;
}
st->used_keys |= BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL);
return err;
out:
NL_SET_ERR_MSG_MOD(st->fco->common.extack, "ip_frag parse error");
return err;
}
static int
sparx5_tc_flower_handler_cvlan_usage(struct vcap_tc_flower_parse_usage *st)
{
if (st->admin->vtype != VCAP_TYPE_IS0) {
NL_SET_ERR_MSG_MOD(st->fco->common.extack,
"cvlan not supported in this VCAP");
return -EINVAL;
}
return vcap_tc_flower_handler_cvlan_usage(st);
}
static int
sparx5_tc_flower_handler_vlan_usage(struct vcap_tc_flower_parse_usage *st)
{
enum vcap_key_field vid_key = VCAP_KF_8021Q_VID_CLS;
enum vcap_key_field pcp_key = VCAP_KF_8021Q_PCP_CLS;
int err;
if (st->admin->vtype == VCAP_TYPE_IS0) {
vid_key = VCAP_KF_8021Q_VID0;
pcp_key = VCAP_KF_8021Q_PCP0;
}
err = vcap_tc_flower_handler_vlan_usage(st, vid_key, pcp_key);
if (err)
return err;
if (st->admin->vtype == VCAP_TYPE_ES0 && st->tpid)
err = sparx5_tc_flower_es0_tpid(st);
return err;
}
static int (*sparx5_tc_flower_usage_handlers[])(struct vcap_tc_flower_parse_usage *st) = {
[FLOW_DISSECTOR_KEY_ETH_ADDRS] = vcap_tc_flower_handler_ethaddr_usage,
[FLOW_DISSECTOR_KEY_IPV4_ADDRS] = vcap_tc_flower_handler_ipv4_usage,
[FLOW_DISSECTOR_KEY_IPV6_ADDRS] = vcap_tc_flower_handler_ipv6_usage,
[FLOW_DISSECTOR_KEY_CONTROL] = sparx5_tc_flower_handler_control_usage,
[FLOW_DISSECTOR_KEY_PORTS] = vcap_tc_flower_handler_portnum_usage,
[FLOW_DISSECTOR_KEY_BASIC] = sparx5_tc_flower_handler_basic_usage,
[FLOW_DISSECTOR_KEY_CVLAN] = sparx5_tc_flower_handler_cvlan_usage,
[FLOW_DISSECTOR_KEY_VLAN] = sparx5_tc_flower_handler_vlan_usage,
[FLOW_DISSECTOR_KEY_TCP] = vcap_tc_flower_handler_tcp_usage,
[FLOW_DISSECTOR_KEY_ARP] = vcap_tc_flower_handler_arp_usage,
[FLOW_DISSECTOR_KEY_IP] = vcap_tc_flower_handler_ip_usage,
};
static int sparx5_tc_use_dissectors(struct vcap_tc_flower_parse_usage *st,
struct vcap_admin *admin,
struct vcap_rule *vrule)
{
int idx, err = 0;
for (idx = 0; idx < ARRAY_SIZE(sparx5_tc_flower_usage_handlers); ++idx) {
if (!flow_rule_match_key(st->frule, idx))
continue;
if (!sparx5_tc_flower_usage_handlers[idx])
continue;
err = sparx5_tc_flower_usage_handlers[idx](st);
if (err)
return err;
}
if (st->frule->match.dissector->used_keys ^ st->used_keys) {
NL_SET_ERR_MSG_MOD(st->fco->common.extack,
"Unsupported match item");
return -ENOENT;
}
return err;
}
static int sparx5_tc_flower_action_check(struct vcap_control *vctrl,
struct net_device *ndev,
struct flow_cls_offload *fco,
bool ingress)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(fco);
struct flow_action_entry *actent, *last_actent = NULL;
struct flow_action *act = &rule->action;
u64 action_mask = 0;
int idx;
if (!flow_action_has_entries(act)) {
NL_SET_ERR_MSG_MOD(fco->common.extack, "No actions");
return -EINVAL;
}
if (!flow_action_basic_hw_stats_check(act, fco->common.extack))
return -EOPNOTSUPP;
flow_action_for_each(idx, actent, act) {
if (action_mask & BIT(actent->id)) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"More actions of the same type");
return -EINVAL;
}
action_mask |= BIT(actent->id);
last_actent = actent; /* Save last action for later check */
}
/* Check if last action is a goto
* The last chain/lookup does not need to have a goto action
*/
if (last_actent->id == FLOW_ACTION_GOTO) {
/* Check if the destination chain is in one of the VCAPs */
if (!vcap_is_next_lookup(vctrl, fco->common.chain_index,
last_actent->chain_index)) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Invalid goto chain");
return -EINVAL;
}
} else if (!vcap_is_last_chain(vctrl, fco->common.chain_index,
ingress)) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Last action must be 'goto'");
return -EINVAL;
}
/* Catch unsupported combinations of actions */
if (action_mask & BIT(FLOW_ACTION_TRAP) &&
action_mask & BIT(FLOW_ACTION_ACCEPT)) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Cannot combine pass and trap action");
return -EOPNOTSUPP;
}
if (action_mask & BIT(FLOW_ACTION_VLAN_PUSH) &&
action_mask & BIT(FLOW_ACTION_VLAN_POP)) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Cannot combine vlan push and pop action");
return -EOPNOTSUPP;
}
if (action_mask & BIT(FLOW_ACTION_VLAN_PUSH) &&
action_mask & BIT(FLOW_ACTION_VLAN_MANGLE)) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Cannot combine vlan push and modify action");
return -EOPNOTSUPP;
}
if (action_mask & BIT(FLOW_ACTION_VLAN_POP) &&
action_mask & BIT(FLOW_ACTION_VLAN_MANGLE)) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Cannot combine vlan pop and modify action");
return -EOPNOTSUPP;
}
return 0;
}
/* Add a rule counter action */
static int sparx5_tc_add_rule_counter(struct vcap_admin *admin,
struct vcap_rule *vrule)
{
int err;
switch (admin->vtype) {
case VCAP_TYPE_IS0:
break;
case VCAP_TYPE_ES0:
err = vcap_rule_mod_action_u32(vrule, VCAP_AF_ESDX,
vrule->id);
if (err)
return err;
vcap_rule_set_counter_id(vrule, vrule->id);
break;
case VCAP_TYPE_IS2:
case VCAP_TYPE_ES2:
err = vcap_rule_mod_action_u32(vrule, VCAP_AF_CNT_ID,
vrule->id);
if (err)
return err;
vcap_rule_set_counter_id(vrule, vrule->id);
break;
default:
pr_err("%s:%d: vcap type: %d not supported\n",
__func__, __LINE__, admin->vtype);
break;
}
return 0;
}
/* Collect all port keysets and apply the first of them, possibly wildcarded */
static int sparx5_tc_select_protocol_keyset(struct net_device *ndev,
struct vcap_rule *vrule,
struct vcap_admin *admin,
u16 l3_proto,
struct sparx5_multiple_rules *multi)
{
struct sparx5_port *port = netdev_priv(ndev);
struct vcap_keyset_list portkeysetlist = {};
enum vcap_keyfield_set portkeysets[10] = {};
struct vcap_keyset_list matches = {};
enum vcap_keyfield_set keysets[10];
int idx, jdx, err = 0, count = 0;
struct sparx5_wildcard_rule *mru;
const struct vcap_set *kinfo;
struct vcap_control *vctrl;
vctrl = port->sparx5->vcap_ctrl;
/* Find the keysets that the rule can use */
matches.keysets = keysets;
matches.max = ARRAY_SIZE(keysets);
if (!vcap_rule_find_keysets(vrule, &matches))
return -EINVAL;
/* Find the keysets that the port configuration supports */
portkeysetlist.max = ARRAY_SIZE(portkeysets);
portkeysetlist.keysets = portkeysets;
err = sparx5_vcap_get_port_keyset(ndev,
admin, vrule->vcap_chain_id,
l3_proto,
&portkeysetlist);
if (err)
return err;
/* Find the intersection of the two sets of keyset */
for (idx = 0; idx < portkeysetlist.cnt; ++idx) {
kinfo = vcap_keyfieldset(vctrl, admin->vtype,
portkeysetlist.keysets[idx]);
if (!kinfo)
continue;
/* Find a port keyset that matches the required keys
* If there are multiple keysets then compose a type id mask
*/
for (jdx = 0; jdx < matches.cnt; ++jdx) {
if (portkeysetlist.keysets[idx] != matches.keysets[jdx])
continue;
mru = &multi->rule[kinfo->sw_per_item];
if (!mru->selected) {
mru->selected = true;
mru->keyset = portkeysetlist.keysets[idx];
mru->value = kinfo->type_id;
}
mru->value &= kinfo->type_id;
mru->mask |= kinfo->type_id;
++count;
}
}
if (count == 0)
return -EPROTO;
if (l3_proto == ETH_P_ALL && count < portkeysetlist.cnt)
return -ENOENT;
for (idx = 0; idx < SPX5_MAX_RULE_SIZE; ++idx) {
mru = &multi->rule[idx];
if (!mru->selected)
continue;
/* Align the mask to the combined value */
mru->mask ^= mru->value;
}
/* Set the chosen keyset on the rule and set a wildcarded type if there
* are more than one keyset
*/
for (idx = 0; idx < SPX5_MAX_RULE_SIZE; ++idx) {
mru = &multi->rule[idx];
if (!mru->selected)
continue;
vcap_set_rule_set_keyset(vrule, mru->keyset);
if (count > 1)
/* Some keysets do not have a type field */
vcap_rule_mod_key_u32(vrule, VCAP_KF_TYPE,
mru->value,
~mru->mask);
mru->selected = false; /* mark as done */
break; /* Stop here and add more rules later */
}
return err;
}
static int sparx5_tc_add_rule_copy(struct vcap_control *vctrl,
struct flow_cls_offload *fco,
struct vcap_rule *erule,
struct vcap_admin *admin,
struct sparx5_wildcard_rule *rule)
{
enum vcap_key_field keylist[] = {
VCAP_KF_IF_IGR_PORT_MASK,
VCAP_KF_IF_IGR_PORT_MASK_SEL,
VCAP_KF_IF_IGR_PORT_MASK_RNG,
VCAP_KF_LOOKUP_FIRST_IS,
VCAP_KF_TYPE,
};
struct vcap_rule *vrule;
int err;
/* Add an extra rule with a special user and the new keyset */
erule->user = VCAP_USER_TC_EXTRA;
vrule = vcap_copy_rule(erule);
if (IS_ERR(vrule))
return PTR_ERR(vrule);
/* Link the new rule to the existing rule with the cookie */
vrule->cookie = erule->cookie;
vcap_filter_rule_keys(vrule, keylist, ARRAY_SIZE(keylist), true);
err = vcap_set_rule_set_keyset(vrule, rule->keyset);
if (err) {
pr_err("%s:%d: could not set keyset %s in rule: %u\n",
__func__, __LINE__,
vcap_keyset_name(vctrl, rule->keyset),
vrule->id);
goto out;
}
/* Some keysets do not have a type field, so ignore return value */
vcap_rule_mod_key_u32(vrule, VCAP_KF_TYPE, rule->value, ~rule->mask);
err = vcap_set_rule_set_actionset(vrule, erule->actionset);
if (err)
goto out;
err = sparx5_tc_add_rule_counter(admin, vrule);
if (err)
goto out;
err = vcap_val_rule(vrule, ETH_P_ALL);
if (err) {
pr_err("%s:%d: could not validate rule: %u\n",
__func__, __LINE__, vrule->id);
vcap_set_tc_exterr(fco, vrule);
goto out;
}
err = vcap_add_rule(vrule);
if (err) {
pr_err("%s:%d: could not add rule: %u\n",
__func__, __LINE__, vrule->id);
goto out;
}
out:
vcap_free_rule(vrule);
return err;
}
static int sparx5_tc_add_remaining_rules(struct vcap_control *vctrl,
struct flow_cls_offload *fco,
struct vcap_rule *erule,
struct vcap_admin *admin,
struct sparx5_multiple_rules *multi)
{
int idx, err = 0;
for (idx = 0; idx < SPX5_MAX_RULE_SIZE; ++idx) {
if (!multi->rule[idx].selected)
continue;
err = sparx5_tc_add_rule_copy(vctrl, fco, erule, admin,
&multi->rule[idx]);
if (err)
break;
}
return err;
}
/* Add the actionset that is the default for the VCAP type */
static int sparx5_tc_set_actionset(struct vcap_admin *admin,
struct vcap_rule *vrule)
{
enum vcap_actionfield_set aset;
int err = 0;
switch (admin->vtype) {
case VCAP_TYPE_IS0:
aset = VCAP_AFS_CLASSIFICATION;
break;
case VCAP_TYPE_IS2:
aset = VCAP_AFS_BASE_TYPE;
break;
case VCAP_TYPE_ES0:
aset = VCAP_AFS_ES0;
break;
case VCAP_TYPE_ES2:
aset = VCAP_AFS_BASE_TYPE;
break;
default:
pr_err("%s:%d: %s\n", __func__, __LINE__, "Invalid VCAP type");
return -EINVAL;
}
/* Do not overwrite any current actionset */
if (vrule->actionset == VCAP_AFS_NO_VALUE)
err = vcap_set_rule_set_actionset(vrule, aset);
return err;
}
/* Add the VCAP key to match on for a rule target value */
static int sparx5_tc_add_rule_link_target(struct vcap_admin *admin,
struct vcap_rule *vrule,
int target_cid)
{
int link_val = target_cid % VCAP_CID_LOOKUP_SIZE;
int err;
if (!link_val)
return 0;
switch (admin->vtype) {
case VCAP_TYPE_IS0:
/* Add NXT_IDX key for chaining rules between IS0 instances */
err = vcap_rule_add_key_u32(vrule, VCAP_KF_LOOKUP_GEN_IDX_SEL,
1, /* enable */
~0);
if (err)
return err;
return vcap_rule_add_key_u32(vrule, VCAP_KF_LOOKUP_GEN_IDX,
link_val, /* target */
~0);
case VCAP_TYPE_IS2:
/* Add PAG key for chaining rules from IS0 */
return vcap_rule_add_key_u32(vrule, VCAP_KF_LOOKUP_PAG,
link_val, /* target */
~0);
case VCAP_TYPE_ES0:
case VCAP_TYPE_ES2:
/* Add ISDX key for chaining rules from IS0 */
return vcap_rule_add_key_u32(vrule, VCAP_KF_ISDX_CLS, link_val,
~0);
default:
break;
}
return 0;
}
/* Add the VCAP action that adds a target value to a rule */
static int sparx5_tc_add_rule_link(struct vcap_control *vctrl,
struct vcap_admin *admin,
struct vcap_rule *vrule,
int from_cid, int to_cid)
{
struct vcap_admin *to_admin = vcap_find_admin(vctrl, to_cid);
int diff, err = 0;
if (!to_admin) {
pr_err("%s:%d: unsupported chain direction: %d\n",
__func__, __LINE__, to_cid);
return -EINVAL;
}
diff = vcap_chain_offset(vctrl, from_cid, to_cid);
if (!diff)
return 0;
if (admin->vtype == VCAP_TYPE_IS0 &&
to_admin->vtype == VCAP_TYPE_IS0) {
/* Between IS0 instances the G_IDX value is used */
err = vcap_rule_add_action_u32(vrule, VCAP_AF_NXT_IDX, diff);
if (err)
goto out;
err = vcap_rule_add_action_u32(vrule, VCAP_AF_NXT_IDX_CTRL,
1); /* Replace */
if (err)
goto out;
} else if (admin->vtype == VCAP_TYPE_IS0 &&
to_admin->vtype == VCAP_TYPE_IS2) {
/* Between IS0 and IS2 the PAG value is used */
err = vcap_rule_add_action_u32(vrule, VCAP_AF_PAG_VAL, diff);
if (err)
goto out;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_PAG_OVERRIDE_MASK,
0xff);
if (err)
goto out;
} else if (admin->vtype == VCAP_TYPE_IS0 &&
(to_admin->vtype == VCAP_TYPE_ES0 ||
to_admin->vtype == VCAP_TYPE_ES2)) {
/* Between IS0 and ES0/ES2 the ISDX value is used */
err = vcap_rule_add_action_u32(vrule, VCAP_AF_ISDX_VAL,
diff);
if (err)
goto out;
err = vcap_rule_add_action_bit(vrule,
VCAP_AF_ISDX_ADD_REPLACE_SEL,
VCAP_BIT_1);
if (err)
goto out;
} else {
pr_err("%s:%d: unsupported chain destination: %d\n",
__func__, __LINE__, to_cid);
err = -EOPNOTSUPP;
}
out:
return err;
}
static int sparx5_tc_flower_parse_act_gate(struct sparx5_psfp_sg *sg,
struct flow_action_entry *act,
struct netlink_ext_ack *extack)
{
int i;
if (act->gate.prio < -1 || act->gate.prio > SPX5_PSFP_SG_MAX_IPV) {
NL_SET_ERR_MSG_MOD(extack, "Invalid gate priority");
return -EINVAL;
}
if (act->gate.cycletime < SPX5_PSFP_SG_MIN_CYCLE_TIME_NS ||
act->gate.cycletime > SPX5_PSFP_SG_MAX_CYCLE_TIME_NS) {
NL_SET_ERR_MSG_MOD(extack, "Invalid gate cycletime");
return -EINVAL;
}
if (act->gate.cycletimeext > SPX5_PSFP_SG_MAX_CYCLE_TIME_NS) {
NL_SET_ERR_MSG_MOD(extack, "Invalid gate cycletimeext");
return -EINVAL;
}
if (act->gate.num_entries >= SPX5_PSFP_GCE_CNT) {
NL_SET_ERR_MSG_MOD(extack, "Invalid number of gate entries");
return -EINVAL;
}
sg->gate_state = true;
sg->ipv = act->gate.prio;
sg->num_entries = act->gate.num_entries;
sg->cycletime = act->gate.cycletime;
sg->cycletimeext = act->gate.cycletimeext;
for (i = 0; i < sg->num_entries; i++) {
sg->gce[i].gate_state = !!act->gate.entries[i].gate_state;
sg->gce[i].interval = act->gate.entries[i].interval;
sg->gce[i].ipv = act->gate.entries[i].ipv;
sg->gce[i].maxoctets = act->gate.entries[i].maxoctets;
}
return 0;
}
static int sparx5_tc_flower_parse_act_police(struct sparx5_policer *pol,
struct flow_action_entry *act,
struct netlink_ext_ack *extack)
{
pol->type = SPX5_POL_SERVICE;
pol->rate = div_u64(act->police.rate_bytes_ps, 1000) * 8;
pol->burst = act->police.burst;
pol->idx = act->hw_index;
/* rate is now in kbit */
if (pol->rate > DIV_ROUND_UP(SPX5_SDLB_GROUP_RATE_MAX, 1000)) {
NL_SET_ERR_MSG_MOD(extack, "Maximum rate exceeded");
return -EINVAL;
}
if (act->police.exceed.act_id != FLOW_ACTION_DROP) {
NL_SET_ERR_MSG_MOD(extack, "Offload not supported when exceed action is not drop");
return -EOPNOTSUPP;
}
if (act->police.notexceed.act_id != FLOW_ACTION_PIPE &&
act->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
NL_SET_ERR_MSG_MOD(extack, "Offload not supported when conform action is not pipe or ok");
return -EOPNOTSUPP;
}
return 0;
}
static int sparx5_tc_flower_psfp_setup(struct sparx5 *sparx5,
struct vcap_rule *vrule, int sg_idx,
int pol_idx, struct sparx5_psfp_sg *sg,
struct sparx5_psfp_fm *fm,
struct sparx5_psfp_sf *sf)
{
u32 psfp_sfid = 0, psfp_fmid = 0, psfp_sgid = 0;
int ret;
/* Must always have a stream gate - max sdu (filter option) is evaluated
* after frames have passed the gate, so in case of only a policer, we
* allocate a stream gate that is always open.
*/
if (sg_idx < 0) {
sg_idx = sparx5_pool_idx_to_id(SPX5_PSFP_SG_OPEN);
sg->ipv = 0; /* Disabled */
sg->cycletime = SPX5_PSFP_SG_CYCLE_TIME_DEFAULT;
sg->num_entries = 1;
sg->gate_state = 1; /* Open */
sg->gate_enabled = 1;
sg->gce[0].gate_state = 1;
sg->gce[0].interval = SPX5_PSFP_SG_CYCLE_TIME_DEFAULT;
sg->gce[0].ipv = 0;
sg->gce[0].maxoctets = 0; /* Disabled */
}
ret = sparx5_psfp_sg_add(sparx5, sg_idx, sg, &psfp_sgid);
if (ret < 0)
return ret;
if (pol_idx >= 0) {
/* Add new flow-meter */
ret = sparx5_psfp_fm_add(sparx5, pol_idx, fm, &psfp_fmid);
if (ret < 0)
return ret;
}
/* Map stream filter to stream gate */
sf->sgid = psfp_sgid;
/* Add new stream-filter and map it to a steam gate */
ret = sparx5_psfp_sf_add(sparx5, sf, &psfp_sfid);
if (ret < 0)
return ret;
/* Streams are classified by ISDX - map ISDX 1:1 to sfid for now. */
sparx5_isdx_conf_set(sparx5, psfp_sfid, psfp_sfid, psfp_fmid);
ret = vcap_rule_add_action_bit(vrule, VCAP_AF_ISDX_ADD_REPLACE_SEL,
VCAP_BIT_1);
if (ret)
return ret;
ret = vcap_rule_add_action_u32(vrule, VCAP_AF_ISDX_VAL, psfp_sfid);
if (ret)
return ret;
return 0;
}
/* Handle the action trap for a VCAP rule */
static int sparx5_tc_action_trap(struct vcap_admin *admin,
struct vcap_rule *vrule,
struct flow_cls_offload *fco)
{
int err = 0;
switch (admin->vtype) {
case VCAP_TYPE_IS2:
err = vcap_rule_add_action_bit(vrule,
VCAP_AF_CPU_COPY_ENA,
VCAP_BIT_1);
if (err)
break;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_CPU_QUEUE_NUM, 0);
if (err)
break;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_MASK_MODE,
SPX5_PMM_REPLACE_ALL);
break;
case VCAP_TYPE_ES0:
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_FWD_SEL,
SPX5_FWSEL_REDIRECT_TO_LOOPBACK);
break;
case VCAP_TYPE_ES2:
err = vcap_rule_add_action_bit(vrule,
VCAP_AF_CPU_COPY_ENA,
VCAP_BIT_1);
if (err)
break;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_CPU_QUEUE_NUM, 0);
break;
default:
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Trap action not supported in this VCAP");
err = -EOPNOTSUPP;
break;
}
return err;
}
static int sparx5_tc_action_vlan_pop(struct vcap_admin *admin,
struct vcap_rule *vrule,
struct flow_cls_offload *fco,
u16 tpid)
{
int err = 0;
switch (admin->vtype) {
case VCAP_TYPE_ES0:
break;
default:
NL_SET_ERR_MSG_MOD(fco->common.extack,
"VLAN pop action not supported in this VCAP");
return -EOPNOTSUPP;
}
switch (tpid) {
case ETH_P_8021Q:
case ETH_P_8021AD:
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_PUSH_OUTER_TAG,
SPX5_OTAG_UNTAG);
break;
default:
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Invalid vlan proto");
err = -EINVAL;
}
return err;
}
static int sparx5_tc_action_vlan_modify(struct vcap_admin *admin,
struct vcap_rule *vrule,
struct flow_cls_offload *fco,
struct flow_action_entry *act,
u16 tpid)
{
int err = 0;
switch (admin->vtype) {
case VCAP_TYPE_ES0:
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_PUSH_OUTER_TAG,
SPX5_OTAG_TAG_A);
if (err)
return err;
break;
default:
NL_SET_ERR_MSG_MOD(fco->common.extack,
"VLAN modify action not supported in this VCAP");
return -EOPNOTSUPP;
}
switch (tpid) {
case ETH_P_8021Q:
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_TAG_A_TPID_SEL,
SPX5_TPID_A_8100);
break;
case ETH_P_8021AD:
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_TAG_A_TPID_SEL,
SPX5_TPID_A_88A8);
break;
default:
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Invalid vlan proto");
err = -EINVAL;
}
if (err)
return err;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_TAG_A_VID_SEL,
SPX5_VID_A_VAL);
if (err)
return err;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_VID_A_VAL,
act->vlan.vid);
if (err)
return err;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_TAG_A_PCP_SEL,
SPX5_PCP_A_VAL);
if (err)
return err;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_PCP_A_VAL,
act->vlan.prio);
if (err)
return err;
return vcap_rule_add_action_u32(vrule,
VCAP_AF_TAG_A_DEI_SEL,
SPX5_DEI_A_CLASSIFIED);
}
static int sparx5_tc_action_vlan_push(struct vcap_admin *admin,
struct vcap_rule *vrule,
struct flow_cls_offload *fco,
struct flow_action_entry *act,
u16 tpid)
{
u16 act_tpid = be16_to_cpu(act->vlan.proto);
int err = 0;
switch (admin->vtype) {
case VCAP_TYPE_ES0:
break;
default:
NL_SET_ERR_MSG_MOD(fco->common.extack,
"VLAN push action not supported in this VCAP");
return -EOPNOTSUPP;
}
if (tpid == ETH_P_8021AD) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Cannot push on double tagged frames");
return -EOPNOTSUPP;
}
err = sparx5_tc_action_vlan_modify(admin, vrule, fco, act, act_tpid);
if (err)
return err;
switch (act_tpid) {
case ETH_P_8021Q:
break;
case ETH_P_8021AD:
/* Push classified tag as inner tag */
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_PUSH_INNER_TAG,
SPX5_ITAG_PUSH_B_TAG);
if (err)
break;
err = vcap_rule_add_action_u32(vrule,
VCAP_AF_TAG_B_TPID_SEL,
SPX5_TPID_B_CLASSIFIED);
break;
default:
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Invalid vlan proto");
err = -EINVAL;
}
return err;
}
/* Remove rule keys that may prevent templates from matching a keyset */
static void sparx5_tc_flower_simplify_rule(struct vcap_admin *admin,
struct vcap_rule *vrule,
u16 l3_proto)
{
switch (admin->vtype) {
case VCAP_TYPE_IS0:
vcap_rule_rem_key(vrule, VCAP_KF_ETYPE);
switch (l3_proto) {
case ETH_P_IP:
break;
case ETH_P_IPV6:
vcap_rule_rem_key(vrule, VCAP_KF_IP_SNAP_IS);
break;
default:
break;
}
break;
case VCAP_TYPE_ES2:
switch (l3_proto) {
case ETH_P_IP:
if (vrule->keyset == VCAP_KFS_IP4_OTHER)
vcap_rule_rem_key(vrule, VCAP_KF_TCP_IS);
break;
case ETH_P_IPV6:
if (vrule->keyset == VCAP_KFS_IP6_STD)
vcap_rule_rem_key(vrule, VCAP_KF_TCP_IS);
vcap_rule_rem_key(vrule, VCAP_KF_IP4_IS);
break;
default:
break;
}
break;
case VCAP_TYPE_IS2:
switch (l3_proto) {
case ETH_P_IP:
case ETH_P_IPV6:
vcap_rule_rem_key(vrule, VCAP_KF_IP4_IS);
break;
default:
break;
}
break;
default:
break;
}
}
static bool sparx5_tc_flower_use_template(struct net_device *ndev,
struct flow_cls_offload *fco,
struct vcap_admin *admin,
struct vcap_rule *vrule)
{
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5_tc_flower_template *ftp;
list_for_each_entry(ftp, &port->tc_templates, list) {
if (ftp->cid != fco->common.chain_index)
continue;
vcap_set_rule_set_keyset(vrule, ftp->keyset);
sparx5_tc_flower_simplify_rule(admin, vrule, ftp->l3_proto);
return true;
}
return false;
}
static int sparx5_tc_flower_replace(struct net_device *ndev,
struct flow_cls_offload *fco,
struct vcap_admin *admin,
bool ingress)
{
struct sparx5_psfp_sf sf = { .max_sdu = SPX5_PSFP_SF_MAX_SDU };
struct netlink_ext_ack *extack = fco->common.extack;
int err, idx, tc_sg_idx = -1, tc_pol_idx = -1;
struct vcap_tc_flower_parse_usage state = {
.fco = fco,
.l3_proto = ETH_P_ALL,
.admin = admin,
};
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5_multiple_rules multi = {};
struct sparx5 *sparx5 = port->sparx5;
struct sparx5_psfp_sg sg = { 0 };
struct sparx5_psfp_fm fm = { 0 };
struct flow_action_entry *act;
struct vcap_control *vctrl;
struct flow_rule *frule;
struct vcap_rule *vrule;
vctrl = port->sparx5->vcap_ctrl;
err = sparx5_tc_flower_action_check(vctrl, ndev, fco, ingress);
if (err)
return err;
vrule = vcap_alloc_rule(vctrl, ndev, fco->common.chain_index, VCAP_USER_TC,
fco->common.prio, 0);
if (IS_ERR(vrule))
return PTR_ERR(vrule);
vrule->cookie = fco->cookie;
state.vrule = vrule;
state.frule = flow_cls_offload_flow_rule(fco);
err = sparx5_tc_use_dissectors(&state, admin, vrule);
if (err)
goto out;
err = sparx5_tc_add_rule_counter(admin, vrule);
if (err)
goto out;
err = sparx5_tc_add_rule_link_target(admin, vrule,
fco->common.chain_index);
if (err)
goto out;
frule = flow_cls_offload_flow_rule(fco);
flow_action_for_each(idx, act, &frule->action) {
switch (act->id) {
case FLOW_ACTION_GATE: {
err = sparx5_tc_flower_parse_act_gate(&sg, act, extack);
if (err < 0)
goto out;
tc_sg_idx = act->hw_index;
break;
}
case FLOW_ACTION_POLICE: {
err = sparx5_tc_flower_parse_act_police(&fm.pol, act,
extack);
if (err < 0)
goto out;
tc_pol_idx = fm.pol.idx;
sf.max_sdu = act->police.mtu;
break;
}
case FLOW_ACTION_TRAP:
err = sparx5_tc_action_trap(admin, vrule, fco);
if (err)
goto out;
break;
case FLOW_ACTION_ACCEPT:
err = sparx5_tc_set_actionset(admin, vrule);
if (err)
goto out;
break;
case FLOW_ACTION_GOTO:
err = sparx5_tc_set_actionset(admin, vrule);
if (err)
goto out;
sparx5_tc_add_rule_link(vctrl, admin, vrule,
fco->common.chain_index,
act->chain_index);
break;
case FLOW_ACTION_VLAN_POP:
err = sparx5_tc_action_vlan_pop(admin, vrule, fco,
state.tpid);
if (err)
goto out;
break;
case FLOW_ACTION_VLAN_PUSH:
err = sparx5_tc_action_vlan_push(admin, vrule, fco,
act, state.tpid);
if (err)
goto out;
break;
case FLOW_ACTION_VLAN_MANGLE:
err = sparx5_tc_action_vlan_modify(admin, vrule, fco,
act, state.tpid);
if (err)
goto out;
break;
default:
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Unsupported TC action");
err = -EOPNOTSUPP;
goto out;
}
}
/* Setup PSFP */
if (tc_sg_idx >= 0 || tc_pol_idx >= 0) {
err = sparx5_tc_flower_psfp_setup(sparx5, vrule, tc_sg_idx,
tc_pol_idx, &sg, &fm, &sf);
if (err)
goto out;
}
if (!sparx5_tc_flower_use_template(ndev, fco, admin, vrule)) {
err = sparx5_tc_select_protocol_keyset(ndev, vrule, admin,
state.l3_proto, &multi);
if (err) {
NL_SET_ERR_MSG_MOD(fco->common.extack,
"No matching port keyset for filter protocol and keys");
goto out;
}
}
/* provide the l3 protocol to guide the keyset selection */
err = vcap_val_rule(vrule, state.l3_proto);
if (err) {
vcap_set_tc_exterr(fco, vrule);
goto out;
}
err = vcap_add_rule(vrule);
if (err)
NL_SET_ERR_MSG_MOD(fco->common.extack,
"Could not add the filter");
if (state.l3_proto == ETH_P_ALL)
err = sparx5_tc_add_remaining_rules(vctrl, fco, vrule, admin,
&multi);
out:
vcap_free_rule(vrule);
return err;
}
static void sparx5_tc_free_psfp_resources(struct sparx5 *sparx5,
struct vcap_rule *vrule)
{
struct vcap_client_actionfield *afield;
u32 isdx, sfid, sgid, fmid;
/* Check if VCAP_AF_ISDX_VAL action is set for this rule - and if
* it is used for stream and/or flow-meter classification.
*/
afield = vcap_find_actionfield(vrule, VCAP_AF_ISDX_VAL);
if (!afield)
return;
isdx = afield->data.u32.value;
sfid = sparx5_psfp_isdx_get_sf(sparx5, isdx);
if (!sfid)
return;
fmid = sparx5_psfp_isdx_get_fm(sparx5, isdx);
sgid = sparx5_psfp_sf_get_sg(sparx5, sfid);
if (fmid && sparx5_psfp_fm_del(sparx5, fmid) < 0)
pr_err("%s:%d Could not delete invalid fmid: %d", __func__,
__LINE__, fmid);
if (sgid && sparx5_psfp_sg_del(sparx5, sgid) < 0)
pr_err("%s:%d Could not delete invalid sgid: %d", __func__,
__LINE__, sgid);
if (sparx5_psfp_sf_del(sparx5, sfid) < 0)
pr_err("%s:%d Could not delete invalid sfid: %d", __func__,
__LINE__, sfid);
sparx5_isdx_conf_set(sparx5, isdx, 0, 0);
}
static int sparx5_tc_free_rule_resources(struct net_device *ndev,
struct vcap_control *vctrl,
int rule_id)
{
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5 *sparx5 = port->sparx5;
struct vcap_rule *vrule;
int ret = 0;
vrule = vcap_get_rule(vctrl, rule_id);
if (IS_ERR(vrule))
return -EINVAL;
sparx5_tc_free_psfp_resources(sparx5, vrule);
vcap_free_rule(vrule);
return ret;
}
static int sparx5_tc_flower_destroy(struct net_device *ndev,
struct flow_cls_offload *fco,
struct vcap_admin *admin)
{
struct sparx5_port *port = netdev_priv(ndev);
int err = -ENOENT, count = 0, rule_id;
struct vcap_control *vctrl;
vctrl = port->sparx5->vcap_ctrl;
while (true) {
rule_id = vcap_lookup_rule_by_cookie(vctrl, fco->cookie);
if (rule_id <= 0)
break;
if (count == 0) {
/* Resources are attached to the first rule of
* a set of rules. Only works if the rules are
* in the correct order.
*/
err = sparx5_tc_free_rule_resources(ndev, vctrl,
rule_id);
if (err)
pr_err("%s:%d: could not free resources %d\n",
__func__, __LINE__, rule_id);
}
err = vcap_del_rule(vctrl, ndev, rule_id);
if (err) {
pr_err("%s:%d: could not delete rule %d\n",
__func__, __LINE__, rule_id);
break;
}
}
return err;
}
static int sparx5_tc_flower_stats(struct net_device *ndev,
struct flow_cls_offload *fco,
struct vcap_admin *admin)
{
struct sparx5_port *port = netdev_priv(ndev);
struct vcap_counter ctr = {};
struct vcap_control *vctrl;
ulong lastused = 0;
int err;
vctrl = port->sparx5->vcap_ctrl;
err = vcap_get_rule_count_by_cookie(vctrl, &ctr, fco->cookie);
if (err)
return err;
flow_stats_update(&fco->stats, 0x0, ctr.value, 0, lastused,
FLOW_ACTION_HW_STATS_IMMEDIATE);
return err;
}
static int sparx5_tc_flower_template_create(struct net_device *ndev,
struct flow_cls_offload *fco,
struct vcap_admin *admin)
{
struct sparx5_port *port = netdev_priv(ndev);
struct vcap_tc_flower_parse_usage state = {
.fco = fco,
.l3_proto = ETH_P_ALL,
.admin = admin,
};
struct sparx5_tc_flower_template *ftp;
struct vcap_keyset_list kslist = {};
enum vcap_keyfield_set keysets[10];
struct vcap_control *vctrl;
struct vcap_rule *vrule;
int count, err;
if (admin->vtype == VCAP_TYPE_ES0) {
pr_err("%s:%d: %s\n", __func__, __LINE__,
"VCAP does not support templates");
return -EINVAL;
}
count = vcap_admin_rule_count(admin, fco->common.chain_index);
if (count > 0) {
pr_err("%s:%d: %s\n", __func__, __LINE__,
"Filters are already present");
return -EBUSY;
}
ftp = kzalloc(sizeof(*ftp), GFP_KERNEL);
if (!ftp)
return -ENOMEM;
ftp->cid = fco->common.chain_index;
ftp->orig = VCAP_KFS_NO_VALUE;
ftp->keyset = VCAP_KFS_NO_VALUE;
vctrl = port->sparx5->vcap_ctrl;
vrule = vcap_alloc_rule(vctrl, ndev, fco->common.chain_index,
VCAP_USER_TC, fco->common.prio, 0);
if (IS_ERR(vrule)) {
err = PTR_ERR(vrule);
goto err_rule;
}
state.vrule = vrule;
state.frule = flow_cls_offload_flow_rule(fco);
err = sparx5_tc_use_dissectors(&state, admin, vrule);
if (err) {
pr_err("%s:%d: key error: %d\n", __func__, __LINE__, err);
goto out;
}
ftp->l3_proto = state.l3_proto;
sparx5_tc_flower_simplify_rule(admin, vrule, state.l3_proto);
/* Find the keysets that the rule can use */
kslist.keysets = keysets;
kslist.max = ARRAY_SIZE(keysets);
if (!vcap_rule_find_keysets(vrule, &kslist)) {
pr_err("%s:%d: %s\n", __func__, __LINE__,
"Could not find a suitable keyset");
err = -ENOENT;
goto out;
}
ftp->keyset = vcap_select_min_rule_keyset(vctrl, admin->vtype, &kslist);
kslist.cnt = 0;
sparx5_vcap_set_port_keyset(ndev, admin, fco->common.chain_index,
state.l3_proto,
ftp->keyset,
&kslist);
if (kslist.cnt > 0)
ftp->orig = kslist.keysets[0];
/* Store new template */
list_add_tail(&ftp->list, &port->tc_templates);
vcap_free_rule(vrule);
return 0;
out:
vcap_free_rule(vrule);
err_rule:
kfree(ftp);
return err;
}
static int sparx5_tc_flower_template_destroy(struct net_device *ndev,
struct flow_cls_offload *fco,
struct vcap_admin *admin)
{
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5_tc_flower_template *ftp, *tmp;
int err = -ENOENT;
/* Rules using the template are removed by the tc framework */
list_for_each_entry_safe(ftp, tmp, &port->tc_templates, list) {
if (ftp->cid != fco->common.chain_index)
continue;
sparx5_vcap_set_port_keyset(ndev, admin,
fco->common.chain_index,
ftp->l3_proto, ftp->orig,
NULL);
list_del(&ftp->list);
kfree(ftp);
break;
}
return err;
}
int sparx5_tc_flower(struct net_device *ndev, struct flow_cls_offload *fco,
bool ingress)
{
struct sparx5_port *port = netdev_priv(ndev);
struct vcap_control *vctrl;
struct vcap_admin *admin;
int err = -EINVAL;
/* Get vcap instance from the chain id */
vctrl = port->sparx5->vcap_ctrl;
admin = vcap_find_admin(vctrl, fco->common.chain_index);
if (!admin) {
NL_SET_ERR_MSG_MOD(fco->common.extack, "Invalid chain");
return err;
}
switch (fco->command) {
case FLOW_CLS_REPLACE:
return sparx5_tc_flower_replace(ndev, fco, admin, ingress);
case FLOW_CLS_DESTROY:
return sparx5_tc_flower_destroy(ndev, fco, admin);
case FLOW_CLS_STATS:
return sparx5_tc_flower_stats(ndev, fco, admin);
case FLOW_CLS_TMPLT_CREATE:
return sparx5_tc_flower_template_create(ndev, fco, admin);
case FLOW_CLS_TMPLT_DESTROY:
return sparx5_tc_flower_template_destroy(ndev, fco, admin);
default:
return -EOPNOTSUPP;
}
}