1*ab3f3d5eSRoi Dayan // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
2*ab3f3d5eSRoi Dayan // Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3*ab3f3d5eSRoi Dayan 
4*ab3f3d5eSRoi Dayan #include <linux/if_macvlan.h>
5*ab3f3d5eSRoi Dayan #include <linux/if_vlan.h>
6*ab3f3d5eSRoi Dayan #include <net/bareudp.h>
7*ab3f3d5eSRoi Dayan #include <net/bonding.h>
8*ab3f3d5eSRoi Dayan #include "act.h"
9*ab3f3d5eSRoi Dayan #include "vlan.h"
10*ab3f3d5eSRoi Dayan #include "en/tc_tun_encap.h"
11*ab3f3d5eSRoi Dayan #include "en/tc_priv.h"
12*ab3f3d5eSRoi Dayan #include "en_rep.h"
13*ab3f3d5eSRoi Dayan 
14*ab3f3d5eSRoi Dayan static bool
15*ab3f3d5eSRoi Dayan same_vf_reps(struct mlx5e_priv *priv, struct net_device *out_dev)
16*ab3f3d5eSRoi Dayan {
17*ab3f3d5eSRoi Dayan 	return mlx5e_eswitch_vf_rep(priv->netdev) &&
18*ab3f3d5eSRoi Dayan 	       priv->netdev == out_dev;
19*ab3f3d5eSRoi Dayan }
20*ab3f3d5eSRoi Dayan 
21*ab3f3d5eSRoi Dayan static int
22*ab3f3d5eSRoi Dayan verify_uplink_forwarding(struct mlx5e_priv *priv,
23*ab3f3d5eSRoi Dayan 			 struct mlx5_flow_attr *attr,
24*ab3f3d5eSRoi Dayan 			 struct net_device *out_dev,
25*ab3f3d5eSRoi Dayan 			 struct netlink_ext_ack *extack)
26*ab3f3d5eSRoi Dayan {
27*ab3f3d5eSRoi Dayan 	struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
28*ab3f3d5eSRoi Dayan 	struct mlx5e_rep_priv *rep_priv;
29*ab3f3d5eSRoi Dayan 
30*ab3f3d5eSRoi Dayan 	/* Forwarding non encapsulated traffic between
31*ab3f3d5eSRoi Dayan 	 * uplink ports is allowed only if
32*ab3f3d5eSRoi Dayan 	 * termination_table_raw_traffic cap is set.
33*ab3f3d5eSRoi Dayan 	 *
34*ab3f3d5eSRoi Dayan 	 * Input vport was stored attr->in_rep.
35*ab3f3d5eSRoi Dayan 	 * In LAG case, *priv* is the private data of
36*ab3f3d5eSRoi Dayan 	 * uplink which may be not the input vport.
37*ab3f3d5eSRoi Dayan 	 */
38*ab3f3d5eSRoi Dayan 	rep_priv = mlx5e_rep_to_rep_priv(attr->esw_attr->in_rep);
39*ab3f3d5eSRoi Dayan 
40*ab3f3d5eSRoi Dayan 	if (!(mlx5e_eswitch_uplink_rep(rep_priv->netdev) &&
41*ab3f3d5eSRoi Dayan 	      mlx5e_eswitch_uplink_rep(out_dev)))
42*ab3f3d5eSRoi Dayan 		return 0;
43*ab3f3d5eSRoi Dayan 
44*ab3f3d5eSRoi Dayan 	if (!MLX5_CAP_ESW_FLOWTABLE_FDB(esw->dev,
45*ab3f3d5eSRoi Dayan 					termination_table_raw_traffic)) {
46*ab3f3d5eSRoi Dayan 		NL_SET_ERR_MSG_MOD(extack,
47*ab3f3d5eSRoi Dayan 				   "devices are both uplink, can't offload forwarding");
48*ab3f3d5eSRoi Dayan 			pr_err("devices %s %s are both uplink, can't offload forwarding\n",
49*ab3f3d5eSRoi Dayan 			       priv->netdev->name, out_dev->name);
50*ab3f3d5eSRoi Dayan 			return -EOPNOTSUPP;
51*ab3f3d5eSRoi Dayan 	} else if (out_dev != rep_priv->netdev) {
52*ab3f3d5eSRoi Dayan 		NL_SET_ERR_MSG_MOD(extack,
53*ab3f3d5eSRoi Dayan 				   "devices are not the same uplink, can't offload forwarding");
54*ab3f3d5eSRoi Dayan 		pr_err("devices %s %s are both uplink but not the same, can't offload forwarding\n",
55*ab3f3d5eSRoi Dayan 		       priv->netdev->name, out_dev->name);
56*ab3f3d5eSRoi Dayan 		return -EOPNOTSUPP;
57*ab3f3d5eSRoi Dayan 	}
58*ab3f3d5eSRoi Dayan 	return 0;
59*ab3f3d5eSRoi Dayan }
60*ab3f3d5eSRoi Dayan 
61*ab3f3d5eSRoi Dayan static bool
62*ab3f3d5eSRoi Dayan is_duplicated_output_device(struct net_device *dev,
63*ab3f3d5eSRoi Dayan 			    struct net_device *out_dev,
64*ab3f3d5eSRoi Dayan 			    int *ifindexes, int if_count,
65*ab3f3d5eSRoi Dayan 			    struct netlink_ext_ack *extack)
66*ab3f3d5eSRoi Dayan {
67*ab3f3d5eSRoi Dayan 	int i;
68*ab3f3d5eSRoi Dayan 
69*ab3f3d5eSRoi Dayan 	for (i = 0; i < if_count; i++) {
70*ab3f3d5eSRoi Dayan 		if (ifindexes[i] == out_dev->ifindex) {
71*ab3f3d5eSRoi Dayan 			NL_SET_ERR_MSG_MOD(extack, "can't duplicate output to same device");
72*ab3f3d5eSRoi Dayan 			netdev_err(dev, "can't duplicate output to same device: %s\n",
73*ab3f3d5eSRoi Dayan 				   out_dev->name);
74*ab3f3d5eSRoi Dayan 			return true;
75*ab3f3d5eSRoi Dayan 		}
76*ab3f3d5eSRoi Dayan 	}
77*ab3f3d5eSRoi Dayan 
78*ab3f3d5eSRoi Dayan 	return false;
79*ab3f3d5eSRoi Dayan }
80*ab3f3d5eSRoi Dayan 
81*ab3f3d5eSRoi Dayan static struct net_device *
82*ab3f3d5eSRoi Dayan get_fdb_out_dev(struct net_device *uplink_dev, struct net_device *out_dev)
83*ab3f3d5eSRoi Dayan {
84*ab3f3d5eSRoi Dayan 	struct net_device *fdb_out_dev = out_dev;
85*ab3f3d5eSRoi Dayan 	struct net_device *uplink_upper;
86*ab3f3d5eSRoi Dayan 
87*ab3f3d5eSRoi Dayan 	rcu_read_lock();
88*ab3f3d5eSRoi Dayan 	uplink_upper = netdev_master_upper_dev_get_rcu(uplink_dev);
89*ab3f3d5eSRoi Dayan 	if (uplink_upper && netif_is_lag_master(uplink_upper) &&
90*ab3f3d5eSRoi Dayan 	    uplink_upper == out_dev) {
91*ab3f3d5eSRoi Dayan 		fdb_out_dev = uplink_dev;
92*ab3f3d5eSRoi Dayan 	} else if (netif_is_lag_master(out_dev)) {
93*ab3f3d5eSRoi Dayan 		fdb_out_dev = bond_option_active_slave_get_rcu(netdev_priv(out_dev));
94*ab3f3d5eSRoi Dayan 		if (fdb_out_dev &&
95*ab3f3d5eSRoi Dayan 		    (!mlx5e_eswitch_rep(fdb_out_dev) ||
96*ab3f3d5eSRoi Dayan 		     !netdev_port_same_parent_id(fdb_out_dev, uplink_dev)))
97*ab3f3d5eSRoi Dayan 			fdb_out_dev = NULL;
98*ab3f3d5eSRoi Dayan 	}
99*ab3f3d5eSRoi Dayan 	rcu_read_unlock();
100*ab3f3d5eSRoi Dayan 	return fdb_out_dev;
101*ab3f3d5eSRoi Dayan }
102*ab3f3d5eSRoi Dayan 
103*ab3f3d5eSRoi Dayan static bool
104*ab3f3d5eSRoi Dayan tc_act_can_offload_mirred(struct mlx5e_tc_act_parse_state *parse_state,
105*ab3f3d5eSRoi Dayan 			  const struct flow_action_entry *act,
106*ab3f3d5eSRoi Dayan 			  int act_index)
107*ab3f3d5eSRoi Dayan {
108*ab3f3d5eSRoi Dayan 	struct netlink_ext_ack *extack = parse_state->extack;
109*ab3f3d5eSRoi Dayan 	struct mlx5e_tc_flow *flow = parse_state->flow;
110*ab3f3d5eSRoi Dayan 	struct mlx5e_tc_flow_parse_attr *parse_attr;
111*ab3f3d5eSRoi Dayan 	struct net_device *out_dev = act->dev;
112*ab3f3d5eSRoi Dayan 	struct mlx5e_priv *priv = flow->priv;
113*ab3f3d5eSRoi Dayan 	struct mlx5_esw_flow_attr *esw_attr;
114*ab3f3d5eSRoi Dayan 
115*ab3f3d5eSRoi Dayan 	parse_attr = flow->attr->parse_attr;
116*ab3f3d5eSRoi Dayan 	esw_attr = flow->attr->esw_attr;
117*ab3f3d5eSRoi Dayan 
118*ab3f3d5eSRoi Dayan 	if (!out_dev) {
119*ab3f3d5eSRoi Dayan 		/* out_dev is NULL when filters with
120*ab3f3d5eSRoi Dayan 		 * non-existing mirred device are replayed to
121*ab3f3d5eSRoi Dayan 		 * the driver.
122*ab3f3d5eSRoi Dayan 		 */
123*ab3f3d5eSRoi Dayan 		return false;
124*ab3f3d5eSRoi Dayan 	}
125*ab3f3d5eSRoi Dayan 
126*ab3f3d5eSRoi Dayan 	if (parse_state->mpls_push && !netif_is_bareudp(out_dev)) {
127*ab3f3d5eSRoi Dayan 		NL_SET_ERR_MSG_MOD(extack, "mpls is supported only through a bareudp device");
128*ab3f3d5eSRoi Dayan 		return false;
129*ab3f3d5eSRoi Dayan 	}
130*ab3f3d5eSRoi Dayan 
131*ab3f3d5eSRoi Dayan 	if (mlx5e_is_ft_flow(flow) && out_dev == priv->netdev) {
132*ab3f3d5eSRoi Dayan 		/* Ignore forward to self rules generated
133*ab3f3d5eSRoi Dayan 		 * by adding both mlx5 devs to the flow table
134*ab3f3d5eSRoi Dayan 		 * block on a normal nft offload setup.
135*ab3f3d5eSRoi Dayan 		 */
136*ab3f3d5eSRoi Dayan 		return false;
137*ab3f3d5eSRoi Dayan 	}
138*ab3f3d5eSRoi Dayan 
139*ab3f3d5eSRoi Dayan 	if (esw_attr->out_count >= MLX5_MAX_FLOW_FWD_VPORTS) {
140*ab3f3d5eSRoi Dayan 		NL_SET_ERR_MSG_MOD(extack,
141*ab3f3d5eSRoi Dayan 				   "can't support more output ports, can't offload forwarding");
142*ab3f3d5eSRoi Dayan 		netdev_warn(priv->netdev,
143*ab3f3d5eSRoi Dayan 			    "can't support more than %d output ports, can't offload forwarding\n",
144*ab3f3d5eSRoi Dayan 			    esw_attr->out_count);
145*ab3f3d5eSRoi Dayan 		return false;
146*ab3f3d5eSRoi Dayan 	}
147*ab3f3d5eSRoi Dayan 
148*ab3f3d5eSRoi Dayan 	if (parse_state->encap ||
149*ab3f3d5eSRoi Dayan 	    netdev_port_same_parent_id(priv->netdev, out_dev) ||
150*ab3f3d5eSRoi Dayan 	    netif_is_ovs_master(out_dev))
151*ab3f3d5eSRoi Dayan 		return true;
152*ab3f3d5eSRoi Dayan 
153*ab3f3d5eSRoi Dayan 	if (parse_attr->filter_dev != priv->netdev) {
154*ab3f3d5eSRoi Dayan 		/* All mlx5 devices are called to configure
155*ab3f3d5eSRoi Dayan 		 * high level device filters. Therefore, the
156*ab3f3d5eSRoi Dayan 		 * *attempt* to  install a filter on invalid
157*ab3f3d5eSRoi Dayan 		 * eswitch should not trigger an explicit error
158*ab3f3d5eSRoi Dayan 		 */
159*ab3f3d5eSRoi Dayan 		return false;
160*ab3f3d5eSRoi Dayan 	}
161*ab3f3d5eSRoi Dayan 
162*ab3f3d5eSRoi Dayan 	NL_SET_ERR_MSG_MOD(extack, "devices are not on same switch HW, can't offload forwarding");
163*ab3f3d5eSRoi Dayan 	netdev_warn(priv->netdev,
164*ab3f3d5eSRoi Dayan 		    "devices %s %s not on same switch HW, can't offload forwarding\n",
165*ab3f3d5eSRoi Dayan 		    netdev_name(priv->netdev),
166*ab3f3d5eSRoi Dayan 		    out_dev->name);
167*ab3f3d5eSRoi Dayan 
168*ab3f3d5eSRoi Dayan 	return false;
169*ab3f3d5eSRoi Dayan }
170*ab3f3d5eSRoi Dayan 
171*ab3f3d5eSRoi Dayan static int
172*ab3f3d5eSRoi Dayan parse_mirred_encap(struct mlx5e_tc_act_parse_state *parse_state,
173*ab3f3d5eSRoi Dayan 		   const struct flow_action_entry *act,
174*ab3f3d5eSRoi Dayan 		   struct mlx5_flow_attr *attr)
175*ab3f3d5eSRoi Dayan {
176*ab3f3d5eSRoi Dayan 	struct mlx5e_tc_flow_parse_attr *parse_attr = attr->parse_attr;
177*ab3f3d5eSRoi Dayan 	struct mlx5_esw_flow_attr *esw_attr = attr->esw_attr;
178*ab3f3d5eSRoi Dayan 	struct net_device *out_dev = act->dev;
179*ab3f3d5eSRoi Dayan 
180*ab3f3d5eSRoi Dayan 	parse_attr->mirred_ifindex[esw_attr->out_count] = out_dev->ifindex;
181*ab3f3d5eSRoi Dayan 	parse_attr->tun_info[esw_attr->out_count] =
182*ab3f3d5eSRoi Dayan 		mlx5e_dup_tun_info(parse_state->tun_info);
183*ab3f3d5eSRoi Dayan 
184*ab3f3d5eSRoi Dayan 	if (!parse_attr->tun_info[esw_attr->out_count])
185*ab3f3d5eSRoi Dayan 		return -ENOMEM;
186*ab3f3d5eSRoi Dayan 
187*ab3f3d5eSRoi Dayan 	parse_state->encap = false;
188*ab3f3d5eSRoi Dayan 	esw_attr->dests[esw_attr->out_count].flags |= MLX5_ESW_DEST_ENCAP;
189*ab3f3d5eSRoi Dayan 	esw_attr->out_count++;
190*ab3f3d5eSRoi Dayan 	/* attr->dests[].rep is resolved when we handle encap */
191*ab3f3d5eSRoi Dayan 
192*ab3f3d5eSRoi Dayan 	return 0;
193*ab3f3d5eSRoi Dayan }
194*ab3f3d5eSRoi Dayan 
195*ab3f3d5eSRoi Dayan static int
196*ab3f3d5eSRoi Dayan parse_mirred(struct mlx5e_tc_act_parse_state *parse_state,
197*ab3f3d5eSRoi Dayan 	     const struct flow_action_entry *act,
198*ab3f3d5eSRoi Dayan 	     struct mlx5e_priv *priv,
199*ab3f3d5eSRoi Dayan 	     struct mlx5_flow_attr *attr)
200*ab3f3d5eSRoi Dayan {
201*ab3f3d5eSRoi Dayan 	struct mlx5e_tc_flow_parse_attr *parse_attr = attr->parse_attr;
202*ab3f3d5eSRoi Dayan 	struct mlx5_esw_flow_attr *esw_attr = attr->esw_attr;
203*ab3f3d5eSRoi Dayan 	struct netlink_ext_ack *extack = parse_state->extack;
204*ab3f3d5eSRoi Dayan 	struct mlx5e_rep_priv *rpriv = priv->ppriv;
205*ab3f3d5eSRoi Dayan 	struct net_device *out_dev = act->dev;
206*ab3f3d5eSRoi Dayan 	struct net_device *uplink_dev;
207*ab3f3d5eSRoi Dayan 	struct mlx5e_priv *out_priv;
208*ab3f3d5eSRoi Dayan 	struct mlx5_eswitch *esw;
209*ab3f3d5eSRoi Dayan 	int *ifindexes;
210*ab3f3d5eSRoi Dayan 	int if_count;
211*ab3f3d5eSRoi Dayan 	int err;
212*ab3f3d5eSRoi Dayan 
213*ab3f3d5eSRoi Dayan 	esw = priv->mdev->priv.eswitch;
214*ab3f3d5eSRoi Dayan 	uplink_dev = mlx5_eswitch_uplink_get_proto_dev(esw, REP_ETH);
215*ab3f3d5eSRoi Dayan 	ifindexes = parse_state->ifindexes;
216*ab3f3d5eSRoi Dayan 	if_count = parse_state->if_count;
217*ab3f3d5eSRoi Dayan 
218*ab3f3d5eSRoi Dayan 	if (is_duplicated_output_device(priv->netdev, out_dev, ifindexes, if_count, extack))
219*ab3f3d5eSRoi Dayan 		return -EOPNOTSUPP;
220*ab3f3d5eSRoi Dayan 
221*ab3f3d5eSRoi Dayan 	parse_state->ifindexes[if_count] = out_dev->ifindex;
222*ab3f3d5eSRoi Dayan 	parse_state->if_count++;
223*ab3f3d5eSRoi Dayan 
224*ab3f3d5eSRoi Dayan 	out_dev = get_fdb_out_dev(uplink_dev, out_dev);
225*ab3f3d5eSRoi Dayan 	if (!out_dev)
226*ab3f3d5eSRoi Dayan 		return -ENODEV;
227*ab3f3d5eSRoi Dayan 
228*ab3f3d5eSRoi Dayan 	if (is_vlan_dev(out_dev)) {
229*ab3f3d5eSRoi Dayan 		err = mlx5e_tc_act_vlan_add_push_action(priv, attr, &out_dev, extack);
230*ab3f3d5eSRoi Dayan 		if (err)
231*ab3f3d5eSRoi Dayan 			return err;
232*ab3f3d5eSRoi Dayan 	}
233*ab3f3d5eSRoi Dayan 
234*ab3f3d5eSRoi Dayan 	if (is_vlan_dev(parse_attr->filter_dev)) {
235*ab3f3d5eSRoi Dayan 		err = mlx5e_tc_act_vlan_add_pop_action(priv, attr, extack);
236*ab3f3d5eSRoi Dayan 		if (err)
237*ab3f3d5eSRoi Dayan 			return err;
238*ab3f3d5eSRoi Dayan 	}
239*ab3f3d5eSRoi Dayan 
240*ab3f3d5eSRoi Dayan 	if (netif_is_macvlan(out_dev))
241*ab3f3d5eSRoi Dayan 		out_dev = macvlan_dev_real_dev(out_dev);
242*ab3f3d5eSRoi Dayan 
243*ab3f3d5eSRoi Dayan 	err = verify_uplink_forwarding(priv, attr, out_dev, extack);
244*ab3f3d5eSRoi Dayan 	if (err)
245*ab3f3d5eSRoi Dayan 		return err;
246*ab3f3d5eSRoi Dayan 
247*ab3f3d5eSRoi Dayan 	if (!mlx5e_is_valid_eswitch_fwd_dev(priv, out_dev)) {
248*ab3f3d5eSRoi Dayan 		NL_SET_ERR_MSG_MOD(extack,
249*ab3f3d5eSRoi Dayan 				   "devices are not on same switch HW, can't offload forwarding");
250*ab3f3d5eSRoi Dayan 		return -EOPNOTSUPP;
251*ab3f3d5eSRoi Dayan 	}
252*ab3f3d5eSRoi Dayan 
253*ab3f3d5eSRoi Dayan 	if (same_vf_reps(priv, out_dev)) {
254*ab3f3d5eSRoi Dayan 		NL_SET_ERR_MSG_MOD(extack, "can't forward from a VF to itself");
255*ab3f3d5eSRoi Dayan 		return -EOPNOTSUPP;
256*ab3f3d5eSRoi Dayan 	}
257*ab3f3d5eSRoi Dayan 
258*ab3f3d5eSRoi Dayan 	out_priv = netdev_priv(out_dev);
259*ab3f3d5eSRoi Dayan 	rpriv = out_priv->ppriv;
260*ab3f3d5eSRoi Dayan 	esw_attr->dests[esw_attr->out_count].rep = rpriv->rep;
261*ab3f3d5eSRoi Dayan 	esw_attr->dests[esw_attr->out_count].mdev = out_priv->mdev;
262*ab3f3d5eSRoi Dayan 	esw_attr->out_count++;
263*ab3f3d5eSRoi Dayan 
264*ab3f3d5eSRoi Dayan 	return 0;
265*ab3f3d5eSRoi Dayan }
266*ab3f3d5eSRoi Dayan 
267*ab3f3d5eSRoi Dayan static int
268*ab3f3d5eSRoi Dayan parse_mirred_ovs_master(struct mlx5e_tc_act_parse_state *parse_state,
269*ab3f3d5eSRoi Dayan 			const struct flow_action_entry *act,
270*ab3f3d5eSRoi Dayan 			struct mlx5e_priv *priv,
271*ab3f3d5eSRoi Dayan 			struct mlx5_flow_attr *attr)
272*ab3f3d5eSRoi Dayan {
273*ab3f3d5eSRoi Dayan 	struct mlx5_esw_flow_attr *esw_attr = attr->esw_attr;
274*ab3f3d5eSRoi Dayan 	struct net_device *out_dev = act->dev;
275*ab3f3d5eSRoi Dayan 	int err;
276*ab3f3d5eSRoi Dayan 
277*ab3f3d5eSRoi Dayan 	err = mlx5e_set_fwd_to_int_port_actions(priv, attr, out_dev->ifindex,
278*ab3f3d5eSRoi Dayan 						MLX5E_TC_INT_PORT_EGRESS,
279*ab3f3d5eSRoi Dayan 						&attr->action, esw_attr->out_count);
280*ab3f3d5eSRoi Dayan 	if (err)
281*ab3f3d5eSRoi Dayan 		return err;
282*ab3f3d5eSRoi Dayan 
283*ab3f3d5eSRoi Dayan 	esw_attr->out_count++;
284*ab3f3d5eSRoi Dayan 	return 0;
285*ab3f3d5eSRoi Dayan }
286*ab3f3d5eSRoi Dayan 
287*ab3f3d5eSRoi Dayan static int
288*ab3f3d5eSRoi Dayan tc_act_parse_mirred(struct mlx5e_tc_act_parse_state *parse_state,
289*ab3f3d5eSRoi Dayan 		    const struct flow_action_entry *act,
290*ab3f3d5eSRoi Dayan 		    struct mlx5e_priv *priv,
291*ab3f3d5eSRoi Dayan 		    struct mlx5_flow_attr *attr)
292*ab3f3d5eSRoi Dayan {
293*ab3f3d5eSRoi Dayan 	struct net_device *out_dev = act->dev;
294*ab3f3d5eSRoi Dayan 	int err = -EOPNOTSUPP;
295*ab3f3d5eSRoi Dayan 
296*ab3f3d5eSRoi Dayan 	if (parse_state->encap)
297*ab3f3d5eSRoi Dayan 		err = parse_mirred_encap(parse_state, act, attr);
298*ab3f3d5eSRoi Dayan 	else if (netdev_port_same_parent_id(priv->netdev, out_dev))
299*ab3f3d5eSRoi Dayan 		err = parse_mirred(parse_state, act, priv, attr);
300*ab3f3d5eSRoi Dayan 	else if (netif_is_ovs_master(out_dev))
301*ab3f3d5eSRoi Dayan 		err = parse_mirred_ovs_master(parse_state, act, priv, attr);
302*ab3f3d5eSRoi Dayan 
303*ab3f3d5eSRoi Dayan 	if (err)
304*ab3f3d5eSRoi Dayan 		return err;
305*ab3f3d5eSRoi Dayan 
306*ab3f3d5eSRoi Dayan 	attr->action |= MLX5_FLOW_CONTEXT_ACTION_FWD_DEST |
307*ab3f3d5eSRoi Dayan 			MLX5_FLOW_CONTEXT_ACTION_COUNT;
308*ab3f3d5eSRoi Dayan 
309*ab3f3d5eSRoi Dayan 	return 0;
310*ab3f3d5eSRoi Dayan }
311*ab3f3d5eSRoi Dayan 
312*ab3f3d5eSRoi Dayan struct mlx5e_tc_act mlx5e_tc_act_mirred = {
313*ab3f3d5eSRoi Dayan 	.can_offload = tc_act_can_offload_mirred,
314*ab3f3d5eSRoi Dayan 	.parse_action = tc_act_parse_mirred,
315*ab3f3d5eSRoi Dayan };
316