| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright 2020 NXP |
| */ |
| #include "sja1105.h" |
| #include "sja1105_vl.h" |
| |
| struct sja1105_rule *sja1105_rule_find(struct sja1105_private *priv, |
| unsigned long cookie) |
| { |
| struct sja1105_rule *rule; |
| |
| list_for_each_entry(rule, &priv->flow_block.rules, list) |
| if (rule->cookie == cookie) |
| return rule; |
| |
| return NULL; |
| } |
| |
| static int sja1105_find_free_l2_policer(struct sja1105_private *priv) |
| { |
| int i; |
| |
| for (i = 0; i < SJA1105_NUM_L2_POLICERS; i++) |
| if (!priv->flow_block.l2_policer_used[i]) |
| return i; |
| |
| return -1; |
| } |
| |
| static int sja1105_setup_bcast_policer(struct sja1105_private *priv, |
| struct netlink_ext_ack *extack, |
| unsigned long cookie, int port, |
| u64 rate_bytes_per_sec, |
| u32 burst) |
| { |
| struct sja1105_rule *rule = sja1105_rule_find(priv, cookie); |
| struct sja1105_l2_policing_entry *policing; |
| struct dsa_switch *ds = priv->ds; |
| bool new_rule = false; |
| unsigned long p; |
| int rc; |
| |
| if (!rule) { |
| rule = kzalloc(sizeof(*rule), GFP_KERNEL); |
| if (!rule) |
| return -ENOMEM; |
| |
| rule->cookie = cookie; |
| rule->type = SJA1105_RULE_BCAST_POLICER; |
| rule->bcast_pol.sharindx = sja1105_find_free_l2_policer(priv); |
| rule->key.type = SJA1105_KEY_BCAST; |
| new_rule = true; |
| } |
| |
| if (rule->bcast_pol.sharindx == -1) { |
| NL_SET_ERR_MSG_MOD(extack, "No more L2 policers free"); |
| rc = -ENOSPC; |
| goto out; |
| } |
| |
| policing = priv->static_config.tables[BLK_IDX_L2_POLICING].entries; |
| |
| if (policing[(ds->num_ports * SJA1105_NUM_TC) + port].sharindx != port) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Port already has a broadcast policer"); |
| rc = -EEXIST; |
| goto out; |
| } |
| |
| rule->port_mask |= BIT(port); |
| |
| /* Make the broadcast policers of all ports attached to this block |
| * point to the newly allocated policer |
| */ |
| for_each_set_bit(p, &rule->port_mask, SJA1105_MAX_NUM_PORTS) { |
| int bcast = (ds->num_ports * SJA1105_NUM_TC) + p; |
| |
| policing[bcast].sharindx = rule->bcast_pol.sharindx; |
| } |
| |
| policing[rule->bcast_pol.sharindx].rate = div_u64(rate_bytes_per_sec * |
| 512, 1000000); |
| policing[rule->bcast_pol.sharindx].smax = burst; |
| |
| /* TODO: support per-flow MTU */ |
| policing[rule->bcast_pol.sharindx].maxlen = VLAN_ETH_FRAME_LEN + |
| ETH_FCS_LEN; |
| |
| rc = sja1105_static_config_reload(priv, SJA1105_BEST_EFFORT_POLICING); |
| |
| out: |
| if (rc == 0 && new_rule) { |
| priv->flow_block.l2_policer_used[rule->bcast_pol.sharindx] = true; |
| list_add(&rule->list, &priv->flow_block.rules); |
| } else if (new_rule) { |
| kfree(rule); |
| } |
| |
| return rc; |
| } |
| |
| static int sja1105_setup_tc_policer(struct sja1105_private *priv, |
| struct netlink_ext_ack *extack, |
| unsigned long cookie, int port, int tc, |
| u64 rate_bytes_per_sec, |
| u32 burst) |
| { |
| struct sja1105_rule *rule = sja1105_rule_find(priv, cookie); |
| struct sja1105_l2_policing_entry *policing; |
| bool new_rule = false; |
| unsigned long p; |
| int rc; |
| |
| if (!rule) { |
| rule = kzalloc(sizeof(*rule), GFP_KERNEL); |
| if (!rule) |
| return -ENOMEM; |
| |
| rule->cookie = cookie; |
| rule->type = SJA1105_RULE_TC_POLICER; |
| rule->tc_pol.sharindx = sja1105_find_free_l2_policer(priv); |
| rule->key.type = SJA1105_KEY_TC; |
| rule->key.tc.pcp = tc; |
| new_rule = true; |
| } |
| |
| if (rule->tc_pol.sharindx == -1) { |
| NL_SET_ERR_MSG_MOD(extack, "No more L2 policers free"); |
| rc = -ENOSPC; |
| goto out; |
| } |
| |
| policing = priv->static_config.tables[BLK_IDX_L2_POLICING].entries; |
| |
| if (policing[(port * SJA1105_NUM_TC) + tc].sharindx != port) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Port-TC pair already has an L2 policer"); |
| rc = -EEXIST; |
| goto out; |
| } |
| |
| rule->port_mask |= BIT(port); |
| |
| /* Make the policers for traffic class @tc of all ports attached to |
| * this block point to the newly allocated policer |
| */ |
| for_each_set_bit(p, &rule->port_mask, SJA1105_MAX_NUM_PORTS) { |
| int index = (p * SJA1105_NUM_TC) + tc; |
| |
| policing[index].sharindx = rule->tc_pol.sharindx; |
| } |
| |
| policing[rule->tc_pol.sharindx].rate = div_u64(rate_bytes_per_sec * |
| 512, 1000000); |
| policing[rule->tc_pol.sharindx].smax = burst; |
| |
| /* TODO: support per-flow MTU */ |
| policing[rule->tc_pol.sharindx].maxlen = VLAN_ETH_FRAME_LEN + |
| ETH_FCS_LEN; |
| |
| rc = sja1105_static_config_reload(priv, SJA1105_BEST_EFFORT_POLICING); |
| |
| out: |
| if (rc == 0 && new_rule) { |
| priv->flow_block.l2_policer_used[rule->tc_pol.sharindx] = true; |
| list_add(&rule->list, &priv->flow_block.rules); |
| } else if (new_rule) { |
| kfree(rule); |
| } |
| |
| return rc; |
| } |
| |
| static int sja1105_flower_policer(struct sja1105_private *priv, int port, |
| struct netlink_ext_ack *extack, |
| unsigned long cookie, |
| struct sja1105_key *key, |
| u64 rate_bytes_per_sec, |
| u32 burst) |
| { |
| switch (key->type) { |
| case SJA1105_KEY_BCAST: |
| return sja1105_setup_bcast_policer(priv, extack, cookie, port, |
| rate_bytes_per_sec, burst); |
| case SJA1105_KEY_TC: |
| return sja1105_setup_tc_policer(priv, extack, cookie, port, |
| key->tc.pcp, rate_bytes_per_sec, |
| burst); |
| default: |
| NL_SET_ERR_MSG_MOD(extack, "Unknown keys for policing"); |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int sja1105_flower_parse_key(struct sja1105_private *priv, |
| struct netlink_ext_ack *extack, |
| struct flow_cls_offload *cls, |
| struct sja1105_key *key) |
| { |
| struct flow_rule *rule = flow_cls_offload_flow_rule(cls); |
| struct flow_dissector *dissector = rule->match.dissector; |
| bool is_bcast_dmac = false; |
| u64 dmac = U64_MAX; |
| u16 vid = U16_MAX; |
| u16 pcp = U16_MAX; |
| |
| if (dissector->used_keys & |
| ~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
| BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) | |
| BIT_ULL(FLOW_DISSECTOR_KEY_VLAN) | |
| BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS))) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Unsupported keys used"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (flow_rule_match_has_control_flags(rule, extack)) |
| return -EOPNOTSUPP; |
| |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) { |
| struct flow_match_basic match; |
| |
| flow_rule_match_basic(rule, &match); |
| if (match.key->n_proto) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Matching on protocol not supported"); |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { |
| u8 bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
| u8 null[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
| struct flow_match_eth_addrs match; |
| |
| flow_rule_match_eth_addrs(rule, &match); |
| |
| if (!ether_addr_equal_masked(match.key->src, null, |
| match.mask->src)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Matching on source MAC not supported"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (!ether_addr_equal(match.mask->dst, bcast)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Masked matching on MAC not supported"); |
| return -EOPNOTSUPP; |
| } |
| |
| dmac = ether_addr_to_u64(match.key->dst); |
| is_bcast_dmac = ether_addr_equal(match.key->dst, bcast); |
| } |
| |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) { |
| struct flow_match_vlan match; |
| |
| flow_rule_match_vlan(rule, &match); |
| |
| if (match.mask->vlan_id && |
| match.mask->vlan_id != VLAN_VID_MASK) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Masked matching on VID is not supported"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (match.mask->vlan_priority && |
| match.mask->vlan_priority != 0x7) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Masked matching on PCP is not supported"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (match.mask->vlan_id) |
| vid = match.key->vlan_id; |
| if (match.mask->vlan_priority) |
| pcp = match.key->vlan_priority; |
| } |
| |
| if (is_bcast_dmac && vid == U16_MAX && pcp == U16_MAX) { |
| key->type = SJA1105_KEY_BCAST; |
| return 0; |
| } |
| if (dmac == U64_MAX && vid == U16_MAX && pcp != U16_MAX) { |
| key->type = SJA1105_KEY_TC; |
| key->tc.pcp = pcp; |
| return 0; |
| } |
| if (dmac != U64_MAX && vid != U16_MAX && pcp != U16_MAX) { |
| key->type = SJA1105_KEY_VLAN_AWARE_VL; |
| key->vl.dmac = dmac; |
| key->vl.vid = vid; |
| key->vl.pcp = pcp; |
| return 0; |
| } |
| if (dmac != U64_MAX) { |
| key->type = SJA1105_KEY_VLAN_UNAWARE_VL; |
| key->vl.dmac = dmac; |
| return 0; |
| } |
| |
| NL_SET_ERR_MSG_MOD(extack, "Not matching on any known key"); |
| return -EOPNOTSUPP; |
| } |
| |
| static int sja1105_policer_validate(const struct flow_action *action, |
| const struct flow_action_entry *act, |
| struct netlink_ext_ack *extack) |
| { |
| if (act->police.exceed.act_id != FLOW_ACTION_DROP) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Offload not supported when exceed action is not drop"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (act->police.notexceed.act_id != FLOW_ACTION_PIPE && |
| act->police.notexceed.act_id != FLOW_ACTION_ACCEPT) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Offload not supported when conform action is not pipe or ok"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (act->police.notexceed.act_id == FLOW_ACTION_ACCEPT && |
| !flow_action_is_last_entry(action, act)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Offload not supported when conform action is ok, but action is not last"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (act->police.peakrate_bytes_ps || |
| act->police.avrate || act->police.overhead) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Offload not supported when peakrate/avrate/overhead is configured"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (act->police.rate_pkt_ps) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "QoS offload not support packets per second"); |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| int sja1105_cls_flower_add(struct dsa_switch *ds, int port, |
| struct flow_cls_offload *cls, bool ingress) |
| { |
| struct flow_rule *rule = flow_cls_offload_flow_rule(cls); |
| struct netlink_ext_ack *extack = cls->common.extack; |
| struct sja1105_private *priv = ds->priv; |
| const struct flow_action_entry *act; |
| unsigned long cookie = cls->cookie; |
| bool routing_rule = false; |
| struct sja1105_key key; |
| bool gate_rule = false; |
| bool vl_rule = false; |
| int rc, i; |
| |
| rc = sja1105_flower_parse_key(priv, extack, cls, &key); |
| if (rc) |
| return rc; |
| |
| flow_action_for_each(i, act, &rule->action) { |
| switch (act->id) { |
| case FLOW_ACTION_POLICE: |
| rc = sja1105_policer_validate(&rule->action, act, extack); |
| if (rc) |
| goto out; |
| |
| rc = sja1105_flower_policer(priv, port, extack, cookie, |
| &key, |
| act->police.rate_bytes_ps, |
| act->police.burst); |
| if (rc) |
| goto out; |
| break; |
| case FLOW_ACTION_TRAP: { |
| int cpu = dsa_upstream_port(ds, port); |
| |
| routing_rule = true; |
| vl_rule = true; |
| |
| rc = sja1105_vl_redirect(priv, port, extack, cookie, |
| &key, BIT(cpu), true); |
| if (rc) |
| goto out; |
| break; |
| } |
| case FLOW_ACTION_REDIRECT: { |
| struct dsa_port *to_dp; |
| |
| to_dp = dsa_port_from_netdev(act->dev); |
| if (IS_ERR(to_dp)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Destination not a switch port"); |
| return -EOPNOTSUPP; |
| } |
| |
| routing_rule = true; |
| vl_rule = true; |
| |
| rc = sja1105_vl_redirect(priv, port, extack, cookie, |
| &key, BIT(to_dp->index), true); |
| if (rc) |
| goto out; |
| break; |
| } |
| case FLOW_ACTION_DROP: |
| vl_rule = true; |
| |
| rc = sja1105_vl_redirect(priv, port, extack, cookie, |
| &key, 0, false); |
| if (rc) |
| goto out; |
| break; |
| case FLOW_ACTION_GATE: |
| gate_rule = true; |
| vl_rule = true; |
| |
| rc = sja1105_vl_gate(priv, port, extack, cookie, |
| &key, act->hw_index, |
| act->gate.prio, |
| act->gate.basetime, |
| act->gate.cycletime, |
| act->gate.cycletimeext, |
| act->gate.num_entries, |
| act->gate.entries); |
| if (rc) |
| goto out; |
| break; |
| default: |
| NL_SET_ERR_MSG_MOD(extack, |
| "Action not supported"); |
| rc = -EOPNOTSUPP; |
| goto out; |
| } |
| } |
| |
| if (vl_rule && !rc) { |
| /* Delay scheduling configuration until DESTPORTS has been |
| * populated by all other actions. |
| */ |
| if (gate_rule) { |
| if (!routing_rule) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Can only offload gate action together with redirect or trap"); |
| return -EOPNOTSUPP; |
| } |
| rc = sja1105_init_scheduling(priv); |
| if (rc) |
| goto out; |
| } |
| |
| rc = sja1105_static_config_reload(priv, SJA1105_VIRTUAL_LINKS); |
| } |
| |
| out: |
| return rc; |
| } |
| |
| int sja1105_cls_flower_del(struct dsa_switch *ds, int port, |
| struct flow_cls_offload *cls, bool ingress) |
| { |
| struct sja1105_private *priv = ds->priv; |
| struct sja1105_rule *rule = sja1105_rule_find(priv, cls->cookie); |
| struct sja1105_l2_policing_entry *policing; |
| int old_sharindx; |
| |
| if (!rule) |
| return 0; |
| |
| if (rule->type == SJA1105_RULE_VL) |
| return sja1105_vl_delete(priv, port, rule, cls->common.extack); |
| |
| policing = priv->static_config.tables[BLK_IDX_L2_POLICING].entries; |
| |
| if (rule->type == SJA1105_RULE_BCAST_POLICER) { |
| int bcast = (ds->num_ports * SJA1105_NUM_TC) + port; |
| |
| old_sharindx = policing[bcast].sharindx; |
| policing[bcast].sharindx = port; |
| } else if (rule->type == SJA1105_RULE_TC_POLICER) { |
| int index = (port * SJA1105_NUM_TC) + rule->key.tc.pcp; |
| |
| old_sharindx = policing[index].sharindx; |
| policing[index].sharindx = port; |
| } else { |
| return -EINVAL; |
| } |
| |
| rule->port_mask &= ~BIT(port); |
| if (!rule->port_mask) { |
| priv->flow_block.l2_policer_used[old_sharindx] = false; |
| list_del(&rule->list); |
| kfree(rule); |
| } |
| |
| return sja1105_static_config_reload(priv, SJA1105_BEST_EFFORT_POLICING); |
| } |
| |
| int sja1105_cls_flower_stats(struct dsa_switch *ds, int port, |
| struct flow_cls_offload *cls, bool ingress) |
| { |
| struct sja1105_private *priv = ds->priv; |
| struct sja1105_rule *rule = sja1105_rule_find(priv, cls->cookie); |
| int rc; |
| |
| if (!rule) |
| return 0; |
| |
| if (rule->type != SJA1105_RULE_VL) |
| return 0; |
| |
| rc = sja1105_vl_stats(priv, port, rule, &cls->stats, |
| cls->common.extack); |
| if (rc) |
| return rc; |
| |
| return 0; |
| } |
| |
| void sja1105_flower_setup(struct dsa_switch *ds) |
| { |
| struct sja1105_private *priv = ds->priv; |
| int port; |
| |
| INIT_LIST_HEAD(&priv->flow_block.rules); |
| |
| for (port = 0; port < ds->num_ports; port++) |
| priv->flow_block.l2_policer_used[port] = true; |
| } |
| |
| void sja1105_flower_teardown(struct dsa_switch *ds) |
| { |
| struct sja1105_private *priv = ds->priv; |
| struct sja1105_rule *rule; |
| struct list_head *pos, *n; |
| |
| list_for_each_safe(pos, n, &priv->flow_block.rules) { |
| rule = list_entry(pos, struct sja1105_rule, list); |
| list_del(&rule->list); |
| kfree(rule); |
| } |
| } |