linux/drivers/net/ethernet/mscc/ocelot_flower.c

951 lines
26 KiB
C
Raw Normal View History

// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Microsemi Ocelot Switch driver
* Copyright (c) 2019 Microsemi Corporation
*/
#include <net/pkt_cls.h>
#include <net/tc_act/tc_gact.h>
#include <soc/mscc/ocelot_vcap.h>
#include "ocelot_vcap.h"
/* Arbitrarily chosen constants for encoding the VCAP block and lookup number
* into the chain number. This is UAPI.
*/
#define VCAP_BLOCK 10000
#define VCAP_LOOKUP 1000
#define VCAP_IS1_NUM_LOOKUPS 3
#define VCAP_IS2_NUM_LOOKUPS 2
#define VCAP_IS2_NUM_PAG 256
#define VCAP_IS1_CHAIN(lookup) \
(1 * VCAP_BLOCK + (lookup) * VCAP_LOOKUP)
#define VCAP_IS2_CHAIN(lookup, pag) \
(2 * VCAP_BLOCK + (lookup) * VCAP_LOOKUP + (pag))
/* PSFP chain and block ID */
#define PSFP_BLOCK_ID OCELOT_NUM_VCAP_BLOCKS
#define OCELOT_PSFP_CHAIN (3 * VCAP_BLOCK)
static int ocelot_chain_to_block(int chain, bool ingress)
{
int lookup, pag;
if (!ingress) {
if (chain == 0)
return VCAP_ES0;
return -EOPNOTSUPP;
}
/* Backwards compatibility with older, single-chain tc-flower
* offload support in Ocelot
*/
if (chain == 0)
return VCAP_IS2;
for (lookup = 0; lookup < VCAP_IS1_NUM_LOOKUPS; lookup++)
if (chain == VCAP_IS1_CHAIN(lookup))
return VCAP_IS1;
for (lookup = 0; lookup < VCAP_IS2_NUM_LOOKUPS; lookup++)
for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++)
if (chain == VCAP_IS2_CHAIN(lookup, pag))
return VCAP_IS2;
if (chain == OCELOT_PSFP_CHAIN)
return PSFP_BLOCK_ID;
return -EOPNOTSUPP;
}
/* Caller must ensure this is a valid IS1 or IS2 chain first,
* by calling ocelot_chain_to_block.
*/
static int ocelot_chain_to_lookup(int chain)
{
return (chain / VCAP_LOOKUP) % 10;
}
/* Caller must ensure this is a valid IS2 chain first,
* by calling ocelot_chain_to_block.
*/
static int ocelot_chain_to_pag(int chain)
{
int lookup = ocelot_chain_to_lookup(chain);
/* calculate PAG value as chain index relative to the first PAG */
return chain - VCAP_IS2_CHAIN(lookup, 0);
}
static bool ocelot_is_goto_target_valid(int goto_target, int chain,
bool ingress)
{
int pag;
/* Can't offload GOTO in VCAP ES0 */
if (!ingress)
return (goto_target < 0);
/* Non-optional GOTOs */
if (chain == 0)
/* VCAP IS1 can be skipped, either partially or completely */
return (goto_target == VCAP_IS1_CHAIN(0) ||
goto_target == VCAP_IS1_CHAIN(1) ||
goto_target == VCAP_IS1_CHAIN(2) ||
goto_target == VCAP_IS2_CHAIN(0, 0) ||
goto_target == VCAP_IS2_CHAIN(1, 0) ||
goto_target == OCELOT_PSFP_CHAIN);
if (chain == VCAP_IS1_CHAIN(0))
return (goto_target == VCAP_IS1_CHAIN(1));
if (chain == VCAP_IS1_CHAIN(1))
return (goto_target == VCAP_IS1_CHAIN(2));
/* Lookup 2 of VCAP IS1 can really support non-optional GOTOs,
* using a Policy Association Group (PAG) value, which is an 8-bit
* value encoding a VCAP IS2 target chain.
*/
if (chain == VCAP_IS1_CHAIN(2)) {
for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++)
if (goto_target == VCAP_IS2_CHAIN(0, pag))
return true;
return false;
}
/* Non-optional GOTO from VCAP IS2 lookup 0 to lookup 1.
* We cannot change the PAG at this point.
*/
for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++)
if (chain == VCAP_IS2_CHAIN(0, pag))
return (goto_target == VCAP_IS2_CHAIN(1, pag));
/* VCAP IS2 lookup 1 can goto to PSFP block if hardware support */
for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++)
if (chain == VCAP_IS2_CHAIN(1, pag))
return (goto_target == OCELOT_PSFP_CHAIN);
return false;
}
static struct ocelot_vcap_filter *
ocelot_find_vcap_filter_that_points_at(struct ocelot *ocelot, int chain)
{
struct ocelot_vcap_filter *filter;
struct ocelot_vcap_block *block;
int block_id;
block_id = ocelot_chain_to_block(chain, true);
if (block_id < 0)
return NULL;
if (block_id == VCAP_IS2) {
block = &ocelot->block[VCAP_IS1];
list_for_each_entry(filter, &block->rules, list)
if (filter->type == OCELOT_VCAP_FILTER_PAG &&
filter->goto_target == chain)
return filter;
}
list_for_each_entry(filter, &ocelot->dummy_rules, list)
if (filter->goto_target == chain)
return filter;
return NULL;
}
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0 Currently the ocelot driver does support the 'vlan modify' action, but in the ingress chain, and it is offloaded to VCAP IS1. This action changes the classified VLAN before the packet enters the bridging service, and the bridging works with the classified VLAN modified by VCAP IS1. That is good for some use cases, but there are others where the VLAN must be modified at the stage of the egress port, after the packet has exited the bridging service. One example is simulating IEEE 802.1CB active stream identification filters ("active" means that not only the rule matches on a packet flow, but it is also able to change some headers). For example, a stream is replicated on two egress ports, but they must have different VLAN IDs on egress ports A and B. This seems like a task for the VCAP ES0, but that currently only supports pushing the ES0 tag A, which is specified in the rule. Pushing another VLAN header is not what we want, but rather overwriting the existing one. It looks like when we push the ES0 tag A, it is actually possible to not only take the ES0 tag A's value from the rule itself (VID_A_VAL), but derive it from the following formula: ES0_TAG_A = Classified VID + VID_A_VAL Otherwise said, ES0_TAG_A can be used to increment with a given value the VLAN ID that the packet was already classified to, and the packet will have this value as an outer VLAN tag. This new VLAN ID value then gets stripped on egress (or not) according to the value of the native VLAN from the bridging service. While the hardware will happily increment the classified VLAN ID for all packets that match the ES0 rule, in practice this would be rather insane, so we only allow this kind of ES0 action if the ES0 filter contains a VLAN ID too, so as to restrict the matching on a known classified VLAN. If we program VID_A_VAL with the delta between the desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the desired behavior. It doesn't look like it is possible with the tc-vlan action to modify the VLAN ID but not the PCP. In hardware it is possible to leave the PCP to the classified value, but we unconditionally program it to overwrite it with the PCP value from the rule. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-01 15:15:26 +00:00
static int
ocelot_flower_parse_ingress_vlan_modify(struct ocelot *ocelot, int port,
struct ocelot_vcap_filter *filter,
const struct flow_action_entry *a,
struct netlink_ext_ack *extack)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
if (filter->goto_target != -1) {
NL_SET_ERR_MSG_MOD(extack,
"Last action must be GOTO");
return -EOPNOTSUPP;
}
if (!ocelot_port->vlan_aware) {
NL_SET_ERR_MSG_MOD(extack,
"Can only modify VLAN under VLAN aware bridge");
return -EOPNOTSUPP;
}
filter->action.vid_replace_ena = true;
filter->action.pcp_dei_ena = true;
filter->action.vid = a->vlan.vid;
filter->action.pcp = a->vlan.prio;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
return 0;
}
static int
ocelot_flower_parse_egress_vlan_modify(struct ocelot_vcap_filter *filter,
const struct flow_action_entry *a,
struct netlink_ext_ack *extack)
{
enum ocelot_tag_tpid_sel tpid;
switch (ntohs(a->vlan.proto)) {
case ETH_P_8021Q:
tpid = OCELOT_TAG_TPID_SEL_8021Q;
break;
case ETH_P_8021AD:
tpid = OCELOT_TAG_TPID_SEL_8021AD;
break;
default:
NL_SET_ERR_MSG_MOD(extack,
"Cannot modify custom TPID");
return -EOPNOTSUPP;
}
filter->action.tag_a_tpid_sel = tpid;
filter->action.push_outer_tag = OCELOT_ES0_TAG;
filter->action.tag_a_vid_sel = OCELOT_ES0_VID_PLUS_CLASSIFIED_VID;
filter->action.vid_a_val = a->vlan.vid;
filter->action.pcp_a_val = a->vlan.prio;
filter->action.tag_a_pcp_sel = OCELOT_ES0_PCP;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
return 0;
}
net: mscc: ocelot: offload VLAN mangle action to VCAP IS1 The VCAP_IS1_ACT_VID_REPLACE_ENA action, from the VCAP IS1 ingress TCAM, changes the classified VLAN. We are only exposing this ability for switch ports that are under VLAN aware bridges. This is because in standalone ports mode and under a bridge with vlan_filtering=0, the ocelot driver configures the switch to operate as VLAN-unaware, so the classified VLAN is not derived from the 802.1Q header from the packet, but instead is always equal to the port-based VLAN ID of the ingress port. We _can_ still change the classified VLAN for packets when operating in this mode, but the end result will most likely be a drop, since both the ingress and the egress port need to be members of the modified VLAN. And even if we install the new classified VLAN into the VLAN table of the switch, the result would still not be as expected: we wouldn't see, on the output port, the modified VLAN tag, but the original one, even though the classified VLAN was indeed modified. This is because of how the hardware works: on egress, what is pushed to the frame is a "port tag", which gives us the following options: - Tag all frames with port tag (derived from the classified VLAN) - Tag all frames with port tag, except if the classified VLAN is 0 or equal to the native VLAN of the egress port - No port tag Needless to say, in VLAN-unaware mode we are disabling the port tag. Otherwise, the existing VLAN tag would be ignored, and a second VLAN tag (the port tag), holding the classified VLAN, would be pushed (instead of replacing the existing 802.1Q tag). This is definitely not what the user wanted when installing a "vlan modify" action. So it is simply not worth bothering with VLAN modify rules under other configurations except when the ports are fully VLAN-aware. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2020-10-08 11:56:58 +00:00
static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
bool ingress, struct flow_cls_offload *f,
struct ocelot_vcap_filter *filter)
{
struct netlink_ext_ack *extack = f->common.extack;
bool allow_missing_goto_target = false;
const struct flow_action_entry *a;
enum ocelot_tag_tpid_sel tpid;
int i, chain, egress_port;
u32 pol_ix, pol_max;
u64 rate;
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0 Currently the ocelot driver does support the 'vlan modify' action, but in the ingress chain, and it is offloaded to VCAP IS1. This action changes the classified VLAN before the packet enters the bridging service, and the bridging works with the classified VLAN modified by VCAP IS1. That is good for some use cases, but there are others where the VLAN must be modified at the stage of the egress port, after the packet has exited the bridging service. One example is simulating IEEE 802.1CB active stream identification filters ("active" means that not only the rule matches on a packet flow, but it is also able to change some headers). For example, a stream is replicated on two egress ports, but they must have different VLAN IDs on egress ports A and B. This seems like a task for the VCAP ES0, but that currently only supports pushing the ES0 tag A, which is specified in the rule. Pushing another VLAN header is not what we want, but rather overwriting the existing one. It looks like when we push the ES0 tag A, it is actually possible to not only take the ES0 tag A's value from the rule itself (VID_A_VAL), but derive it from the following formula: ES0_TAG_A = Classified VID + VID_A_VAL Otherwise said, ES0_TAG_A can be used to increment with a given value the VLAN ID that the packet was already classified to, and the packet will have this value as an outer VLAN tag. This new VLAN ID value then gets stripped on egress (or not) according to the value of the native VLAN from the bridging service. While the hardware will happily increment the classified VLAN ID for all packets that match the ES0 rule, in practice this would be rather insane, so we only allow this kind of ES0 action if the ES0 filter contains a VLAN ID too, so as to restrict the matching on a known classified VLAN. If we program VID_A_VAL with the delta between the desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the desired behavior. It doesn't look like it is possible with the tc-vlan action to modify the VLAN ID but not the PCP. In hardware it is possible to leave the PCP to the classified value, but we unconditionally program it to overwrite it with the PCP value from the rule. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-01 15:15:26 +00:00
int err;
if (!flow_action_basic_hw_stats_check(&f->rule->action,
f->common.extack))
return -EOPNOTSUPP;
chain = f->common.chain_index;
filter->block_id = ocelot_chain_to_block(chain, ingress);
if (filter->block_id < 0) {
NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain");
return -EOPNOTSUPP;
}
if (filter->block_id == VCAP_IS1 || filter->block_id == VCAP_IS2)
filter->lookup = ocelot_chain_to_lookup(chain);
if (filter->block_id == VCAP_IS2)
filter->pag = ocelot_chain_to_pag(chain);
filter->goto_target = -1;
filter->type = OCELOT_VCAP_FILTER_DUMMY;
flow_action_for_each(i, a, &f->rule->action) {
switch (a->id) {
case FLOW_ACTION_DROP:
if (filter->block_id != VCAP_IS2) {
NL_SET_ERR_MSG_MOD(extack,
"Drop action can only be offloaded to VCAP IS2");
return -EOPNOTSUPP;
}
if (filter->goto_target != -1) {
NL_SET_ERR_MSG_MOD(extack,
"Last action must be GOTO");
return -EOPNOTSUPP;
}
filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY;
filter->action.port_mask = 0;
filter->action.police_ena = true;
filter->action.pol_ix = OCELOT_POLICER_DISCARD;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break;
case FLOW_ACTION_TRAP:
if (filter->block_id != VCAP_IS2) {
NL_SET_ERR_MSG_MOD(extack,
"Trap action can only be offloaded to VCAP IS2");
return -EOPNOTSUPP;
}
if (filter->goto_target != -1) {
NL_SET_ERR_MSG_MOD(extack,
"Last action must be GOTO");
return -EOPNOTSUPP;
}
filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY;
filter->action.port_mask = 0;
filter->action.cpu_copy_ena = true;
filter->action.cpu_qu_num = 0;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break;
case FLOW_ACTION_POLICE:
if (filter->block_id == PSFP_BLOCK_ID) {
filter->type = OCELOT_PSFP_FILTER_OFFLOAD;
break;
}
if (filter->block_id != VCAP_IS2 ||
filter->lookup != 0) {
NL_SET_ERR_MSG_MOD(extack,
"Police action can only be offloaded to VCAP IS2 lookup 0 or PSFP");
return -EOPNOTSUPP;
}
if (filter->goto_target != -1) {
NL_SET_ERR_MSG_MOD(extack,
"Last action must be GOTO");
return -EOPNOTSUPP;
}
if (a->police.rate_pkt_ps) {
NL_SET_ERR_MSG_MOD(extack,
"QoS offload not support packets per second");
return -EOPNOTSUPP;
}
filter->action.police_ena = true;
pol_ix = a->hw_index + ocelot->vcap_pol.base;
pol_max = ocelot->vcap_pol.max;
if (ocelot->vcap_pol.max2 && pol_ix > pol_max) {
pol_ix += ocelot->vcap_pol.base2 - pol_max - 1;
pol_max = ocelot->vcap_pol.max2;
}
if (pol_ix >= pol_max)
return -EINVAL;
filter->action.pol_ix = pol_ix;
rate = a->police.rate_bytes_ps;
filter->action.pol.rate = div_u64(rate, 1000) * 8;
filter->action.pol.burst = a->police.burst;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break;
case FLOW_ACTION_REDIRECT:
if (filter->block_id != VCAP_IS2) {
NL_SET_ERR_MSG_MOD(extack,
"Redirect action can only be offloaded to VCAP IS2");
return -EOPNOTSUPP;
}
if (filter->goto_target != -1) {
NL_SET_ERR_MSG_MOD(extack,
"Last action must be GOTO");
return -EOPNOTSUPP;
}
egress_port = ocelot->ops->netdev_to_port(a->dev);
if (egress_port < 0) {
NL_SET_ERR_MSG_MOD(extack,
"Destination not an ocelot port");
return -EOPNOTSUPP;
}
filter->action.mask_mode = OCELOT_MASK_MODE_REDIRECT;
filter->action.port_mask = BIT(egress_port);
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break;
case FLOW_ACTION_VLAN_POP:
if (filter->block_id != VCAP_IS1) {
NL_SET_ERR_MSG_MOD(extack,
"VLAN pop action can only be offloaded to VCAP IS1");
return -EOPNOTSUPP;
}
if (filter->goto_target != -1) {
NL_SET_ERR_MSG_MOD(extack,
"Last action must be GOTO");
return -EOPNOTSUPP;
}
filter->action.vlan_pop_cnt_ena = true;
filter->action.vlan_pop_cnt++;
if (filter->action.vlan_pop_cnt > 2) {
NL_SET_ERR_MSG_MOD(extack,
"Cannot pop more than 2 VLAN headers");
return -EOPNOTSUPP;
}
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break;
net: mscc: ocelot: offload VLAN mangle action to VCAP IS1 The VCAP_IS1_ACT_VID_REPLACE_ENA action, from the VCAP IS1 ingress TCAM, changes the classified VLAN. We are only exposing this ability for switch ports that are under VLAN aware bridges. This is because in standalone ports mode and under a bridge with vlan_filtering=0, the ocelot driver configures the switch to operate as VLAN-unaware, so the classified VLAN is not derived from the 802.1Q header from the packet, but instead is always equal to the port-based VLAN ID of the ingress port. We _can_ still change the classified VLAN for packets when operating in this mode, but the end result will most likely be a drop, since both the ingress and the egress port need to be members of the modified VLAN. And even if we install the new classified VLAN into the VLAN table of the switch, the result would still not be as expected: we wouldn't see, on the output port, the modified VLAN tag, but the original one, even though the classified VLAN was indeed modified. This is because of how the hardware works: on egress, what is pushed to the frame is a "port tag", which gives us the following options: - Tag all frames with port tag (derived from the classified VLAN) - Tag all frames with port tag, except if the classified VLAN is 0 or equal to the native VLAN of the egress port - No port tag Needless to say, in VLAN-unaware mode we are disabling the port tag. Otherwise, the existing VLAN tag would be ignored, and a second VLAN tag (the port tag), holding the classified VLAN, would be pushed (instead of replacing the existing 802.1Q tag). This is definitely not what the user wanted when installing a "vlan modify" action. So it is simply not worth bothering with VLAN modify rules under other configurations except when the ports are fully VLAN-aware. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2020-10-08 11:56:58 +00:00
case FLOW_ACTION_VLAN_MANGLE:
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0 Currently the ocelot driver does support the 'vlan modify' action, but in the ingress chain, and it is offloaded to VCAP IS1. This action changes the classified VLAN before the packet enters the bridging service, and the bridging works with the classified VLAN modified by VCAP IS1. That is good for some use cases, but there are others where the VLAN must be modified at the stage of the egress port, after the packet has exited the bridging service. One example is simulating IEEE 802.1CB active stream identification filters ("active" means that not only the rule matches on a packet flow, but it is also able to change some headers). For example, a stream is replicated on two egress ports, but they must have different VLAN IDs on egress ports A and B. This seems like a task for the VCAP ES0, but that currently only supports pushing the ES0 tag A, which is specified in the rule. Pushing another VLAN header is not what we want, but rather overwriting the existing one. It looks like when we push the ES0 tag A, it is actually possible to not only take the ES0 tag A's value from the rule itself (VID_A_VAL), but derive it from the following formula: ES0_TAG_A = Classified VID + VID_A_VAL Otherwise said, ES0_TAG_A can be used to increment with a given value the VLAN ID that the packet was already classified to, and the packet will have this value as an outer VLAN tag. This new VLAN ID value then gets stripped on egress (or not) according to the value of the native VLAN from the bridging service. While the hardware will happily increment the classified VLAN ID for all packets that match the ES0 rule, in practice this would be rather insane, so we only allow this kind of ES0 action if the ES0 filter contains a VLAN ID too, so as to restrict the matching on a known classified VLAN. If we program VID_A_VAL with the delta between the desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the desired behavior. It doesn't look like it is possible with the tc-vlan action to modify the VLAN ID but not the PCP. In hardware it is possible to leave the PCP to the classified value, but we unconditionally program it to overwrite it with the PCP value from the rule. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-01 15:15:26 +00:00
if (filter->block_id == VCAP_IS1) {
err = ocelot_flower_parse_ingress_vlan_modify(ocelot, port,
filter, a,
extack);
} else if (filter->block_id == VCAP_ES0) {
err = ocelot_flower_parse_egress_vlan_modify(filter, a,
extack);
} else {
net: mscc: ocelot: offload VLAN mangle action to VCAP IS1 The VCAP_IS1_ACT_VID_REPLACE_ENA action, from the VCAP IS1 ingress TCAM, changes the classified VLAN. We are only exposing this ability for switch ports that are under VLAN aware bridges. This is because in standalone ports mode and under a bridge with vlan_filtering=0, the ocelot driver configures the switch to operate as VLAN-unaware, so the classified VLAN is not derived from the 802.1Q header from the packet, but instead is always equal to the port-based VLAN ID of the ingress port. We _can_ still change the classified VLAN for packets when operating in this mode, but the end result will most likely be a drop, since both the ingress and the egress port need to be members of the modified VLAN. And even if we install the new classified VLAN into the VLAN table of the switch, the result would still not be as expected: we wouldn't see, on the output port, the modified VLAN tag, but the original one, even though the classified VLAN was indeed modified. This is because of how the hardware works: on egress, what is pushed to the frame is a "port tag", which gives us the following options: - Tag all frames with port tag (derived from the classified VLAN) - Tag all frames with port tag, except if the classified VLAN is 0 or equal to the native VLAN of the egress port - No port tag Needless to say, in VLAN-unaware mode we are disabling the port tag. Otherwise, the existing VLAN tag would be ignored, and a second VLAN tag (the port tag), holding the classified VLAN, would be pushed (instead of replacing the existing 802.1Q tag). This is definitely not what the user wanted when installing a "vlan modify" action. So it is simply not worth bothering with VLAN modify rules under other configurations except when the ports are fully VLAN-aware. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2020-10-08 11:56:58 +00:00
NL_SET_ERR_MSG_MOD(extack,
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0 Currently the ocelot driver does support the 'vlan modify' action, but in the ingress chain, and it is offloaded to VCAP IS1. This action changes the classified VLAN before the packet enters the bridging service, and the bridging works with the classified VLAN modified by VCAP IS1. That is good for some use cases, but there are others where the VLAN must be modified at the stage of the egress port, after the packet has exited the bridging service. One example is simulating IEEE 802.1CB active stream identification filters ("active" means that not only the rule matches on a packet flow, but it is also able to change some headers). For example, a stream is replicated on two egress ports, but they must have different VLAN IDs on egress ports A and B. This seems like a task for the VCAP ES0, but that currently only supports pushing the ES0 tag A, which is specified in the rule. Pushing another VLAN header is not what we want, but rather overwriting the existing one. It looks like when we push the ES0 tag A, it is actually possible to not only take the ES0 tag A's value from the rule itself (VID_A_VAL), but derive it from the following formula: ES0_TAG_A = Classified VID + VID_A_VAL Otherwise said, ES0_TAG_A can be used to increment with a given value the VLAN ID that the packet was already classified to, and the packet will have this value as an outer VLAN tag. This new VLAN ID value then gets stripped on egress (or not) according to the value of the native VLAN from the bridging service. While the hardware will happily increment the classified VLAN ID for all packets that match the ES0 rule, in practice this would be rather insane, so we only allow this kind of ES0 action if the ES0 filter contains a VLAN ID too, so as to restrict the matching on a known classified VLAN. If we program VID_A_VAL with the delta between the desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the desired behavior. It doesn't look like it is possible with the tc-vlan action to modify the VLAN ID but not the PCP. In hardware it is possible to leave the PCP to the classified value, but we unconditionally program it to overwrite it with the PCP value from the rule. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-01 15:15:26 +00:00
"VLAN modify action can only be offloaded to VCAP IS1 or ES0");
err = -EOPNOTSUPP;
net: mscc: ocelot: offload VLAN mangle action to VCAP IS1 The VCAP_IS1_ACT_VID_REPLACE_ENA action, from the VCAP IS1 ingress TCAM, changes the classified VLAN. We are only exposing this ability for switch ports that are under VLAN aware bridges. This is because in standalone ports mode and under a bridge with vlan_filtering=0, the ocelot driver configures the switch to operate as VLAN-unaware, so the classified VLAN is not derived from the 802.1Q header from the packet, but instead is always equal to the port-based VLAN ID of the ingress port. We _can_ still change the classified VLAN for packets when operating in this mode, but the end result will most likely be a drop, since both the ingress and the egress port need to be members of the modified VLAN. And even if we install the new classified VLAN into the VLAN table of the switch, the result would still not be as expected: we wouldn't see, on the output port, the modified VLAN tag, but the original one, even though the classified VLAN was indeed modified. This is because of how the hardware works: on egress, what is pushed to the frame is a "port tag", which gives us the following options: - Tag all frames with port tag (derived from the classified VLAN) - Tag all frames with port tag, except if the classified VLAN is 0 or equal to the native VLAN of the egress port - No port tag Needless to say, in VLAN-unaware mode we are disabling the port tag. Otherwise, the existing VLAN tag would be ignored, and a second VLAN tag (the port tag), holding the classified VLAN, would be pushed (instead of replacing the existing 802.1Q tag). This is definitely not what the user wanted when installing a "vlan modify" action. So it is simply not worth bothering with VLAN modify rules under other configurations except when the ports are fully VLAN-aware. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2020-10-08 11:56:58 +00:00
}
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0 Currently the ocelot driver does support the 'vlan modify' action, but in the ingress chain, and it is offloaded to VCAP IS1. This action changes the classified VLAN before the packet enters the bridging service, and the bridging works with the classified VLAN modified by VCAP IS1. That is good for some use cases, but there are others where the VLAN must be modified at the stage of the egress port, after the packet has exited the bridging service. One example is simulating IEEE 802.1CB active stream identification filters ("active" means that not only the rule matches on a packet flow, but it is also able to change some headers). For example, a stream is replicated on two egress ports, but they must have different VLAN IDs on egress ports A and B. This seems like a task for the VCAP ES0, but that currently only supports pushing the ES0 tag A, which is specified in the rule. Pushing another VLAN header is not what we want, but rather overwriting the existing one. It looks like when we push the ES0 tag A, it is actually possible to not only take the ES0 tag A's value from the rule itself (VID_A_VAL), but derive it from the following formula: ES0_TAG_A = Classified VID + VID_A_VAL Otherwise said, ES0_TAG_A can be used to increment with a given value the VLAN ID that the packet was already classified to, and the packet will have this value as an outer VLAN tag. This new VLAN ID value then gets stripped on egress (or not) according to the value of the native VLAN from the bridging service. While the hardware will happily increment the classified VLAN ID for all packets that match the ES0 rule, in practice this would be rather insane, so we only allow this kind of ES0 action if the ES0 filter contains a VLAN ID too, so as to restrict the matching on a known classified VLAN. If we program VID_A_VAL with the delta between the desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the desired behavior. It doesn't look like it is possible with the tc-vlan action to modify the VLAN ID but not the PCP. In hardware it is possible to leave the PCP to the classified value, but we unconditionally program it to overwrite it with the PCP value from the rule. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-01 15:15:26 +00:00
if (err)
return err;
net: mscc: ocelot: offload VLAN mangle action to VCAP IS1 The VCAP_IS1_ACT_VID_REPLACE_ENA action, from the VCAP IS1 ingress TCAM, changes the classified VLAN. We are only exposing this ability for switch ports that are under VLAN aware bridges. This is because in standalone ports mode and under a bridge with vlan_filtering=0, the ocelot driver configures the switch to operate as VLAN-unaware, so the classified VLAN is not derived from the 802.1Q header from the packet, but instead is always equal to the port-based VLAN ID of the ingress port. We _can_ still change the classified VLAN for packets when operating in this mode, but the end result will most likely be a drop, since both the ingress and the egress port need to be members of the modified VLAN. And even if we install the new classified VLAN into the VLAN table of the switch, the result would still not be as expected: we wouldn't see, on the output port, the modified VLAN tag, but the original one, even though the classified VLAN was indeed modified. This is because of how the hardware works: on egress, what is pushed to the frame is a "port tag", which gives us the following options: - Tag all frames with port tag (derived from the classified VLAN) - Tag all frames with port tag, except if the classified VLAN is 0 or equal to the native VLAN of the egress port - No port tag Needless to say, in VLAN-unaware mode we are disabling the port tag. Otherwise, the existing VLAN tag would be ignored, and a second VLAN tag (the port tag), holding the classified VLAN, would be pushed (instead of replacing the existing 802.1Q tag). This is definitely not what the user wanted when installing a "vlan modify" action. So it is simply not worth bothering with VLAN modify rules under other configurations except when the ports are fully VLAN-aware. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2020-10-08 11:56:58 +00:00
break;
case FLOW_ACTION_PRIORITY:
if (filter->block_id != VCAP_IS1) {
NL_SET_ERR_MSG_MOD(extack,
"Priority action can only be offloaded to VCAP IS1");
return -EOPNOTSUPP;
}
if (filter->goto_target != -1) {
NL_SET_ERR_MSG_MOD(extack,
"Last action must be GOTO");
return -EOPNOTSUPP;
}
filter->action.qos_ena = true;
filter->action.qos_val = a->priority;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break;
case FLOW_ACTION_GOTO:
filter->goto_target = a->chain_index;
if (filter->block_id == VCAP_IS1 && filter->lookup == 2) {
int pag = ocelot_chain_to_pag(filter->goto_target);
filter->action.pag_override_mask = 0xff;
filter->action.pag_val = pag;
filter->type = OCELOT_VCAP_FILTER_PAG;
}
break;
case FLOW_ACTION_VLAN_PUSH:
if (filter->block_id != VCAP_ES0) {
NL_SET_ERR_MSG_MOD(extack,
"VLAN push action can only be offloaded to VCAP ES0");
return -EOPNOTSUPP;
}
switch (ntohs(a->vlan.proto)) {
case ETH_P_8021Q:
tpid = OCELOT_TAG_TPID_SEL_8021Q;
break;
case ETH_P_8021AD:
tpid = OCELOT_TAG_TPID_SEL_8021AD;
break;
default:
NL_SET_ERR_MSG_MOD(extack,
"Cannot push custom TPID");
return -EOPNOTSUPP;
}
filter->action.tag_a_tpid_sel = tpid;
filter->action.push_outer_tag = OCELOT_ES0_TAG;
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0 Currently the ocelot driver does support the 'vlan modify' action, but in the ingress chain, and it is offloaded to VCAP IS1. This action changes the classified VLAN before the packet enters the bridging service, and the bridging works with the classified VLAN modified by VCAP IS1. That is good for some use cases, but there are others where the VLAN must be modified at the stage of the egress port, after the packet has exited the bridging service. One example is simulating IEEE 802.1CB active stream identification filters ("active" means that not only the rule matches on a packet flow, but it is also able to change some headers). For example, a stream is replicated on two egress ports, but they must have different VLAN IDs on egress ports A and B. This seems like a task for the VCAP ES0, but that currently only supports pushing the ES0 tag A, which is specified in the rule. Pushing another VLAN header is not what we want, but rather overwriting the existing one. It looks like when we push the ES0 tag A, it is actually possible to not only take the ES0 tag A's value from the rule itself (VID_A_VAL), but derive it from the following formula: ES0_TAG_A = Classified VID + VID_A_VAL Otherwise said, ES0_TAG_A can be used to increment with a given value the VLAN ID that the packet was already classified to, and the packet will have this value as an outer VLAN tag. This new VLAN ID value then gets stripped on egress (or not) according to the value of the native VLAN from the bridging service. While the hardware will happily increment the classified VLAN ID for all packets that match the ES0 rule, in practice this would be rather insane, so we only allow this kind of ES0 action if the ES0 filter contains a VLAN ID too, so as to restrict the matching on a known classified VLAN. If we program VID_A_VAL with the delta between the desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the desired behavior. It doesn't look like it is possible with the tc-vlan action to modify the VLAN ID but not the PCP. In hardware it is possible to leave the PCP to the classified value, but we unconditionally program it to overwrite it with the PCP value from the rule. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-01 15:15:26 +00:00
filter->action.tag_a_vid_sel = OCELOT_ES0_VID;
filter->action.vid_a_val = a->vlan.vid;
filter->action.pcp_a_val = a->vlan.prio;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
break;
case FLOW_ACTION_GATE:
if (filter->block_id != PSFP_BLOCK_ID) {
NL_SET_ERR_MSG_MOD(extack,
"Gate action can only be offloaded to PSFP chain");
return -EOPNOTSUPP;
}
filter->type = OCELOT_PSFP_FILTER_OFFLOAD;
break;
default:
NL_SET_ERR_MSG_MOD(extack, "Cannot offload action");
return -EOPNOTSUPP;
}
}
if (filter->goto_target == -1) {
if ((filter->block_id == VCAP_IS2 && filter->lookup == 1) ||
chain == 0 || filter->block_id == PSFP_BLOCK_ID) {
allow_missing_goto_target = true;
} else {
NL_SET_ERR_MSG_MOD(extack, "Missing GOTO action");
return -EOPNOTSUPP;
}
}
if (!ocelot_is_goto_target_valid(filter->goto_target, chain, ingress) &&
!allow_missing_goto_target) {
NL_SET_ERR_MSG_MOD(extack, "Cannot offload this GOTO target");
return -EOPNOTSUPP;
}
return 0;
}
static int ocelot_flower_parse_indev(struct ocelot *ocelot, int port,
struct flow_cls_offload *f,
struct ocelot_vcap_filter *filter)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(f);
const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0];
int key_length = vcap->keys[VCAP_ES0_IGR_PORT].length;
struct netlink_ext_ack *extack = f->common.extack;
struct net_device *dev, *indev;
struct flow_match_meta match;
int ingress_port;
flow_rule_match_meta(rule, &match);
if (!match.mask->ingress_ifindex)
return 0;
if (match.mask->ingress_ifindex != 0xFFFFFFFF) {
NL_SET_ERR_MSG_MOD(extack, "Unsupported ingress ifindex mask");
return -EOPNOTSUPP;
}
dev = ocelot->ops->port_to_netdev(ocelot, port);
if (!dev)
return -EINVAL;
indev = __dev_get_by_index(dev_net(dev), match.key->ingress_ifindex);
if (!indev) {
NL_SET_ERR_MSG_MOD(extack,
"Can't find the ingress port to match on");
return -ENOENT;
}
ingress_port = ocelot->ops->netdev_to_port(indev);
if (ingress_port < 0) {
NL_SET_ERR_MSG_MOD(extack,
"Can only offload an ocelot ingress port");
return -EOPNOTSUPP;
}
if (ingress_port == port) {
NL_SET_ERR_MSG_MOD(extack,
"Ingress port is equal to the egress port");
return -EINVAL;
}
filter->ingress_port.value = ingress_port;
filter->ingress_port.mask = GENMASK(key_length - 1, 0);
return 0;
}
static int
ocelot_flower_parse_key(struct ocelot *ocelot, int port, bool ingress,
struct flow_cls_offload *f,
struct ocelot_vcap_filter *filter)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(f);
struct flow_dissector *dissector = rule->match.dissector;
struct netlink_ext_ack *extack = f->common.extack;
u16 proto = ntohs(f->common.protocol);
bool match_protocol = true;
int ret;
if (dissector->used_keys &
~(BIT(FLOW_DISSECTOR_KEY_CONTROL) |
BIT(FLOW_DISSECTOR_KEY_BASIC) |
BIT(FLOW_DISSECTOR_KEY_META) |
BIT(FLOW_DISSECTOR_KEY_PORTS) |
BIT(FLOW_DISSECTOR_KEY_VLAN) |
BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS))) {
return -EOPNOTSUPP;
}
/* For VCAP ES0 (egress rewriter) we can match on the ingress port */
if (!ingress) {
ret = ocelot_flower_parse_indev(ocelot, port, f, filter);
if (ret)
return ret;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
struct flow_match_control match;
flow_rule_match_control(rule, &match);
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
struct flow_match_eth_addrs match;
if (filter->block_id == VCAP_ES0) {
NL_SET_ERR_MSG_MOD(extack,
"VCAP ES0 cannot match on MAC address");
return -EOPNOTSUPP;
}
/* The hw support mac matches only for MAC_ETYPE key,
* therefore if other matches(port, tcp flags, etc) are added
* then just bail out
*/
if ((dissector->used_keys &
(BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_BASIC) |
BIT(FLOW_DISSECTOR_KEY_CONTROL))) !=
(BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_BASIC) |
BIT(FLOW_DISSECTOR_KEY_CONTROL)))
return -EOPNOTSUPP;
flow_rule_match_eth_addrs(rule, &match);
if (filter->block_id == VCAP_IS1 &&
!is_zero_ether_addr(match.mask->dst)) {
NL_SET_ERR_MSG_MOD(extack,
"Key type S1_NORMAL cannot match on destination MAC");
return -EOPNOTSUPP;
}
filter->key_type = OCELOT_VCAP_KEY_ETYPE;
ether_addr_copy(filter->key.etype.dmac.value,
match.key->dst);
ether_addr_copy(filter->key.etype.smac.value,
match.key->src);
ether_addr_copy(filter->key.etype.dmac.mask,
match.mask->dst);
ether_addr_copy(filter->key.etype.smac.mask,
match.mask->src);
goto finished_key_parsing;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
struct flow_match_basic match;
flow_rule_match_basic(rule, &match);
if (ntohs(match.key->n_proto) == ETH_P_IP) {
if (filter->block_id == VCAP_ES0) {
NL_SET_ERR_MSG_MOD(extack,
"VCAP ES0 cannot match on IP protocol");
return -EOPNOTSUPP;
}
filter->key_type = OCELOT_VCAP_KEY_IPV4;
filter->key.ipv4.proto.value[0] =
match.key->ip_proto;
filter->key.ipv4.proto.mask[0] =
match.mask->ip_proto;
match_protocol = false;
}
if (ntohs(match.key->n_proto) == ETH_P_IPV6) {
if (filter->block_id == VCAP_ES0) {
NL_SET_ERR_MSG_MOD(extack,
"VCAP ES0 cannot match on IP protocol");
return -EOPNOTSUPP;
}
filter->key_type = OCELOT_VCAP_KEY_IPV6;
filter->key.ipv6.proto.value[0] =
match.key->ip_proto;
filter->key.ipv6.proto.mask[0] =
match.mask->ip_proto;
match_protocol = false;
}
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS) &&
proto == ETH_P_IP) {
struct flow_match_ipv4_addrs match;
u8 *tmp;
if (filter->block_id == VCAP_ES0) {
NL_SET_ERR_MSG_MOD(extack,
"VCAP ES0 cannot match on IP address");
return -EOPNOTSUPP;
}
flow_rule_match_ipv4_addrs(rule, &match);
if (filter->block_id == VCAP_IS1 && *(u32 *)&match.mask->dst) {
NL_SET_ERR_MSG_MOD(extack,
"Key type S1_NORMAL cannot match on destination IP");
return -EOPNOTSUPP;
}
tmp = &filter->key.ipv4.sip.value.addr[0];
memcpy(tmp, &match.key->src, 4);
tmp = &filter->key.ipv4.sip.mask.addr[0];
memcpy(tmp, &match.mask->src, 4);
tmp = &filter->key.ipv4.dip.value.addr[0];
memcpy(tmp, &match.key->dst, 4);
tmp = &filter->key.ipv4.dip.mask.addr[0];
memcpy(tmp, &match.mask->dst, 4);
match_protocol = false;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS) &&
proto == ETH_P_IPV6) {
return -EOPNOTSUPP;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
struct flow_match_ports match;
if (filter->block_id == VCAP_ES0) {
NL_SET_ERR_MSG_MOD(extack,
"VCAP ES0 cannot match on L4 ports");
return -EOPNOTSUPP;
}
flow_rule_match_ports(rule, &match);
filter->key.ipv4.sport.value = ntohs(match.key->src);
filter->key.ipv4.sport.mask = ntohs(match.mask->src);
filter->key.ipv4.dport.value = ntohs(match.key->dst);
filter->key.ipv4.dport.mask = ntohs(match.mask->dst);
match_protocol = false;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) {
struct flow_match_vlan match;
flow_rule_match_vlan(rule, &match);
filter->key_type = OCELOT_VCAP_KEY_ANY;
filter->vlan.vid.value = match.key->vlan_id;
filter->vlan.vid.mask = match.mask->vlan_id;
filter->vlan.pcp.value[0] = match.key->vlan_priority;
filter->vlan.pcp.mask[0] = match.mask->vlan_priority;
match_protocol = false;
}
finished_key_parsing:
if (match_protocol && proto != ETH_P_ALL) {
if (filter->block_id == VCAP_ES0) {
NL_SET_ERR_MSG_MOD(extack,
"VCAP ES0 cannot match on L2 proto");
return -EOPNOTSUPP;
}
/* TODO: support SNAP, LLC etc */
if (proto < ETH_P_802_3_MIN)
return -EOPNOTSUPP;
filter->key_type = OCELOT_VCAP_KEY_ETYPE;
*(__be16 *)filter->key.etype.etype.value = htons(proto);
*(__be16 *)filter->key.etype.etype.mask = htons(0xffff);
}
/* else, a filter of type OCELOT_VCAP_KEY_ANY is implicitly added */
return 0;
}
static int ocelot_flower_parse(struct ocelot *ocelot, int port, bool ingress,
struct flow_cls_offload *f,
struct ocelot_vcap_filter *filter)
{
int ret;
filter->prio = f->common.prio;
filter->id.cookie = f->cookie;
filter->id.tc_offload = true;
net: mscc: ocelot: offload VLAN mangle action to VCAP IS1 The VCAP_IS1_ACT_VID_REPLACE_ENA action, from the VCAP IS1 ingress TCAM, changes the classified VLAN. We are only exposing this ability for switch ports that are under VLAN aware bridges. This is because in standalone ports mode and under a bridge with vlan_filtering=0, the ocelot driver configures the switch to operate as VLAN-unaware, so the classified VLAN is not derived from the 802.1Q header from the packet, but instead is always equal to the port-based VLAN ID of the ingress port. We _can_ still change the classified VLAN for packets when operating in this mode, but the end result will most likely be a drop, since both the ingress and the egress port need to be members of the modified VLAN. And even if we install the new classified VLAN into the VLAN table of the switch, the result would still not be as expected: we wouldn't see, on the output port, the modified VLAN tag, but the original one, even though the classified VLAN was indeed modified. This is because of how the hardware works: on egress, what is pushed to the frame is a "port tag", which gives us the following options: - Tag all frames with port tag (derived from the classified VLAN) - Tag all frames with port tag, except if the classified VLAN is 0 or equal to the native VLAN of the egress port - No port tag Needless to say, in VLAN-unaware mode we are disabling the port tag. Otherwise, the existing VLAN tag would be ignored, and a second VLAN tag (the port tag), holding the classified VLAN, would be pushed (instead of replacing the existing 802.1Q tag). This is definitely not what the user wanted when installing a "vlan modify" action. So it is simply not worth bothering with VLAN modify rules under other configurations except when the ports are fully VLAN-aware. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2020-10-08 11:56:58 +00:00
ret = ocelot_flower_parse_action(ocelot, port, ingress, f, filter);
if (ret)
return ret;
/* PSFP filter need to parse key by stream identification function. */
if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD)
return 0;
return ocelot_flower_parse_key(ocelot, port, ingress, f, filter);
}
static struct ocelot_vcap_filter
*ocelot_vcap_filter_create(struct ocelot *ocelot, int port, bool ingress,
struct flow_cls_offload *f)
{
struct ocelot_vcap_filter *filter;
filter = kzalloc(sizeof(*filter), GFP_KERNEL);
if (!filter)
return NULL;
if (ingress) {
filter->ingress_port_mask = BIT(port);
} else {
const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0];
int key_length = vcap->keys[VCAP_ES0_EGR_PORT].length;
filter->egress_port.value = port;
filter->egress_port.mask = GENMASK(key_length - 1, 0);
}
return filter;
}
static int ocelot_vcap_dummy_filter_add(struct ocelot *ocelot,
struct ocelot_vcap_filter *filter)
{
list_add(&filter->list, &ocelot->dummy_rules);
return 0;
}
static int ocelot_vcap_dummy_filter_del(struct ocelot *ocelot,
struct ocelot_vcap_filter *filter)
{
list_del(&filter->list);
kfree(filter);
return 0;
}
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0 Currently the ocelot driver does support the 'vlan modify' action, but in the ingress chain, and it is offloaded to VCAP IS1. This action changes the classified VLAN before the packet enters the bridging service, and the bridging works with the classified VLAN modified by VCAP IS1. That is good for some use cases, but there are others where the VLAN must be modified at the stage of the egress port, after the packet has exited the bridging service. One example is simulating IEEE 802.1CB active stream identification filters ("active" means that not only the rule matches on a packet flow, but it is also able to change some headers). For example, a stream is replicated on two egress ports, but they must have different VLAN IDs on egress ports A and B. This seems like a task for the VCAP ES0, but that currently only supports pushing the ES0 tag A, which is specified in the rule. Pushing another VLAN header is not what we want, but rather overwriting the existing one. It looks like when we push the ES0 tag A, it is actually possible to not only take the ES0 tag A's value from the rule itself (VID_A_VAL), but derive it from the following formula: ES0_TAG_A = Classified VID + VID_A_VAL Otherwise said, ES0_TAG_A can be used to increment with a given value the VLAN ID that the packet was already classified to, and the packet will have this value as an outer VLAN tag. This new VLAN ID value then gets stripped on egress (or not) according to the value of the native VLAN from the bridging service. While the hardware will happily increment the classified VLAN ID for all packets that match the ES0 rule, in practice this would be rather insane, so we only allow this kind of ES0 action if the ES0 filter contains a VLAN ID too, so as to restrict the matching on a known classified VLAN. If we program VID_A_VAL with the delta between the desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the desired behavior. It doesn't look like it is possible with the tc-vlan action to modify the VLAN ID but not the PCP. In hardware it is possible to leave the PCP to the classified value, but we unconditionally program it to overwrite it with the PCP value from the rule. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-01 15:15:26 +00:00
/* If we have an egress VLAN modification rule, we need to actually write the
* delta between the input VLAN (from the key) and the output VLAN (from the
* action), but the action was parsed first. So we need to patch the delta into
* the action here.
*/
static int
ocelot_flower_patch_es0_vlan_modify(struct ocelot_vcap_filter *filter,
struct netlink_ext_ack *extack)
{
if (filter->block_id != VCAP_ES0 ||
filter->action.tag_a_vid_sel != OCELOT_ES0_VID_PLUS_CLASSIFIED_VID)
return 0;
if (filter->vlan.vid.mask != VLAN_VID_MASK) {
NL_SET_ERR_MSG_MOD(extack,
"VCAP ES0 VLAN rewriting needs a full VLAN in the key");
return -EOPNOTSUPP;
}
filter->action.vid_a_val -= filter->vlan.vid.value;
filter->action.vid_a_val &= VLAN_VID_MASK;
return 0;
}
net: mscc: ocelot: simplify tc-flower offload structures The ocelot tc-flower offload binds a second flow block callback (apart from the one for matchall) just because it uses a different block private structure (ocelot_port_private for matchall, ocelot_port_block for flower). But ocelot_port_block just appears to be boilerplate, and doesn't help with anything in particular at all, it's just useless glue between the (global!) struct ocelot_acl_block *block pointer, and a per-netdevice struct ocelot_port_private *priv. So let's just simplify that, and make struct ocelot_port_private be the private structure for the block offload. This makes us able to use the same flow callback as in the case of matchall. This also reveals that the struct ocelot_acl_block *block is used rather strangely, as mentioned above: it is defined globally, allocated at probe time, and freed at unbind time. So just move the structure to the main ocelot structure, which gives further opportunity for simplification. Also get rid of backpointers from struct ocelot_acl_block and struct ocelot_ace_rule back to struct ocelot, by reworking the function prototypes, where necessary, to use a more DSA-friendly "struct ocelot *ocelot, int port" format. And finally, remove the debugging prints that were added during development, since they provide no useful information at this point. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Tested-by: Horatiu Vultur <horatiu.vultur@microchip.com> Reviewed-by: Allan W. Nielsen <allan.nielsen@microchip.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2020-02-29 14:31:06 +00:00
int ocelot_cls_flower_replace(struct ocelot *ocelot, int port,
struct flow_cls_offload *f, bool ingress)
{
struct netlink_ext_ack *extack = f->common.extack;
struct ocelot_vcap_filter *filter;
int chain = f->common.chain_index;
net: mscc: ocelot: don't dereference NULL pointers with shared tc filters The following command sequence: tc qdisc del dev swp0 clsact tc qdisc add dev swp0 ingress_block 1 clsact tc qdisc add dev swp1 ingress_block 1 clsact tc filter add block 1 flower action drop tc qdisc del dev swp0 clsact produces the following NPD: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000014 pc : vcap_entry_set+0x14/0x70 lr : ocelot_vcap_filter_del+0x198/0x234 Call trace: vcap_entry_set+0x14/0x70 ocelot_vcap_filter_del+0x198/0x234 ocelot_cls_flower_destroy+0x94/0xe4 felix_cls_flower_del+0x70/0x84 dsa_slave_setup_tc_block_cb+0x13c/0x60c dsa_slave_setup_tc_block_cb_ig+0x20/0x30 tc_setup_cb_reoffload+0x44/0x120 fl_reoffload+0x280/0x320 tcf_block_playback_offloads+0x6c/0x184 tcf_block_unbind+0x80/0xe0 tcf_block_setup+0x174/0x214 tcf_block_offload_cmd.isra.0+0x100/0x13c tcf_block_offload_unbind+0x5c/0xa0 __tcf_block_put+0x54/0x174 tcf_block_put_ext+0x5c/0x74 clsact_destroy+0x40/0x60 qdisc_destroy+0x4c/0x150 qdisc_put+0x70/0x90 qdisc_graft+0x3f0/0x4c0 tc_get_qdisc+0x1cc/0x364 rtnetlink_rcv_msg+0x124/0x340 The reason is that the driver isn't prepared to receive two tc filters with the same cookie. It unconditionally creates a new struct ocelot_vcap_filter for each tc filter, and it adds all filters with the same identifier (cookie) to the ocelot_vcap_block. The problem is here, in ocelot_vcap_filter_del(): /* Gets index of the filter */ index = ocelot_vcap_block_get_filter_index(block, filter); if (index < 0) return index; /* Delete filter */ ocelot_vcap_block_remove_filter(ocelot, block, filter); /* Move up all the blocks over the deleted filter */ for (i = index; i < block->count; i++) { struct ocelot_vcap_filter *tmp; tmp = ocelot_vcap_block_find_filter_by_index(block, i); vcap_entry_set(ocelot, i, tmp); } what will happen is ocelot_vcap_block_get_filter_index() will return the index (@index) of the first filter found with that cookie. This is _not_ the index of _this_ filter, but the other one with the same cookie, because ocelot_vcap_filter_equal() gets fooled. Then later, ocelot_vcap_block_remove_filter() is coded to remove all filters that are ocelot_vcap_filter_equal() with the passed @filter. So unexpectedly, both filters get deleted from the list. Then ocelot_vcap_filter_del() will attempt to move all the other filters up, again finding them by index (@i). The block count is 2, @index was 0, so it will attempt to move up filter @i=0 and @i=1. It assigns tmp = ocelot_vcap_block_find_filter_by_index(block, i), which is now a NULL pointer because ocelot_vcap_block_remove_filter() has removed more than one filter. As far as I can see, this problem has been there since the introduction of tc offload support, however I cannot test beyond the blamed commit due to hardware availability. In any case, any fix cannot be backported that far, due to lots of changes to the code base. Therefore, let's go for the correct solution, which is to not call ocelot_vcap_filter_add() and ocelot_vcap_filter_del(), unless the filter is actually unique and not shared. For the shared filters, we should just modify the ingress port mask and call ocelot_vcap_filter_replace(), a function introduced by commit 95706be13b9f ("net: mscc: ocelot: create a function that replaces an existing VCAP filter"). This way, block->rules will only contain filters with unique cookies, by design. Fixes: 07d985eef073 ("net: dsa: felix: Wire up the ocelot cls_flower methods") Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2022-01-14 13:36:37 +00:00
int block_id, ret;
if (chain && !ocelot_find_vcap_filter_that_points_at(ocelot, chain)) {
NL_SET_ERR_MSG_MOD(extack, "No default GOTO action points to this chain");
return -EOPNOTSUPP;
}
net: mscc: ocelot: don't dereference NULL pointers with shared tc filters The following command sequence: tc qdisc del dev swp0 clsact tc qdisc add dev swp0 ingress_block 1 clsact tc qdisc add dev swp1 ingress_block 1 clsact tc filter add block 1 flower action drop tc qdisc del dev swp0 clsact produces the following NPD: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000014 pc : vcap_entry_set+0x14/0x70 lr : ocelot_vcap_filter_del+0x198/0x234 Call trace: vcap_entry_set+0x14/0x70 ocelot_vcap_filter_del+0x198/0x234 ocelot_cls_flower_destroy+0x94/0xe4 felix_cls_flower_del+0x70/0x84 dsa_slave_setup_tc_block_cb+0x13c/0x60c dsa_slave_setup_tc_block_cb_ig+0x20/0x30 tc_setup_cb_reoffload+0x44/0x120 fl_reoffload+0x280/0x320 tcf_block_playback_offloads+0x6c/0x184 tcf_block_unbind+0x80/0xe0 tcf_block_setup+0x174/0x214 tcf_block_offload_cmd.isra.0+0x100/0x13c tcf_block_offload_unbind+0x5c/0xa0 __tcf_block_put+0x54/0x174 tcf_block_put_ext+0x5c/0x74 clsact_destroy+0x40/0x60 qdisc_destroy+0x4c/0x150 qdisc_put+0x70/0x90 qdisc_graft+0x3f0/0x4c0 tc_get_qdisc+0x1cc/0x364 rtnetlink_rcv_msg+0x124/0x340 The reason is that the driver isn't prepared to receive two tc filters with the same cookie. It unconditionally creates a new struct ocelot_vcap_filter for each tc filter, and it adds all filters with the same identifier (cookie) to the ocelot_vcap_block. The problem is here, in ocelot_vcap_filter_del(): /* Gets index of the filter */ index = ocelot_vcap_block_get_filter_index(block, filter); if (index < 0) return index; /* Delete filter */ ocelot_vcap_block_remove_filter(ocelot, block, filter); /* Move up all the blocks over the deleted filter */ for (i = index; i < block->count; i++) { struct ocelot_vcap_filter *tmp; tmp = ocelot_vcap_block_find_filter_by_index(block, i); vcap_entry_set(ocelot, i, tmp); } what will happen is ocelot_vcap_block_get_filter_index() will return the index (@index) of the first filter found with that cookie. This is _not_ the index of _this_ filter, but the other one with the same cookie, because ocelot_vcap_filter_equal() gets fooled. Then later, ocelot_vcap_block_remove_filter() is coded to remove all filters that are ocelot_vcap_filter_equal() with the passed @filter. So unexpectedly, both filters get deleted from the list. Then ocelot_vcap_filter_del() will attempt to move all the other filters up, again finding them by index (@i). The block count is 2, @index was 0, so it will attempt to move up filter @i=0 and @i=1. It assigns tmp = ocelot_vcap_block_find_filter_by_index(block, i), which is now a NULL pointer because ocelot_vcap_block_remove_filter() has removed more than one filter. As far as I can see, this problem has been there since the introduction of tc offload support, however I cannot test beyond the blamed commit due to hardware availability. In any case, any fix cannot be backported that far, due to lots of changes to the code base. Therefore, let's go for the correct solution, which is to not call ocelot_vcap_filter_add() and ocelot_vcap_filter_del(), unless the filter is actually unique and not shared. For the shared filters, we should just modify the ingress port mask and call ocelot_vcap_filter_replace(), a function introduced by commit 95706be13b9f ("net: mscc: ocelot: create a function that replaces an existing VCAP filter"). This way, block->rules will only contain filters with unique cookies, by design. Fixes: 07d985eef073 ("net: dsa: felix: Wire up the ocelot cls_flower methods") Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2022-01-14 13:36:37 +00:00
block_id = ocelot_chain_to_block(chain, ingress);
if (block_id < 0) {
NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain");
return -EOPNOTSUPP;
}
filter = ocelot_vcap_block_find_filter_by_id(&ocelot->block[block_id],
f->cookie, true);
if (filter) {
/* Filter already exists on other ports */
if (!ingress) {
NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 does not support shared filters");
return -EOPNOTSUPP;
}
filter->ingress_port_mask |= BIT(port);
return ocelot_vcap_filter_replace(ocelot, filter);
}
/* Filter didn't exist, create it now */
filter = ocelot_vcap_filter_create(ocelot, port, ingress, f);
if (!filter)
return -ENOMEM;
ret = ocelot_flower_parse(ocelot, port, ingress, f, filter);
if (ret) {
kfree(filter);
return ret;
}
net: mscc: ocelot: support egress VLAN rewriting via VCAP ES0 Currently the ocelot driver does support the 'vlan modify' action, but in the ingress chain, and it is offloaded to VCAP IS1. This action changes the classified VLAN before the packet enters the bridging service, and the bridging works with the classified VLAN modified by VCAP IS1. That is good for some use cases, but there are others where the VLAN must be modified at the stage of the egress port, after the packet has exited the bridging service. One example is simulating IEEE 802.1CB active stream identification filters ("active" means that not only the rule matches on a packet flow, but it is also able to change some headers). For example, a stream is replicated on two egress ports, but they must have different VLAN IDs on egress ports A and B. This seems like a task for the VCAP ES0, but that currently only supports pushing the ES0 tag A, which is specified in the rule. Pushing another VLAN header is not what we want, but rather overwriting the existing one. It looks like when we push the ES0 tag A, it is actually possible to not only take the ES0 tag A's value from the rule itself (VID_A_VAL), but derive it from the following formula: ES0_TAG_A = Classified VID + VID_A_VAL Otherwise said, ES0_TAG_A can be used to increment with a given value the VLAN ID that the packet was already classified to, and the packet will have this value as an outer VLAN tag. This new VLAN ID value then gets stripped on egress (or not) according to the value of the native VLAN from the bridging service. While the hardware will happily increment the classified VLAN ID for all packets that match the ES0 rule, in practice this would be rather insane, so we only allow this kind of ES0 action if the ES0 filter contains a VLAN ID too, so as to restrict the matching on a known classified VLAN. If we program VID_A_VAL with the delta between the desired final VLAN (ES0_TAG_A) and the classified VLAN, we obtain the desired behavior. It doesn't look like it is possible with the tc-vlan action to modify the VLAN ID but not the PCP. In hardware it is possible to leave the PCP to the classified value, but we unconditionally program it to overwrite it with the PCP value from the rule. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-01 15:15:26 +00:00
ret = ocelot_flower_patch_es0_vlan_modify(filter, extack);
if (ret) {
kfree(filter);
return ret;
}
/* The non-optional GOTOs for the TCAM skeleton don't need
* to be actually offloaded.
*/
if (filter->type == OCELOT_VCAP_FILTER_DUMMY)
return ocelot_vcap_dummy_filter_add(ocelot, filter);
if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD) {
kfree(filter);
if (ocelot->ops->psfp_filter_add)
return ocelot->ops->psfp_filter_add(ocelot, port, f);
NL_SET_ERR_MSG_MOD(extack, "PSFP chain is not supported in HW");
return -EOPNOTSUPP;
}
return ocelot_vcap_filter_add(ocelot, filter, f->common.extack);
}
net: mscc: ocelot: simplify tc-flower offload structures The ocelot tc-flower offload binds a second flow block callback (apart from the one for matchall) just because it uses a different block private structure (ocelot_port_private for matchall, ocelot_port_block for flower). But ocelot_port_block just appears to be boilerplate, and doesn't help with anything in particular at all, it's just useless glue between the (global!) struct ocelot_acl_block *block pointer, and a per-netdevice struct ocelot_port_private *priv. So let's just simplify that, and make struct ocelot_port_private be the private structure for the block offload. This makes us able to use the same flow callback as in the case of matchall. This also reveals that the struct ocelot_acl_block *block is used rather strangely, as mentioned above: it is defined globally, allocated at probe time, and freed at unbind time. So just move the structure to the main ocelot structure, which gives further opportunity for simplification. Also get rid of backpointers from struct ocelot_acl_block and struct ocelot_ace_rule back to struct ocelot, by reworking the function prototypes, where necessary, to use a more DSA-friendly "struct ocelot *ocelot, int port" format. And finally, remove the debugging prints that were added during development, since they provide no useful information at this point. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Tested-by: Horatiu Vultur <horatiu.vultur@microchip.com> Reviewed-by: Allan W. Nielsen <allan.nielsen@microchip.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2020-02-29 14:31:06 +00:00
EXPORT_SYMBOL_GPL(ocelot_cls_flower_replace);
net: mscc: ocelot: simplify tc-flower offload structures The ocelot tc-flower offload binds a second flow block callback (apart from the one for matchall) just because it uses a different block private structure (ocelot_port_private for matchall, ocelot_port_block for flower). But ocelot_port_block just appears to be boilerplate, and doesn't help with anything in particular at all, it's just useless glue between the (global!) struct ocelot_acl_block *block pointer, and a per-netdevice struct ocelot_port_private *priv. So let's just simplify that, and make struct ocelot_port_private be the private structure for the block offload. This makes us able to use the same flow callback as in the case of matchall. This also reveals that the struct ocelot_acl_block *block is used rather strangely, as mentioned above: it is defined globally, allocated at probe time, and freed at unbind time. So just move the structure to the main ocelot structure, which gives further opportunity for simplification. Also get rid of backpointers from struct ocelot_acl_block and struct ocelot_ace_rule back to struct ocelot, by reworking the function prototypes, where necessary, to use a more DSA-friendly "struct ocelot *ocelot, int port" format. And finally, remove the debugging prints that were added during development, since they provide no useful information at this point. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Tested-by: Horatiu Vultur <horatiu.vultur@microchip.com> Reviewed-by: Allan W. Nielsen <allan.nielsen@microchip.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2020-02-29 14:31:06 +00:00
int ocelot_cls_flower_destroy(struct ocelot *ocelot, int port,
struct flow_cls_offload *f, bool ingress)
{
struct ocelot_vcap_filter *filter;
struct ocelot_vcap_block *block;
int block_id;
block_id = ocelot_chain_to_block(f->common.chain_index, ingress);
if (block_id < 0)
return 0;
if (block_id == PSFP_BLOCK_ID) {
if (ocelot->ops->psfp_filter_del)
return ocelot->ops->psfp_filter_del(ocelot, f);
return -EOPNOTSUPP;
}
block = &ocelot->block[block_id];
filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie, true);
if (!filter)
return 0;
if (filter->type == OCELOT_VCAP_FILTER_DUMMY)
return ocelot_vcap_dummy_filter_del(ocelot, filter);
net: mscc: ocelot: don't dereference NULL pointers with shared tc filters The following command sequence: tc qdisc del dev swp0 clsact tc qdisc add dev swp0 ingress_block 1 clsact tc qdisc add dev swp1 ingress_block 1 clsact tc filter add block 1 flower action drop tc qdisc del dev swp0 clsact produces the following NPD: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000014 pc : vcap_entry_set+0x14/0x70 lr : ocelot_vcap_filter_del+0x198/0x234 Call trace: vcap_entry_set+0x14/0x70 ocelot_vcap_filter_del+0x198/0x234 ocelot_cls_flower_destroy+0x94/0xe4 felix_cls_flower_del+0x70/0x84 dsa_slave_setup_tc_block_cb+0x13c/0x60c dsa_slave_setup_tc_block_cb_ig+0x20/0x30 tc_setup_cb_reoffload+0x44/0x120 fl_reoffload+0x280/0x320 tcf_block_playback_offloads+0x6c/0x184 tcf_block_unbind+0x80/0xe0 tcf_block_setup+0x174/0x214 tcf_block_offload_cmd.isra.0+0x100/0x13c tcf_block_offload_unbind+0x5c/0xa0 __tcf_block_put+0x54/0x174 tcf_block_put_ext+0x5c/0x74 clsact_destroy+0x40/0x60 qdisc_destroy+0x4c/0x150 qdisc_put+0x70/0x90 qdisc_graft+0x3f0/0x4c0 tc_get_qdisc+0x1cc/0x364 rtnetlink_rcv_msg+0x124/0x340 The reason is that the driver isn't prepared to receive two tc filters with the same cookie. It unconditionally creates a new struct ocelot_vcap_filter for each tc filter, and it adds all filters with the same identifier (cookie) to the ocelot_vcap_block. The problem is here, in ocelot_vcap_filter_del(): /* Gets index of the filter */ index = ocelot_vcap_block_get_filter_index(block, filter); if (index < 0) return index; /* Delete filter */ ocelot_vcap_block_remove_filter(ocelot, block, filter); /* Move up all the blocks over the deleted filter */ for (i = index; i < block->count; i++) { struct ocelot_vcap_filter *tmp; tmp = ocelot_vcap_block_find_filter_by_index(block, i); vcap_entry_set(ocelot, i, tmp); } what will happen is ocelot_vcap_block_get_filter_index() will return the index (@index) of the first filter found with that cookie. This is _not_ the index of _this_ filter, but the other one with the same cookie, because ocelot_vcap_filter_equal() gets fooled. Then later, ocelot_vcap_block_remove_filter() is coded to remove all filters that are ocelot_vcap_filter_equal() with the passed @filter. So unexpectedly, both filters get deleted from the list. Then ocelot_vcap_filter_del() will attempt to move all the other filters up, again finding them by index (@i). The block count is 2, @index was 0, so it will attempt to move up filter @i=0 and @i=1. It assigns tmp = ocelot_vcap_block_find_filter_by_index(block, i), which is now a NULL pointer because ocelot_vcap_block_remove_filter() has removed more than one filter. As far as I can see, this problem has been there since the introduction of tc offload support, however I cannot test beyond the blamed commit due to hardware availability. In any case, any fix cannot be backported that far, due to lots of changes to the code base. Therefore, let's go for the correct solution, which is to not call ocelot_vcap_filter_add() and ocelot_vcap_filter_del(), unless the filter is actually unique and not shared. For the shared filters, we should just modify the ingress port mask and call ocelot_vcap_filter_replace(), a function introduced by commit 95706be13b9f ("net: mscc: ocelot: create a function that replaces an existing VCAP filter"). This way, block->rules will only contain filters with unique cookies, by design. Fixes: 07d985eef073 ("net: dsa: felix: Wire up the ocelot cls_flower methods") Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2022-01-14 13:36:37 +00:00
if (ingress) {
filter->ingress_port_mask &= ~BIT(port);
if (filter->ingress_port_mask)
return ocelot_vcap_filter_replace(ocelot, filter);
}
return ocelot_vcap_filter_del(ocelot, filter);
}
net: mscc: ocelot: simplify tc-flower offload structures The ocelot tc-flower offload binds a second flow block callback (apart from the one for matchall) just because it uses a different block private structure (ocelot_port_private for matchall, ocelot_port_block for flower). But ocelot_port_block just appears to be boilerplate, and doesn't help with anything in particular at all, it's just useless glue between the (global!) struct ocelot_acl_block *block pointer, and a per-netdevice struct ocelot_port_private *priv. So let's just simplify that, and make struct ocelot_port_private be the private structure for the block offload. This makes us able to use the same flow callback as in the case of matchall. This also reveals that the struct ocelot_acl_block *block is used rather strangely, as mentioned above: it is defined globally, allocated at probe time, and freed at unbind time. So just move the structure to the main ocelot structure, which gives further opportunity for simplification. Also get rid of backpointers from struct ocelot_acl_block and struct ocelot_ace_rule back to struct ocelot, by reworking the function prototypes, where necessary, to use a more DSA-friendly "struct ocelot *ocelot, int port" format. And finally, remove the debugging prints that were added during development, since they provide no useful information at this point. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Tested-by: Horatiu Vultur <horatiu.vultur@microchip.com> Reviewed-by: Allan W. Nielsen <allan.nielsen@microchip.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2020-02-29 14:31:06 +00:00
EXPORT_SYMBOL_GPL(ocelot_cls_flower_destroy);
net: mscc: ocelot: simplify tc-flower offload structures The ocelot tc-flower offload binds a second flow block callback (apart from the one for matchall) just because it uses a different block private structure (ocelot_port_private for matchall, ocelot_port_block for flower). But ocelot_port_block just appears to be boilerplate, and doesn't help with anything in particular at all, it's just useless glue between the (global!) struct ocelot_acl_block *block pointer, and a per-netdevice struct ocelot_port_private *priv. So let's just simplify that, and make struct ocelot_port_private be the private structure for the block offload. This makes us able to use the same flow callback as in the case of matchall. This also reveals that the struct ocelot_acl_block *block is used rather strangely, as mentioned above: it is defined globally, allocated at probe time, and freed at unbind time. So just move the structure to the main ocelot structure, which gives further opportunity for simplification. Also get rid of backpointers from struct ocelot_acl_block and struct ocelot_ace_rule back to struct ocelot, by reworking the function prototypes, where necessary, to use a more DSA-friendly "struct ocelot *ocelot, int port" format. And finally, remove the debugging prints that were added during development, since they provide no useful information at this point. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Tested-by: Horatiu Vultur <horatiu.vultur@microchip.com> Reviewed-by: Allan W. Nielsen <allan.nielsen@microchip.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2020-02-29 14:31:06 +00:00
int ocelot_cls_flower_stats(struct ocelot *ocelot, int port,
struct flow_cls_offload *f, bool ingress)
{
struct ocelot_vcap_filter *filter;
struct ocelot_vcap_block *block;
struct flow_stats stats = {0};
int block_id, ret;
block_id = ocelot_chain_to_block(f->common.chain_index, ingress);
if (block_id < 0)
return 0;
if (block_id == PSFP_BLOCK_ID) {
if (ocelot->ops->psfp_stats_get) {
ret = ocelot->ops->psfp_stats_get(ocelot, f, &stats);
if (ret)
return ret;
goto stats_update;
}
return -EOPNOTSUPP;
}
block = &ocelot->block[block_id];
filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie, true);
if (!filter || filter->type == OCELOT_VCAP_FILTER_DUMMY)
return 0;
ret = ocelot_vcap_filter_stats_update(ocelot, filter);
if (ret)
return ret;
stats.pkts = filter->stats.pkts;
stats_update:
flow_stats_update(&f->stats, 0x0, stats.pkts, stats.drops, 0x0,
FLOW_ACTION_HW_STATS_IMMEDIATE);
return 0;
}
net: mscc: ocelot: simplify tc-flower offload structures The ocelot tc-flower offload binds a second flow block callback (apart from the one for matchall) just because it uses a different block private structure (ocelot_port_private for matchall, ocelot_port_block for flower). But ocelot_port_block just appears to be boilerplate, and doesn't help with anything in particular at all, it's just useless glue between the (global!) struct ocelot_acl_block *block pointer, and a per-netdevice struct ocelot_port_private *priv. So let's just simplify that, and make struct ocelot_port_private be the private structure for the block offload. This makes us able to use the same flow callback as in the case of matchall. This also reveals that the struct ocelot_acl_block *block is used rather strangely, as mentioned above: it is defined globally, allocated at probe time, and freed at unbind time. So just move the structure to the main ocelot structure, which gives further opportunity for simplification. Also get rid of backpointers from struct ocelot_acl_block and struct ocelot_ace_rule back to struct ocelot, by reworking the function prototypes, where necessary, to use a more DSA-friendly "struct ocelot *ocelot, int port" format. And finally, remove the debugging prints that were added during development, since they provide no useful information at this point. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Tested-by: Horatiu Vultur <horatiu.vultur@microchip.com> Reviewed-by: Allan W. Nielsen <allan.nielsen@microchip.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2020-02-29 14:31:06 +00:00
EXPORT_SYMBOL_GPL(ocelot_cls_flower_stats);