// 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 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 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 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 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; }