Commit 9dd15d50 authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'sparx5-port-mirroring'

Daniel Machon says:

====================
net: sparx5: add support for port mirroring

This series adds support for port mirroring, and port mirroring stats,
through tc matchall action FLOW_ACTION_MIRRED.

The hardware has three independent mirroring probes. Each probe can be
configured with a separate set of filtering conditions that must be
fulfilled before traffic is mirrored.

A mirror probe can have up to 64 source ports and a single monitor port.
The direction of a mirror probe determines if rx or tx traffic is
mirrored from the source port to the monitor port.

To: David S. Miller <davem@davemloft.net>
To: Eric Dumazet <edumazet@google.com>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
To: Lars Povlsen <lars.povlsen@microchip.com>
To: Steen Hegelund <Steen.Hegelund@microchip.com>
To: UNGLinuxDriver@microchip.com
To: Russell King <linux@armlinux.org.uk>
Cc: netdev@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: Horatiu Vultur <horatiu.vultur@microchip.com>
Cc: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Cc: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Cc: Vladimir Oltean <vladimir.oltean@nxp.com>
Cc: Yue Haibing <yuehaibing@huawei.com>

---
Changes in v3:
- Ditch do_div() (patch #3) to fix warning on hexagon arch, reported by intel bot
- Link to v2: https://lore.kernel.org/r/20240418-port-mirroring-v2-0-20642868b386@microchip.com

Changes in v2:
- Fix clang build warning about uninitialized variable 'err'
- Link to v1: https://lore.kernel.org/r/20240418-port-mirroring-v1-0-e05c35007c55@microchip.com


====================

Signed-off-by: default avatarDaniel Machon <daniel.machon@microchip.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents a2d2cadc 5af946f4
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -10,7 +10,8 @@ sparx5-switch-y := sparx5_main.o sparx5_packet.o \
 sparx5_switchdev.o sparx5_calendar.o sparx5_ethtool.o sparx5_fdma.o \
 sparx5_ptp.o sparx5_pgid.o sparx5_tc.o sparx5_qos.o \
 sparx5_vcap_impl.o sparx5_vcap_ag_api.o sparx5_tc_flower.o \
 sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o sparx5_psfp.o
 sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o \
 sparx5_psfp.o sparx5_mirror.o

sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o
sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o
+3 −0
Original line number Diff line number Diff line
@@ -899,6 +899,9 @@ static int mchp_sparx5_probe(struct platform_device *pdev)
		dev_err(sparx5->dev, "PTP failed\n");
		goto cleanup_ports;
	}

	INIT_LIST_HEAD(&sparx5->mall_entries);

	goto cleanup_config;

cleanup_ports:
+25 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include <linux/ptp_clock_kernel.h>
#include <linux/hrtimer.h>
#include <linux/debugfs.h>
#include <net/flow_offload.h>

#include "sparx5_main_regs.h"

@@ -173,6 +174,7 @@ struct sparx5_port {
	struct phylink_config phylink_config;
	struct phylink *phylink;
	struct phylink_pcs phylink_pcs;
	struct flow_stats mirror_stats;
	u16 portno;
	/* Ingress default VLAN (pvid) */
	u16 pvid;
@@ -227,6 +229,22 @@ struct sparx5_mdb_entry {
	u16 pgid_idx;
};

struct sparx5_mall_mirror_entry {
	u32 idx;
	struct sparx5_port *port;
};

struct sparx5_mall_entry {
	struct list_head list;
	struct sparx5_port *port;
	unsigned long cookie;
	enum flow_action_id type;
	bool ingress;
	union {
		struct sparx5_mall_mirror_entry mirror;
	};
};

#define SPARX5_PTP_TIMEOUT		msecs_to_jiffies(10)
#define SPARX5_SKB_CB(skb) \
	((struct sparx5_skb_cb *)((skb)->cb))
@@ -295,6 +313,7 @@ struct sparx5 {
	struct vcap_control *vcap_ctrl;
	/* PGID allocation map */
	u8 pgid_map[PGID_TABLE_SIZE];
	struct list_head mall_entries;
	/* Common root for debugfs */
	struct dentry *debugfs_root;
};
@@ -541,6 +560,12 @@ void sparx5_psfp_init(struct sparx5 *sparx5);
void sparx5_new_base_time(struct sparx5 *sparx5, const u32 cycle_time,
			  const ktime_t org_base_time, ktime_t *new_base_time);

/* sparx5_mirror.c */
int sparx5_mirror_add(struct sparx5_mall_entry *entry);
void sparx5_mirror_del(struct sparx5_mall_entry *entry);
void sparx5_mirror_stats(struct sparx5_mall_entry *entry,
			 struct flow_stats *fstats);

/* Clock period in picoseconds */
static inline u32 sparx5_clk_period(enum sparx5_core_clockfreq cclock)
{
+68 −0
Original line number Diff line number Diff line
@@ -83,6 +83,64 @@ enum sparx5_target {
#define ANA_AC_OWN_UPSID_OWN_UPSID_GET(x)\
	FIELD_GET(ANA_AC_OWN_UPSID_OWN_UPSID, x)

/*      ANA_AC:MIRROR_PROBE:PROBE_CFG */
#define ANA_AC_PROBE_CFG(g) \
	__REG(TARGET_ANA_AC, 0, 1, 893696, g, 3, 32, 0, 0, 1, 4)

#define ANA_AC_PROBE_CFG_PROBE_RX_CPU_AND_VD GENMASK(31, 27)
#define ANA_AC_PROBE_CFG_PROBE_RX_CPU_AND_VD_SET(x)\
	FIELD_PREP(ANA_AC_PROBE_CFG_PROBE_RX_CPU_AND_VD, x)
#define ANA_AC_PROBE_CFG_PROBE_RX_CPU_AND_VD_GET(x)\
	FIELD_GET(ANA_AC_PROBE_CFG_PROBE_RX_CPU_AND_VD, x)

#define ANA_AC_PROBE_CFG_PROBE_CPU_SET      GENMASK(26, 19)
#define ANA_AC_PROBE_CFG_PROBE_CPU_SET_SET(x)\
	FIELD_PREP(ANA_AC_PROBE_CFG_PROBE_CPU_SET, x)
#define ANA_AC_PROBE_CFG_PROBE_CPU_SET_GET(x)\
	FIELD_GET(ANA_AC_PROBE_CFG_PROBE_CPU_SET, x)

#define ANA_AC_PROBE_CFG_PROBE_VID          GENMASK(18, 6)
#define ANA_AC_PROBE_CFG_PROBE_VID_SET(x)\
	FIELD_PREP(ANA_AC_PROBE_CFG_PROBE_VID, x)
#define ANA_AC_PROBE_CFG_PROBE_VID_GET(x)\
	FIELD_GET(ANA_AC_PROBE_CFG_PROBE_VID, x)

#define ANA_AC_PROBE_CFG_PROBE_VLAN_MODE    GENMASK(5, 4)
#define ANA_AC_PROBE_CFG_PROBE_VLAN_MODE_SET(x)\
	FIELD_PREP(ANA_AC_PROBE_CFG_PROBE_VLAN_MODE, x)
#define ANA_AC_PROBE_CFG_PROBE_VLAN_MODE_GET(x)\
	FIELD_GET(ANA_AC_PROBE_CFG_PROBE_VLAN_MODE, x)

#define ANA_AC_PROBE_CFG_PROBE_MAC_MODE     GENMASK(3, 2)
#define ANA_AC_PROBE_CFG_PROBE_MAC_MODE_SET(x)\
	FIELD_PREP(ANA_AC_PROBE_CFG_PROBE_MAC_MODE, x)
#define ANA_AC_PROBE_CFG_PROBE_MAC_MODE_GET(x)\
	FIELD_GET(ANA_AC_PROBE_CFG_PROBE_MAC_MODE, x)

#define ANA_AC_PROBE_CFG_PROBE_DIRECTION    GENMASK(1, 0)
#define ANA_AC_PROBE_CFG_PROBE_DIRECTION_SET(x)\
	FIELD_PREP(ANA_AC_PROBE_CFG_PROBE_DIRECTION, x)
#define ANA_AC_PROBE_CFG_PROBE_DIRECTION_GET(x)\
	FIELD_GET(ANA_AC_PROBE_CFG_PROBE_DIRECTION, x)

/*      ANA_AC:MIRROR_PROBE:PROBE_PORT_CFG */
#define ANA_AC_PROBE_PORT_CFG(g) \
	__REG(TARGET_ANA_AC, 0, 1, 893696, g, 3, 32, 8, 0, 1, 4)

/*      ANA_AC:MIRROR_PROBE:PROBE_PORT_CFG1 */
#define ANA_AC_PROBE_PORT_CFG1(g) \
	__REG(TARGET_ANA_AC, 0, 1, 893696, g, 3, 32, 12, 0, 1, 4)

/*      ANA_AC:MIRROR_PROBE:PROBE_PORT_CFG2 */
#define ANA_AC_PROBE_PORT_CFG2(g) \
	__REG(TARGET_ANA_AC, 0, 1, 893696, g, 3, 32, 16, 0, 1, 4)

#define ANA_AC_PROBE_PORT_CFG2_PROBE_PORT_MASK2 BIT(0)
#define ANA_AC_PROBE_PORT_CFG2_PROBE_PORT_MASK2_SET(x)\
	FIELD_PREP(ANA_AC_PROBE_PORT_CFG2_PROBE_PORT_MASK2, x)
#define ANA_AC_PROBE_PORT_CFG2_PROBE_PORT_MASK2_GET(x)\
	FIELD_GET(ANA_AC_PROBE_PORT_CFG2_PROBE_PORT_MASK2, x)

/*      ANA_AC:SRC:SRC_CFG */
#define ANA_AC_SRC_CFG(g)         __REG(TARGET_ANA_AC,\
					0, 1, 849920, g, 102, 16, 0, 0, 1, 4)
@@ -6203,6 +6261,16 @@ enum sparx5_target {
#define QFWD_SWITCH_PORT_MODE_LEARNALL_MORE_GET(x)\
	FIELD_GET(QFWD_SWITCH_PORT_MODE_LEARNALL_MORE, x)

/*      QFWD:SYSTEM:FRAME_COPY_CFG */
#define QFWD_FRAME_COPY_CFG(r)\
	__REG(TARGET_QFWD, 0, 1, 0, 0, 1, 340, 284, r, 12, 4)

#define QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL   GENMASK(12, 6)
#define QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL_SET(x)\
	FIELD_PREP(QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL, x)
#define QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL_GET(x)\
	FIELD_GET(QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL, x)

/*      QRES:RES_CTRL:RES_CFG */
#define QRES_RES_CFG(g)           __REG(TARGET_QRES,\
					0, 1, 0, g, 5120, 16, 0, 0, 1, 4)
+235 −0
Original line number Diff line number Diff line
// 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)
{
	u32 val, reg = portno;

	reg = portno / BITS_PER_BYTE;
	val = BIT(portno % BITS_PER_BYTE);

	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)
{
	u32 val, reg = portno;

	reg = portno / BITS_PER_BYTE;
	val = BIT(portno % BITS_PER_BYTE);

	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;
	}
}
Loading