diff options
Diffstat (limited to 'drivers/net/ethernet/mscc/ocelot_flower.c')
-rw-r--r-- | drivers/net/ethernet/mscc/ocelot_flower.c | 773 |
1 files changed, 773 insertions, 0 deletions
diff --git a/drivers/net/ethernet/mscc/ocelot_flower.c b/drivers/net/ethernet/mscc/ocelot_flower.c new file mode 100644 index 000000000..e56e540ce --- /dev/null +++ b/drivers/net/ethernet/mscc/ocelot_flower.c @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* Microsemi Ocelot Switch driver + * Copyright (c) 2019 Microsemi Corporation + */ + +#include <net/pkt_cls.h> +#include <net/tc_act/tc_gact.h> +#include <soc/mscc/ocelot_vcap.h> +#include "ocelot_vcap.h" + +/* Arbitrarily chosen constants for encoding the VCAP block and lookup number + * into the chain number. This is UAPI. + */ +#define VCAP_BLOCK 10000 +#define VCAP_LOOKUP 1000 +#define VCAP_IS1_NUM_LOOKUPS 3 +#define VCAP_IS2_NUM_LOOKUPS 2 +#define VCAP_IS2_NUM_PAG 256 +#define VCAP_IS1_CHAIN(lookup) \ + (1 * VCAP_BLOCK + (lookup) * VCAP_LOOKUP) +#define VCAP_IS2_CHAIN(lookup, pag) \ + (2 * VCAP_BLOCK + (lookup) * VCAP_LOOKUP + (pag)) + +static int ocelot_chain_to_block(int chain, bool ingress) +{ + int lookup, pag; + + if (!ingress) { + if (chain == 0) + return VCAP_ES0; + return -EOPNOTSUPP; + } + + /* Backwards compatibility with older, single-chain tc-flower + * offload support in Ocelot + */ + if (chain == 0) + return VCAP_IS2; + + for (lookup = 0; lookup < VCAP_IS1_NUM_LOOKUPS; lookup++) + if (chain == VCAP_IS1_CHAIN(lookup)) + return VCAP_IS1; + + for (lookup = 0; lookup < VCAP_IS2_NUM_LOOKUPS; lookup++) + for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) + if (chain == VCAP_IS2_CHAIN(lookup, pag)) + return VCAP_IS2; + + return -EOPNOTSUPP; +} + +/* Caller must ensure this is a valid IS1 or IS2 chain first, + * by calling ocelot_chain_to_block. + */ +static int ocelot_chain_to_lookup(int chain) +{ + /* Backwards compatibility with older, single-chain tc-flower + * offload support in Ocelot + */ + if (chain == 0) + return 0; + + return (chain / VCAP_LOOKUP) % 10; +} + +/* Caller must ensure this is a valid IS2 chain first, + * by calling ocelot_chain_to_block. + */ +static int ocelot_chain_to_pag(int chain) +{ + int lookup; + + /* Backwards compatibility with older, single-chain tc-flower + * offload support in Ocelot + */ + if (chain == 0) + return 0; + + lookup = ocelot_chain_to_lookup(chain); + + /* calculate PAG value as chain index relative to the first PAG */ + return chain - VCAP_IS2_CHAIN(lookup, 0); +} + +static bool ocelot_is_goto_target_valid(int goto_target, int chain, + bool ingress) +{ + int pag; + + /* Can't offload GOTO in VCAP ES0 */ + if (!ingress) + return (goto_target < 0); + + /* Non-optional GOTOs */ + if (chain == 0) + /* VCAP IS1 can be skipped, either partially or completely */ + return (goto_target == VCAP_IS1_CHAIN(0) || + goto_target == VCAP_IS1_CHAIN(1) || + goto_target == VCAP_IS1_CHAIN(2) || + goto_target == VCAP_IS2_CHAIN(0, 0) || + goto_target == VCAP_IS2_CHAIN(1, 0)); + + if (chain == VCAP_IS1_CHAIN(0)) + return (goto_target == VCAP_IS1_CHAIN(1)); + + if (chain == VCAP_IS1_CHAIN(1)) + return (goto_target == VCAP_IS1_CHAIN(2)); + + /* Lookup 2 of VCAP IS1 can really support non-optional GOTOs, + * using a Policy Association Group (PAG) value, which is an 8-bit + * value encoding a VCAP IS2 target chain. + */ + if (chain == VCAP_IS1_CHAIN(2)) { + for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) + if (goto_target == VCAP_IS2_CHAIN(0, pag)) + return true; + + return false; + } + + /* Non-optional GOTO from VCAP IS2 lookup 0 to lookup 1. + * We cannot change the PAG at this point. + */ + for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) + if (chain == VCAP_IS2_CHAIN(0, pag)) + return (goto_target == VCAP_IS2_CHAIN(1, pag)); + + /* VCAP IS2 lookup 1 cannot jump anywhere */ + return false; +} + +static struct ocelot_vcap_filter * +ocelot_find_vcap_filter_that_points_at(struct ocelot *ocelot, int chain) +{ + struct ocelot_vcap_filter *filter; + struct ocelot_vcap_block *block; + int block_id; + + block_id = ocelot_chain_to_block(chain, true); + if (block_id < 0) + return NULL; + + if (block_id == VCAP_IS2) { + block = &ocelot->block[VCAP_IS1]; + + list_for_each_entry(filter, &block->rules, list) + if (filter->type == OCELOT_VCAP_FILTER_PAG && + filter->goto_target == chain) + return filter; + } + + list_for_each_entry(filter, &ocelot->dummy_rules, list) + if (filter->goto_target == chain) + return filter; + + return NULL; +} + +static int ocelot_flower_parse_action(struct ocelot *ocelot, int port, + bool ingress, struct flow_cls_offload *f, + struct ocelot_vcap_filter *filter) +{ + struct ocelot_port *ocelot_port = ocelot->ports[port]; + struct netlink_ext_ack *extack = f->common.extack; + bool allow_missing_goto_target = false; + const struct flow_action_entry *a; + enum ocelot_tag_tpid_sel tpid; + int i, chain, egress_port; + u64 rate; + + if (!flow_action_basic_hw_stats_check(&f->rule->action, + f->common.extack)) + return -EOPNOTSUPP; + + chain = f->common.chain_index; + filter->block_id = ocelot_chain_to_block(chain, ingress); + if (filter->block_id < 0) { + NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain"); + return -EOPNOTSUPP; + } + if (filter->block_id == VCAP_IS1 || filter->block_id == VCAP_IS2) + filter->lookup = ocelot_chain_to_lookup(chain); + if (filter->block_id == VCAP_IS2) + filter->pag = ocelot_chain_to_pag(chain); + + filter->goto_target = -1; + filter->type = OCELOT_VCAP_FILTER_DUMMY; + + flow_action_for_each(i, a, &f->rule->action) { + switch (a->id) { + case FLOW_ACTION_DROP: + if (filter->block_id != VCAP_IS2) { + NL_SET_ERR_MSG_MOD(extack, + "Drop action can only be offloaded to VCAP IS2"); + return -EOPNOTSUPP; + } + if (filter->goto_target != -1) { + NL_SET_ERR_MSG_MOD(extack, + "Last action must be GOTO"); + return -EOPNOTSUPP; + } + filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY; + filter->action.port_mask = 0; + filter->action.police_ena = true; + filter->action.pol_ix = OCELOT_POLICER_DISCARD; + filter->type = OCELOT_VCAP_FILTER_OFFLOAD; + break; + case FLOW_ACTION_TRAP: + if (filter->block_id != VCAP_IS2 || + filter->lookup != 0) { + NL_SET_ERR_MSG_MOD(extack, + "Trap action can only be offloaded to VCAP IS2 lookup 0"); + return -EOPNOTSUPP; + } + if (filter->goto_target != -1) { + NL_SET_ERR_MSG_MOD(extack, + "Last action must be GOTO"); + return -EOPNOTSUPP; + } + filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY; + filter->action.port_mask = 0; + filter->action.cpu_copy_ena = true; + filter->action.cpu_qu_num = 0; + filter->type = OCELOT_VCAP_FILTER_OFFLOAD; + break; + case FLOW_ACTION_POLICE: + if (filter->block_id != VCAP_IS2 || + filter->lookup != 0) { + NL_SET_ERR_MSG_MOD(extack, + "Police action can only be offloaded to VCAP IS2 lookup 0"); + return -EOPNOTSUPP; + } + if (filter->goto_target != -1) { + NL_SET_ERR_MSG_MOD(extack, + "Last action must be GOTO"); + return -EOPNOTSUPP; + } + filter->action.police_ena = true; + rate = a->police.rate_bytes_ps; + filter->action.pol.rate = div_u64(rate, 1000) * 8; + filter->action.pol.burst = a->police.burst; + filter->type = OCELOT_VCAP_FILTER_OFFLOAD; + break; + case FLOW_ACTION_REDIRECT: + if (filter->block_id != VCAP_IS2) { + NL_SET_ERR_MSG_MOD(extack, + "Redirect action can only be offloaded to VCAP IS2"); + return -EOPNOTSUPP; + } + if (filter->goto_target != -1) { + NL_SET_ERR_MSG_MOD(extack, + "Last action must be GOTO"); + return -EOPNOTSUPP; + } + egress_port = ocelot->ops->netdev_to_port(a->dev); + if (egress_port < 0) { + NL_SET_ERR_MSG_MOD(extack, + "Destination not an ocelot port"); + return -EOPNOTSUPP; + } + filter->action.mask_mode = OCELOT_MASK_MODE_REDIRECT; + filter->action.port_mask = BIT(egress_port); + filter->type = OCELOT_VCAP_FILTER_OFFLOAD; + break; + case FLOW_ACTION_VLAN_POP: + if (filter->block_id != VCAP_IS1) { + NL_SET_ERR_MSG_MOD(extack, + "VLAN pop action can only be offloaded to VCAP IS1"); + return -EOPNOTSUPP; + } + if (filter->goto_target != -1) { + NL_SET_ERR_MSG_MOD(extack, + "Last action must be GOTO"); + return -EOPNOTSUPP; + } + filter->action.vlan_pop_cnt_ena = true; + filter->action.vlan_pop_cnt++; + if (filter->action.vlan_pop_cnt > 2) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot pop more than 2 VLAN headers"); + return -EOPNOTSUPP; + } + filter->type = OCELOT_VCAP_FILTER_OFFLOAD; + break; + case FLOW_ACTION_VLAN_MANGLE: + if (filter->block_id != VCAP_IS1) { + NL_SET_ERR_MSG_MOD(extack, + "VLAN modify action can only be offloaded to VCAP IS1"); + return -EOPNOTSUPP; + } + if (filter->goto_target != -1) { + NL_SET_ERR_MSG_MOD(extack, + "Last action must be GOTO"); + return -EOPNOTSUPP; + } + if (!ocelot_port->vlan_aware) { + NL_SET_ERR_MSG_MOD(extack, + "Can only modify VLAN under VLAN aware bridge"); + return -EOPNOTSUPP; + } + filter->action.vid_replace_ena = true; + filter->action.pcp_dei_ena = true; + filter->action.vid = a->vlan.vid; + filter->action.pcp = a->vlan.prio; + filter->type = OCELOT_VCAP_FILTER_OFFLOAD; + break; + case FLOW_ACTION_PRIORITY: + if (filter->block_id != VCAP_IS1) { + NL_SET_ERR_MSG_MOD(extack, + "Priority action can only be offloaded to VCAP IS1"); + return -EOPNOTSUPP; + } + if (filter->goto_target != -1) { + NL_SET_ERR_MSG_MOD(extack, + "Last action must be GOTO"); + return -EOPNOTSUPP; + } + filter->action.qos_ena = true; + filter->action.qos_val = a->priority; + filter->type = OCELOT_VCAP_FILTER_OFFLOAD; + break; + case FLOW_ACTION_GOTO: + filter->goto_target = a->chain_index; + + if (filter->block_id == VCAP_IS1 && filter->lookup == 2) { + int pag = ocelot_chain_to_pag(filter->goto_target); + + filter->action.pag_override_mask = 0xff; + filter->action.pag_val = pag; + filter->type = OCELOT_VCAP_FILTER_PAG; + } + break; + case FLOW_ACTION_VLAN_PUSH: + if (filter->block_id != VCAP_ES0) { + NL_SET_ERR_MSG_MOD(extack, + "VLAN push action can only be offloaded to VCAP ES0"); + return -EOPNOTSUPP; + } + switch (ntohs(a->vlan.proto)) { + case ETH_P_8021Q: + tpid = OCELOT_TAG_TPID_SEL_8021Q; + break; + case ETH_P_8021AD: + tpid = OCELOT_TAG_TPID_SEL_8021AD; + break; + default: + NL_SET_ERR_MSG_MOD(extack, + "Cannot push custom TPID"); + return -EOPNOTSUPP; + } + filter->action.tag_a_tpid_sel = tpid; + filter->action.push_outer_tag = OCELOT_ES0_TAG; + filter->action.tag_a_vid_sel = 1; + filter->action.vid_a_val = a->vlan.vid; + filter->action.pcp_a_val = a->vlan.prio; + filter->type = OCELOT_VCAP_FILTER_OFFLOAD; + break; + default: + NL_SET_ERR_MSG_MOD(extack, "Cannot offload action"); + return -EOPNOTSUPP; + } + } + + if (filter->goto_target == -1) { + if ((filter->block_id == VCAP_IS2 && filter->lookup == 1) || + chain == 0) { + allow_missing_goto_target = true; + } else { + NL_SET_ERR_MSG_MOD(extack, "Missing GOTO action"); + return -EOPNOTSUPP; + } + } + + if (!ocelot_is_goto_target_valid(filter->goto_target, chain, ingress) && + !allow_missing_goto_target) { + NL_SET_ERR_MSG_MOD(extack, "Cannot offload this GOTO target"); + return -EOPNOTSUPP; + } + + return 0; +} + +static int ocelot_flower_parse_indev(struct ocelot *ocelot, int port, + struct flow_cls_offload *f, + struct ocelot_vcap_filter *filter) +{ + struct flow_rule *rule = flow_cls_offload_flow_rule(f); + const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0]; + int key_length = vcap->keys[VCAP_ES0_IGR_PORT].length; + struct netlink_ext_ack *extack = f->common.extack; + struct net_device *dev, *indev; + struct flow_match_meta match; + int ingress_port; + + flow_rule_match_meta(rule, &match); + + if (!match.mask->ingress_ifindex) + return 0; + + if (match.mask->ingress_ifindex != 0xFFFFFFFF) { + NL_SET_ERR_MSG_MOD(extack, "Unsupported ingress ifindex mask"); + return -EOPNOTSUPP; + } + + dev = ocelot->ops->port_to_netdev(ocelot, port); + if (!dev) + return -EINVAL; + + indev = __dev_get_by_index(dev_net(dev), match.key->ingress_ifindex); + if (!indev) { + NL_SET_ERR_MSG_MOD(extack, + "Can't find the ingress port to match on"); + return -ENOENT; + } + + ingress_port = ocelot->ops->netdev_to_port(indev); + if (ingress_port < 0) { + NL_SET_ERR_MSG_MOD(extack, + "Can only offload an ocelot ingress port"); + return -EOPNOTSUPP; + } + if (ingress_port == port) { + NL_SET_ERR_MSG_MOD(extack, + "Ingress port is equal to the egress port"); + return -EINVAL; + } + + filter->ingress_port.value = ingress_port; + filter->ingress_port.mask = GENMASK(key_length - 1, 0); + + return 0; +} + +static int +ocelot_flower_parse_key(struct ocelot *ocelot, int port, bool ingress, + struct flow_cls_offload *f, + struct ocelot_vcap_filter *filter) +{ + struct flow_rule *rule = flow_cls_offload_flow_rule(f); + struct flow_dissector *dissector = rule->match.dissector; + struct netlink_ext_ack *extack = f->common.extack; + u16 proto = ntohs(f->common.protocol); + bool match_protocol = true; + int ret; + + if (dissector->used_keys & + ~(BIT(FLOW_DISSECTOR_KEY_CONTROL) | + BIT(FLOW_DISSECTOR_KEY_BASIC) | + BIT(FLOW_DISSECTOR_KEY_META) | + BIT(FLOW_DISSECTOR_KEY_PORTS) | + BIT(FLOW_DISSECTOR_KEY_VLAN) | + BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS) | + BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | + BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS))) { + return -EOPNOTSUPP; + } + + /* For VCAP ES0 (egress rewriter) we can match on the ingress port */ + if (!ingress) { + ret = ocelot_flower_parse_indev(ocelot, port, f, filter); + if (ret) + return ret; + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) { + struct flow_match_control match; + + flow_rule_match_control(rule, &match); + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) { + struct flow_match_vlan match; + + flow_rule_match_vlan(rule, &match); + filter->key_type = OCELOT_VCAP_KEY_ANY; + filter->vlan.vid.value = match.key->vlan_id; + filter->vlan.vid.mask = match.mask->vlan_id; + filter->vlan.pcp.value[0] = match.key->vlan_priority; + filter->vlan.pcp.mask[0] = match.mask->vlan_priority; + match_protocol = false; + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { + struct flow_match_eth_addrs match; + + if (filter->block_id == VCAP_ES0) { + NL_SET_ERR_MSG_MOD(extack, + "VCAP ES0 cannot match on MAC address"); + return -EOPNOTSUPP; + } + + /* The hw support mac matches only for MAC_ETYPE key, + * therefore if other matches(port, tcp flags, etc) are added + * then just bail out + */ + if ((dissector->used_keys & + (BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) | + BIT(FLOW_DISSECTOR_KEY_BASIC) | + BIT(FLOW_DISSECTOR_KEY_CONTROL))) != + (BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) | + BIT(FLOW_DISSECTOR_KEY_BASIC) | + BIT(FLOW_DISSECTOR_KEY_CONTROL))) + return -EOPNOTSUPP; + + flow_rule_match_eth_addrs(rule, &match); + + if (filter->block_id == VCAP_IS1 && + !is_zero_ether_addr(match.mask->dst)) { + NL_SET_ERR_MSG_MOD(extack, + "Key type S1_NORMAL cannot match on destination MAC"); + return -EOPNOTSUPP; + } + + filter->key_type = OCELOT_VCAP_KEY_ETYPE; + ether_addr_copy(filter->key.etype.dmac.value, + match.key->dst); + ether_addr_copy(filter->key.etype.smac.value, + match.key->src); + ether_addr_copy(filter->key.etype.dmac.mask, + match.mask->dst); + ether_addr_copy(filter->key.etype.smac.mask, + match.mask->src); + goto finished_key_parsing; + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) { + struct flow_match_basic match; + + flow_rule_match_basic(rule, &match); + if (ntohs(match.key->n_proto) == ETH_P_IP) { + if (filter->block_id == VCAP_ES0) { + NL_SET_ERR_MSG_MOD(extack, + "VCAP ES0 cannot match on IP protocol"); + return -EOPNOTSUPP; + } + + filter->key_type = OCELOT_VCAP_KEY_IPV4; + filter->key.ipv4.proto.value[0] = + match.key->ip_proto; + filter->key.ipv4.proto.mask[0] = + match.mask->ip_proto; + match_protocol = false; + } + if (ntohs(match.key->n_proto) == ETH_P_IPV6) { + if (filter->block_id == VCAP_ES0) { + NL_SET_ERR_MSG_MOD(extack, + "VCAP ES0 cannot match on IP protocol"); + return -EOPNOTSUPP; + } + + filter->key_type = OCELOT_VCAP_KEY_IPV6; + filter->key.ipv6.proto.value[0] = + match.key->ip_proto; + filter->key.ipv6.proto.mask[0] = + match.mask->ip_proto; + match_protocol = false; + } + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS) && + proto == ETH_P_IP) { + struct flow_match_ipv4_addrs match; + u8 *tmp; + + if (filter->block_id == VCAP_ES0) { + NL_SET_ERR_MSG_MOD(extack, + "VCAP ES0 cannot match on IP address"); + return -EOPNOTSUPP; + } + + flow_rule_match_ipv4_addrs(rule, &match); + + if (filter->block_id == VCAP_IS1 && *(u32 *)&match.mask->dst) { + NL_SET_ERR_MSG_MOD(extack, + "Key type S1_NORMAL cannot match on destination IP"); + return -EOPNOTSUPP; + } + + tmp = &filter->key.ipv4.sip.value.addr[0]; + memcpy(tmp, &match.key->src, 4); + + tmp = &filter->key.ipv4.sip.mask.addr[0]; + memcpy(tmp, &match.mask->src, 4); + + tmp = &filter->key.ipv4.dip.value.addr[0]; + memcpy(tmp, &match.key->dst, 4); + + tmp = &filter->key.ipv4.dip.mask.addr[0]; + memcpy(tmp, &match.mask->dst, 4); + match_protocol = false; + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS) && + proto == ETH_P_IPV6) { + return -EOPNOTSUPP; + } + + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) { + struct flow_match_ports match; + + if (filter->block_id == VCAP_ES0) { + NL_SET_ERR_MSG_MOD(extack, + "VCAP ES0 cannot match on L4 ports"); + return -EOPNOTSUPP; + } + + flow_rule_match_ports(rule, &match); + filter->key.ipv4.sport.value = ntohs(match.key->src); + filter->key.ipv4.sport.mask = ntohs(match.mask->src); + filter->key.ipv4.dport.value = ntohs(match.key->dst); + filter->key.ipv4.dport.mask = ntohs(match.mask->dst); + match_protocol = false; + } + +finished_key_parsing: + if (match_protocol && proto != ETH_P_ALL) { + if (filter->block_id == VCAP_ES0) { + NL_SET_ERR_MSG_MOD(extack, + "VCAP ES0 cannot match on L2 proto"); + return -EOPNOTSUPP; + } + + /* TODO: support SNAP, LLC etc */ + if (proto < ETH_P_802_3_MIN) + return -EOPNOTSUPP; + filter->key_type = OCELOT_VCAP_KEY_ETYPE; + *(__be16 *)filter->key.etype.etype.value = htons(proto); + *(__be16 *)filter->key.etype.etype.mask = htons(0xffff); + } + /* else, a filter of type OCELOT_VCAP_KEY_ANY is implicitly added */ + + return 0; +} + +static int ocelot_flower_parse(struct ocelot *ocelot, int port, bool ingress, + struct flow_cls_offload *f, + struct ocelot_vcap_filter *filter) +{ + int ret; + + filter->prio = f->common.prio; + filter->id = f->cookie; + + ret = ocelot_flower_parse_action(ocelot, port, ingress, f, filter); + if (ret) + return ret; + + return ocelot_flower_parse_key(ocelot, port, ingress, f, filter); +} + +static struct ocelot_vcap_filter +*ocelot_vcap_filter_create(struct ocelot *ocelot, int port, bool ingress, + struct flow_cls_offload *f) +{ + struct ocelot_vcap_filter *filter; + + filter = kzalloc(sizeof(*filter), GFP_KERNEL); + if (!filter) + return NULL; + + if (ingress) { + filter->ingress_port_mask = BIT(port); + } else { + const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0]; + int key_length = vcap->keys[VCAP_ES0_EGR_PORT].length; + + filter->egress_port.value = port; + filter->egress_port.mask = GENMASK(key_length - 1, 0); + } + + return filter; +} + +static int ocelot_vcap_dummy_filter_add(struct ocelot *ocelot, + struct ocelot_vcap_filter *filter) +{ + list_add(&filter->list, &ocelot->dummy_rules); + + return 0; +} + +static int ocelot_vcap_dummy_filter_del(struct ocelot *ocelot, + struct ocelot_vcap_filter *filter) +{ + list_del(&filter->list); + kfree(filter); + + return 0; +} + +int ocelot_cls_flower_replace(struct ocelot *ocelot, int port, + struct flow_cls_offload *f, bool ingress) +{ + struct netlink_ext_ack *extack = f->common.extack; + struct ocelot_vcap_filter *filter; + int chain = f->common.chain_index; + int ret; + + if (chain && !ocelot_find_vcap_filter_that_points_at(ocelot, chain)) { + NL_SET_ERR_MSG_MOD(extack, "No default GOTO action points to this chain"); + return -EOPNOTSUPP; + } + + filter = ocelot_vcap_filter_create(ocelot, port, ingress, f); + if (!filter) + return -ENOMEM; + + ret = ocelot_flower_parse(ocelot, port, ingress, f, filter); + if (ret) { + kfree(filter); + return ret; + } + + /* The non-optional GOTOs for the TCAM skeleton don't need + * to be actually offloaded. + */ + if (filter->type == OCELOT_VCAP_FILTER_DUMMY) + return ocelot_vcap_dummy_filter_add(ocelot, filter); + + return ocelot_vcap_filter_add(ocelot, filter, f->common.extack); +} +EXPORT_SYMBOL_GPL(ocelot_cls_flower_replace); + +int ocelot_cls_flower_destroy(struct ocelot *ocelot, int port, + struct flow_cls_offload *f, bool ingress) +{ + struct ocelot_vcap_filter *filter; + struct ocelot_vcap_block *block; + int block_id; + + block_id = ocelot_chain_to_block(f->common.chain_index, ingress); + if (block_id < 0) + return 0; + + block = &ocelot->block[block_id]; + + filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie); + if (!filter) + return 0; + + if (filter->type == OCELOT_VCAP_FILTER_DUMMY) + return ocelot_vcap_dummy_filter_del(ocelot, filter); + + return ocelot_vcap_filter_del(ocelot, filter); +} +EXPORT_SYMBOL_GPL(ocelot_cls_flower_destroy); + +int ocelot_cls_flower_stats(struct ocelot *ocelot, int port, + struct flow_cls_offload *f, bool ingress) +{ + struct ocelot_vcap_filter *filter; + struct ocelot_vcap_block *block; + int block_id, ret; + + block_id = ocelot_chain_to_block(f->common.chain_index, ingress); + if (block_id < 0) + return 0; + + block = &ocelot->block[block_id]; + + filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie); + if (!filter || filter->type == OCELOT_VCAP_FILTER_DUMMY) + return 0; + + ret = ocelot_vcap_filter_stats_update(ocelot, filter); + if (ret) + return ret; + + flow_stats_update(&f->stats, 0x0, filter->stats.pkts, 0, 0x0, + FLOW_ACTION_HW_STATS_IMMEDIATE); + return 0; +} +EXPORT_SYMBOL_GPL(ocelot_cls_flower_stats); |