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