root/drivers/net/ethernet/microchip/lan966x/lan966x_mirror.c
// SPDX-License-Identifier: GPL-2.0+

#include "lan966x_main.h"

int lan966x_mirror_port_add(struct lan966x_port *port,
                            struct flow_action_entry *action,
                            unsigned long mirror_id,
                            bool ingress,
                            struct netlink_ext_ack *extack)
{
        struct lan966x *lan966x = port->lan966x;
        struct lan966x_port *monitor_port;

        if (!lan966x_netdevice_check(action->dev)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Destination not an lan966x port");
                return -EOPNOTSUPP;
        }

        monitor_port = netdev_priv(action->dev);

        if (lan966x->mirror_mask[ingress] & BIT(port->chip_port)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Mirror already exists");
                return -EEXIST;
        }

        if (lan966x->mirror_monitor &&
            lan966x->mirror_monitor != monitor_port) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Cannot change mirror port while in use");
                return -EBUSY;
        }

        if (port == monitor_port) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Cannot mirror the monitor port");
                return -EINVAL;
        }

        lan966x->mirror_mask[ingress] |= BIT(port->chip_port);

        lan966x->mirror_monitor = monitor_port;
        lan_wr(BIT(monitor_port->chip_port), lan966x, ANA_MIRRORPORTS);

        if (ingress) {
                lan_rmw(ANA_PORT_CFG_SRC_MIRROR_ENA_SET(1),
                        ANA_PORT_CFG_SRC_MIRROR_ENA,
                        lan966x, ANA_PORT_CFG(port->chip_port));
        } else {
                lan_wr(lan966x->mirror_mask[0], lan966x,
                       ANA_EMIRRORPORTS);
        }

        lan966x->mirror_count++;

        if (ingress)
                port->tc.ingress_mirror_id = mirror_id;
        else
                port->tc.egress_mirror_id = mirror_id;

        return 0;
}

int lan966x_mirror_port_del(struct lan966x_port *port,
                            bool ingress,
                            struct netlink_ext_ack *extack)
{
        struct lan966x *lan966x = port->lan966x;

        if (!(lan966x->mirror_mask[ingress] & BIT(port->chip_port))) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "There is no mirroring for this port");
                return -ENOENT;
        }

        lan966x->mirror_mask[ingress] &= ~BIT(port->chip_port);

        if (ingress) {
                lan_rmw(ANA_PORT_CFG_SRC_MIRROR_ENA_SET(0),
                        ANA_PORT_CFG_SRC_MIRROR_ENA,
                        lan966x, ANA_PORT_CFG(port->chip_port));
        } else {
                lan_wr(lan966x->mirror_mask[0], lan966x,
                       ANA_EMIRRORPORTS);
        }

        lan966x->mirror_count--;

        if (lan966x->mirror_count == 0) {
                lan966x->mirror_monitor = NULL;
                lan_wr(0, lan966x, ANA_MIRRORPORTS);
        }

        if (ingress)
                port->tc.ingress_mirror_id = 0;
        else
                port->tc.egress_mirror_id = 0;

        return 0;
}

void lan966x_mirror_port_stats(struct lan966x_port *port,
                               struct flow_stats *stats,
                               bool ingress)
{
        struct rtnl_link_stats64 new_stats;
        struct flow_stats *old_stats;

        old_stats = &port->tc.mirror_stat;
        lan966x_stats_get(port->dev, &new_stats);

        if (ingress) {
                flow_stats_update(stats,
                                  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(stats,
                                  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;
        }
}