| // SPDX-License-Identifier: GPL-2.0+ |
| /* Microchip VCAP API |
| * |
| * Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries. |
| */ |
| |
| #include "sparx5_tc.h" |
| #include "vcap_api.h" |
| #include "vcap_api_client.h" |
| #include "sparx5_main_regs.h" |
| #include "sparx5_main.h" |
| #include "sparx5_vcap_impl.h" |
| |
| static struct sparx5_mall_entry * |
| sparx5_tc_matchall_entry_find(struct list_head *entries, unsigned long cookie) |
| { |
| struct sparx5_mall_entry *entry; |
| |
| list_for_each_entry(entry, entries, list) { |
| if (entry->cookie == cookie) |
| return entry; |
| } |
| |
| return NULL; |
| } |
| |
| static void sparx5_tc_matchall_parse_action(struct sparx5_port *port, |
| struct sparx5_mall_entry *entry, |
| struct flow_action_entry *action, |
| bool ingress, |
| unsigned long cookie) |
| { |
| entry->port = port; |
| entry->type = action->id; |
| entry->ingress = ingress; |
| entry->cookie = cookie; |
| } |
| |
| static void |
| sparx5_tc_matchall_parse_mirror_action(struct sparx5_mall_entry *entry, |
| struct flow_action_entry *action) |
| { |
| entry->mirror.port = netdev_priv(action->dev); |
| } |
| |
| static int sparx5_tc_matchall_replace(struct net_device *ndev, |
| struct tc_cls_matchall_offload *tmo, |
| bool ingress) |
| { |
| struct sparx5_port *port = netdev_priv(ndev); |
| struct sparx5_mall_entry *mall_entry; |
| struct flow_action_entry *action; |
| struct sparx5 *sparx5; |
| int err; |
| |
| if (!flow_offload_has_one_action(&tmo->rule->action)) { |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "Only one action per filter is supported"); |
| return -EOPNOTSUPP; |
| } |
| action = &tmo->rule->action.entries[0]; |
| |
| mall_entry = kzalloc(sizeof(*mall_entry), GFP_KERNEL); |
| if (!mall_entry) |
| return -ENOMEM; |
| |
| sparx5_tc_matchall_parse_action(port, |
| mall_entry, |
| action, |
| ingress, |
| tmo->cookie); |
| |
| sparx5 = port->sparx5; |
| switch (action->id) { |
| case FLOW_ACTION_MIRRED: |
| sparx5_tc_matchall_parse_mirror_action(mall_entry, action); |
| err = sparx5_mirror_add(mall_entry); |
| if (err) { |
| switch (err) { |
| case -EEXIST: |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "Mirroring already exists"); |
| break; |
| case -EINVAL: |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "Cannot mirror a monitor port"); |
| break; |
| case -ENOENT: |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "No more mirror probes available"); |
| break; |
| default: |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "Unknown error"); |
| break; |
| } |
| return err; |
| } |
| /* Get baseline stats for this port */ |
| sparx5_mirror_stats(mall_entry, &tmo->stats); |
| break; |
| case FLOW_ACTION_GOTO: |
| err = vcap_enable_lookups(sparx5->vcap_ctrl, ndev, |
| tmo->common.chain_index, |
| action->chain_index, tmo->cookie, |
| true); |
| if (err == -EFAULT) { |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "Unsupported goto chain"); |
| return -EOPNOTSUPP; |
| } |
| if (err == -EADDRINUSE) { |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "VCAP already enabled"); |
| return -EOPNOTSUPP; |
| } |
| if (err == -EADDRNOTAVAIL) { |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "Already matching this chain"); |
| return -EOPNOTSUPP; |
| } |
| if (err) { |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, |
| "Could not enable VCAP lookups"); |
| return err; |
| } |
| break; |
| default: |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, "Unsupported action"); |
| return -EOPNOTSUPP; |
| } |
| |
| list_add_tail(&mall_entry->list, &sparx5->mall_entries); |
| |
| return 0; |
| } |
| |
| static int sparx5_tc_matchall_destroy(struct net_device *ndev, |
| struct tc_cls_matchall_offload *tmo, |
| bool ingress) |
| { |
| struct sparx5_port *port = netdev_priv(ndev); |
| struct sparx5 *sparx5 = port->sparx5; |
| struct sparx5_mall_entry *entry; |
| int err = 0; |
| |
| entry = sparx5_tc_matchall_entry_find(&sparx5->mall_entries, |
| tmo->cookie); |
| if (!entry) |
| return -ENOENT; |
| |
| if (entry->type == FLOW_ACTION_MIRRED) { |
| sparx5_mirror_del(entry); |
| } else if (entry->type == FLOW_ACTION_GOTO) { |
| err = vcap_enable_lookups(sparx5->vcap_ctrl, ndev, |
| 0, 0, tmo->cookie, false); |
| } else { |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, "Unsupported action"); |
| err = -EOPNOTSUPP; |
| } |
| |
| list_del(&entry->list); |
| |
| return err; |
| } |
| |
| static int sparx5_tc_matchall_stats(struct net_device *ndev, |
| struct tc_cls_matchall_offload *tmo, |
| bool ingress) |
| { |
| struct sparx5_port *port = netdev_priv(ndev); |
| struct sparx5 *sparx5 = port->sparx5; |
| struct sparx5_mall_entry *entry; |
| |
| entry = sparx5_tc_matchall_entry_find(&sparx5->mall_entries, |
| tmo->cookie); |
| if (!entry) |
| return -ENOENT; |
| |
| if (entry->type == FLOW_ACTION_MIRRED) { |
| sparx5_mirror_stats(entry, &tmo->stats); |
| } else { |
| NL_SET_ERR_MSG_MOD(tmo->common.extack, "Unsupported action"); |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| int sparx5_tc_matchall(struct net_device *ndev, |
| struct tc_cls_matchall_offload *tmo, |
| bool ingress) |
| { |
| switch (tmo->command) { |
| case TC_CLSMATCHALL_REPLACE: |
| return sparx5_tc_matchall_replace(ndev, tmo, ingress); |
| case TC_CLSMATCHALL_DESTROY: |
| return sparx5_tc_matchall_destroy(ndev, tmo, ingress); |
| case TC_CLSMATCHALL_STATS: |
| return sparx5_tc_matchall_stats(ndev, tmo, ingress); |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |