root/drivers/net/dsa/mxl862xx/mxl862xx.h
/* SPDX-License-Identifier: GPL-2.0-or-later */

#ifndef __MXL862XX_H
#define __MXL862XX_H

#include <asm/byteorder.h>
#include <linux/mdio.h>
#include <linux/workqueue.h>
#include <net/dsa.h>

struct mxl862xx_priv;

#define MXL862XX_MAX_PORTS              17
#define MXL862XX_FIRST_SERDES_PORT      9
#define MXL862XX_SERDES_SLOTS           4

#define MXL862XX_DEFAULT_BRIDGE         0
#define MXL862XX_MAX_BRIDGES            48
#define MXL862XX_MAX_BRIDGE_PORTS       128
#define MXL862XX_TOTAL_EVLAN_ENTRIES    1024
#define MXL862XX_TOTAL_VF_ENTRIES       1024

/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
#define MXL862XX_FW_PORTMAP_WORDS       (MXL862XX_MAX_BRIDGE_PORTS / 16)

/**
 * mxl862xx_fw_portmap_set_bit - set a single port bit in a firmware portmap
 * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
 * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
 */
static inline void mxl862xx_fw_portmap_set_bit(__le16 *map, int port)
{
        map[port / 16] |= cpu_to_le16(BIT(port % 16));
}

/**
 * mxl862xx_fw_portmap_clear_bit - clear a single port bit in a firmware portmap
 * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
 * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
 */
static inline void mxl862xx_fw_portmap_clear_bit(__le16 *map, int port)
{
        map[port / 16] &= ~cpu_to_le16(BIT(port % 16));
}

/**
 * mxl862xx_fw_portmap_is_empty - check whether a firmware portmap has no
 *                                bits set
 * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
 *
 * Return: true if every word in @map is zero.
 */
static inline bool mxl862xx_fw_portmap_is_empty(const __le16 *map)
{
        int i;

        for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
                if (map[i])
                        return false;
        return true;
}

/**
 * struct mxl862xx_vf_vid - Per-VID entry within a VLAN Filter block
 * @list:     Linked into &mxl862xx_vf_block.vids
 * @vid:      VLAN ID
 * @index:    Entry index within the VLAN Filter HW block
 * @untagged: Strip tag on egress for this VID (drives EVLAN tag-stripping)
 */
struct mxl862xx_vf_vid {
        struct list_head list;
        u16 vid;
        u16 index;
        bool untagged;
};

/**
 * struct mxl862xx_vf_block - Per-port VLAN Filter block
 * @allocated:    Whether the HW block has been allocated via VLANFILTER_ALLOC
 * @block_id:     HW VLAN Filter block ID from VLANFILTER_ALLOC
 * @block_size:   Total entries allocated in this block
 * @active_count: Number of ALLOW entries at indices [0, active_count).
 *                The bridge port config sends max(active_count, 1) as
 *                block_size to narrow the HW scan window.
 *                discard_unmatched_tagged handles frames outside this range.
 * @vids:         List of &mxl862xx_vf_vid entries programmed in this block
 */
struct mxl862xx_vf_block {
        bool allocated;
        u16 block_id;
        u16 block_size;
        u16 active_count;
        struct list_head vids;
};

/**
 * struct mxl862xx_evlan_block - Per-port per-direction extended VLAN block
 * @allocated:  Whether the HW block has been allocated via EXTENDEDVLAN_ALLOC.
 *              Guards alloc/free idempotency--the block_id is only valid
 *              while allocated is true.
 * @in_use:     Whether the EVLAN engine should be enabled for this block
 *              on the bridge port (sent as the enable flag in
 *              set_bridge_port). Can be false while allocated is still
 *              true -- e.g. when all egress VIDs are removed (idx == 0 in
 *              evlan_program_egress) the block stays allocated for
 *              potential reuse, but the engine is disabled so an empty
 *              rule set does not discard all traffic.
 * @block_id:   HW block ID from EXTENDEDVLAN_ALLOC
 * @block_size: Total entries allocated
 * @n_active:   Number of HW entries currently written. The bridge port
 *              config sends this as the egress scan window, so entries
 *              beyond n_active are never scanned. Always equals
 *              block_size for ingress blocks (fixed catchall rules).
 */
struct mxl862xx_evlan_block {
        bool allocated;
        bool in_use;
        u16 block_id;
        u16 block_size;
        u16 n_active;
};

/**
 * struct mxl862xx_port_stats - 64-bit accumulated hardware port statistics
 * @rx_packets: total received packets
 * @tx_packets: total transmitted packets
 * @rx_bytes: total received bytes
 * @tx_bytes: total transmitted bytes
 * @rx_errors: total receive errors
 * @tx_errors: total transmit errors
 * @rx_dropped: total received packets dropped
 * @tx_dropped: total transmitted packets dropped
 * @multicast: total received multicast packets
 * @collisions: total transmit collisions
 * @rx_length_errors: received length errors (undersize + oversize)
 * @rx_crc_errors: received FCS errors
 * @rx_frame_errors: received alignment errors
 * @prev_rx_good_pkts: previous snapshot of rx good packet counter
 * @prev_tx_good_pkts: previous snapshot of tx good packet counter
 * @prev_rx_good_bytes: previous snapshot of rx good byte counter
 * @prev_tx_good_bytes: previous snapshot of tx good byte counter
 * @prev_rx_fcserror_pkts: previous snapshot of rx FCS error counter
 * @prev_rx_under_size_error_pkts: previous snapshot of rx undersize
 *                                 error counter
 * @prev_rx_oversize_error_pkts: previous snapshot of rx oversize
 *                               error counter
 * @prev_rx_align_error_pkts: previous snapshot of rx alignment
 *                            error counter
 * @prev_tx_dropped_pkts: previous snapshot of tx dropped counter
 * @prev_rx_dropped_pkts: previous snapshot of rx dropped counter
 * @prev_rx_evlan_discard_pkts: previous snapshot of extended VLAN
 *                              discard counter
 * @prev_mtu_exceed_discard_pkts: previous snapshot of MTU exceed
 *                                discard counter
 * @prev_tx_acm_dropped_pkts: previous snapshot of tx ACM dropped
 *                            counter
 * @prev_rx_multicast_pkts: previous snapshot of rx multicast counter
 * @prev_tx_coll_count: previous snapshot of tx collision counter
 *
 * The firmware RMON counters are 32-bit free-running (64-bit for byte
 * counters). This structure holds 64-bit accumulators alongside the
 * previous raw snapshot so that deltas can be computed across polls,
 * handling 32-bit wrap correctly via unsigned subtraction.
 */
struct mxl862xx_port_stats {
        u64 rx_packets;
        u64 tx_packets;
        u64 rx_bytes;
        u64 tx_bytes;
        u64 rx_errors;
        u64 tx_errors;
        u64 rx_dropped;
        u64 tx_dropped;
        u64 multicast;
        u64 collisions;
        u64 rx_length_errors;
        u64 rx_crc_errors;
        u64 rx_frame_errors;
        u32 prev_rx_good_pkts;
        u32 prev_tx_good_pkts;
        u64 prev_rx_good_bytes;
        u64 prev_tx_good_bytes;
        u32 prev_rx_fcserror_pkts;
        u32 prev_rx_under_size_error_pkts;
        u32 prev_rx_oversize_error_pkts;
        u32 prev_rx_align_error_pkts;
        u32 prev_tx_dropped_pkts;
        u32 prev_rx_dropped_pkts;
        u32 prev_rx_evlan_discard_pkts;
        u32 prev_mtu_exceed_discard_pkts;
        u32 prev_tx_acm_dropped_pkts;
        u32 prev_rx_multicast_pkts;
        u32 prev_tx_coll_count;
};

/**
 * struct mxl862xx_port - per-port state tracked by the driver
 * @priv:                back-pointer to switch private data; needed by
 *                       deferred work handlers to access ds and priv
 * @fid:                 firmware FID for the permanent single-port bridge;
 *                       kept alive for the lifetime of the port so traffic is
 *                       never forwarded while the port is unbridged
 * @flood_block:         bitmask of firmware meter indices that are currently
 *                       rate-limiting flood traffic on this port (zero-rate
 *                       meters used to block flooding)
 * @learning:            true when address learning is enabled on this port
 * @setup_done:          set at end of port_setup, cleared at start of
 *                       port_teardown; guards deferred work against
 *                       acting on torn-down state
 * @pvid:                port VLAN ID (native VLAN) assigned to untagged traffic
 * @vlan_filtering:      true when VLAN filtering is enabled on this port
 * @vf:                  per-port VLAN Filter block state
 * @ingress_evlan:       ingress extended VLAN block state
 * @egress_evlan:        egress extended VLAN block state
 * @host_flood_uc:       desired host unicast flood state (true = flood);
 *                       updated atomically by port_set_host_flood, consumed
 *                       by the deferred host_flood_work
 * @host_flood_mc:       desired host multicast flood state (true = flood)
 * @host_flood_work:     deferred work for applying host flood changes;
 *                       port_set_host_flood runs in atomic context (under
 *                       netif_addr_lock) so firmware calls must be deferred.
 *                       The worker acquires rtnl_lock() to serialize with
 *                       DSA callbacks and checks @setup_done to avoid
 *                       acting on torn-down ports.
 * @stats:               64-bit accumulated hardware statistics; updated
 *                       periodically by the stats polling work
 * @stats_lock:          protects accumulator reads in .get_stats64 against
 *                       concurrent updates from the polling work
 */
struct mxl862xx_port {
        struct mxl862xx_priv *priv;
        u16 fid;
        unsigned long flood_block;
        bool learning;
        bool setup_done;
        u16 pvid;
        bool vlan_filtering;
        struct mxl862xx_vf_block vf;
        struct mxl862xx_evlan_block ingress_evlan;
        struct mxl862xx_evlan_block egress_evlan;
        bool host_flood_uc;
        bool host_flood_mc;
        struct work_struct host_flood_work;
        struct mxl862xx_port_stats stats;
        spinlock_t stats_lock; /* protects stats accumulators */
};

/**
 * struct mxl862xx_pcs - link SerDes interfaces to bridge ports
 * @pcs:       &struct phylink_pcs instance
 * @priv:      pointer to &struct mxl862xx_priv
 * @serdes_id: SerDes instance index (0 or 1)
 * @slot:      slot within the SerDes (0-3 for QSGMII/QUSXGMII, 0 otherwise)
 * @interface: cached PHY interface, last value passed to pcs_config().
 *             %PHY_INTERFACE_MODE_NA before the first successful
 *             pcs_config().  Used by pcs_an_restart() to populate the
 *             firmware command and by pcs_disable() to skip the
 *             firmware power-down for shared (QSGMII/QUSXGMII) modes.
 */
struct mxl862xx_pcs {
        struct phylink_pcs pcs;
        struct mxl862xx_priv *priv;
        int serdes_id;
        int slot;
        phy_interface_t interface;
};

/**
 * struct mxl862xx_fw_version - firmware version for comparison and display
 * @major: firmware major version
 * @minor: firmware minor version
 * @revision: firmware revision number
 */
struct mxl862xx_fw_version {
        u8 major;
        u8 minor;
        u16 revision;
};

#define MXL862XX_FW_VER(maj, min, rev) \
        (((u32)(maj) << 24) | ((u32)(min) << 16) | (rev))
#define MXL862XX_FW_VER_MIN(priv, maj, min, rev) \
        (MXL862XX_FW_VER((priv)->fw_version.major, (priv)->fw_version.minor, \
                         (priv)->fw_version.revision) >= \
         MXL862XX_FW_VER(maj, min, rev))

/* Bit indices for struct mxl862xx_priv::flags */
#define MXL862XX_FLAG_CRC_ERR           0
#define MXL862XX_FLAG_WORK_STOPPED      1

/**
 * struct mxl862xx_priv - driver private data for an MxL862xx switch
 * @ds:                 pointer to the DSA switch instance
 * @mdiodev:            MDIO device used to communicate with the switch firmware
 * @crc_err_work:       deferred work for shutting down all ports on MDIO CRC
 *                      errors
 * @flags:              atomic status flags; %MXL862XX_FLAG_CRC_ERR is set
 *                      before CRC-triggered shutdown and cleared after;
 *                      %MXL862XX_FLAG_WORK_STOPPED is set before cancelling
 *                      stats_work to prevent rescheduling during teardown
 * @drop_meter:         index of the single shared zero-rate firmware meter
 *                      used to unconditionally drop traffic (used to block
 *                      flooding)
 * @fw_version:         cached firmware version, populated at probe and
 *                      compared with MXL862XX_FW_VER_MIN()
 * @serdes_ports:       SerDes interfaces incl. sub-interfaces in case of
 *                      10G_QXGMII or QSGMII
 * @serdes_refcount:    per-XPCS count of sub-ports enabled by phylink;
 *                      pcs_disable powers an XPCS down when the count
 *                      reaches zero. Protected by @serdes_lock.
 * @serdes_lock:        serializes the @serdes_refcount transitions with
 *                      the XPCS power-down so a sibling sub-port enable
 *                      cannot race a power-down to zero
 * @ports:              per-port state, indexed by switch port number
 * @bridges:            maps DSA bridge number to firmware bridge ID;
 *                      zero means no firmware bridge allocated for that
 *                      DSA bridge number. Indexed by dsa_bridge.num
 *                      (0 .. ds->max_num_bridges).
 * @evlan_ingress_size: per-port ingress Extended VLAN block size
 * @evlan_egress_size:  per-port egress Extended VLAN block size
 * @vf_block_size:      per-port VLAN Filter block size
 * @stats_work:         periodic work item that polls RMON hardware counters
 *                      and accumulates them into 64-bit per-port stats
 */
struct mxl862xx_priv {
        struct dsa_switch *ds;
        struct mdio_device *mdiodev;
        struct work_struct crc_err_work;
        unsigned long flags;
        u16 drop_meter;
        struct mxl862xx_fw_version fw_version;
        struct mxl862xx_pcs serdes_ports[8];
        int serdes_refcount[2];
        struct mutex serdes_lock;
        struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
        u16 bridges[MXL862XX_MAX_BRIDGES + 1];
        u16 evlan_ingress_size;
        u16 evlan_egress_size;
        u16 vf_block_size;
        struct delayed_work stats_work;
};

#endif /* __MXL862XX_H */