881 lines
23 KiB
C
881 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;
|
||
|
int ret = -EOPNOTSUPP;
|
||
|
|
||
|
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;
|
||
|
ret = 0;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|