diff options
Diffstat (limited to 'drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-flower.c')
-rw-r--r-- | drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-flower.c | 530 |
1 files changed, 458 insertions, 72 deletions
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-flower.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-flower.c index f9451ec5f2cb..d6eefbbf163f 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-flower.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-flower.c @@ -111,11 +111,11 @@ static int dpaa2_switch_flower_parse_key(struct flow_cls_offload *cls, return 0; } -int dpaa2_switch_acl_entry_add(struct dpaa2_switch_acl_tbl *acl_tbl, +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 = acl_tbl->ethsw; + struct ethsw_core *ethsw = filter_block->ethsw; struct dpsw_acl_key *acl_key = &entry->key; struct device *dev = ethsw->dev; u8 *cmd_buff; @@ -136,7 +136,7 @@ int dpaa2_switch_acl_entry_add(struct dpaa2_switch_acl_tbl *acl_tbl, } err = dpsw_acl_add_entry(ethsw->mc_io, 0, ethsw->dpsw_handle, - acl_tbl->id, acl_entry_cfg); + filter_block->acl_id, acl_entry_cfg); dma_unmap_single(dev, acl_entry_cfg->key_iova, sizeof(cmd_buff), DMA_TO_DEVICE); @@ -150,12 +150,13 @@ int dpaa2_switch_acl_entry_add(struct dpaa2_switch_acl_tbl *acl_tbl, return 0; } -static int dpaa2_switch_acl_entry_remove(struct dpaa2_switch_acl_tbl *acl_tbl, - struct dpaa2_switch_acl_entry *entry) +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 = acl_tbl->ethsw; + struct ethsw_core *ethsw = block->ethsw; struct device *dev = ethsw->dev; u8 *cmd_buff; int err; @@ -175,7 +176,7 @@ static int dpaa2_switch_acl_entry_remove(struct dpaa2_switch_acl_tbl *acl_tbl, } err = dpsw_acl_remove_entry(ethsw->mc_io, 0, ethsw->dpsw_handle, - acl_tbl->id, acl_entry_cfg); + block->acl_id, acl_entry_cfg); dma_unmap_single(dev, acl_entry_cfg->key_iova, sizeof(cmd_buff), DMA_TO_DEVICE); @@ -190,19 +191,19 @@ static int dpaa2_switch_acl_entry_remove(struct dpaa2_switch_acl_tbl *acl_tbl, } static int -dpaa2_switch_acl_entry_add_to_list(struct dpaa2_switch_acl_tbl *acl_tbl, +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(&acl_tbl->entries)) { - list_add(&entry->list, &acl_tbl->entries); + if (list_empty(&block->acl_entries)) { + list_add(&entry->list, &block->acl_entries); return index; } - list_for_each_safe(pos, n, &acl_tbl->entries) { + 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; @@ -213,13 +214,13 @@ dpaa2_switch_acl_entry_add_to_list(struct dpaa2_switch_acl_tbl *acl_tbl, } static struct dpaa2_switch_acl_entry* -dpaa2_switch_acl_entry_get_by_index(struct dpaa2_switch_acl_tbl *acl_tbl, +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, &acl_tbl->entries, list) { + list_for_each_entry(tmp, &block->acl_entries, list) { if (i == index) return tmp; ++i; @@ -229,37 +230,38 @@ dpaa2_switch_acl_entry_get_by_index(struct dpaa2_switch_acl_tbl *acl_tbl, } static int -dpaa2_switch_acl_entry_set_precedence(struct dpaa2_switch_acl_tbl *acl_tbl, +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(acl_tbl, entry); + err = dpaa2_switch_acl_entry_remove(block, entry); if (err) return err; entry->cfg.precedence = precedence; - return dpaa2_switch_acl_entry_add(acl_tbl, entry); + return dpaa2_switch_acl_entry_add(block, entry); } -static int dpaa2_switch_acl_tbl_add_entry(struct dpaa2_switch_acl_tbl *acl_tbl, - struct dpaa2_switch_acl_entry *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(acl_tbl, entry); + 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 - acl_tbl->num_rules - 1; + 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(acl_tbl, i); + tmp = dpaa2_switch_acl_entry_get_by_index(block, i); - err = dpaa2_switch_acl_entry_set_precedence(acl_tbl, tmp, + err = dpaa2_switch_acl_entry_set_precedence(block, tmp, precedence); if (err) return err; @@ -269,19 +271,19 @@ static int dpaa2_switch_acl_tbl_add_entry(struct dpaa2_switch_acl_tbl *acl_tbl, /* Add the new entry to hardware */ entry->cfg.precedence = precedence; - err = dpaa2_switch_acl_entry_add(acl_tbl, entry); - acl_tbl->num_rules++; + 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_acl_tbl *acl_tbl, +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, &acl_tbl->entries, list) { + list_for_each_entry_safe(tmp, n, &block->acl_entries, list) { if (tmp->cookie == cookie) return tmp; } @@ -289,13 +291,13 @@ dpaa2_switch_acl_tbl_find_entry_by_cookie(struct dpaa2_switch_acl_tbl *acl_tbl, } static int -dpaa2_switch_acl_entry_get_index(struct dpaa2_switch_acl_tbl *acl_tbl, +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, &acl_tbl->entries, list) { + list_for_each_entry_safe(tmp, n, &block->acl_entries, list) { if (tmp->cookie == entry->cookie) return index; index++; @@ -303,21 +305,34 @@ dpaa2_switch_acl_entry_get_index(struct dpaa2_switch_acl_tbl *acl_tbl, 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_acl_tbl *acl_tbl, +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(acl_tbl, entry); + index = dpaa2_switch_acl_entry_get_index(block, entry); /* Remove from hardware the ACL entry */ - err = dpaa2_switch_acl_entry_remove(acl_tbl, entry); + err = dpaa2_switch_acl_entry_remove(block, entry); if (err) return err; - acl_tbl->num_rules--; + block->num_acl_rules--; /* Remove it from the list also */ list_del(&entry->list); @@ -325,8 +340,8 @@ dpaa2_switch_acl_tbl_remove_entry(struct dpaa2_switch_acl_tbl *acl_tbl, /* 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(acl_tbl, i); - err = dpaa2_switch_acl_entry_set_precedence(acl_tbl, tmp, + tmp = dpaa2_switch_acl_entry_get_by_index(block, i); + err = dpaa2_switch_acl_entry_set_precedence(block, tmp, precedence); if (err) return err; @@ -339,10 +354,10 @@ dpaa2_switch_acl_tbl_remove_entry(struct dpaa2_switch_acl_tbl *acl_tbl, return 0; } -static int dpaa2_switch_tc_parse_action(struct ethsw_core *ethsw, - struct flow_action_entry *cls_act, - struct dpsw_acl_result *dpsw_act, - struct netlink_ext_ack *extack) +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; @@ -374,22 +389,110 @@ out: return err; } -int dpaa2_switch_cls_flower_replace(struct dpaa2_switch_acl_tbl *acl_tbl, +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 ethsw_core *ethsw = acl_tbl->ethsw; struct dpaa2_switch_acl_entry *acl_entry; + struct ethsw_core *ethsw = block->ethsw; struct flow_action_entry *act; int err; - if (!flow_offload_has_one_action(&rule->action)) { - NL_SET_ERR_MSG(extack, "Only singular actions are supported"); - return -EOPNOTSUPP; - } - - if (dpaa2_switch_acl_tbl_is_full(acl_tbl)) { + if (dpaa2_switch_acl_tbl_is_full(block)) { NL_SET_ERR_MSG(extack, "Maximum filter capacity reached"); return -ENOMEM; } @@ -403,15 +506,15 @@ int dpaa2_switch_cls_flower_replace(struct dpaa2_switch_acl_tbl *acl_tbl, goto free_acl_entry; act = &rule->action.entries[0]; - err = dpaa2_switch_tc_parse_action(ethsw, act, - &acl_entry->cfg.result, extack); + 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(acl_tbl, acl_entry); + err = dpaa2_switch_acl_tbl_add_entry(block, acl_entry); if (err) goto free_acl_entry; @@ -423,33 +526,171 @@ free_acl_entry: return err; } -int dpaa2_switch_cls_flower_destroy(struct dpaa2_switch_acl_tbl *acl_tbl, - struct flow_cls_offload *cls) +static int dpaa2_switch_flower_parse_mirror_key(struct flow_cls_offload *cls, + u16 *vlan) { - struct dpaa2_switch_acl_entry *entry; + 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); - entry = dpaa2_switch_acl_tbl_find_entry_by_cookie(acl_tbl, cls->cookie); - if (!entry) - return 0; + 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; + } - return dpaa2_switch_acl_tbl_remove_entry(acl_tbl, entry); + 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; } -int dpaa2_switch_cls_matchall_replace(struct dpaa2_switch_acl_tbl *acl_tbl, - struct tc_cls_matchall_offload *cls) +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 ethsw_core *ethsw = acl_tbl->ethsw; - struct dpaa2_switch_acl_entry *acl_entry; - struct flow_action_entry *act; + 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; - if (!flow_offload_has_one_action(&cls->rule->action)) { + 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; } - if (dpaa2_switch_acl_tbl_is_full(acl_tbl)) { + 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; } @@ -459,15 +700,15 @@ int dpaa2_switch_cls_matchall_replace(struct dpaa2_switch_acl_tbl *acl_tbl, return -ENOMEM; act = &cls->rule->action.entries[0]; - err = dpaa2_switch_tc_parse_action(ethsw, act, - &acl_entry->cfg.result, extack); + 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(acl_tbl, acl_entry); + err = dpaa2_switch_acl_tbl_add_entry(block, acl_entry); if (err) goto free_acl_entry; @@ -479,14 +720,159 @@ free_acl_entry: return err; } -int dpaa2_switch_cls_matchall_destroy(struct dpaa2_switch_acl_tbl *acl_tbl, +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_acl_entry *entry; + 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); - entry = dpaa2_switch_acl_tbl_find_entry_by_cookie(acl_tbl, cls->cookie); - if (!entry) - return 0; + /* 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 dpaa2_switch_acl_tbl_remove_entry(acl_tbl, entry); + return 0; } |