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 */ 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 */ 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) */ 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) */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 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