mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-02 14:34:13 -04:00
Merge branch 'mirroring-to-dsa-cpu-port'
Vladimir Oltean says:
====================
Mirroring to DSA CPU port
Users of the NXP LS1028A SoC (drivers/net/dsa/ocelot L2 switch inside)
have requested to mirror packets from the ingress of a switch port to
software. Both port-based and flow-based mirroring is required.
The simplest way I could come up with was to set up tc mirred actions
towards a dummy net_device, and make the offloading of that be accepted
by the driver. Currently, the pattern in drivers is to reject mirred
towards ports they don't know about, but I'm now permitting that,
precisely by mirroring "to the CPU".
For testers, this series depends on commit 34d35b4edb ("net/sched:
act_api: deny mismatched skip_sw/skip_hw flags for actions created by
classifiers") from net/main, which is absent from net-next as of the
day of posting (Oct 23). Without the bug fix it is possible to create
invalid configurations which are not rejected by the kernel.
v2: https://lore.kernel.org/20241017165215.3709000-1-vladimir.oltean@nxp.com
RFC: https://lore.kernel.org/20240913152915.2981126-1-vladimir.oltean@nxp.com
For historical purposes, link to a much older (and much different) attempt:
https://lore.kernel.org/20191002233750.13566-1-olteanv@gmail.com
====================
Link: https://patch.msgid.link/20241023135251.1752488-1-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -228,6 +228,32 @@ ocelot_flower_parse_egress_vlan_modify(struct ocelot_vcap_filter *filter,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
ocelot_flower_parse_egress_port(struct ocelot *ocelot, struct flow_cls_offload *f,
|
||||
const struct flow_action_entry *a, bool mirror,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
const char *act_string = mirror ? "mirror" : "redirect";
|
||||
int egress_port = ocelot->ops->netdev_to_port(a->dev);
|
||||
enum flow_action_id offloadable_act_id;
|
||||
|
||||
offloadable_act_id = mirror ? FLOW_ACTION_MIRRED : FLOW_ACTION_REDIRECT;
|
||||
|
||||
/* Mirroring towards foreign interfaces is handled in software */
|
||||
if (egress_port < 0 || a->id != offloadable_act_id) {
|
||||
if (f->common.skip_sw) {
|
||||
NL_SET_ERR_MSG_FMT(extack,
|
||||
"Can only %s to %s if filter also runs in software",
|
||||
act_string, egress_port < 0 ?
|
||||
"CPU" : "ingress of ocelot port");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
egress_port = ocelot->num_phys_ports;
|
||||
}
|
||||
|
||||
return egress_port;
|
||||
}
|
||||
|
||||
static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
|
||||
bool ingress, struct flow_cls_offload *f,
|
||||
struct ocelot_vcap_filter *filter)
|
||||
@@ -356,6 +382,7 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
|
||||
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
|
||||
break;
|
||||
case FLOW_ACTION_REDIRECT:
|
||||
case FLOW_ACTION_REDIRECT_INGRESS:
|
||||
if (filter->block_id != VCAP_IS2) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Redirect action can only be offloaded to VCAP IS2");
|
||||
@@ -366,17 +393,19 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
|
||||
"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;
|
||||
}
|
||||
|
||||
egress_port = ocelot_flower_parse_egress_port(ocelot, f,
|
||||
a, false,
|
||||
extack);
|
||||
if (egress_port < 0)
|
||||
return egress_port;
|
||||
|
||||
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_MIRRED:
|
||||
case FLOW_ACTION_MIRRED_INGRESS:
|
||||
if (filter->block_id != VCAP_IS2) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Mirror action can only be offloaded to VCAP IS2");
|
||||
@@ -387,12 +416,13 @@ static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
|
||||
"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;
|
||||
}
|
||||
|
||||
egress_port = ocelot_flower_parse_egress_port(ocelot, f,
|
||||
a, true,
|
||||
extack);
|
||||
if (egress_port < 0)
|
||||
return egress_port;
|
||||
|
||||
filter->egress_port.value = egress_port;
|
||||
filter->action.mirror_ena = true;
|
||||
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
|
||||
|
||||
@@ -685,6 +685,7 @@ struct flow_cls_common_offload {
|
||||
u32 chain_index;
|
||||
__be16 protocol;
|
||||
u32 prio;
|
||||
bool skip_sw;
|
||||
struct netlink_ext_ack *extack;
|
||||
};
|
||||
|
||||
|
||||
@@ -755,6 +755,7 @@ tc_cls_common_offload_init(struct flow_cls_common_offload *cls_common,
|
||||
cls_common->chain_index = tp->chain->index;
|
||||
cls_common->protocol = tp->protocol;
|
||||
cls_common->prio = tp->prio >> 16;
|
||||
cls_common->skip_sw = tc_skip_sw(flags);
|
||||
if (tc_skip_sw(flags) || flags & TCA_CLS_FLAGS_VERBOSE)
|
||||
cls_common->extack = extack;
|
||||
}
|
||||
|
||||
@@ -1364,7 +1364,7 @@ dsa_user_mall_tc_entry_find(struct net_device *dev, unsigned long cookie)
|
||||
static int
|
||||
dsa_user_add_cls_matchall_mirred(struct net_device *dev,
|
||||
struct tc_cls_matchall_offload *cls,
|
||||
bool ingress)
|
||||
bool ingress, bool ingress_target)
|
||||
{
|
||||
struct netlink_ext_ack *extack = cls->common.extack;
|
||||
struct dsa_port *dp = dsa_user_to_port(dev);
|
||||
@@ -1376,11 +1376,19 @@ dsa_user_add_cls_matchall_mirred(struct net_device *dev,
|
||||
struct dsa_port *to_dp;
|
||||
int err;
|
||||
|
||||
if (!ds->ops->port_mirror_add)
|
||||
if (cls->common.protocol != htons(ETH_P_ALL)) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Can only offload \"protocol all\" matchall filter");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (!flow_action_basic_hw_stats_check(&cls->rule->action,
|
||||
cls->common.extack))
|
||||
if (!ds->ops->port_mirror_add) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Switch does not support mirroring operation");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (!flow_action_basic_hw_stats_check(&cls->rule->action, extack))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
act = &cls->rule->action.entries[0];
|
||||
@@ -1388,10 +1396,30 @@ dsa_user_add_cls_matchall_mirred(struct net_device *dev,
|
||||
if (!act->dev)
|
||||
return -EINVAL;
|
||||
|
||||
if (!dsa_user_dev_check(act->dev))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
to_dp = dsa_user_to_port(act->dev);
|
||||
if (dsa_user_dev_check(act->dev)) {
|
||||
if (ingress_target) {
|
||||
/* We can only fulfill this using software assist */
|
||||
if (cls->common.skip_sw) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Can only mirred to ingress of DSA user port if filter also runs in software");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
to_dp = dp->cpu_dp;
|
||||
} else {
|
||||
to_dp = dsa_user_to_port(act->dev);
|
||||
}
|
||||
} else {
|
||||
/* Handle mirroring to foreign target ports as a mirror towards
|
||||
* the CPU. The software tc rule will take the packets from
|
||||
* there.
|
||||
*/
|
||||
if (cls->common.skip_sw) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Can only mirred to CPU if filter also runs in software");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
to_dp = dp->cpu_dp;
|
||||
}
|
||||
|
||||
if (dp->ds != to_dp->ds) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
@@ -1446,8 +1474,7 @@ dsa_user_add_cls_matchall_police(struct net_device *dev,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (!flow_action_basic_hw_stats_check(&cls->rule->action,
|
||||
cls->common.extack))
|
||||
if (!flow_action_basic_hw_stats_check(&cls->rule->action, extack))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
list_for_each_entry(mall_tc_entry, &p->mall_tc_list, list) {
|
||||
@@ -1485,17 +1512,30 @@ static int dsa_user_add_cls_matchall(struct net_device *dev,
|
||||
struct tc_cls_matchall_offload *cls,
|
||||
bool ingress)
|
||||
{
|
||||
int err = -EOPNOTSUPP;
|
||||
const struct flow_action *action = &cls->rule->action;
|
||||
struct netlink_ext_ack *extack = cls->common.extack;
|
||||
|
||||
if (cls->common.protocol == htons(ETH_P_ALL) &&
|
||||
flow_offload_has_one_action(&cls->rule->action) &&
|
||||
cls->rule->action.entries[0].id == FLOW_ACTION_MIRRED)
|
||||
err = dsa_user_add_cls_matchall_mirred(dev, cls, ingress);
|
||||
else if (flow_offload_has_one_action(&cls->rule->action) &&
|
||||
cls->rule->action.entries[0].id == FLOW_ACTION_POLICE)
|
||||
err = dsa_user_add_cls_matchall_police(dev, cls, ingress);
|
||||
if (!flow_offload_has_one_action(action)) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Cannot offload matchall filter with more than one action");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return err;
|
||||
switch (action->entries[0].id) {
|
||||
case FLOW_ACTION_MIRRED:
|
||||
return dsa_user_add_cls_matchall_mirred(dev, cls, ingress,
|
||||
false);
|
||||
case FLOW_ACTION_MIRRED_INGRESS:
|
||||
return dsa_user_add_cls_matchall_mirred(dev, cls, ingress,
|
||||
true);
|
||||
case FLOW_ACTION_POLICE:
|
||||
return dsa_user_add_cls_matchall_police(dev, cls, ingress);
|
||||
default:
|
||||
NL_SET_ERR_MSG_MOD(extack, "Unknown action");
|
||||
break;
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static void dsa_user_del_cls_matchall(struct net_device *dev,
|
||||
|
||||
Reference in New Issue
Block a user