123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005 |
- // 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_police.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))
- /* PSFP chain and block ID */
- #define PSFP_BLOCK_ID OCELOT_NUM_VCAP_BLOCKS
- #define OCELOT_PSFP_CHAIN (3 * VCAP_BLOCK)
- 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;
- if (chain == OCELOT_PSFP_CHAIN)
- return PSFP_BLOCK_ID;
- 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) ||
- goto_target == OCELOT_PSFP_CHAIN);
- 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 can goto to PSFP block if hardware support */
- for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++)
- if (chain == VCAP_IS2_CHAIN(1, pag))
- return (goto_target == OCELOT_PSFP_CHAIN);
- 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_ingress_vlan_modify(struct ocelot *ocelot, int port,
- struct ocelot_vcap_filter *filter,
- const struct flow_action_entry *a,
- struct netlink_ext_ack *extack)
- {
- struct ocelot_port *ocelot_port = ocelot->ports[port];
- 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;
- return 0;
- }
- static int
- ocelot_flower_parse_egress_vlan_modify(struct ocelot_vcap_filter *filter,
- const struct flow_action_entry *a,
- struct netlink_ext_ack *extack)
- {
- enum ocelot_tag_tpid_sel tpid;
- 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 modify 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 = OCELOT_ES0_VID_PLUS_CLASSIFIED_VID;
- filter->action.vid_a_val = a->vlan.vid;
- filter->action.pcp_a_val = a->vlan.prio;
- filter->action.tag_a_pcp_sel = OCELOT_ES0_PCP;
- filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
- return 0;
- }
- static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
- bool ingress, struct flow_cls_offload *f,
- struct ocelot_vcap_filter *filter)
- {
- const struct flow_action *action = &f->rule->action;
- 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;
- u32 pol_ix, pol_max;
- u64 rate;
- int err;
- 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, 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_ACCEPT:
- if (filter->block_id != VCAP_ES0 &&
- filter->block_id != VCAP_IS1 &&
- filter->block_id != VCAP_IS2) {
- NL_SET_ERR_MSG_MOD(extack,
- "Accept action can only be offloaded to VCAP chains");
- return -EOPNOTSUPP;
- }
- if (filter->block_id != VCAP_ES0 &&
- filter->goto_target != -1) {
- NL_SET_ERR_MSG_MOD(extack,
- "Last action must be GOTO");
- return -EOPNOTSUPP;
- }
- 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;
- filter->is_trap = true;
- break;
- case FLOW_ACTION_POLICE:
- if (filter->block_id == PSFP_BLOCK_ID) {
- filter->type = OCELOT_PSFP_FILTER_OFFLOAD;
- break;
- }
- 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 or PSFP");
- return -EOPNOTSUPP;
- }
- if (filter->goto_target != -1) {
- NL_SET_ERR_MSG_MOD(extack,
- "Last action must be GOTO");
- return -EOPNOTSUPP;
- }
- err = ocelot_policer_validate(action, a, extack);
- if (err)
- return err;
- filter->action.police_ena = true;
- pol_ix = a->hw_index + ocelot->vcap_pol.base;
- pol_max = ocelot->vcap_pol.max;
- if (ocelot->vcap_pol.max2 && pol_ix > pol_max) {
- pol_ix += ocelot->vcap_pol.base2 - pol_max - 1;
- pol_max = ocelot->vcap_pol.max2;
- }
- if (pol_ix >= pol_max)
- return -EINVAL;
- filter->action.pol_ix = pol_ix;
- 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_MIRRED:
- if (filter->block_id != VCAP_IS2) {
- NL_SET_ERR_MSG_MOD(extack,
- "Mirror 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->egress_port.value = egress_port;
- filter->action.mirror_ena = true;
- 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) {
- err = ocelot_flower_parse_ingress_vlan_modify(ocelot, port,
- filter, a,
- extack);
- } else if (filter->block_id == VCAP_ES0) {
- err = ocelot_flower_parse_egress_vlan_modify(filter, a,
- extack);
- } else {
- NL_SET_ERR_MSG_MOD(extack,
- "VLAN modify action can only be offloaded to VCAP IS1 or ES0");
- err = -EOPNOTSUPP;
- }
- if (err)
- return err;
- 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 = OCELOT_ES0_VID;
- filter->action.vid_a_val = a->vlan.vid;
- filter->action.pcp_a_val = a->vlan.prio;
- filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
- break;
- case FLOW_ACTION_GATE:
- if (filter->block_id != PSFP_BLOCK_ID) {
- NL_SET_ERR_MSG_MOD(extack,
- "Gate action can only be offloaded to PSFP chain");
- return -EOPNOTSUPP;
- }
- filter->type = OCELOT_PSFP_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 || filter->block_id == PSFP_BLOCK_ID) {
- 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.cookie = f->cookie;
- filter->id.tc_offload = true;
- ret = ocelot_flower_parse_action(ocelot, port, ingress, f, filter);
- if (ret)
- return ret;
- /* PSFP filter need to parse key by stream identification function. */
- if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD)
- return 0;
- 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;
- }
- /* If we have an egress VLAN modification rule, we need to actually write the
- * delta between the input VLAN (from the key) and the output VLAN (from the
- * action), but the action was parsed first. So we need to patch the delta into
- * the action here.
- */
- static int
- ocelot_flower_patch_es0_vlan_modify(struct ocelot_vcap_filter *filter,
- struct netlink_ext_ack *extack)
- {
- if (filter->block_id != VCAP_ES0 ||
- filter->action.tag_a_vid_sel != OCELOT_ES0_VID_PLUS_CLASSIFIED_VID)
- return 0;
- if (filter->vlan.vid.mask != VLAN_VID_MASK) {
- NL_SET_ERR_MSG_MOD(extack,
- "VCAP ES0 VLAN rewriting needs a full VLAN in the key");
- return -EOPNOTSUPP;
- }
- filter->action.vid_a_val -= filter->vlan.vid.value;
- filter->action.vid_a_val &= VLAN_VID_MASK;
- 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 block_id, 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;
- }
- block_id = ocelot_chain_to_block(chain, ingress);
- if (block_id < 0) {
- NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain");
- return -EOPNOTSUPP;
- }
- filter = ocelot_vcap_block_find_filter_by_id(&ocelot->block[block_id],
- f->cookie, true);
- if (filter) {
- /* Filter already exists on other ports */
- if (!ingress) {
- NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 does not support shared filters");
- return -EOPNOTSUPP;
- }
- filter->ingress_port_mask |= BIT(port);
- return ocelot_vcap_filter_replace(ocelot, filter);
- }
- /* Filter didn't exist, create it now */
- 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;
- }
- ret = ocelot_flower_patch_es0_vlan_modify(filter, extack);
- 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);
- if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD) {
- kfree(filter);
- if (ocelot->ops->psfp_filter_add)
- return ocelot->ops->psfp_filter_add(ocelot, port, f);
- NL_SET_ERR_MSG_MOD(extack, "PSFP chain is not supported in HW");
- return -EOPNOTSUPP;
- }
- 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;
- if (block_id == PSFP_BLOCK_ID) {
- if (ocelot->ops->psfp_filter_del)
- return ocelot->ops->psfp_filter_del(ocelot, f);
- return -EOPNOTSUPP;
- }
- block = &ocelot->block[block_id];
- filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie, true);
- if (!filter)
- return 0;
- if (filter->type == OCELOT_VCAP_FILTER_DUMMY)
- return ocelot_vcap_dummy_filter_del(ocelot, filter);
- if (ingress) {
- filter->ingress_port_mask &= ~BIT(port);
- if (filter->ingress_port_mask)
- return ocelot_vcap_filter_replace(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;
- struct flow_stats stats = {0};
- int block_id, ret;
- block_id = ocelot_chain_to_block(f->common.chain_index, ingress);
- if (block_id < 0)
- return 0;
- if (block_id == PSFP_BLOCK_ID) {
- if (ocelot->ops->psfp_stats_get) {
- ret = ocelot->ops->psfp_stats_get(ocelot, f, &stats);
- if (ret)
- return ret;
- goto stats_update;
- }
- return -EOPNOTSUPP;
- }
- block = &ocelot->block[block_id];
- filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie, true);
- if (!filter || filter->type == OCELOT_VCAP_FILTER_DUMMY)
- return 0;
- ret = ocelot_vcap_filter_stats_update(ocelot, filter);
- if (ret)
- return ret;
- stats.pkts = filter->stats.pkts;
- stats_update:
- flow_stats_update(&f->stats, 0x0, stats.pkts, stats.drops, 0x0,
- FLOW_ACTION_HW_STATS_IMMEDIATE);
- return 0;
- }
- EXPORT_SYMBOL_GPL(ocelot_cls_flower_stats);
|