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