| // SPDX-License-Identifier: GPL-2.0+ |
| |
| #include "lan966x_main.h" |
| #include "vcap_api.h" |
| #include "vcap_api_client.h" |
| #include "vcap_tc.h" |
| |
| #define LAN966X_FORCE_UNTAGED 3 |
| |
| static bool lan966x_tc_is_known_etype(struct vcap_tc_flower_parse_usage *st, |
| u16 etype) |
| { |
| switch (st->admin->vtype) { |
| case VCAP_TYPE_IS1: |
| switch (etype) { |
| case ETH_P_ALL: |
| case ETH_P_ARP: |
| case ETH_P_IP: |
| case ETH_P_IPV6: |
| return true; |
| } |
| break; |
| case VCAP_TYPE_IS2: |
| switch (etype) { |
| case ETH_P_ALL: |
| case ETH_P_ARP: |
| case ETH_P_IP: |
| case ETH_P_IPV6: |
| case ETH_P_SNAP: |
| case ETH_P_802_2: |
| return true; |
| } |
| break; |
| case VCAP_TYPE_ES0: |
| return true; |
| default: |
| NL_SET_ERR_MSG_MOD(st->fco->common.extack, |
| "VCAP type not supported"); |
| return false; |
| } |
| |
| return false; |
| } |
| |
| static int |
| lan966x_tc_flower_handler_control_usage(struct vcap_tc_flower_parse_usage *st) |
| { |
| struct netlink_ext_ack *extack = st->fco->common.extack; |
| struct flow_match_control match; |
| int err = 0; |
| |
| flow_rule_match_control(st->frule, &match); |
| if (match.mask->flags & FLOW_DIS_IS_FRAGMENT) { |
| if (match.key->flags & FLOW_DIS_IS_FRAGMENT) |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_L3_FRAGMENT, |
| VCAP_BIT_1); |
| else |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_L3_FRAGMENT, |
| VCAP_BIT_0); |
| if (err) |
| goto bad_frag_out; |
| } |
| |
| if (match.mask->flags & FLOW_DIS_FIRST_FRAG) { |
| if (match.key->flags & FLOW_DIS_FIRST_FRAG) |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_L3_FRAG_OFS_GT0, |
| VCAP_BIT_0); |
| else |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_L3_FRAG_OFS_GT0, |
| VCAP_BIT_1); |
| if (err) |
| goto bad_frag_out; |
| } |
| |
| if (!flow_rule_is_supp_control_flags(FLOW_DIS_IS_FRAGMENT | |
| FLOW_DIS_FIRST_FRAG, |
| match.mask->flags, extack)) |
| return -EOPNOTSUPP; |
| |
| st->used_keys |= BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL); |
| |
| return err; |
| |
| bad_frag_out: |
| NL_SET_ERR_MSG_MOD(extack, "ip_frag parse error"); |
| return err; |
| } |
| |
| static int |
| lan966x_tc_flower_handler_basic_usage(struct vcap_tc_flower_parse_usage *st) |
| { |
| struct flow_match_basic match; |
| int err = 0; |
| |
| flow_rule_match_basic(st->frule, &match); |
| if (match.mask->n_proto) { |
| st->l3_proto = be16_to_cpu(match.key->n_proto); |
| if (!lan966x_tc_is_known_etype(st, 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 && |
| st->admin->vtype == VCAP_TYPE_IS1) { |
| /* Don't set any keys in this case */ |
| } else if (st->l3_proto == ETH_P_SNAP && |
| st->admin->vtype == VCAP_TYPE_IS1) { |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_ETYPE_LEN_IS, |
| VCAP_BIT_0); |
| if (err) |
| goto out; |
| |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_IP_SNAP_IS, |
| VCAP_BIT_1); |
| if (err) |
| goto out; |
| } else if (st->admin->vtype == VCAP_TYPE_IS1) { |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_ETYPE_LEN_IS, |
| VCAP_BIT_1); |
| if (err) |
| goto out; |
| |
| err = vcap_rule_add_key_u32(st->vrule, VCAP_KF_ETYPE, |
| st->l3_proto, ~0); |
| if (err) |
| goto out; |
| } |
| } |
| if (match.mask->ip_proto) { |
| st->l4_proto = match.key->ip_proto; |
| |
| if (st->l4_proto == IPPROTO_TCP) { |
| if (st->admin->vtype == VCAP_TYPE_IS1) { |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_TCP_UDP_IS, |
| VCAP_BIT_1); |
| if (err) |
| goto out; |
| } |
| |
| 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) { |
| if (st->admin->vtype == VCAP_TYPE_IS1) { |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_TCP_UDP_IS, |
| VCAP_BIT_1); |
| if (err) |
| goto out; |
| } |
| |
| err = vcap_rule_add_key_bit(st->vrule, |
| VCAP_KF_TCP_IS, |
| VCAP_BIT_0); |
| 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 |
| lan966x_tc_flower_handler_cvlan_usage(struct vcap_tc_flower_parse_usage *st) |
| { |
| if (st->admin->vtype != VCAP_TYPE_IS1) { |
| 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 |
| lan966x_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; |
| |
| if (st->admin->vtype == VCAP_TYPE_IS1) { |
| vid_key = VCAP_KF_8021Q_VID0; |
| pcp_key = VCAP_KF_8021Q_PCP0; |
| } |
| |
| return vcap_tc_flower_handler_vlan_usage(st, vid_key, pcp_key); |
| } |
| |
| static int |
| (*lan966x_tc_flower_handlers_usage[])(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] = lan966x_tc_flower_handler_control_usage, |
| [FLOW_DISSECTOR_KEY_PORTS] = vcap_tc_flower_handler_portnum_usage, |
| [FLOW_DISSECTOR_KEY_BASIC] = lan966x_tc_flower_handler_basic_usage, |
| [FLOW_DISSECTOR_KEY_CVLAN] = lan966x_tc_flower_handler_cvlan_usage, |
| [FLOW_DISSECTOR_KEY_VLAN] = lan966x_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 lan966x_tc_flower_use_dissectors(struct flow_cls_offload *f, |
| struct vcap_admin *admin, |
| struct vcap_rule *vrule, |
| u16 *l3_proto) |
| { |
| struct vcap_tc_flower_parse_usage state = { |
| .fco = f, |
| .vrule = vrule, |
| .l3_proto = ETH_P_ALL, |
| .admin = admin, |
| }; |
| int err = 0; |
| |
| state.frule = flow_cls_offload_flow_rule(f); |
| for (int i = 0; i < ARRAY_SIZE(lan966x_tc_flower_handlers_usage); ++i) { |
| if (!flow_rule_match_key(state.frule, i) || |
| !lan966x_tc_flower_handlers_usage[i]) |
| continue; |
| |
| err = lan966x_tc_flower_handlers_usage[i](&state); |
| if (err) |
| return err; |
| } |
| |
| if (l3_proto) |
| *l3_proto = state.l3_proto; |
| |
| return err; |
| } |
| |
| static int lan966x_tc_flower_action_check(struct vcap_control *vctrl, |
| struct net_device *dev, |
| 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 that last action is a goto |
| * The last chain/lookup does not need to have 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; |
| } |
| |
| return 0; |
| } |
| |
| /* Add the actionset that is the default for the VCAP type */ |
| static int lan966x_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_IS1: |
| aset = VCAP_AFS_S1; |
| break; |
| case VCAP_TYPE_IS2: |
| aset = VCAP_AFS_BASE_TYPE; |
| break; |
| case VCAP_TYPE_ES0: |
| aset = VCAP_AFS_VID; |
| break; |
| default: |
| 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; |
| } |
| |
| static int lan966x_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_IS1: |
| /* Choose IS1 specific NXT_IDX key (for chaining rules from IS1) */ |
| err = vcap_rule_add_key_u32(vrule, VCAP_KF_LOOKUP_GEN_IDX_SEL, |
| 1, ~0); |
| if (err) |
| return err; |
| |
| return vcap_rule_add_key_u32(vrule, VCAP_KF_LOOKUP_GEN_IDX, |
| link_val, ~0); |
| case VCAP_TYPE_IS2: |
| /* Add IS2 specific PAG key (for chaining rules from IS1) */ |
| return vcap_rule_add_key_u32(vrule, VCAP_KF_LOOKUP_PAG, |
| link_val, ~0); |
| case VCAP_TYPE_ES0: |
| /* Add ES0 specific ISDX key (for chaining rules from IS1) */ |
| return vcap_rule_add_key_u32(vrule, VCAP_KF_ISDX_CLS, |
| link_val, ~0); |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static int lan966x_tc_add_rule_link(struct vcap_control *vctrl, |
| struct vcap_admin *admin, |
| struct vcap_rule *vrule, |
| struct flow_cls_offload *f, |
| int to_cid) |
| { |
| struct vcap_admin *to_admin = vcap_find_admin(vctrl, to_cid); |
| int diff, err = 0; |
| |
| if (!to_admin) { |
| NL_SET_ERR_MSG_MOD(f->common.extack, |
| "Unknown destination chain"); |
| return -EINVAL; |
| } |
| |
| diff = vcap_chain_offset(vctrl, f->common.chain_index, to_cid); |
| if (!diff) |
| return 0; |
| |
| /* Between IS1 and IS2 the PAG value is used */ |
| if (admin->vtype == VCAP_TYPE_IS1 && to_admin->vtype == VCAP_TYPE_IS2) { |
| /* This works for IS1->IS2 */ |
| err = vcap_rule_add_action_u32(vrule, VCAP_AF_PAG_VAL, diff); |
| if (err) |
| return err; |
| |
| err = vcap_rule_add_action_u32(vrule, VCAP_AF_PAG_OVERRIDE_MASK, |
| 0xff); |
| if (err) |
| return err; |
| } else if (admin->vtype == VCAP_TYPE_IS1 && |
| to_admin->vtype == VCAP_TYPE_ES0) { |
| /* This works for IS1->ES0 */ |
| err = vcap_rule_add_action_u32(vrule, VCAP_AF_ISDX_ADD_VAL, |
| diff); |
| if (err) |
| return err; |
| |
| err = vcap_rule_add_action_bit(vrule, VCAP_AF_ISDX_REPLACE_ENA, |
| VCAP_BIT_1); |
| if (err) |
| return err; |
| } else { |
| NL_SET_ERR_MSG_MOD(f->common.extack, |
| "Unsupported chain destination"); |
| return -EOPNOTSUPP; |
| } |
| |
| return err; |
| } |
| |
| static int lan966x_tc_add_rule_counter(struct vcap_admin *admin, |
| struct vcap_rule *vrule) |
| { |
| int err = 0; |
| |
| switch (admin->vtype) { |
| case VCAP_TYPE_ES0: |
| err = vcap_rule_mod_action_u32(vrule, VCAP_AF_ESDX, |
| vrule->id); |
| break; |
| default: |
| break; |
| } |
| |
| return err; |
| } |
| |
| static int lan966x_tc_flower_add(struct lan966x_port *port, |
| struct flow_cls_offload *f, |
| struct vcap_admin *admin, |
| bool ingress) |
| { |
| struct flow_action_entry *act; |
| u16 l3_proto = ETH_P_ALL; |
| struct flow_rule *frule; |
| struct vcap_rule *vrule; |
| int err, idx; |
| |
| err = lan966x_tc_flower_action_check(port->lan966x->vcap_ctrl, |
| port->dev, f, ingress); |
| if (err) |
| return err; |
| |
| vrule = vcap_alloc_rule(port->lan966x->vcap_ctrl, port->dev, |
| f->common.chain_index, VCAP_USER_TC, |
| f->common.prio, 0); |
| if (IS_ERR(vrule)) |
| return PTR_ERR(vrule); |
| |
| vrule->cookie = f->cookie; |
| err = lan966x_tc_flower_use_dissectors(f, admin, vrule, &l3_proto); |
| if (err) |
| goto out; |
| |
| err = lan966x_tc_add_rule_link_target(admin, vrule, |
| f->common.chain_index); |
| if (err) |
| goto out; |
| |
| frule = flow_cls_offload_flow_rule(f); |
| |
| flow_action_for_each(idx, act, &frule->action) { |
| switch (act->id) { |
| case FLOW_ACTION_TRAP: |
| if (admin->vtype != VCAP_TYPE_IS2) { |
| NL_SET_ERR_MSG_MOD(f->common.extack, |
| "Trap action not supported in this VCAP"); |
| err = -EOPNOTSUPP; |
| goto out; |
| } |
| |
| err = vcap_rule_add_action_bit(vrule, |
| VCAP_AF_CPU_COPY_ENA, |
| VCAP_BIT_1); |
| err |= vcap_rule_add_action_u32(vrule, |
| VCAP_AF_CPU_QUEUE_NUM, |
| 0); |
| err |= vcap_rule_add_action_u32(vrule, VCAP_AF_MASK_MODE, |
| LAN966X_PMM_REPLACE); |
| if (err) |
| goto out; |
| |
| break; |
| case FLOW_ACTION_GOTO: |
| err = lan966x_tc_set_actionset(admin, vrule); |
| if (err) |
| goto out; |
| |
| err = lan966x_tc_add_rule_link(port->lan966x->vcap_ctrl, |
| admin, vrule, |
| f, act->chain_index); |
| if (err) |
| goto out; |
| |
| break; |
| case FLOW_ACTION_VLAN_POP: |
| if (admin->vtype != VCAP_TYPE_ES0) { |
| NL_SET_ERR_MSG_MOD(f->common.extack, |
| "Cannot use vlan pop on non es0"); |
| err = -EOPNOTSUPP; |
| goto out; |
| } |
| |
| /* Force untag */ |
| err = vcap_rule_add_action_u32(vrule, VCAP_AF_PUSH_OUTER_TAG, |
| LAN966X_FORCE_UNTAGED); |
| if (err) |
| goto out; |
| |
| break; |
| default: |
| NL_SET_ERR_MSG_MOD(f->common.extack, |
| "Unsupported TC action"); |
| err = -EOPNOTSUPP; |
| goto out; |
| } |
| } |
| |
| err = lan966x_tc_add_rule_counter(admin, vrule); |
| if (err) { |
| vcap_set_tc_exterr(f, vrule); |
| goto out; |
| } |
| |
| err = vcap_val_rule(vrule, l3_proto); |
| if (err) { |
| vcap_set_tc_exterr(f, vrule); |
| goto out; |
| } |
| |
| err = vcap_add_rule(vrule); |
| if (err) |
| NL_SET_ERR_MSG_MOD(f->common.extack, |
| "Could not add the filter"); |
| out: |
| vcap_free_rule(vrule); |
| return err; |
| } |
| |
| static int lan966x_tc_flower_del(struct lan966x_port *port, |
| struct flow_cls_offload *f, |
| struct vcap_admin *admin) |
| { |
| struct vcap_control *vctrl; |
| int err = -ENOENT, rule_id; |
| |
| vctrl = port->lan966x->vcap_ctrl; |
| while (true) { |
| rule_id = vcap_lookup_rule_by_cookie(vctrl, f->cookie); |
| if (rule_id <= 0) |
| break; |
| |
| err = vcap_del_rule(vctrl, port->dev, rule_id); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(f->common.extack, |
| "Cannot delete rule"); |
| break; |
| } |
| } |
| |
| return err; |
| } |
| |
| static int lan966x_tc_flower_stats(struct lan966x_port *port, |
| struct flow_cls_offload *f, |
| struct vcap_admin *admin) |
| { |
| struct vcap_counter count = {}; |
| int err; |
| |
| err = vcap_get_rule_count_by_cookie(port->lan966x->vcap_ctrl, |
| &count, f->cookie); |
| if (err) |
| return err; |
| |
| flow_stats_update(&f->stats, 0x0, count.value, 0, 0, |
| FLOW_ACTION_HW_STATS_IMMEDIATE); |
| |
| return err; |
| } |
| |
| int lan966x_tc_flower(struct lan966x_port *port, |
| struct flow_cls_offload *f, |
| bool ingress) |
| { |
| struct vcap_admin *admin; |
| |
| admin = vcap_find_admin(port->lan966x->vcap_ctrl, |
| f->common.chain_index); |
| if (!admin) { |
| NL_SET_ERR_MSG_MOD(f->common.extack, "Invalid chain"); |
| return -EINVAL; |
| } |
| |
| switch (f->command) { |
| case FLOW_CLS_REPLACE: |
| return lan966x_tc_flower_add(port, f, admin, ingress); |
| case FLOW_CLS_DESTROY: |
| return lan966x_tc_flower_del(port, f, admin); |
| case FLOW_CLS_STATS: |
| return lan966x_tc_flower_stats(port, f, admin); |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |