236 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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;
 | |
| 	}
 | |
| }
 |