// 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 */
};
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) {
if (mt.mask->flags & FLOW_DIS_FIRST_FRAG) {
if (mt.key->flags & FLOW_DIS_FIRST_FRAG) {
value = 1; /* initial fragment */
mask = 0x3;
} else {
if (mt.mask->flags & FLOW_DIS_IS_FRAGMENT) {
value = 3; /* follow up fragment */
mask = 0x3;
} else {
value = 0; /* no fragment */
mask = 0x3;
}
}
} else {
if (mt.mask->flags & FLOW_DIS_IS_FRAGMENT) {
value = 3; /* follow up fragment */
mask = 0x3;
} else {
value = 0; /* no fragment */
mask = 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 -EI
|