xref: /linux/drivers/net/ethernet/microchip/sparx5/sparx5_mirror.c (revision f72aa1b276281b4e4f75261af8425bc99d903f3e)
1 // SPDX-License-Identifier: GPL-2.0+
2 /* Microchip Sparx5 Switch driver
3  *
4  * Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
5  */
6 
7 #include "sparx5_main.h"
8 #include "sparx5_main_regs.h"
9 #include "sparx5_tc.h"
10 
11 #define SPX5_MIRROR_PROBE_MAX 3
12 #define SPX5_MIRROR_DISABLED 0
13 #define SPX5_MIRROR_EGRESS 1
14 #define SPX5_MIRROR_INGRESS 2
15 #define SPX5_MIRROR_MONITOR_PORT_DEFAULT 65
16 #define SPX5_QFWD_MP_OFFSET 9 /* Mirror port offset in the QFWD register */
17 
18 /* Convert from bool ingress/egress to mirror direction */
19 static u32 sparx5_mirror_to_dir(bool ingress)
20 {
21 	return ingress ? SPX5_MIRROR_INGRESS : SPX5_MIRROR_EGRESS;
22 }
23 
24 /* Get ports belonging to this mirror */
25 static u64 sparx5_mirror_port_get(struct sparx5 *sparx5, u32 idx)
26 {
27 	u64 val;
28 
29 	val = spx5_rd(sparx5, ANA_AC_PROBE_PORT_CFG(idx));
30 
31 	if (is_sparx5(sparx5))
32 		val |= (u64)spx5_rd(sparx5, ANA_AC_PROBE_PORT_CFG1(idx)) << 32;
33 
34 	return val;
35 }
36 
37 /* Add port to mirror (only front ports) */
38 static void sparx5_mirror_port_add(struct sparx5 *sparx5, u32 idx, u32 portno)
39 {
40 	u64 reg = portno;
41 	u32 val;
42 
43 	val = BIT(do_div(reg, 32));
44 
45 	if (reg == 0)
46 		return spx5_rmw(val, val, sparx5, ANA_AC_PROBE_PORT_CFG(idx));
47 	else
48 		return spx5_rmw(val, val, sparx5, ANA_AC_PROBE_PORT_CFG1(idx));
49 }
50 
51 /* Delete port from mirror (only front ports) */
52 static void sparx5_mirror_port_del(struct sparx5 *sparx5, u32 idx, u32 portno)
53 {
54 	u64 reg = portno;
55 	u32 val;
56 
57 	val = BIT(do_div(reg, 32));
58 
59 	if (reg == 0)
60 		return spx5_rmw(0, val, sparx5, ANA_AC_PROBE_PORT_CFG(idx));
61 	else
62 		return spx5_rmw(0, val, sparx5, ANA_AC_PROBE_PORT_CFG1(idx));
63 }
64 
65 /* Check if mirror contains port */
66 static bool sparx5_mirror_contains(struct sparx5 *sparx5, u32 idx, u32 portno)
67 {
68 	return (sparx5_mirror_port_get(sparx5, idx) & BIT_ULL(portno)) != 0;
69 }
70 
71 /* Check if mirror is empty */
72 static bool sparx5_mirror_is_empty(struct sparx5 *sparx5, u32 idx)
73 {
74 	return sparx5_mirror_port_get(sparx5, idx) == 0;
75 }
76 
77 /* Get direction of mirror */
78 static u32 sparx5_mirror_dir_get(struct sparx5 *sparx5, u32 idx)
79 {
80 	u32 val = spx5_rd(sparx5, ANA_AC_PROBE_CFG(idx));
81 
82 	return ANA_AC_PROBE_CFG_PROBE_DIRECTION_GET(val);
83 }
84 
85 /* Set direction of mirror */
86 static void sparx5_mirror_dir_set(struct sparx5 *sparx5, u32 idx, u32 dir)
87 {
88 	spx5_rmw(ANA_AC_PROBE_CFG_PROBE_DIRECTION_SET(dir),
89 		 ANA_AC_PROBE_CFG_PROBE_DIRECTION, sparx5,
90 		 ANA_AC_PROBE_CFG(idx));
91 }
92 
93 /* Set the monitor port for this mirror */
94 static void sparx5_mirror_monitor_set(struct sparx5 *sparx5, u32 idx,
95 				      u32 portno)
96 {
97 	spx5_rmw(QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL_SET(portno),
98 		 QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL, sparx5,
99 		 QFWD_FRAME_COPY_CFG(idx + SPX5_QFWD_MP_OFFSET));
100 }
101 
102 /* Get the monitor port of this mirror */
103 static u32 sparx5_mirror_monitor_get(struct sparx5 *sparx5, u32 idx)
104 {
105 	u32 val = spx5_rd(sparx5,
106 			  QFWD_FRAME_COPY_CFG(idx + SPX5_QFWD_MP_OFFSET));
107 
108 	return QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL_GET(val);
109 }
110 
111 /* Check if port is the monitor port of this mirror */
112 static bool sparx5_mirror_has_monitor(struct sparx5 *sparx5, u32 idx,
113 				      u32 portno)
114 {
115 	return sparx5_mirror_monitor_get(sparx5, idx) == portno;
116 }
117 
118 /* Get a suitable mirror for this port */
119 static int sparx5_mirror_get(struct sparx5_port *sport,
120 			     struct sparx5_port *mport, u32 dir, u32 *idx)
121 {
122 	struct sparx5 *sparx5 = sport->sparx5;
123 	u32 i;
124 
125 	/* Check if this port is already used as a monitor port */
126 	for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++)
127 		if (sparx5_mirror_has_monitor(sparx5, i, sport->portno))
128 			return -EINVAL;
129 
130 	/* Check if existing mirror can be reused
131 	 * (same direction and monitor port).
132 	 */
133 	for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++) {
134 		if (sparx5_mirror_dir_get(sparx5, i) == dir &&
135 		    sparx5_mirror_has_monitor(sparx5, i, mport->portno)) {
136 			*idx = i;
137 			return 0;
138 		}
139 	}
140 
141 	/* Return free mirror */
142 	for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++) {
143 		if (sparx5_mirror_is_empty(sparx5, i)) {
144 			*idx = i;
145 			return 0;
146 		}
147 	}
148 
149 	return -ENOENT;
150 }
151 
152 int sparx5_mirror_add(struct sparx5_mall_entry *entry)
153 {
154 	u32 mirror_idx, dir = sparx5_mirror_to_dir(entry->ingress);
155 	struct sparx5_port *sport, *mport;
156 	struct sparx5 *sparx5;
157 	int err;
158 
159 	/* Source port */
160 	sport = entry->port;
161 	/* monitor port */
162 	mport = entry->mirror.port;
163 	sparx5 = sport->sparx5;
164 
165 	if (sport->portno == mport->portno)
166 		return -EINVAL;
167 
168 	err = sparx5_mirror_get(sport, mport, dir, &mirror_idx);
169 	if (err)
170 		return err;
171 
172 	if (sparx5_mirror_contains(sparx5, mirror_idx, sport->portno))
173 		return -EEXIST;
174 
175 	/* Add port to mirror */
176 	sparx5_mirror_port_add(sparx5, mirror_idx, sport->portno);
177 
178 	/* Set direction of mirror */
179 	sparx5_mirror_dir_set(sparx5, mirror_idx, dir);
180 
181 	/* Set monitor port for mirror */
182 	sparx5_mirror_monitor_set(sparx5, mirror_idx, mport->portno);
183 
184 	entry->mirror.idx = mirror_idx;
185 
186 	return 0;
187 }
188 
189 void sparx5_mirror_del(struct sparx5_mall_entry *entry)
190 {
191 	struct sparx5_port *port = entry->port;
192 	struct sparx5 *sparx5 = port->sparx5;
193 	u32 mirror_idx = entry->mirror.idx;
194 
195 	sparx5_mirror_port_del(sparx5, mirror_idx, port->portno);
196 	if (!sparx5_mirror_is_empty(sparx5, mirror_idx))
197 		return;
198 
199 	sparx5_mirror_dir_set(sparx5, mirror_idx, SPX5_MIRROR_DISABLED);
200 
201 	sparx5_mirror_monitor_set(sparx5,
202 				  mirror_idx,
203 				  SPX5_MIRROR_MONITOR_PORT_DEFAULT);
204 }
205 
206 void sparx5_mirror_stats(struct sparx5_mall_entry *entry,
207 			 struct flow_stats *fstats)
208 {
209 	struct sparx5_port *port = entry->port;
210 	struct rtnl_link_stats64 new_stats;
211 	struct flow_stats *old_stats;
212 
213 	old_stats = &entry->port->mirror_stats;
214 	sparx5_get_stats64(port->ndev, &new_stats);
215 
216 	if (entry->ingress) {
217 		flow_stats_update(fstats,
218 				  new_stats.rx_bytes - old_stats->bytes,
219 				  new_stats.rx_packets - old_stats->pkts,
220 				  new_stats.rx_dropped - old_stats->drops,
221 				  old_stats->lastused,
222 				  FLOW_ACTION_HW_STATS_IMMEDIATE);
223 
224 		old_stats->bytes = new_stats.rx_bytes;
225 		old_stats->pkts = new_stats.rx_packets;
226 		old_stats->drops = new_stats.rx_dropped;
227 		old_stats->lastused = jiffies;
228 	} else {
229 		flow_stats_update(fstats,
230 				  new_stats.tx_bytes - old_stats->bytes,
231 				  new_stats.tx_packets - old_stats->pkts,
232 				  new_stats.tx_dropped - old_stats->drops,
233 				  old_stats->lastused,
234 				  FLOW_ACTION_HW_STATS_IMMEDIATE);
235 
236 		old_stats->bytes = new_stats.tx_bytes;
237 		old_stats->pkts = new_stats.tx_packets;
238 		old_stats->drops = new_stats.tx_dropped;
239 		old_stats->lastused = jiffies;
240 	}
241 }
242