| // SPDX-License-Identifier: GPL-2.0+ |
| /* Microchip VCAP API |
| * |
| * Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries. |
| */ |
| |
| #include <net/tcp.h> |
| |
| #include "sparx5_tc.h" |
| #include "vcap_api.h" |
| #include "vcap_api_client.h" |
| #include "sparx5_main.h" |
| #include "sparx5_vcap_impl.h" |
| |
| struct sparx5_tc_flower_parse_usage { |
| struct flow_cls_offload *fco; |
| struct flow_rule *frule; |
| struct vcap_rule *vrule; |
| unsigned int used_keys; |
| }; |
| |
| static int sparx5_tc_flower_handler_ethaddr_usage(struct sparx5_tc_flower_parse_usage *st) |
| { |
| enum vcap_key_field smac_key = VCAP_KF_L2_SMAC; |
| enum vcap_key_field dmac_key = VCAP_KF_L2_DMAC; |
| struct flow_match_eth_addrs match; |
| struct vcap_u48_key smac, dmac; |
| int err = 0; |
| |
| flow_rule_match_eth_addrs(st->frule, &match); |
| |
| if (!is_zero_ether_addr(match.mask->src)) { |
| vcap_netbytes_copy(smac.value, match.key->src, ETH_ALEN); |
| vcap_netbytes_copy(smac.mask, match.mask->src, ETH_ALEN); |
| err = vcap_rule_add_key_u48(st->vrule, smac_key, &smac); |
| if (err) |
| goto out; |
| } |
| |
| if (!is_zero_ether_addr(match.mask->dst)) { |
| vcap_netbytes_copy(dmac.value, match.key->dst, ETH_ALEN); |
| vcap_netbytes_copy(dmac.mask, match.mask->dst, ETH_ALEN); |
| err = vcap_rule_add_key_u48(st->vrule, dmac_key, &dmac); |
| if (err) |
| goto out; |
| } |
| |
| st->used_keys |= BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS); |
| |
| return err; |
| |
| out: |
| NL_SET_ERR_MSG_MOD(st->fco->common.extack, "eth_addr parse error"); |
| return err; |
| } |
| |
| static int (*sparx5_tc_flower_usage_handlers[])(struct sparx5_tc_flower_parse_usage *st) = { |
| /* More dissector handlers will be added here later */ |
| [FLOW_DISSECTOR_KEY_ETH_ADDRS] = sparx5_tc_flower_handler_ethaddr_usage, |
| }; |
| |
| static int sparx5_tc_use_dissectors(struct flow_cls_offload *fco, |
| struct vcap_admin *admin, |
| struct vcap_rule *vrule) |
| { |
| struct sparx5_tc_flower_parse_usage state = { |
| .fco = fco, |
| .vrule = vrule, |
| }; |
| int idx, err = 0; |
| |
| state.frule = flow_cls_offload_flow_rule(fco); |
| for (idx = 0; idx < ARRAY_SIZE(sparx5_tc_flower_usage_handlers); ++idx) { |
| if (!flow_rule_match_key(state.frule, idx)) |
| continue; |
| if (!sparx5_tc_flower_usage_handlers[idx]) |
| continue; |
| err = sparx5_tc_flower_usage_handlers[idx](&state); |
| if (err) |
| return err; |
| } |
| return err; |
| } |
| |
| static int sparx5_tc_flower_replace(struct net_device *ndev, |
| struct flow_cls_offload *fco, |
| struct vcap_admin *admin) |
| { |
| struct sparx5_port *port = netdev_priv(ndev); |
| struct flow_action_entry *act; |
| struct vcap_control *vctrl; |
| struct flow_rule *frule; |
| struct vcap_rule *vrule; |
| int err, idx; |
| |
| frule = flow_cls_offload_flow_rule(fco); |
| if (!flow_action_has_entries(&frule->action)) { |
| NL_SET_ERR_MSG_MOD(fco->common.extack, "No actions"); |
| return -EINVAL; |
| } |
| |
| if (!flow_action_basic_hw_stats_check(&frule->action, fco->common.extack)) |
| return -EOPNOTSUPP; |
| |
| 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)) |
| return PTR_ERR(vrule); |
| |
| vrule->cookie = fco->cookie; |
| sparx5_tc_use_dissectors(fco, admin, vrule); |
| flow_action_for_each(idx, act, &frule->action) { |
| switch (act->id) { |
| case FLOW_ACTION_TRAP: |
| err = vcap_rule_add_action_bit(vrule, |
| VCAP_AF_CPU_COPY_ENA, |
| VCAP_BIT_1); |
| if (err) |
| goto out; |
| err = vcap_rule_add_action_u32(vrule, |
| VCAP_AF_CPU_QUEUE_NUM, 0); |
| if (err) |
| goto out; |
| err = vcap_rule_add_action_u32(vrule, VCAP_AF_MASK_MODE, |
| SPX5_PMM_REPLACE_ALL); |
| if (err) |
| goto out; |
| /* For now the actionset is hardcoded */ |
| err = vcap_set_rule_set_actionset(vrule, |
| VCAP_AFS_BASE_TYPE); |
| if (err) |
| goto out; |
| break; |
| case FLOW_ACTION_ACCEPT: |
| /* For now the actionset is hardcoded */ |
| err = vcap_set_rule_set_actionset(vrule, |
| VCAP_AFS_BASE_TYPE); |
| if (err) |
| goto out; |
| break; |
| default: |
| NL_SET_ERR_MSG_MOD(fco->common.extack, |
| "Unsupported TC action"); |
| err = -EOPNOTSUPP; |
| goto out; |
| } |
| } |
| /* For now the keyset is hardcoded */ |
| err = vcap_set_rule_set_keyset(vrule, VCAP_KFS_MAC_ETYPE); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(fco->common.extack, |
| "No matching port keyset for filter protocol and keys"); |
| goto out; |
| } |
| err = vcap_val_rule(vrule, ETH_P_ALL); |
| 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"); |
| out: |
| vcap_free_rule(vrule); |
| return err; |
| } |
| |
| 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); |
| struct vcap_control *vctrl; |
| int err = -ENOENT, rule_id; |
| |
| vctrl = port->sparx5->vcap_ctrl; |
| while (true) { |
| rule_id = vcap_lookup_rule_by_cookie(vctrl, fco->cookie); |
| if (rule_id <= 0) |
| break; |
| 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; |
| } |
| |
| 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); |
| case FLOW_CLS_DESTROY: |
| return sparx5_tc_flower_destroy(ndev, fco, admin); |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |