1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-flower.c
Ioana Ciornei 7a91f9078d dpaa2-switch: offload shared block mirror filters when binding to a port
When mirroring rules are added in shared filter blocks, the same
mirroring rule has to be configured on all the switch ports that are
part of the same block.

In case a switch port joins a shared block after mirroring filters have
been already added to it, then all the mirror rules should be offloaded
to the port. The reverse, removal of mirroring rules, has to be done at
block unbind.

For this purpose, the dpaa2_switch_block_offload_mirror() and
dpaa2_switch_block_unoffload_mirror() functions are added and called
upon binding and unbinding a switch port to/from a block.

Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-07-29 21:34:45 +01:00

878 lines
23 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* DPAA2 Ethernet Switch flower support
*
* Copyright 2021 NXP
*
*/
#include "dpaa2-switch.h"
static int dpaa2_switch_flower_parse_key(struct flow_cls_offload *cls,
struct dpsw_acl_key *acl_key)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct flow_dissector *dissector = rule->match.dissector;
struct netlink_ext_ack *extack = cls->common.extack;
struct dpsw_acl_fields *acl_h, *acl_m;
if (dissector->used_keys &
~(BIT(FLOW_DISSECTOR_KEY_BASIC) |
BIT(FLOW_DISSECTOR_KEY_CONTROL) |
BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_VLAN) |
BIT(FLOW_DISSECTOR_KEY_PORTS) |
BIT(FLOW_DISSECTOR_KEY_IP) |
BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS))) {
NL_SET_ERR_MSG_MOD(extack,
"Unsupported keys used");
return -EOPNOTSUPP;
}
acl_h = &acl_key->match;
acl_m = &acl_key->mask;
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
struct flow_match_basic match;
flow_rule_match_basic(rule, &match);
acl_h->l3_protocol = match.key->ip_proto;
acl_h->l2_ether_type = be16_to_cpu(match.key->n_proto);
acl_m->l3_protocol = match.mask->ip_proto;
acl_m->l2_ether_type = be16_to_cpu(match.mask->n_proto);
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
struct flow_match_eth_addrs match;
flow_rule_match_eth_addrs(rule, &match);
ether_addr_copy(acl_h->l2_dest_mac, &match.key->dst[0]);
ether_addr_copy(acl_h->l2_source_mac, &match.key->src[0]);
ether_addr_copy(acl_m->l2_dest_mac, &match.mask->dst[0]);
ether_addr_copy(acl_m->l2_source_mac, &match.mask->src[0]);
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) {
struct flow_match_vlan match;
flow_rule_match_vlan(rule, &match);
acl_h->l2_vlan_id = match.key->vlan_id;
acl_h->l2_tpid = be16_to_cpu(match.key->vlan_tpid);
acl_h->l2_pcp_dei = match.key->vlan_priority << 1 |
match.key->vlan_dei;
acl_m->l2_vlan_id = match.mask->vlan_id;
acl_m->l2_tpid = be16_to_cpu(match.mask->vlan_tpid);
acl_m->l2_pcp_dei = match.mask->vlan_priority << 1 |
match.mask->vlan_dei;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
struct flow_match_ipv4_addrs match;
flow_rule_match_ipv4_addrs(rule, &match);
acl_h->l3_source_ip = be32_to_cpu(match.key->src);
acl_h->l3_dest_ip = be32_to_cpu(match.key->dst);
acl_m->l3_source_ip = be32_to_cpu(match.mask->src);
acl_m->l3_dest_ip = be32_to_cpu(match.mask->dst);
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
struct flow_match_ports match;
flow_rule_match_ports(rule, &match);
acl_h->l4_source_port = be16_to_cpu(match.key->src);
acl_h->l4_dest_port = be16_to_cpu(match.key->dst);
acl_m->l4_source_port = be16_to_cpu(match.mask->src);
acl_m->l4_dest_port = be16_to_cpu(match.mask->dst);
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) {
struct flow_match_ip match;
flow_rule_match_ip(rule, &match);
if (match.mask->ttl != 0) {
NL_SET_ERR_MSG_MOD(extack,
"Matching on TTL not supported");
return -EOPNOTSUPP;
}
if ((match.mask->tos & 0x3) != 0) {
NL_SET_ERR_MSG_MOD(extack,
"Matching on ECN not supported, only DSCP");
return -EOPNOTSUPP;
}
acl_h->l3_dscp = match.key->tos >> 2;
acl_m->l3_dscp = match.mask->tos >> 2;
}
return 0;
}
int dpaa2_switch_acl_entry_add(struct dpaa2_switch_filter_block *filter_block,
struct dpaa2_switch_acl_entry *entry)
{
struct dpsw_acl_entry_cfg *acl_entry_cfg = &entry->cfg;
struct ethsw_core *ethsw = filter_block->ethsw;
struct dpsw_acl_key *acl_key = &entry->key;
struct device *dev = ethsw->dev;
u8 *cmd_buff;
int err;
cmd_buff = kzalloc(DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, GFP_KERNEL);
if (!cmd_buff)
return -ENOMEM;
dpsw_acl_prepare_entry_cfg(acl_key, cmd_buff);
acl_entry_cfg->key_iova = dma_map_single(dev, cmd_buff,
DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE,
DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(dev, acl_entry_cfg->key_iova))) {
dev_err(dev, "DMA mapping failed\n");
return -EFAULT;
}
err = dpsw_acl_add_entry(ethsw->mc_io, 0, ethsw->dpsw_handle,
filter_block->acl_id, acl_entry_cfg);
dma_unmap_single(dev, acl_entry_cfg->key_iova, sizeof(cmd_buff),
DMA_TO_DEVICE);
if (err) {
dev_err(dev, "dpsw_acl_add_entry() failed %d\n", err);
return err;
}
kfree(cmd_buff);
return 0;
}
static int
dpaa2_switch_acl_entry_remove(struct dpaa2_switch_filter_block *block,
struct dpaa2_switch_acl_entry *entry)
{
struct dpsw_acl_entry_cfg *acl_entry_cfg = &entry->cfg;
struct dpsw_acl_key *acl_key = &entry->key;
struct ethsw_core *ethsw = block->ethsw;
struct device *dev = ethsw->dev;
u8 *cmd_buff;
int err;
cmd_buff = kzalloc(DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, GFP_KERNEL);
if (!cmd_buff)
return -ENOMEM;
dpsw_acl_prepare_entry_cfg(acl_key, cmd_buff);
acl_entry_cfg->key_iova = dma_map_single(dev, cmd_buff,
DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE,
DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(dev, acl_entry_cfg->key_iova))) {
dev_err(dev, "DMA mapping failed\n");
return -EFAULT;
}
err = dpsw_acl_remove_entry(ethsw->mc_io, 0, ethsw->dpsw_handle,
block->acl_id, acl_entry_cfg);
dma_unmap_single(dev, acl_entry_cfg->key_iova, sizeof(cmd_buff),
DMA_TO_DEVICE);
if (err) {
dev_err(dev, "dpsw_acl_remove_entry() failed %d\n", err);
return err;
}
kfree(cmd_buff);
return 0;
}
static int
dpaa2_switch_acl_entry_add_to_list(struct dpaa2_switch_filter_block *block,
struct dpaa2_switch_acl_entry *entry)
{
struct dpaa2_switch_acl_entry *tmp;
struct list_head *pos, *n;
int index = 0;
if (list_empty(&block->acl_entries)) {
list_add(&entry->list, &block->acl_entries);
return index;
}
list_for_each_safe(pos, n, &block->acl_entries) {
tmp = list_entry(pos, struct dpaa2_switch_acl_entry, list);
if (entry->prio < tmp->prio)
break;
index++;
}
list_add(&entry->list, pos->prev);
return index;
}
static struct dpaa2_switch_acl_entry*
dpaa2_switch_acl_entry_get_by_index(struct dpaa2_switch_filter_block *block,
int index)
{
struct dpaa2_switch_acl_entry *tmp;
int i = 0;
list_for_each_entry(tmp, &block->acl_entries, list) {
if (i == index)
return tmp;
++i;
}
return NULL;
}
static int
dpaa2_switch_acl_entry_set_precedence(struct dpaa2_switch_filter_block *block,
struct dpaa2_switch_acl_entry *entry,
int precedence)
{
int err;
err = dpaa2_switch_acl_entry_remove(block, entry);
if (err)
return err;
entry->cfg.precedence = precedence;
return dpaa2_switch_acl_entry_add(block, entry);
}
static int
dpaa2_switch_acl_tbl_add_entry(struct dpaa2_switch_filter_block *block,
struct dpaa2_switch_acl_entry *entry)
{
struct dpaa2_switch_acl_entry *tmp;
int index, i, precedence, err;
/* Add the new ACL entry to the linked list and get its index */
index = dpaa2_switch_acl_entry_add_to_list(block, entry);
/* Move up in priority the ACL entries to make space
* for the new filter.
*/
precedence = DPAA2_ETHSW_PORT_MAX_ACL_ENTRIES - block->num_acl_rules - 1;
for (i = 0; i < index; i++) {
tmp = dpaa2_switch_acl_entry_get_by_index(block, i);
err = dpaa2_switch_acl_entry_set_precedence(block, tmp,
precedence);
if (err)
return err;
precedence++;
}
/* Add the new entry to hardware */
entry->cfg.precedence = precedence;
err = dpaa2_switch_acl_entry_add(block, entry);
block->num_acl_rules++;
return err;
}
static struct dpaa2_switch_acl_entry *
dpaa2_switch_acl_tbl_find_entry_by_cookie(struct dpaa2_switch_filter_block *block,
unsigned long cookie)
{
struct dpaa2_switch_acl_entry *tmp, *n;
list_for_each_entry_safe(tmp, n, &block->acl_entries, list) {
if (tmp->cookie == cookie)
return tmp;
}
return NULL;
}
static int
dpaa2_switch_acl_entry_get_index(struct dpaa2_switch_filter_block *block,
struct dpaa2_switch_acl_entry *entry)
{
struct dpaa2_switch_acl_entry *tmp, *n;
int index = 0;
list_for_each_entry_safe(tmp, n, &block->acl_entries, list) {
if (tmp->cookie == entry->cookie)
return index;
index++;
}
return -ENOENT;
}
static struct dpaa2_switch_mirror_entry *
dpaa2_switch_mirror_find_entry_by_cookie(struct dpaa2_switch_filter_block *block,
unsigned long cookie)
{
struct dpaa2_switch_mirror_entry *tmp, *n;
list_for_each_entry_safe(tmp, n, &block->mirror_entries, list) {
if (tmp->cookie == cookie)
return tmp;
}
return NULL;
}
static int
dpaa2_switch_acl_tbl_remove_entry(struct dpaa2_switch_filter_block *block,
struct dpaa2_switch_acl_entry *entry)
{
struct dpaa2_switch_acl_entry *tmp;
int index, i, precedence, err;
index = dpaa2_switch_acl_entry_get_index(block, entry);
/* Remove from hardware the ACL entry */
err = dpaa2_switch_acl_entry_remove(block, entry);
if (err)
return err;
block->num_acl_rules--;
/* Remove it from the list also */
list_del(&entry->list);
/* Move down in priority the entries over the deleted one */
precedence = entry->cfg.precedence;
for (i = index - 1; i >= 0; i--) {
tmp = dpaa2_switch_acl_entry_get_by_index(block, i);
err = dpaa2_switch_acl_entry_set_precedence(block, tmp,
precedence);
if (err)
return err;
precedence--;
}
kfree(entry);
return 0;
}
static int dpaa2_switch_tc_parse_action_acl(struct ethsw_core *ethsw,
struct flow_action_entry *cls_act,
struct dpsw_acl_result *dpsw_act,
struct netlink_ext_ack *extack)
{
int err = 0;
switch (cls_act->id) {
case FLOW_ACTION_TRAP:
dpsw_act->action = DPSW_ACL_ACTION_REDIRECT_TO_CTRL_IF;
break;
case FLOW_ACTION_REDIRECT:
if (!dpaa2_switch_port_dev_check(cls_act->dev)) {
NL_SET_ERR_MSG_MOD(extack,
"Destination not a DPAA2 switch port");
return -EOPNOTSUPP;
}
dpsw_act->if_id = dpaa2_switch_get_index(ethsw, cls_act->dev);
dpsw_act->action = DPSW_ACL_ACTION_REDIRECT;
break;
case FLOW_ACTION_DROP:
dpsw_act->action = DPSW_ACL_ACTION_DROP;
break;
default:
NL_SET_ERR_MSG_MOD(extack,
"Action not supported");
err = -EOPNOTSUPP;
goto out;
}
out:
return err;
}
static int
dpaa2_switch_block_add_mirror(struct dpaa2_switch_filter_block *block,
struct dpaa2_switch_mirror_entry *entry,
u16 to, struct netlink_ext_ack *extack)
{
unsigned long block_ports = block->ports;
struct ethsw_core *ethsw = block->ethsw;
struct ethsw_port_priv *port_priv;
unsigned long ports_added = 0;
u16 vlan = entry->cfg.vlan_id;
bool mirror_port_enabled;
int err, port;
/* Setup the mirroring port */
mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs);
if (!mirror_port_enabled) {
err = dpsw_set_reflection_if(ethsw->mc_io, 0,
ethsw->dpsw_handle, to);
if (err)
return err;
ethsw->mirror_port = to;
}
/* Setup the same egress mirroring configuration on all the switch
* ports that share the same filter block.
*/
for_each_set_bit(port, &block_ports, ethsw->sw_attr.num_ifs) {
port_priv = ethsw->ports[port];
/* We cannot add a per VLAN mirroring rule if the VLAN in
* question is not installed on the switch port.
*/
if (entry->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_VLAN &&
!(port_priv->vlans[vlan] & ETHSW_VLAN_MEMBER)) {
NL_SET_ERR_MSG(extack,
"VLAN must be installed on the switch port");
err = -EINVAL;
goto err_remove_filters;
}
err = dpsw_if_add_reflection(ethsw->mc_io, 0,
ethsw->dpsw_handle,
port, &entry->cfg);
if (err)
goto err_remove_filters;
ports_added |= BIT(port);
}
list_add(&entry->list, &block->mirror_entries);
return 0;
err_remove_filters:
for_each_set_bit(port, &ports_added, ethsw->sw_attr.num_ifs) {
dpsw_if_remove_reflection(ethsw->mc_io, 0, ethsw->dpsw_handle,
port, &entry->cfg);
}
if (!mirror_port_enabled)
ethsw->mirror_port = ethsw->sw_attr.num_ifs;
return err;
}
static int
dpaa2_switch_block_remove_mirror(struct dpaa2_switch_filter_block *block,
struct dpaa2_switch_mirror_entry *entry)
{
struct dpsw_reflection_cfg *cfg = &entry->cfg;
unsigned long block_ports = block->ports;
struct ethsw_core *ethsw = block->ethsw;
int port;
/* Remove this mirroring configuration from all the ports belonging to
* the filter block.
*/
for_each_set_bit(port, &block_ports, ethsw->sw_attr.num_ifs)
dpsw_if_remove_reflection(ethsw->mc_io, 0, ethsw->dpsw_handle,
port, cfg);
/* Also remove it from the list of mirror filters */
list_del(&entry->list);
kfree(entry);
/* If this was the last mirror filter, then unset the mirror port */
if (list_empty(&block->mirror_entries))
ethsw->mirror_port = ethsw->sw_attr.num_ifs;
return 0;
}
static int
dpaa2_switch_cls_flower_replace_acl(struct dpaa2_switch_filter_block *block,
struct flow_cls_offload *cls)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct netlink_ext_ack *extack = cls->common.extack;
struct dpaa2_switch_acl_entry *acl_entry;
struct ethsw_core *ethsw = block->ethsw;
struct flow_action_entry *act;
int err;
if (dpaa2_switch_acl_tbl_is_full(block)) {
NL_SET_ERR_MSG(extack, "Maximum filter capacity reached");
return -ENOMEM;
}
acl_entry = kzalloc(sizeof(*acl_entry), GFP_KERNEL);
if (!acl_entry)
return -ENOMEM;
err = dpaa2_switch_flower_parse_key(cls, &acl_entry->key);
if (err)
goto free_acl_entry;
act = &rule->action.entries[0];
err = dpaa2_switch_tc_parse_action_acl(ethsw, act,
&acl_entry->cfg.result, extack);
if (err)
goto free_acl_entry;
acl_entry->prio = cls->common.prio;
acl_entry->cookie = cls->cookie;
err = dpaa2_switch_acl_tbl_add_entry(block, acl_entry);
if (err)
goto free_acl_entry;
return 0;
free_acl_entry:
kfree(acl_entry);
return err;
}
static int dpaa2_switch_flower_parse_mirror_key(struct flow_cls_offload *cls,
u16 *vlan)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct flow_dissector *dissector = rule->match.dissector;
struct netlink_ext_ack *extack = cls->common.extack;
if (dissector->used_keys &
~(BIT(FLOW_DISSECTOR_KEY_BASIC) |
BIT(FLOW_DISSECTOR_KEY_CONTROL) |
BIT(FLOW_DISSECTOR_KEY_VLAN))) {
NL_SET_ERR_MSG_MOD(extack,
"Mirroring is supported only per VLAN");
return -EOPNOTSUPP;
}
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_priority != 0 ||
match.mask->vlan_dei != 0) {
NL_SET_ERR_MSG_MOD(extack,
"Only matching on VLAN ID supported");
return -EOPNOTSUPP;
}
if (match.mask->vlan_id != 0xFFF) {
NL_SET_ERR_MSG_MOD(extack,
"Masked matching not supported");
return -EOPNOTSUPP;
}
*vlan = (u16)match.key->vlan_id;
}
return 0;
}
static int
dpaa2_switch_cls_flower_replace_mirror(struct dpaa2_switch_filter_block *block,
struct flow_cls_offload *cls)
{
struct netlink_ext_ack *extack = cls->common.extack;
struct dpaa2_switch_mirror_entry *mirror_entry;
struct ethsw_core *ethsw = block->ethsw;
struct dpaa2_switch_mirror_entry *tmp;
struct flow_action_entry *cls_act;
struct list_head *pos, *n;
bool mirror_port_enabled;
u16 if_id, vlan;
int err;
mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs);
cls_act = &cls->rule->action.entries[0];
/* Offload rules only when the destination is a DPAA2 switch port */
if (!dpaa2_switch_port_dev_check(cls_act->dev)) {
NL_SET_ERR_MSG_MOD(extack,
"Destination not a DPAA2 switch port");
return -EOPNOTSUPP;
}
if_id = dpaa2_switch_get_index(ethsw, cls_act->dev);
/* We have a single mirror port but can configure egress mirroring on
* all the other switch ports. We need to allow mirroring rules only
* when the destination port is the same.
*/
if (mirror_port_enabled && ethsw->mirror_port != if_id) {
NL_SET_ERR_MSG_MOD(extack,
"Multiple mirror ports not supported");
return -EBUSY;
}
/* Parse the key */
err = dpaa2_switch_flower_parse_mirror_key(cls, &vlan);
if (err)
return err;
/* Make sure that we don't already have a mirror rule with the same
* configuration.
*/
list_for_each_safe(pos, n, &block->mirror_entries) {
tmp = list_entry(pos, struct dpaa2_switch_mirror_entry, list);
if (tmp->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_VLAN &&
tmp->cfg.vlan_id == vlan) {
NL_SET_ERR_MSG_MOD(extack,
"VLAN mirror filter already installed");
return -EBUSY;
}
}
mirror_entry = kzalloc(sizeof(*mirror_entry), GFP_KERNEL);
if (!mirror_entry)
return -ENOMEM;
mirror_entry->cfg.filter = DPSW_REFLECTION_FILTER_INGRESS_VLAN;
mirror_entry->cfg.vlan_id = vlan;
mirror_entry->cookie = cls->cookie;
return dpaa2_switch_block_add_mirror(block, mirror_entry, if_id,
extack);
}
int dpaa2_switch_cls_flower_replace(struct dpaa2_switch_filter_block *block,
struct flow_cls_offload *cls)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct netlink_ext_ack *extack = cls->common.extack;
struct flow_action_entry *act;
if (!flow_offload_has_one_action(&rule->action)) {
NL_SET_ERR_MSG(extack, "Only singular actions are supported");
return -EOPNOTSUPP;
}
act = &rule->action.entries[0];
switch (act->id) {
case FLOW_ACTION_REDIRECT:
case FLOW_ACTION_TRAP:
case FLOW_ACTION_DROP:
return dpaa2_switch_cls_flower_replace_acl(block, cls);
case FLOW_ACTION_MIRRED:
return dpaa2_switch_cls_flower_replace_mirror(block, cls);
default:
NL_SET_ERR_MSG_MOD(extack, "Action not supported");
return -EOPNOTSUPP;
}
}
int dpaa2_switch_cls_flower_destroy(struct dpaa2_switch_filter_block *block,
struct flow_cls_offload *cls)
{
struct dpaa2_switch_mirror_entry *mirror_entry;
struct dpaa2_switch_acl_entry *acl_entry;
/* If this filter is a an ACL one, remove it */
acl_entry = dpaa2_switch_acl_tbl_find_entry_by_cookie(block,
cls->cookie);
if (acl_entry)
return dpaa2_switch_acl_tbl_remove_entry(block, acl_entry);
/* If not, then it has to be a mirror */
mirror_entry = dpaa2_switch_mirror_find_entry_by_cookie(block,
cls->cookie);
if (mirror_entry)
return dpaa2_switch_block_remove_mirror(block,
mirror_entry);
return 0;
}
static int
dpaa2_switch_cls_matchall_replace_acl(struct dpaa2_switch_filter_block *block,
struct tc_cls_matchall_offload *cls)
{
struct netlink_ext_ack *extack = cls->common.extack;
struct ethsw_core *ethsw = block->ethsw;
struct dpaa2_switch_acl_entry *acl_entry;
struct flow_action_entry *act;
int err;
if (dpaa2_switch_acl_tbl_is_full(block)) {
NL_SET_ERR_MSG(extack, "Maximum filter capacity reached");
return -ENOMEM;
}
acl_entry = kzalloc(sizeof(*acl_entry), GFP_KERNEL);
if (!acl_entry)
return -ENOMEM;
act = &cls->rule->action.entries[0];
err = dpaa2_switch_tc_parse_action_acl(ethsw, act,
&acl_entry->cfg.result, extack);
if (err)
goto free_acl_entry;
acl_entry->prio = cls->common.prio;
acl_entry->cookie = cls->cookie;
err = dpaa2_switch_acl_tbl_add_entry(block, acl_entry);
if (err)
goto free_acl_entry;
return 0;
free_acl_entry:
kfree(acl_entry);
return err;
}
static int
dpaa2_switch_cls_matchall_replace_mirror(struct dpaa2_switch_filter_block *block,
struct tc_cls_matchall_offload *cls)
{
struct netlink_ext_ack *extack = cls->common.extack;
struct dpaa2_switch_mirror_entry *mirror_entry;
struct ethsw_core *ethsw = block->ethsw;
struct dpaa2_switch_mirror_entry *tmp;
struct flow_action_entry *cls_act;
struct list_head *pos, *n;
bool mirror_port_enabled;
u16 if_id;
mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs);
cls_act = &cls->rule->action.entries[0];
/* Offload rules only when the destination is a DPAA2 switch port */
if (!dpaa2_switch_port_dev_check(cls_act->dev)) {
NL_SET_ERR_MSG_MOD(extack,
"Destination not a DPAA2 switch port");
return -EOPNOTSUPP;
}
if_id = dpaa2_switch_get_index(ethsw, cls_act->dev);
/* We have a single mirror port but can configure egress mirroring on
* all the other switch ports. We need to allow mirroring rules only
* when the destination port is the same.
*/
if (mirror_port_enabled && ethsw->mirror_port != if_id) {
NL_SET_ERR_MSG_MOD(extack,
"Multiple mirror ports not supported");
return -EBUSY;
}
/* Make sure that we don't already have a mirror rule with the same
* configuration. One matchall rule per block is the maximum.
*/
list_for_each_safe(pos, n, &block->mirror_entries) {
tmp = list_entry(pos, struct dpaa2_switch_mirror_entry, list);
if (tmp->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_ALL) {
NL_SET_ERR_MSG_MOD(extack,
"Matchall mirror filter already installed");
return -EBUSY;
}
}
mirror_entry = kzalloc(sizeof(*mirror_entry), GFP_KERNEL);
if (!mirror_entry)
return -ENOMEM;
mirror_entry->cfg.filter = DPSW_REFLECTION_FILTER_INGRESS_ALL;
mirror_entry->cookie = cls->cookie;
return dpaa2_switch_block_add_mirror(block, mirror_entry, if_id,
extack);
}
int dpaa2_switch_cls_matchall_replace(struct dpaa2_switch_filter_block *block,
struct tc_cls_matchall_offload *cls)
{
struct netlink_ext_ack *extack = cls->common.extack;
struct flow_action_entry *act;
if (!flow_offload_has_one_action(&cls->rule->action)) {
NL_SET_ERR_MSG(extack, "Only singular actions are supported");
return -EOPNOTSUPP;
}
act = &cls->rule->action.entries[0];
switch (act->id) {
case FLOW_ACTION_REDIRECT:
case FLOW_ACTION_TRAP:
case FLOW_ACTION_DROP:
return dpaa2_switch_cls_matchall_replace_acl(block, cls);
case FLOW_ACTION_MIRRED:
return dpaa2_switch_cls_matchall_replace_mirror(block, cls);
default:
NL_SET_ERR_MSG_MOD(extack, "Action not supported");
return -EOPNOTSUPP;
}
}
int dpaa2_switch_block_offload_mirror(struct dpaa2_switch_filter_block *block,
struct ethsw_port_priv *port_priv)
{
struct ethsw_core *ethsw = port_priv->ethsw_data;
struct dpaa2_switch_mirror_entry *tmp;
int err;
list_for_each_entry(tmp, &block->mirror_entries, list) {
err = dpsw_if_add_reflection(ethsw->mc_io, 0,
ethsw->dpsw_handle,
port_priv->idx, &tmp->cfg);
if (err)
goto unwind_add;
}
return 0;
unwind_add:
list_for_each_entry(tmp, &block->mirror_entries, list)
dpsw_if_remove_reflection(ethsw->mc_io, 0,
ethsw->dpsw_handle,
port_priv->idx, &tmp->cfg);
return err;
}
int dpaa2_switch_block_unoffload_mirror(struct dpaa2_switch_filter_block *block,
struct ethsw_port_priv *port_priv)
{
struct ethsw_core *ethsw = port_priv->ethsw_data;
struct dpaa2_switch_mirror_entry *tmp;
int err;
list_for_each_entry(tmp, &block->mirror_entries, list) {
err = dpsw_if_remove_reflection(ethsw->mc_io, 0,
ethsw->dpsw_handle,
port_priv->idx, &tmp->cfg);
if (err)
goto unwind_remove;
}
return 0;
unwind_remove:
list_for_each_entry(tmp, &block->mirror_entries, list)
dpsw_if_add_reflection(ethsw->mc_io, 0, ethsw->dpsw_handle,
port_priv->idx, &tmp->cfg);
return err;
}
int dpaa2_switch_cls_matchall_destroy(struct dpaa2_switch_filter_block *block,
struct tc_cls_matchall_offload *cls)
{
struct dpaa2_switch_mirror_entry *mirror_entry;
struct dpaa2_switch_acl_entry *acl_entry;
/* If this filter is a an ACL one, remove it */
acl_entry = dpaa2_switch_acl_tbl_find_entry_by_cookie(block,
cls->cookie);
if (acl_entry)
return dpaa2_switch_acl_tbl_remove_entry(block,
acl_entry);
/* If not, then it has to be a mirror */
mirror_entry = dpaa2_switch_mirror_find_entry_by_cookie(block,
cls->cookie);
if (mirror_entry)
return dpaa2_switch_block_remove_mirror(block,
mirror_entry);
return 0;
}