| // SPDX-License-Identifier: GPL-2.0+ |
| /* Microchip Sparx5 Switch driver |
| * |
| * Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries. |
| */ |
| |
| #include "sparx5_main.h" |
| #include "sparx5_main_regs.h" |
| #include "sparx5_tc.h" |
| |
| #define SPX5_MIRROR_PROBE_MAX 3 |
| #define SPX5_MIRROR_DISABLED 0 |
| #define SPX5_MIRROR_EGRESS 1 |
| #define SPX5_MIRROR_INGRESS 2 |
| #define SPX5_MIRROR_MONITOR_PORT_DEFAULT 65 |
| #define SPX5_QFWD_MP_OFFSET 9 /* Mirror port offset in the QFWD register */ |
| |
| /* Convert from bool ingress/egress to mirror direction */ |
| static u32 sparx5_mirror_to_dir(bool ingress) |
| { |
| return ingress ? SPX5_MIRROR_INGRESS : SPX5_MIRROR_EGRESS; |
| } |
| |
| /* Get ports belonging to this mirror */ |
| static u64 sparx5_mirror_port_get(struct sparx5 *sparx5, u32 idx) |
| { |
| return (u64)spx5_rd(sparx5, ANA_AC_PROBE_PORT_CFG1(idx)) << 32 | |
| spx5_rd(sparx5, ANA_AC_PROBE_PORT_CFG(idx)); |
| } |
| |
| /* Add port to mirror (only front ports) */ |
| static void sparx5_mirror_port_add(struct sparx5 *sparx5, u32 idx, u32 portno) |
| { |
| u64 reg = portno; |
| u32 val; |
| |
| val = BIT(do_div(reg, 32)); |
| |
| if (reg == 0) |
| return spx5_rmw(val, val, sparx5, ANA_AC_PROBE_PORT_CFG(idx)); |
| else |
| return spx5_rmw(val, val, sparx5, ANA_AC_PROBE_PORT_CFG1(idx)); |
| } |
| |
| /* Delete port from mirror (only front ports) */ |
| static void sparx5_mirror_port_del(struct sparx5 *sparx5, u32 idx, u32 portno) |
| { |
| u64 reg = portno; |
| u32 val; |
| |
| val = BIT(do_div(reg, 32)); |
| |
| if (reg == 0) |
| return spx5_rmw(0, val, sparx5, ANA_AC_PROBE_PORT_CFG(idx)); |
| else |
| return spx5_rmw(0, val, sparx5, ANA_AC_PROBE_PORT_CFG1(idx)); |
| } |
| |
| /* Check if mirror contains port */ |
| static bool sparx5_mirror_contains(struct sparx5 *sparx5, u32 idx, u32 portno) |
| { |
| return (sparx5_mirror_port_get(sparx5, idx) & BIT_ULL(portno)) != 0; |
| } |
| |
| /* Check if mirror is empty */ |
| static bool sparx5_mirror_is_empty(struct sparx5 *sparx5, u32 idx) |
| { |
| return sparx5_mirror_port_get(sparx5, idx) == 0; |
| } |
| |
| /* Get direction of mirror */ |
| static u32 sparx5_mirror_dir_get(struct sparx5 *sparx5, u32 idx) |
| { |
| u32 val = spx5_rd(sparx5, ANA_AC_PROBE_CFG(idx)); |
| |
| return ANA_AC_PROBE_CFG_PROBE_DIRECTION_GET(val); |
| } |
| |
| /* Set direction of mirror */ |
| static void sparx5_mirror_dir_set(struct sparx5 *sparx5, u32 idx, u32 dir) |
| { |
| spx5_rmw(ANA_AC_PROBE_CFG_PROBE_DIRECTION_SET(dir), |
| ANA_AC_PROBE_CFG_PROBE_DIRECTION, sparx5, |
| ANA_AC_PROBE_CFG(idx)); |
| } |
| |
| /* Set the monitor port for this mirror */ |
| static void sparx5_mirror_monitor_set(struct sparx5 *sparx5, u32 idx, |
| u32 portno) |
| { |
| spx5_rmw(QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL_SET(portno), |
| QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL, sparx5, |
| QFWD_FRAME_COPY_CFG(idx + SPX5_QFWD_MP_OFFSET)); |
| } |
| |
| /* Get the monitor port of this mirror */ |
| static u32 sparx5_mirror_monitor_get(struct sparx5 *sparx5, u32 idx) |
| { |
| u32 val = spx5_rd(sparx5, |
| QFWD_FRAME_COPY_CFG(idx + SPX5_QFWD_MP_OFFSET)); |
| |
| return QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL_GET(val); |
| } |
| |
| /* Check if port is the monitor port of this mirror */ |
| static bool sparx5_mirror_has_monitor(struct sparx5 *sparx5, u32 idx, |
| u32 portno) |
| { |
| return sparx5_mirror_monitor_get(sparx5, idx) == portno; |
| } |
| |
| /* Get a suitable mirror for this port */ |
| static int sparx5_mirror_get(struct sparx5_port *sport, |
| struct sparx5_port *mport, u32 dir, u32 *idx) |
| { |
| struct sparx5 *sparx5 = sport->sparx5; |
| u32 i; |
| |
| /* Check if this port is already used as a monitor port */ |
| for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++) |
| if (sparx5_mirror_has_monitor(sparx5, i, sport->portno)) |
| return -EINVAL; |
| |
| /* Check if existing mirror can be reused |
| * (same direction and monitor port). |
| */ |
| for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++) { |
| if (sparx5_mirror_dir_get(sparx5, i) == dir && |
| sparx5_mirror_has_monitor(sparx5, i, mport->portno)) { |
| *idx = i; |
| return 0; |
| } |
| } |
| |
| /* Return free mirror */ |
| for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++) { |
| if (sparx5_mirror_is_empty(sparx5, i)) { |
| *idx = i; |
| return 0; |
| } |
| } |
| |
| return -ENOENT; |
| } |
| |
| int sparx5_mirror_add(struct sparx5_mall_entry *entry) |
| { |
| u32 mirror_idx, dir = sparx5_mirror_to_dir(entry->ingress); |
| struct sparx5_port *sport, *mport; |
| struct sparx5 *sparx5; |
| int err; |
| |
| /* Source port */ |
| sport = entry->port; |
| /* monitor port */ |
| mport = entry->mirror.port; |
| sparx5 = sport->sparx5; |
| |
| if (sport->portno == mport->portno) |
| return -EINVAL; |
| |
| err = sparx5_mirror_get(sport, mport, dir, &mirror_idx); |
| if (err) |
| return err; |
| |
| if (sparx5_mirror_contains(sparx5, mirror_idx, sport->portno)) |
| return -EEXIST; |
| |
| /* Add port to mirror */ |
| sparx5_mirror_port_add(sparx5, mirror_idx, sport->portno); |
| |
| /* Set direction of mirror */ |
| sparx5_mirror_dir_set(sparx5, mirror_idx, dir); |
| |
| /* Set monitor port for mirror */ |
| sparx5_mirror_monitor_set(sparx5, mirror_idx, mport->portno); |
| |
| entry->mirror.idx = mirror_idx; |
| |
| return 0; |
| } |
| |
| void sparx5_mirror_del(struct sparx5_mall_entry *entry) |
| { |
| struct sparx5_port *port = entry->port; |
| struct sparx5 *sparx5 = port->sparx5; |
| u32 mirror_idx = entry->mirror.idx; |
| |
| sparx5_mirror_port_del(sparx5, mirror_idx, port->portno); |
| if (!sparx5_mirror_is_empty(sparx5, mirror_idx)) |
| return; |
| |
| sparx5_mirror_dir_set(sparx5, mirror_idx, SPX5_MIRROR_DISABLED); |
| |
| sparx5_mirror_monitor_set(sparx5, |
| mirror_idx, |
| SPX5_MIRROR_MONITOR_PORT_DEFAULT); |
| } |
| |
| void sparx5_mirror_stats(struct sparx5_mall_entry *entry, |
| struct flow_stats *fstats) |
| { |
| struct sparx5_port *port = entry->port; |
| struct rtnl_link_stats64 new_stats; |
| struct flow_stats *old_stats; |
| |
| old_stats = &entry->port->mirror_stats; |
| sparx5_get_stats64(port->ndev, &new_stats); |
| |
| if (entry->ingress) { |
| flow_stats_update(fstats, |
| new_stats.rx_bytes - old_stats->bytes, |
| new_stats.rx_packets - old_stats->pkts, |
| new_stats.rx_dropped - old_stats->drops, |
| old_stats->lastused, |
| FLOW_ACTION_HW_STATS_IMMEDIATE); |
| |
| old_stats->bytes = new_stats.rx_bytes; |
| old_stats->pkts = new_stats.rx_packets; |
| old_stats->drops = new_stats.rx_dropped; |
| old_stats->lastused = jiffies; |
| } else { |
| flow_stats_update(fstats, |
| new_stats.tx_bytes - old_stats->bytes, |
| new_stats.tx_packets - old_stats->pkts, |
| new_stats.tx_dropped - old_stats->drops, |
| old_stats->lastused, |
| FLOW_ACTION_HW_STATS_IMMEDIATE); |
| |
| old_stats->bytes = new_stats.tx_bytes; |
| old_stats->pkts = new_stats.tx_packets; |
| old_stats->drops = new_stats.tx_dropped; |
| old_stats->lastused = jiffies; |
| } |
| } |