diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
| commit | 034dd340b08be1f2f0477ad16131d609f9dbd53c (patch) | |
| tree | 2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /drivers/net/dsa/netc | |
| download | linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip | |
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Fix error output of boot instance creation failure
Currently if a boot instance creation fails, instead of printing out
the name of the instance that failed, it prints "(null)". That is
because it prints "cur_str" that had already been processed by
strsep(). Print the saved name instead.
While at it, print the error code of the failure.
- Fix use-after-free for same named historgrams
Histograms can be named so that they can be used in multiple events.
But if the named histogram has a variable attached, the second event
that uses the named histogram which duplicates it and needs to free
the original after duplication leaves the old variable in place and
still visible. If another histogram uses than variable, it will use
the stale one which will try to reference the freed duplicate
histogram and crash the kernel.
Free the duplicate variables along with the duplicated histogram
data.
- Check return value of kthread_run() in event self test
The events self tests uses a kthread for testing but does not check
if it succeeded in creating a kthread. If the kthread creation were
to fail, the code will still try to call kthread_stop() on the error
returned.
- Fix race between reading trace_pipe and updating subbuffer size
If a user is reading the trace_pipe file at the same time they update
the ring buffer sub-buffer size, can cause the trace_pipe read to
read stale data. Add trace_access_lock() around updating the ring
buffer sub-buffer size.
- Fix eventfs_inode on failure path in creation of the events directory
In the creation of the "events" directory, if after allocating the
eventfs_inode a failure is detected, it calls cleanup_ei() which
calls free_ei(). The free_ei() will test if eventfs_inode being freed
has no children. It is a bug if it does. But on the failure case of
the creation of the "events" directory, the children lists have not
yet been initialized and the free will trigger a warning because
list_empty() on an uninitialized list returns false.
Move the initialization into init_ei() where it makes more sense and
makes sure that a created eventfs_inode has its lists initialized
upon creation.
- Check return value of kthread_run() in ftrace direct sample code
The sample code that shows how to use the ftrace direct calls does
not test the return of kthread_run() to see if it succeeds. Return a
failure if the kthread_run() doesn't succeed.
- Clear user events state on fork in case of alloc failure
On fork, the child gets a pointer to the parent's user events state.
It makes a copy of it then updates the child's pointer to it. But if
the allocation fails, the duplication function leaves the child with
a pointer to its parent's descriptor. When the child cleans up its
data, it will free the parent's descriptor while the parent is still
using it.
In the duplication function, set the child's user_event_mm to NULL
before testing if the allocation succeeded, and when it exits it will
not free the parent's descriptor.
- Fix retry exhaustion in simple ring buffer reader swap
simple_ring_buffer_swap_reader_page() starts with retry set to 8 and
post-decrements it only after a failed link replacement. On the final
attempt, a successful replacement leaves retry at zero, while a
failed replacement leaves it at -1.
But the check for success expects the retry value to be non-zero and
exits with an error on zero. This is the opposite result. Fix it.
- Fail nicely when the remote swap_reader_page() returns an error
Currently, if the swap_reader_page() of a remote buffer fails, it
triggers a WARN_ON_ONCE() and continues normally. Instead, have it
exit with an error and a pr_warn() print instead of a full WARNING.
* tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
ring-buffer: Stop remote reader update when page swap fails
tracing: Fix retry exhaustion in simple ring buffer reader swap
tracing/user_events: Clear copied tracing state before fork duplication
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify
eventfs: Initialize ei->children and ei->list in init_ei()
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
tracing: Fix crash passing ERR_PTR to kthread_stop()
tracing: Fix use-after-free with same-name named triggers
tracing: Fix logged instance name on creation failure
Diffstat (limited to 'drivers/net/dsa/netc')
| -rw-r--r-- | drivers/net/dsa/netc/Kconfig | 16 | ||||
| -rw-r--r-- | drivers/net/dsa/netc/Makefile | 3 | ||||
| -rw-r--r-- | drivers/net/dsa/netc/netc_ethtool.c | 291 | ||||
| -rw-r--r-- | drivers/net/dsa/netc/netc_main.c | 2524 | ||||
| -rw-r--r-- | drivers/net/dsa/netc/netc_platform.c | 87 | ||||
| -rw-r--r-- | drivers/net/dsa/netc/netc_switch.h | 206 | ||||
| -rw-r--r-- | drivers/net/dsa/netc/netc_switch_hw.h | 372 |
7 files changed, 3499 insertions, 0 deletions
diff --git a/drivers/net/dsa/netc/Kconfig b/drivers/net/dsa/netc/Kconfig new file mode 100644 index 000000000..793f7691a --- /dev/null +++ b/drivers/net/dsa/netc/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NET_DSA_NETC_SWITCH + tristate "NXP NETC Ethernet switch support" + depends on ARM64 || COMPILE_TEST + depends on NET_DSA && PCI + depends on NET_VENDOR_FREESCALE + select NET_DSA_TAG_NETC + select FSL_ENETC_MDIO + select NXP_NTMP + select NXP_NETC_LIB + help + This driver supports the NXP NETC Ethernet switch, which is embedded + as a PCIe function of the NXP NETC IP. But note that this driver is + only available for NETC v4.3 and later versions. + + If compiled as module (M), the module name is nxp-netc-switch. diff --git a/drivers/net/dsa/netc/Makefile b/drivers/net/dsa/netc/Makefile new file mode 100644 index 000000000..f40b13c70 --- /dev/null +++ b/drivers/net/dsa/netc/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o +nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o diff --git a/drivers/net/dsa/netc/netc_ethtool.c b/drivers/net/dsa/netc/netc_ethtool.c new file mode 100644 index 000000000..e6028851a --- /dev/null +++ b/drivers/net/dsa/netc/netc_ethtool.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * NXP NETC switch driver + * Copyright 2025-2026 NXP + */ + +#include <linux/ethtool_netlink.h> + +#include "netc_switch.h" + +static const struct ethtool_rmon_hist_range netc_rmon_ranges[] = { + { 64, 64 }, + { 65, 127 }, + { 128, 255 }, + { 256, 511 }, + { 512, 1023 }, + { 1024, 1522 }, + { 1523, NETC_MAX_FRAME_LEN }, + { } +}; + +static const struct netc_port_stat netc_port_counters[] = { + { NETC_PTGSLACR, "port gate late arrival frames" }, + { NETC_PSDFTCR, "port SDF transmit frames" }, + { NETC_PSDFDDCR, "port SDF drop duplicate frames" }, + { NETC_PRXDCR, "port rx discard frames" }, + { NETC_PRXDCRRR, "port rx discard read-reset" }, + { NETC_PRXDCRR0, "port rx discard reason 0" }, + { NETC_PRXDCRR1, "port rx discard reason 1" }, + { NETC_PTXDCR, "port tx discard frames" }, + { NETC_BPDCR, "bridge port discard frames" }, +}; + +static const struct netc_port_stat netc_emac_counters[] = { + { NETC_PM_ROCT(0), "eMAC rx octets" }, + { NETC_PM_RVLAN(0), "eMAC rx VLAN frames" }, + { NETC_PM_RERR(0), "eMAC rx frame errors" }, + { NETC_PM_RUCA(0), "eMAC rx unicast frames" }, + { NETC_PM_RDRP(0), "eMAC rx dropped packets" }, + { NETC_PM_RPKT(0), "eMAC rx packets" }, + { NETC_PM_TOCT(0), "eMAC tx octets" }, + { NETC_PM_TVLAN(0), "eMAC tx VLAN frames" }, + { NETC_PM_TFCS(0), "eMAC tx FCS errors" }, + { NETC_PM_TUCA(0), "eMAC tx unicast frames" }, + { NETC_PM_TPKT(0), "eMAC tx packets" }, + { NETC_PM_TUND(0), "eMAC tx undersized packets" }, + { NETC_PM_TIOCT(0), "eMAC tx invalid octets" }, +}; + +static const struct netc_port_stat netc_pmac_counters[] = { + { NETC_PM_ROCT(1), "pMAC rx octets" }, + { NETC_PM_RVLAN(1), "pMAC rx VLAN frames" }, + { NETC_PM_RERR(1), "pMAC rx frame errors" }, + { NETC_PM_RUCA(1), "pMAC rx unicast frames" }, + { NETC_PM_RDRP(1), "pMAC rx dropped packets" }, + { NETC_PM_RPKT(1), "pMAC rx packets" }, + { NETC_PM_TOCT(1), "pMAC tx octets" }, + { NETC_PM_TVLAN(1), "pMAC tx VLAN frames" }, + { NETC_PM_TFCS(1), "pMAC tx FCS errors" }, + { NETC_PM_TUCA(1), "pMAC tx unicast frames" }, + { NETC_PM_TPKT(1), "pMAC tx packets" }, + { NETC_PM_TUND(1), "pMAC tx undersized packets" }, + { NETC_PM_TIOCT(1), "pMAC tx invalid octets" }, +}; + +static void netc_port_pause_stats(struct netc_port *np, int mac, + struct ethtool_pause_stats *stats) +{ + if (mac && !np->caps.pmac) + return; + + stats->tx_pause_frames = netc_port_rd64(np, NETC_PM_TXPF(mac)); + stats->rx_pause_frames = netc_port_rd64(np, NETC_PM_RXPF(mac)); +} + +void netc_port_get_pause_stats(struct dsa_switch *ds, int port, + struct ethtool_pause_stats *pause_stats) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev; + + switch (pause_stats->src) { + case ETHTOOL_MAC_STATS_SRC_EMAC: + netc_port_pause_stats(np, 0, pause_stats); + break; + case ETHTOOL_MAC_STATS_SRC_PMAC: + netc_port_pause_stats(np, 1, pause_stats); + break; + case ETHTOOL_MAC_STATS_SRC_AGGREGATE: + ndev = dsa_to_port(ds, port)->user; + ethtool_aggregate_pause_stats(ndev, pause_stats); + break; + } +} + +static void netc_port_rmon_stats(struct netc_port *np, int mac, + struct ethtool_rmon_stats *stats) +{ + if (mac && !np->caps.pmac) + return; + + stats->undersize_pkts = netc_port_rd64(np, NETC_PM_RUND(mac)); + stats->oversize_pkts = netc_port_rd64(np, NETC_PM_ROVR(mac)); + stats->fragments = netc_port_rd64(np, NETC_PM_RFRG(mac)); + stats->jabbers = netc_port_rd64(np, NETC_PM_RJBR(mac)); + + stats->hist[0] = netc_port_rd64(np, NETC_PM_R64(mac)); + stats->hist[1] = netc_port_rd64(np, NETC_PM_R127(mac)); + stats->hist[2] = netc_port_rd64(np, NETC_PM_R255(mac)); + stats->hist[3] = netc_port_rd64(np, NETC_PM_R511(mac)); + stats->hist[4] = netc_port_rd64(np, NETC_PM_R1023(mac)); + stats->hist[5] = netc_port_rd64(np, NETC_PM_R1522(mac)); + stats->hist[6] = netc_port_rd64(np, NETC_PM_R1523X(mac)); + + stats->hist_tx[0] = netc_port_rd64(np, NETC_PM_T64(mac)); + stats->hist_tx[1] = netc_port_rd64(np, NETC_PM_T127(mac)); + stats->hist_tx[2] = netc_port_rd64(np, NETC_PM_T255(mac)); + stats->hist_tx[3] = netc_port_rd64(np, NETC_PM_T511(mac)); + stats->hist_tx[4] = netc_port_rd64(np, NETC_PM_T1023(mac)); + stats->hist_tx[5] = netc_port_rd64(np, NETC_PM_T1522(mac)); + stats->hist_tx[6] = netc_port_rd64(np, NETC_PM_T1523X(mac)); +} + +void netc_port_get_rmon_stats(struct dsa_switch *ds, int port, + struct ethtool_rmon_stats *rmon_stats, + const struct ethtool_rmon_hist_range **ranges) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev; + + *ranges = netc_rmon_ranges; + + switch (rmon_stats->src) { + case ETHTOOL_MAC_STATS_SRC_EMAC: + netc_port_rmon_stats(np, 0, rmon_stats); + break; + case ETHTOOL_MAC_STATS_SRC_PMAC: + netc_port_rmon_stats(np, 1, rmon_stats); + break; + case ETHTOOL_MAC_STATS_SRC_AGGREGATE: + ndev = dsa_to_port(ds, port)->user; + ethtool_aggregate_rmon_stats(ndev, rmon_stats); + break; + } +} + +static void netc_port_ctrl_stats(struct netc_port *np, int mac, + struct ethtool_eth_ctrl_stats *stats) +{ + if (mac && !np->caps.pmac) + return; + + stats->MACControlFramesTransmitted = + netc_port_rd64(np, NETC_PM_TCNP(mac)); + stats->MACControlFramesReceived = + netc_port_rd64(np, NETC_PM_RCNP(mac)); +} + +void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port, + struct ethtool_eth_ctrl_stats *ctrl_stats) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev; + + switch (ctrl_stats->src) { + case ETHTOOL_MAC_STATS_SRC_EMAC: + netc_port_ctrl_stats(np, 0, ctrl_stats); + break; + case ETHTOOL_MAC_STATS_SRC_PMAC: + netc_port_ctrl_stats(np, 1, ctrl_stats); + break; + case ETHTOOL_MAC_STATS_SRC_AGGREGATE: + ndev = dsa_to_port(ds, port)->user; + ethtool_aggregate_ctrl_stats(ndev, ctrl_stats); + break; + } +} + +static void netc_port_mac_stats(struct netc_port *np, int mac, + struct ethtool_eth_mac_stats *stats) +{ + if (mac && !np->caps.pmac) + return; + + stats->FramesTransmittedOK = netc_port_rd64(np, NETC_PM_TFRM(mac)); + stats->SingleCollisionFrames = netc_port_rd64(np, NETC_PM_TSCOL(mac)); + stats->MultipleCollisionFrames = + netc_port_rd64(np, NETC_PM_TMCOL(mac)); + stats->FramesReceivedOK = netc_port_rd64(np, NETC_PM_RFRM(mac)); + stats->FrameCheckSequenceErrors = + netc_port_rd64(np, NETC_PM_RFCS(mac)); + stats->AlignmentErrors = netc_port_rd64(np, NETC_PM_RALN(mac)); + stats->OctetsTransmittedOK = netc_port_rd64(np, NETC_PM_TEOCT(mac)); + stats->FramesWithDeferredXmissions = + netc_port_rd64(np, NETC_PM_TDFR(mac)); + stats->LateCollisions = netc_port_rd64(np, NETC_PM_TLCOL(mac)); + stats->FramesAbortedDueToXSColls = + netc_port_rd64(np, NETC_PM_TECOL(mac)); + stats->FramesLostDueToIntMACXmitError = + netc_port_rd64(np, NETC_PM_TERR(mac)); + stats->OctetsReceivedOK = netc_port_rd64(np, NETC_PM_REOCT(mac)); + stats->FramesLostDueToIntMACRcvError = + netc_port_rd64(np, NETC_PM_RDRNTP(mac)); + stats->MulticastFramesXmittedOK = + netc_port_rd64(np, NETC_PM_TMCA(mac)); + stats->BroadcastFramesXmittedOK = + netc_port_rd64(np, NETC_PM_TBCA(mac)); + stats->MulticastFramesReceivedOK = + netc_port_rd64(np, NETC_PM_RMCA(mac)); + stats->BroadcastFramesReceivedOK = + netc_port_rd64(np, NETC_PM_RBCA(mac)); + stats->FramesWithExcessiveDeferral = + netc_port_rd64(np, NETC_PM_TEDFR(mac)); +} + +void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port, + struct ethtool_eth_mac_stats *mac_stats) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev; + + switch (mac_stats->src) { + case ETHTOOL_MAC_STATS_SRC_EMAC: + netc_port_mac_stats(np, 0, mac_stats); + break; + case ETHTOOL_MAC_STATS_SRC_PMAC: + netc_port_mac_stats(np, 1, mac_stats); + break; + case ETHTOOL_MAC_STATS_SRC_AGGREGATE: + ndev = dsa_to_port(ds, port)->user; + ethtool_aggregate_mac_stats(ndev, mac_stats); + break; + } +} + +int netc_port_get_sset_count(struct dsa_switch *ds, int port, int sset) +{ + struct netc_port *np = NETC_PORT(ds, port); + int size; + + if (sset != ETH_SS_STATS) + return -EOPNOTSUPP; + + size = ARRAY_SIZE(netc_port_counters) + + ARRAY_SIZE(netc_emac_counters); + + if (np->caps.pmac) + size += ARRAY_SIZE(netc_pmac_counters); + + return size; +} + +void netc_port_get_strings(struct dsa_switch *ds, int port, + u32 sset, u8 *data) +{ + struct netc_port *np = NETC_PORT(ds, port); + int i; + + if (sset != ETH_SS_STATS) + return; + + for (i = 0; i < ARRAY_SIZE(netc_port_counters); i++) + ethtool_cpy(&data, netc_port_counters[i].name); + + for (i = 0; i < ARRAY_SIZE(netc_emac_counters); i++) + ethtool_cpy(&data, netc_emac_counters[i].name); + + if (!np->caps.pmac) + return; + + for (i = 0; i < ARRAY_SIZE(netc_pmac_counters); i++) + ethtool_cpy(&data, netc_pmac_counters[i].name); +} + +void netc_port_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data) +{ + struct netc_port *np = NETC_PORT(ds, port); + int i; + + for (i = 0; i < ARRAY_SIZE(netc_port_counters); i++) + *data++ = netc_port_rd(np, netc_port_counters[i].reg); + + for (i = 0; i < ARRAY_SIZE(netc_emac_counters); i++) + *data++ = netc_port_rd64(np, netc_emac_counters[i].reg); + + if (!np->caps.pmac) + return; + + for (i = 0; i < ARRAY_SIZE(netc_pmac_counters); i++) + *data++ = netc_port_rd64(np, netc_pmac_counters[i].reg); +} diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c new file mode 100644 index 000000000..77077352c --- /dev/null +++ b/drivers/net/dsa/netc/netc_main.c @@ -0,0 +1,2524 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * NXP NETC switch driver + * Copyright 2025-2026 NXP + */ + +#include <linux/clk.h> +#include <linux/etherdevice.h> +#include <linux/fsl/enetc_mdio.h> +#include <linux/if_bridge.h> +#include <linux/if_vlan.h> +#include <linux/of_mdio.h> + +#include "netc_switch.h" + +static struct netc_fdb_entry * +netc_lookup_fdb_entry(struct netc_switch *priv, + const unsigned char *addr, + u16 vid) +{ + struct netc_fdb_entry *entry; + + hlist_for_each_entry(entry, &priv->fdb_list, node) + if (ether_addr_equal(entry->keye.mac_addr, addr) && + le16_to_cpu(entry->keye.fid) == vid) + return entry; + + return NULL; +} + +static void netc_destroy_fdb_list(struct netc_switch *priv) +{ + struct netc_fdb_entry *entry; + struct hlist_node *tmp; + + hlist_for_each_entry_safe(entry, tmp, &priv->fdb_list, node) + netc_del_fdb_entry(entry); +} + +static struct netc_vlan_entry * +netc_lookup_vlan_entry(struct netc_switch *priv, u16 vid) +{ + struct netc_vlan_entry *entry; + + hlist_for_each_entry(entry, &priv->vlan_list, node) + if (entry->vid == vid) + return entry; + + return NULL; +} + +static void netc_destroy_vlan_list(struct netc_switch *priv) +{ + struct netc_vlan_entry *entry; + struct hlist_node *tmp; + + hlist_for_each_entry_safe(entry, tmp, &priv->vlan_list, node) + netc_del_vlan_entry(entry); +} + +static enum dsa_tag_protocol +netc_get_tag_protocol(struct dsa_switch *ds, int port, + enum dsa_tag_protocol mprot) +{ + return DSA_TAG_PROTO_NETC; +} + +static void netc_port_rmw(struct netc_port *np, u32 reg, + u32 mask, u32 val) +{ + u32 old, new; + + WARN_ON((mask | val) != mask); + + old = netc_port_rd(np, reg); + new = (old & ~mask) | val; + if (new == old) + return; + + netc_port_wr(np, reg, new); +} + +static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val) +{ + if (is_netc_pseudo_port(np)) + return; + + netc_port_wr(np, reg, val); + if (np->caps.pmac) + netc_port_wr(np, reg + NETC_PMAC_OFFSET, val); +} + +/* netc_mac_port_rmw() is used to synchronize the configurations of eMAC + * and pMAC to maintain consistency. This function should not be used if + * differentiated settings are required. + */ +static void netc_mac_port_rmw(struct netc_port *np, u32 reg, + u32 mask, u32 val) +{ + u32 old, new; + + if (is_netc_pseudo_port(np)) + return; + + WARN_ON((mask | val) != mask); + + old = netc_port_rd(np, reg); + new = (old & ~mask) | val; + if (new == old) + return; + + netc_port_wr(np, reg, new); + if (np->caps.pmac) + netc_port_wr(np, reg + NETC_PMAC_OFFSET, new); +} + +static void netc_port_get_capability(struct netc_port *np) +{ + u32 val; + + val = netc_port_rd(np, NETC_PMCAPR); + if (val & PMCAPR_HD) + np->caps.half_duplex = true; + + if (FIELD_GET(PMCAPR_FP, val) == FP_SUPPORT) + np->caps.pmac = true; + + val = netc_port_rd(np, NETC_PCAPR); + if (val & PCAPR_LINK_TYPE) + np->caps.pseudo_link = true; +} + +static int netc_port_get_info_from_dt(struct netc_port *np, + struct device_node *node, + struct device *dev) +{ + if (of_find_property(node, "clock-names", NULL)) { + np->ref_clk = devm_get_clk_from_child(dev, node, "ref"); + if (IS_ERR(np->ref_clk)) { + dev_err(dev, "Port %d cannot get reference clock\n", + np->dp->index); + return PTR_ERR(np->ref_clk); + } + } + + return 0; +} + +static int netc_port_create_emdio_bus(struct netc_port *np, + struct device_node *node) +{ + struct netc_switch *priv = np->switch_priv; + struct enetc_mdio_priv *mdio_priv; + struct device *dev = priv->dev; + struct enetc_hw *hw; + struct mii_bus *bus; + int err; + + hw = enetc_hw_alloc(dev, np->iobase); + if (IS_ERR(hw)) + return dev_err_probe(dev, PTR_ERR(hw), + "Failed to allocate enetc_hw\n"); + + bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv)); + if (!bus) + return -ENOMEM; + + bus->name = "NXP NETC switch external MDIO Bus"; + bus->read = enetc_mdio_read_c22; + bus->write = enetc_mdio_write_c22; + bus->read_c45 = enetc_mdio_read_c45; + bus->write_c45 = enetc_mdio_write_c45; + bus->parent = dev; + mdio_priv = bus->priv; + mdio_priv->hw = hw; + mdio_priv->mdio_base = NETC_EMDIO_BASE; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-p%d-emdio", + dev_name(dev), np->dp->index); + + err = devm_of_mdiobus_register(dev, bus, node); + if (err) + return dev_err_probe(dev, err, + "Cannot register EMDIO bus\n"); + + np->emdio = bus; + + return 0; +} + +static int netc_port_create_mdio_bus(struct netc_port *np, + struct device_node *node) +{ + struct device_node *mdio_node; + int err; + + mdio_node = of_get_child_by_name(node, "mdio"); + if (mdio_node) { + err = netc_port_create_emdio_bus(np, mdio_node); + of_node_put(mdio_node); + if (err) + return err; + } + + return 0; +} + +static int netc_init_switch_id(struct netc_switch *priv) +{ + struct netc_switch_regs *regs = &priv->regs; + struct dsa_switch *ds = priv->ds; + + /* The value of 0 is reserved for the VEPA switch and cannot + * be used. So 'dsa,member' is a required property for NETC + * switch, the member is used to specify the switch ID, which + * cannot be zero. This way, the hardware switch ID and the + * software switch ID are consistent. + */ + if (ds->index > FIELD_MAX(SWCR_SWID) || !ds->index) { + dev_err(priv->dev, "Switch index %d out of range\n", + ds->index); + return -ERANGE; + } + + netc_base_wr(regs, NETC_SWCR, ds->index); + + return 0; +} + +static void netc_get_switch_capabilities(struct netc_switch *priv) +{ + struct netc_switch_regs *regs = &priv->regs; + u32 val; + + val = netc_base_rd(regs, NETC_HTMCAPR); + priv->htmcapr_num_words = FIELD_GET(HTMCAPR_NUM_WORDS, val); + + val = netc_base_rd(regs, NETC_BPCAPR); + priv->num_bp = FIELD_GET(BPCAPR_NUM_BP, val); +} + +static int netc_init_all_ports(struct netc_switch *priv) +{ + struct device *dev = priv->dev; + struct netc_port *np; + struct dsa_port *dp; + int ett_offset = 0; + int err; + + priv->ports = devm_kcalloc(dev, priv->info->num_ports, + sizeof(struct netc_port *), + GFP_KERNEL); + if (!priv->ports) + return -ENOMEM; + + /* Some DSA interfaces may set the port even it is disabled, such + * as .port_disable(), .port_stp_state_set() and so on. To avoid + * crash caused by accessing NULL port pointer, each port is + * allocated its own memory. Otherwise, we need to check whether + * the port pointer is NULL in these interfaces. The latter is + * difficult for us to cover. + */ + for (int i = 0; i < priv->info->num_ports; i++) { + np = devm_kzalloc(dev, sizeof(*np), GFP_KERNEL); + if (!np) + return -ENOMEM; + + np->switch_priv = priv; + np->iobase = priv->regs.port + PORT_IOBASE(i); + netc_port_get_capability(np); + priv->ports[i] = np; + } + + dsa_switch_for_each_available_port(dp, priv->ds) { + np = priv->ports[dp->index]; + np->dp = dp; + np->ett_offset = ett_offset++; + priv->port_bitmap |= BIT(dp->index); + + err = netc_port_get_info_from_dt(np, dp->dn, dev); + if (err) + return err; + + if (dsa_port_is_user(dp)) { + err = netc_port_create_mdio_bus(np, dp->dn); + if (err) { + dev_err(dev, "Failed to create MDIO bus\n"); + return err; + } + } + } + + return 0; +} + +static void netc_init_ntmp_tbl_versions(struct netc_switch *priv) +{ + struct ntmp_user *ntmp = &priv->ntmp; + + /* All tables default to version 0 */ + memset(&ntmp->tbl, 0, sizeof(ntmp->tbl)); +} + +static int netc_init_all_cbdrs(struct netc_switch *priv) +{ + struct netc_switch_regs *regs = &priv->regs; + struct ntmp_user *ntmp = &priv->ntmp; + int i, err; + + ntmp->cbdr_num = NETC_CBDR_NUM; + ntmp->dev = priv->dev; + ntmp->ring = devm_kcalloc(ntmp->dev, ntmp->cbdr_num, + sizeof(struct netc_cbdr), + GFP_KERNEL); + if (!ntmp->ring) + return -ENOMEM; + + for (i = 0; i < ntmp->cbdr_num; i++) { + struct netc_cbdr *cbdr = &ntmp->ring[i]; + struct netc_cbdr_regs cbdr_regs; + + cbdr_regs.pir = regs->base + NETC_CBDRPIR(i); + cbdr_regs.cir = regs->base + NETC_CBDRCIR(i); + cbdr_regs.mr = regs->base + NETC_CBDRMR(i); + cbdr_regs.bar0 = regs->base + NETC_CBDRBAR0(i); + cbdr_regs.bar1 = regs->base + NETC_CBDRBAR1(i); + cbdr_regs.lenr = regs->base + NETC_CBDRLENR(i); + + err = ntmp_init_cbdr(cbdr, ntmp->dev, &cbdr_regs); + if (err) + goto free_cbdrs; + } + + return 0; + +free_cbdrs: + for (i--; i >= 0; i--) + ntmp_free_cbdr(&ntmp->ring[i]); + + return err; +} + +static void netc_remove_all_cbdrs(struct netc_switch *priv) +{ + struct ntmp_user *ntmp = &priv->ntmp; + + for (int i = 0; i < NETC_CBDR_NUM; i++) + ntmp_free_cbdr(&ntmp->ring[i]); +} + +static u32 netc_num_available_ports(struct netc_switch *priv) +{ + struct dsa_port *dp; + u32 num_ports = 0; + + dsa_switch_for_each_available_port(dp, priv->ds) + num_ports++; + + return num_ports; +} + +static int netc_init_ntmp_bitmap_sizes(struct netc_switch *priv) +{ + u32 num_ports = netc_num_available_ports(priv); + struct netc_switch_regs *regs = &priv->regs; + struct ntmp_user *ntmp = &priv->ntmp; + u32 val; + + if (!num_ports) + return -EINVAL; + + val = netc_base_rd(regs, NETC_ETTCAPR); + ntmp->ett_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports; + if (!ntmp->ett_bitmap_size) + return -EINVAL; + + val = netc_base_rd(regs, NETC_ECTCAPR); + ntmp->ect_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports; + if (!ntmp->ect_bitmap_size) + return -EINVAL; + + return 0; +} + +static int netc_init_ntmp_bitmaps(struct netc_switch *priv) +{ + struct ntmp_user *ntmp = &priv->ntmp; + + ntmp->ett_gid_bitmap = bitmap_zalloc(ntmp->ett_bitmap_size, + GFP_KERNEL); + if (!ntmp->ett_gid_bitmap) + return -ENOMEM; + + ntmp->ect_gid_bitmap = bitmap_zalloc(ntmp->ect_bitmap_size, + GFP_KERNEL); + if (!ntmp->ect_gid_bitmap) + goto free_ett_gid_bitmap; + + return 0; + +free_ett_gid_bitmap: + bitmap_free(ntmp->ett_gid_bitmap); + ntmp->ett_gid_bitmap = NULL; + + return -ENOMEM; +} + +static void netc_free_ntmp_bitmaps(struct netc_switch *priv) +{ + struct ntmp_user *ntmp = &priv->ntmp; + + bitmap_free(ntmp->ect_gid_bitmap); + ntmp->ect_gid_bitmap = NULL; + + bitmap_free(ntmp->ett_gid_bitmap); + ntmp->ett_gid_bitmap = NULL; +} + +static int netc_init_ntmp_user(struct netc_switch *priv) +{ + int err; + + netc_init_ntmp_tbl_versions(priv); + + err = netc_init_ntmp_bitmap_sizes(priv); + if (err) + return err; + + err = netc_init_ntmp_bitmaps(priv); + if (err) + return err; + + err = netc_init_all_cbdrs(priv); + if (err) + goto free_ntmp_bitmaps; + + return 0; + +free_ntmp_bitmaps: + netc_free_ntmp_bitmaps(priv); + + return err; +} + +static void netc_free_ntmp_user(struct netc_switch *priv) +{ + netc_remove_all_cbdrs(priv); + netc_free_ntmp_bitmaps(priv); +} + +static void netc_clean_fdbt_ageing_entries(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct netc_switch *priv; + + priv = container_of(dwork, struct netc_switch, fdbt_ageing_work); + + /* Update the activity element in FDB table */ + mutex_lock(&priv->fdbt_lock); + ntmp_fdbt_update_activity_element(&priv->ntmp); + /* Delete the ageing entries after the activity element is updated */ + ntmp_fdbt_delete_ageing_entries(&priv->ntmp, NETC_FDBT_AGEING_THRESH); + mutex_unlock(&priv->fdbt_lock); + + if (atomic_read(&priv->br_cnt)) + schedule_delayed_work(&priv->fdbt_ageing_work, + READ_ONCE(priv->fdbt_ageing_delay)); +} + +static void netc_switch_dos_default_config(struct netc_switch *priv) +{ + struct netc_switch_regs *regs = &priv->regs; + u32 val; + + val = DOSL2CR_SAMEADDR | DOSL2CR_MSAMCC; + netc_base_wr(regs, NETC_DOSL2CR, val); + + val = DOSL3CR_SAMEADDR | DOSL3CR_IPSAMCC; + netc_base_wr(regs, NETC_DOSL3CR, val); +} + +static void netc_switch_vfht_default_config(struct netc_switch *priv) +{ + struct netc_switch_regs *regs = &priv->regs; + u32 val; + + val = netc_base_rd(regs, NETC_VFHTDECR2); + + /* If no match is found in the VLAN Filter table, then VFHTDECR2[MLO] + * will take effect. VFHTDECR2[MLO] is set to "Software MAC learning + * secure" by default. Notice BPCR[MLO] will override VFHTDECR2[MLO] + * if its value is not zero. + */ + val = u32_replace_bits(val, MLO_SW_SEC, VFHTDECR2_MLO); + val = u32_replace_bits(val, MFO_NO_MATCH_DISCARD, VFHTDECR2_MFO); + netc_base_wr(regs, NETC_VFHTDECR2, val); +} + +static void netc_port_set_max_frame_size(struct netc_port *np, + u32 max_frame_size) +{ + netc_mac_port_wr(np, NETC_PM_MAXFRM(0), + max_frame_size & PM_MAXFRAM); +} + +static void netc_switch_fixed_config(struct netc_switch *priv) +{ + netc_switch_dos_default_config(priv); + netc_switch_vfht_default_config(priv); +} + +static void netc_port_set_tc_max_sdu(struct netc_port *np, + int tc, u32 max_sdu) +{ + u32 val = FIELD_PREP(PTCTMSDUR_MAXSDU, max_sdu) | + FIELD_PREP(PTCTMSDUR_SDU_TYPE, SDU_TYPE_MPDU); + + netc_port_wr(np, NETC_PTCTMSDUR(tc), val); +} + +static void netc_port_set_all_tc_msdu(struct netc_port *np) +{ + for (int tc = 0; tc < NETC_TC_NUM; tc++) + netc_port_set_tc_max_sdu(np, tc, NETC_MAX_FRAME_LEN); +} + +static void netc_port_set_mlo(struct netc_port *np, enum netc_mlo mlo) +{ + netc_port_rmw(np, NETC_BPCR, BPCR_MLO, FIELD_PREP(BPCR_MLO, mlo)); +} + +static void netc_port_set_pvid(struct netc_port *np, u16 pvid) +{ + netc_port_rmw(np, NETC_BPDVR, BPDVR_VID, pvid); +} + +static void netc_port_set_vlan_aware(struct netc_port *np, bool aware) +{ + netc_port_rmw(np, NETC_BPDVR, BPDVR_RXVAM, + aware ? 0 : BPDVR_RXVAM); +} + +static void netc_port_fixed_config(struct netc_port *np) +{ + /* Default IPV and DR setting */ + netc_port_rmw(np, NETC_PQOSMR, PQOSMR_VS | PQOSMR_VE, + PQOSMR_VS | PQOSMR_VE); + + /* Enable L2 and L3 DOS */ + netc_port_rmw(np, NETC_PCR, PCR_L2DOSE | PCR_L3DOSE, + PCR_L2DOSE | PCR_L3DOSE); + + /* Set the quanta value of TX PAUSE frame */ + netc_mac_port_wr(np, NETC_PM_PAUSE_QUANTA(0), NETC_PAUSE_QUANTA); + + /* When a quanta timer counts down and reaches this value, + * the MAC sends a refresh PAUSE frame with the programmed + * full quanta value if a pause condition still exists. + */ + netc_mac_port_wr(np, NETC_PM_PAUSE_THRESH(0), NETC_PAUSE_THRESH); +} + +static void netc_port_default_config(struct netc_port *np) +{ + netc_port_fixed_config(np); + + /* Default VLAN unaware */ + netc_port_set_vlan_aware(np, false); + + if (dsa_port_is_cpu(np->dp)) + /* For CPU port, source port pruning is disabled */ + netc_port_rmw(np, NETC_BPCR, BPCR_SRCPRND, BPCR_SRCPRND); + else + netc_port_set_mlo(np, MLO_DISABLE); + + netc_port_set_max_frame_size(np, NETC_MAX_FRAME_LEN); + netc_port_set_all_tc_msdu(np); +} + +static u32 netc_available_port_bitmap(struct netc_switch *priv) +{ + struct dsa_port *dp; + u32 bitmap = 0; + + dsa_switch_for_each_available_port(dp, priv->ds) + bitmap |= BIT(dp->index); + + return bitmap; +} + +static int netc_add_standalone_vlan_entry(struct netc_switch *priv) +{ + u32 bitmap_stg = VFT_STG_ID(0) | netc_available_port_bitmap(priv); + struct vft_cfge_data *cfge; + u16 cfg; + int err; + + cfge = kzalloc_obj(*cfge); + if (!cfge) + return -ENOMEM; + + cfge->bitmap_stg = cpu_to_le32(bitmap_stg); + cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + cfge->fid = cpu_to_le16(NETC_STANDALONE_PVID); + + /* For standalone ports, MAC learning needs to be disabled, so frames + * from other user ports will not be forwarded to the standalone ports, + * because there are no FDB entries on the standalone ports. Also, the + * frames received by the standalone ports cannot be flooded to other + * ports, so MAC forwarding option needs to be set to + * MFO_NO_MATCH_DISCARD, so the frames will be discarded rather than + * flooding to other ports. + */ + cfg = FIELD_PREP(VFT_MLO, MLO_DISABLE) | + FIELD_PREP(VFT_MFO, MFO_NO_MATCH_DISCARD); + cfge->cfg = cpu_to_le16(cfg); + + err = ntmp_vft_add_entry(&priv->ntmp, NETC_STANDALONE_PVID, cfge); + if (err) + dev_err(priv->dev, + "Failed to add standalone VLAN entry\n"); + + kfree(cfge); + + return err; +} + +static int netc_port_add_fdb_entry(struct netc_port *np, + const unsigned char *addr, u16 vid) +{ + struct netc_switch *priv = np->switch_priv; + struct netc_fdb_entry *entry; + struct fdbt_keye_data *keye; + struct fdbt_cfge_data *cfge; + int port = np->dp->index; + u32 cfg = 0; + int err; + + entry = kzalloc_obj(*entry); + if (!entry) + return -ENOMEM; + + keye = &entry->keye; + cfge = &entry->cfge; + ether_addr_copy(keye->mac_addr, addr); + keye->fid = cpu_to_le16(vid); + + cfge->port_bitmap = cpu_to_le32(BIT(port)); + cfge->cfg = cpu_to_le32(cfg); + cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + + err = ntmp_fdbt_add_entry(&priv->ntmp, &entry->entry_id, keye, cfge); + if (err) { + kfree(entry); + + return err; + } + + netc_add_fdb_entry(priv, entry); + + return 0; +} + +static int netc_port_set_fdb_entry(struct netc_port *np, + const unsigned char *addr, u16 vid) +{ + struct netc_switch *priv = np->switch_priv; + struct netc_fdb_entry *entry; + struct fdbt_cfge_data *cfge; + int port = np->dp->index; + __le32 old_port_bitmap; + int err = 0; + + mutex_lock(&priv->fdbt_lock); + + entry = netc_lookup_fdb_entry(priv, addr, vid); + if (!entry) { + err = netc_port_add_fdb_entry(np, addr, vid); + if (err) + dev_err(priv->dev, + "Failed to add FDB entry on port %d\n", + port); + + goto unlock_fdbt; + } + + cfge = &entry->cfge; + /* If the entry already exists on the port, return 0 directly */ + if (unlikely(cfge->port_bitmap & cpu_to_le32(BIT(port)))) + goto unlock_fdbt; + + /* If the entry already exists, but not on this port, we need to + * update the port bitmap. In general, it should only be valid + * for multicast or broadcast address. + */ + old_port_bitmap = cfge->port_bitmap; + if (is_multicast_ether_addr(addr)) + cfge->port_bitmap |= cpu_to_le32(BIT(port)); + else + cfge->port_bitmap = cpu_to_le32(BIT(port)); + + err = ntmp_fdbt_update_entry(&priv->ntmp, entry->entry_id, cfge); + if (err) { + cfge->port_bitmap = old_port_bitmap; + dev_err(priv->dev, "Failed to set FDB entry on port %d\n", + port); + } + +unlock_fdbt: + mutex_unlock(&priv->fdbt_lock); + + return err; +} + +static int netc_port_del_fdb_entry(struct netc_port *np, + const unsigned char *addr, u16 vid) +{ + struct netc_switch *priv = np->switch_priv; + struct ntmp_user *ntmp = &priv->ntmp; + struct netc_fdb_entry *entry; + struct fdbt_cfge_data *cfge; + int port = np->dp->index; + int err = 0; + + mutex_lock(&priv->fdbt_lock); + + entry = netc_lookup_fdb_entry(priv, addr, vid); + if (unlikely(!entry)) + /* The hardware-learned dynamic FDB entries cannot be deleted + * through .port_fdb_del() interface. + * For NTF_MASTER path: Since hardware-learned dynamic FDB + * entries are never synchronized back to the bridge software + * database. br_fdb_delete() -> br_fdb_find() cannot find the + * FDB entry, so .port_fdb_del() will not be called. + * For NTF_SELF path: dsa_user_netdev_ops does not implement + * ndo_fdb_del(), so rtnl_fdb_del() falls back to + * ndo_dflt_fdb_del(), which only supports NUD_PERMANENT static + * entries and rejects all others with -EINVAL. + */ + goto unlock_fdbt; + + cfge = &entry->cfge; + if (unlikely(!(cfge->port_bitmap & cpu_to_le32(BIT(port))))) + goto unlock_fdbt; + + if (cfge->port_bitmap != cpu_to_le32(BIT(port))) { + /* If the entry also exists on other ports, we need to + * update the entry in the FDB table. + */ + cfge->port_bitmap &= cpu_to_le32(~BIT(port)); + err = ntmp_fdbt_update_entry(ntmp, entry->entry_id, cfge); + if (err) { + cfge->port_bitmap |= cpu_to_le32(BIT(port)); + goto unlock_fdbt; + } + } else { + /* If the entry only exists on this port, just delete + * it from the FDB table. + */ + err = ntmp_fdbt_delete_entry(ntmp, entry->entry_id); + if (err) + goto unlock_fdbt; + + netc_del_fdb_entry(entry); + } + +unlock_fdbt: + mutex_unlock(&priv->fdbt_lock); + + return err; +} + +static int netc_add_standalone_fdb_bcast_entry(struct netc_switch *priv) +{ + const u8 bcast[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + struct dsa_port *dp, *cpu_dp = NULL; + + dsa_switch_for_each_cpu_port(dp, priv->ds) { + /* The switch has only one CPU port, so only need to find + * the first CPU port to break out of the loop. + */ + cpu_dp = dp; + break; + } + + if (!cpu_dp) + return -ENODEV; + + /* If the user port acts as a standalone port, then its PVID is 0, + * MLO is set to "disable MAC learning" and MFO is set to "discard + * frames if no matching entry found in FDB table". Therefore, we + * need to add a broadcast FDB entry on the CPU port so that the + * broadcast frames received on the user port can be forwarded to + * the CPU port. + */ + return netc_port_set_fdb_entry(NETC_PORT(priv->ds, cpu_dp->index), + bcast, NETC_STANDALONE_PVID); +} + +static void netc_port_set_pbpmcr(struct netc_port *np, u64 mapping) +{ + u32 pbpmcr0 = lower_32_bits(mapping); + u32 pbpmcr1 = upper_32_bits(mapping); + + netc_port_wr(np, NETC_PBPMCR0, pbpmcr0); + netc_port_wr(np, NETC_PBPMCR1, pbpmcr1); +} + +static void netc_ipv_to_buffer_pool_mapping(struct netc_switch *priv) +{ + int bp_per_port = priv->num_bp / priv->info->num_ports; + int q = NETC_IPV_NUM / bp_per_port; + int r = NETC_IPV_NUM % bp_per_port; + int num = q + r; + + /* IPV-to-buffer-pool mapping per port: + * Each port is allocated 'bp_per_port' buffer pools and supports 8 + * IPVs, where a higher IPV indicates a higher frame priority. Each + * IPV can be mapped to only one buffer pool, from hardware design + * perspective, bp_per_port will not be greater than 8. So 'q' will + * not be 0. + * + * The mapping rule is as follows: + * - The first 'num' IPVs share the port's first buffer pool (index + * 'base_id'). + * - After that, every 'q' IPVs share one buffer pool, with pool + * indices increasing sequentially. + */ + for (int i = 0; i < priv->info->num_ports; i++) { + u32 base_id = i * bp_per_port; + u32 bp_id = base_id; + u64 mapping = 0; + + for (int ipv = 0; ipv < NETC_IPV_NUM; ipv++) { + /* Update the buffer pool index */ + if (ipv >= num) + bp_id = base_id + ((ipv - num) / q) + 1; + + mapping |= (u64)bp_id << (ipv * 8); + } + + netc_port_set_pbpmcr(priv->ports[i], mapping); + } +} + +static int netc_switch_bpt_default_config(struct netc_switch *priv) +{ + if (priv->num_bp < priv->info->num_ports) + return -EINVAL; + + priv->bpt_list = devm_kcalloc(priv->dev, priv->num_bp, + sizeof(struct bpt_cfge_data), + GFP_KERNEL); + if (!priv->bpt_list) + return -ENOMEM; + + /* Initialize the maximum threshold of each buffer pool entry */ + for (int i = 0; i < priv->num_bp; i++) { + struct bpt_cfge_data *cfge = &priv->bpt_list[i]; + int err; + + cfge->max_thresh = cpu_to_le16(NETC_BP_THRESH); + err = ntmp_bpt_update_entry(&priv->ntmp, i, cfge); + if (err) + return err; + } + + netc_ipv_to_buffer_pool_mapping(priv); + + return 0; +} + +static int netc_setup(struct dsa_switch *ds) +{ + struct netc_switch *priv = ds->priv; + struct dsa_port *dp; + int err; + + err = netc_init_switch_id(priv); + if (err) + return err; + + netc_get_switch_capabilities(priv); + + err = netc_init_all_ports(priv); + if (err) + return err; + + err = netc_init_ntmp_user(priv); + if (err) + return err; + + INIT_HLIST_HEAD(&priv->fdb_list); + mutex_init(&priv->fdbt_lock); + priv->fdbt_ageing_delay = NETC_FDBT_AGEING_DELAY; + atomic_set(&priv->br_cnt, 0); + INIT_DELAYED_WORK(&priv->fdbt_ageing_work, + netc_clean_fdbt_ageing_entries); + INIT_HLIST_HEAD(&priv->vlan_list); + mutex_init(&priv->vft_lock); + + netc_switch_fixed_config(priv); + + /* default setting for ports */ + dsa_switch_for_each_available_port(dp, ds) + netc_port_default_config(priv->ports[dp->index]); + + err = netc_switch_bpt_default_config(priv); + if (err) + goto free_lock_and_ntmp_user; + + err = netc_add_standalone_vlan_entry(priv); + if (err) + goto free_lock_and_ntmp_user; + + err = netc_add_standalone_fdb_bcast_entry(priv); + if (err) + goto free_lock_and_ntmp_user; + + return 0; + +free_lock_and_ntmp_user: + /* No need to clear the hardware state, netc_setup() is only called + * when the driver is bound, and FLR will be performed to reset the + * hardware state. + */ + mutex_destroy(&priv->fdbt_lock); + mutex_destroy(&priv->vft_lock); + netc_free_ntmp_user(priv); + + return err; +} + +static void netc_destroy_all_lists(struct netc_switch *priv) +{ + netc_destroy_fdb_list(priv); + mutex_destroy(&priv->fdbt_lock); + netc_destroy_vlan_list(priv); + mutex_destroy(&priv->vft_lock); +} + +static void netc_free_host_flood_rules(struct netc_switch *priv) +{ + struct dsa_port *dp; + + dsa_switch_for_each_user_port(dp, priv->ds) { + struct netc_port *np = priv->ports[dp->index]; + + /* No need to clear the hardware IPFT entry. Because PCIe + * FLR will be performed when the switch is re-registered, + * it will reset hardware state. So only need to free the + * memory to avoid memory leak. + */ + kfree(np->host_flood); + np->host_flood = NULL; + } +} + +static void netc_teardown(struct dsa_switch *ds) +{ + struct netc_switch *priv = ds->priv; + + disable_delayed_work_sync(&priv->fdbt_ageing_work); + netc_destroy_all_lists(priv); + netc_free_host_flood_rules(priv); + netc_free_ntmp_user(priv); +} + +static bool netc_port_is_emdio_consumer(struct device_node *node) +{ + struct device_node *mdio_node; + + /* If the port node has phy-handle property and it does + * not contain a mdio child node, then the port is the + * EMDIO consumer. + */ + mdio_node = of_get_child_by_name(node, "mdio"); + if (!mdio_node) + return true; + + of_node_put(mdio_node); + + return false; +} + +/* Currently, phylink_of_phy_connect() is called by dsa_user_create(), + * so if the switch uses the external MDIO controller (like the EMDIO + * function) to manage the external PHYs. The MDIO bus may not be + * created when phylink_of_phy_connect() is called, so it will return + * an error and cause the switch driver to fail to probe. + * This workaround can be removed when DSA phylink_of_phy_connect() + * calls are moved from probe() to ndo_open(). + */ +static int netc_switch_check_emdio_is_ready(struct device *dev) +{ + struct device_node *ports, *phy_node; + struct phy_device *phydev; + int err = 0; + + ports = of_get_child_by_name(dev->of_node, "ethernet-ports"); + if (!ports) { + dev_err(dev, "Cannot find the ethernet-ports node\n"); + return -EINVAL; + } + + for_each_available_child_of_node_scoped(ports, child) { + /* If the node does not have phy-handle property, then the + * port does not connect to a PHY, so the port is not the + * EMDIO consumer. + */ + phy_node = of_parse_phandle(child, "phy-handle", 0); + if (!phy_node) + continue; + + /* Note that from the hardware perspective, the switch ports + * do not support sharing the MDIO bus defined under one port. + * Each port can only access its own external PHY through its + * port MDIO bus. + */ + if (!netc_port_is_emdio_consumer(child)) { + of_node_put(phy_node); + continue; + } + + phydev = of_phy_find_device(phy_node); + of_node_put(phy_node); + if (!phydev) { + err = -EPROBE_DEFER; + goto out; + } + + put_device(&phydev->mdio.dev); + } + +out: + of_node_put(ports); + + return err; +} + +static int netc_switch_pci_init(struct pci_dev *pdev) +{ + struct device *dev = &pdev->dev; + struct netc_switch_regs *regs; + struct netc_switch *priv; + void __iomem *base; + int err; + + pcie_flr(pdev); + err = pcim_enable_device(pdev); + if (err) + return dev_err_probe(dev, err, "Failed to enable device\n"); + + err = pcim_request_all_regions(pdev, KBUILD_MODNAME); + if (err) + return dev_err_probe(dev, err, "Failed to request regions\n"); + + /* The command BD rings and NTMP tables need DMA. No need to check + * the return value, because it never returns fail when the mask is + * DMA_BIT_MASK(64), see dma-api-howto.rst. + */ + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + + if (pci_resource_len(pdev, NETC_REGS_BAR) < NETC_REGS_SIZE) { + return dev_err_probe(dev, -EINVAL, + "Invalid register space size\n"); + } + + base = pcim_iomap(pdev, NETC_REGS_BAR, 0); + if (!base) + return dev_err_probe(dev, -ENXIO, "pcim_iomap() failed\n"); + + pci_set_master(pdev); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + priv->dev = dev; + + regs = &priv->regs; + regs->base = base; + regs->port = regs->base + NETC_REGS_PORT_BASE; + regs->global = regs->base + NETC_REGS_GLOBAL_BASE; + pci_set_drvdata(pdev, priv); + + return 0; +} + +static void netc_switch_get_ip_revision(struct netc_switch *priv) +{ + struct netc_switch_regs *regs = &priv->regs; + u32 val = netc_glb_rd(regs, NETC_IPBRR0); + + priv->revision = FIELD_GET(IPBRR0_IP_REV, val); +} + +static void netc_init_ett_cfge(struct ett_cfge_data *cfge, + bool untagged, u32 ect_eid) +{ + u32 vuda_sqta = FMTEID_VUDA_SQTA; + u16 efm_cfg = 0; + + if (ect_eid != NTMP_NULL_ENTRY_ID) { + /* Increase egress frame counter */ + efm_cfg |= FIELD_PREP(ETT_ECA, ETT_ECA_INC); + cfge->ec_eid = cpu_to_le32(ect_eid); + } + + /* If egress rule is VLAN untagged */ + if (untagged) { + /* delete outer VLAN tag */ + vuda_sqta |= FIELD_PREP(FMTEID_VUDA, FMTEID_VUDA_DEL_OTAG); + /* length change: twos-complement notation */ + efm_cfg |= FIELD_PREP(ETT_EFM_LEN_CHANGE, + ETT_FRM_LEN_DEL_VLAN); + } + + cfge->efm_eid = cpu_to_le32(vuda_sqta); + cfge->efm_cfg = cpu_to_le16(efm_cfg); +} + +static int netc_add_ett_entry(struct netc_switch *priv, bool untagged, + u32 ett_eid, u32 ect_eid) +{ + struct ntmp_user *ntmp = &priv->ntmp; + struct ett_cfge_data cfge = {}; + + netc_init_ett_cfge(&cfge, untagged, ect_eid); + + return ntmp_ett_add_entry(ntmp, ett_eid, &cfge); +} + +static int netc_update_ett_entry(struct netc_switch *priv, bool untagged, + u32 ett_eid, u32 ect_eid) +{ + struct ntmp_user *ntmp = &priv->ntmp; + struct ett_cfge_data cfge = {}; + + netc_init_ett_cfge(&cfge, untagged, ect_eid); + + return ntmp_ett_update_entry(ntmp, ett_eid, &cfge); +} + +static int netc_add_ett_group_entries(struct netc_switch *priv, + u32 untagged_port_bitmap, + u32 ett_base_eid, + u32 ect_base_eid) +{ + struct netc_port **ports = priv->ports; + u32 ett_eid, ect_eid; + bool untagged; + int i, err; + + for (i = 0; i < priv->info->num_ports; i++) { + if (!ports[i]->dp) + continue; + + untagged = !!(untagged_port_bitmap & BIT(i)); + ett_eid = ett_base_eid + ports[i]->ett_offset; + ect_eid = NTMP_NULL_ENTRY_ID; + if (ect_base_eid != NTMP_NULL_ENTRY_ID) + ect_eid = ect_base_eid + ports[i]->ett_offset; + + err = netc_add_ett_entry(priv, untagged, ett_eid, ect_eid); + if (err) + goto clear_ett_entries; + } + + return 0; + +clear_ett_entries: + while (--i >= 0) { + if (!ports[i]->dp) + continue; + + ett_eid = ett_base_eid + ports[i]->ett_offset; + ntmp_ett_delete_entry(&priv->ntmp, ett_eid); + } + + return err; +} + +static int netc_add_vlan_egress_rule(struct netc_switch *priv, + struct netc_vlan_entry *entry) +{ + u32 num_ports = netc_num_available_ports(priv); + struct ntmp_user *ntmp = &priv->ntmp; + u32 ect_eid = NTMP_NULL_ENTRY_ID; + u32 ett_eid, ett_gid, ect_gid; + int err; + + /* Step 1: Find available egress counter table entries and update + * these entries. + */ + ect_gid = ntmp_lookup_free_eid(ntmp->ect_gid_bitmap, + ntmp->ect_bitmap_size); + if (ect_gid == NTMP_NULL_ENTRY_ID) { + dev_info(priv->dev, + "No egress counter table entries available\n"); + } else { + ect_eid = ect_gid * num_ports; + for (int i = 0; i < num_ports; i++) + /* There is no need to check the return value, the only + * issue is that the entry's counter might be inaccurate, + * but it will not affect the functionality, it is only + * for future debugging. + */ + ntmp_ect_update_entry(ntmp, ect_eid + i); + } + + /* Step 2: Find available egress treatment table entries and add + * these entries. + */ + ett_gid = ntmp_lookup_free_eid(ntmp->ett_gid_bitmap, + ntmp->ett_bitmap_size); + if (ett_gid == NTMP_NULL_ENTRY_ID) { + dev_err(priv->dev, + "No egress treatment table entries available\n"); + err = -ENOSPC; + goto clear_ect_gid; + } + + ett_eid = ett_gid * num_ports; + err = netc_add_ett_group_entries(priv, entry->untagged_port_bitmap, + ett_eid, ect_eid); + if (err) + goto clear_ett_gid; + + entry->cfge.et_eid = cpu_to_le32(ett_eid); + entry->ect_gid = ect_gid; + + return 0; + +clear_ett_gid: + ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid); + +clear_ect_gid: + if (ect_gid != NTMP_NULL_ENTRY_ID) + ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, ect_gid); + + return err; +} + +static void netc_delete_vlan_egress_rule(struct netc_switch *priv, + struct netc_vlan_entry *entry) +{ + u32 num_ports = netc_num_available_ports(priv); + struct ntmp_user *ntmp = &priv->ntmp; + u32 ett_eid, ett_gid; + + ett_eid = le32_to_cpu(entry->cfge.et_eid); + if (ett_eid == NTMP_NULL_ENTRY_ID) + return; + + ett_gid = ett_eid / num_ports; + ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid); + for (int i = 0; i < num_ports; i++) + ntmp_ett_delete_entry(ntmp, ett_eid + i); + + if (entry->ect_gid == NTMP_NULL_ENTRY_ID) + return; + + ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, entry->ect_gid); +} + +static int netc_port_update_vlan_egress_rule(struct netc_port *np, + struct netc_vlan_entry *entry) +{ + bool untagged = !!(entry->untagged_port_bitmap & BIT(np->dp->index)); + u32 num_ports = netc_num_available_ports(np->switch_priv); + u32 ett_eid = le32_to_cpu(entry->cfge.et_eid); + struct netc_switch *priv = np->switch_priv; + u32 ect_eid = NTMP_NULL_ENTRY_ID; + int err; + + if (ett_eid == NTMP_NULL_ENTRY_ID) + return 0; + + if (entry->ect_gid != NTMP_NULL_ENTRY_ID) + /* Each ETT entry maps to an ECT entry if ect_gid is not NULL + * entry ID. The offset of the ECT entry corresponding to the + * port in the group is equal to ett_offset. + */ + ect_eid = entry->ect_gid * num_ports + np->ett_offset; + + ett_eid += np->ett_offset; + err = netc_update_ett_entry(priv, untagged, ett_eid, ect_eid); + if (err) { + dev_err(priv->dev, + "Failed to update VLAN %u egress rule on port %d\n", + entry->vid, np->dp->index); + return err; + } + + if (ect_eid != NTMP_NULL_ENTRY_ID) + ntmp_ect_update_entry(&priv->ntmp, ect_eid); + + return 0; +} + +static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid, + bool untagged) +{ + struct netc_switch *priv = np->switch_priv; + struct netc_vlan_entry *entry; + struct vft_cfge_data *cfge; + u32 index = np->dp->index; + u32 bitmap_stg; + int err; + u16 cfg; + + entry = kzalloc_obj(*entry); + if (!entry) + return -ENOMEM; + + entry->vid = vid; + entry->ect_gid = NTMP_NULL_ENTRY_ID; + + bitmap_stg = BIT(index) | VFT_STG_ID(0); + /* If the VID is a VLAN-unaware PVID, the CPU port needs to be + * a member of this VLAN. + */ + if (dsa_port_is_user(np->dp) && + vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) { + struct dsa_port *cpu_dp = np->dp->cpu_dp; + + bitmap_stg |= BIT(cpu_dp->index); + } + + cfg = FIELD_PREP(VFT_MLO, MLO_HW) | + FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD); + + cfge = &entry->cfge; + cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + cfge->bitmap_stg = cpu_to_le32(bitmap_stg); + cfge->fid = cpu_to_le16(vid); + cfge->cfg = cpu_to_le16(cfg); + cfge->eta_port_bitmap = cpu_to_le32(priv->port_bitmap); + + if (untagged) + entry->untagged_port_bitmap = BIT(index); + + err = netc_add_vlan_egress_rule(priv, entry); + if (err) + goto free_vlan_entry; + + err = ntmp_vft_add_entry(&priv->ntmp, vid, cfge); + if (err) { + dev_err(priv->dev, + "Failed to add VLAN %u entry on port %d\n", + vid, index); + goto delete_vlan_egress_rule; + } + + netc_add_vlan_entry(priv, entry); + + return 0; + +delete_vlan_egress_rule: + netc_delete_vlan_egress_rule(priv, entry); +free_vlan_entry: + kfree(entry); + + return err; +} + +static bool netc_port_vlan_egress_rule_changed(struct netc_switch *priv, + struct netc_vlan_entry *entry, + int port, bool untagged) +{ + bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port)); + + /* VLAN-unaware VIDs have no egress rules, so return 'false' */ + if (entry->vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) + return false; + + return old_untagged != untagged; +} + +static int netc_port_set_vlan_entry(struct netc_port *np, u16 vid, + bool untagged) +{ + struct netc_switch *priv = np->switch_priv; + struct netc_vlan_entry *entry; + struct vft_cfge_data *cfge; + int port = np->dp->index; + bool changed; + int err = 0; + + mutex_lock(&priv->vft_lock); + + entry = netc_lookup_vlan_entry(priv, vid); + if (!entry) { + err = netc_port_add_vlan_entry(np, vid, untagged); + goto unlock_vft; + } + + /* Check whether the egress VLAN rule is changed */ + changed = netc_port_vlan_egress_rule_changed(priv, entry, port, + untagged); + if (changed) { + entry->untagged_port_bitmap ^= BIT(port); + err = netc_port_update_vlan_egress_rule(np, entry); + if (err) { + entry->untagged_port_bitmap ^= BIT(port); + goto unlock_vft; + } + } + + cfge = &entry->cfge; + if (cfge->bitmap_stg & cpu_to_le32(BIT(port))) + goto unlock_vft; + + cfge->bitmap_stg |= cpu_to_le32(BIT(port)); + err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge); + if (err) { + dev_err(priv->dev, + "Failed to update VLAN %u entry on port %d\n", + vid, port); + + goto restore_bitmap_stg; + } + + mutex_unlock(&priv->vft_lock); + + return 0; + +restore_bitmap_stg: + cfge->bitmap_stg &= cpu_to_le32(~BIT(port)); + if (changed) { + entry->untagged_port_bitmap ^= BIT(port); + /* Recover the corresponding ETT entry. It doesn't matter + * if it fails because the bit corresponding to the port + * in the port bitmap of the VFT entry is not set. so the + * frame will not match that ETT entry. + */ + if (netc_port_update_vlan_egress_rule(np, entry)) + entry->untagged_port_bitmap ^= BIT(port); + } +unlock_vft: + mutex_unlock(&priv->vft_lock); + + return err; +} + +static int netc_port_del_vlan_entry(struct netc_port *np, u16 vid) +{ + struct netc_switch *priv = np->switch_priv; + struct netc_vlan_entry *entry; + struct vft_cfge_data *cfge; + int port = np->dp->index; + u32 vlan_port_bitmap; + int err = 0; + + mutex_lock(&priv->vft_lock); + + entry = netc_lookup_vlan_entry(priv, vid); + if (!entry) + goto unlock_vft; + + cfge = &entry->cfge; + vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP, + le32_to_cpu(cfge->bitmap_stg)); + /* If the VID is a VLAN-unaware PVID, we need to clear the CPU + * port bit of vlan_port_bitmap, so that the VLAN entry can be + * deleted if no user ports use this VLAN. + */ + if (dsa_port_is_user(np->dp) && + vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) { + struct dsa_port *cpu_dp = np->dp->cpu_dp; + + vlan_port_bitmap &= ~BIT(cpu_dp->index); + } + + /* If the VLAN only belongs to the current port */ + if (vlan_port_bitmap == BIT(port)) { + err = ntmp_vft_delete_entry(&priv->ntmp, vid); + if (err) + goto unlock_vft; + + netc_delete_vlan_egress_rule(priv, entry); + netc_del_vlan_entry(entry); + + goto unlock_vft; + } + + if (!(vlan_port_bitmap & BIT(port))) + goto unlock_vft; + + cfge->bitmap_stg &= cpu_to_le32(~BIT(port)); + err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge); + if (err) { + cfge->bitmap_stg |= cpu_to_le32(BIT(port)); + goto unlock_vft; + } + +unlock_vft: + mutex_unlock(&priv->vft_lock); + + return err; +} + +static int netc_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct netc_port *np = NETC_PORT(ds, port); + int err; + + if (np->enable) + return 0; + + err = clk_prepare_enable(np->ref_clk); + if (err) { + dev_err(ds->dev, + "Failed to enable enet_ref_clk of port %d\n", port); + return err; + } + + np->enable = true; + + return 0; +} + +static void netc_port_disable(struct dsa_switch *ds, int port) +{ + struct netc_port *np = NETC_PORT(ds, port); + + /* When .port_disable() is called, .port_enable() may not have been + * called. In this case, both the prepare_count and enable_count of + * clock are 0. Calling clk_disable_unprepare() at this time will + * cause warnings. + */ + if (!np->enable) + return; + + clk_disable_unprepare(np->ref_clk); + np->enable = false; +} + +static void netc_port_stp_state_set(struct dsa_switch *ds, + int port, u8 state) +{ + struct netc_port *np = NETC_PORT(ds, port); + u32 val; + + switch (state) { + case BR_STATE_DISABLED: + case BR_STATE_LISTENING: + case BR_STATE_BLOCKING: + val = NETC_STG_STATE_DISABLED; + break; + case BR_STATE_LEARNING: + val = NETC_STG_STATE_LEARNING; + break; + case BR_STATE_FORWARDING: + val = NETC_STG_STATE_FORWARDING; + break; + default: + return; + } + + netc_port_wr(np, NETC_BPSTGSR, val); +} + +static int netc_port_change_mtu(struct dsa_switch *ds, + int port, int mtu) +{ + u32 max_frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; + + netc_port_set_max_frame_size(NETC_PORT(ds, port), max_frame_size); + + return 0; +} + +static int netc_port_max_mtu(struct dsa_switch *ds, int port) +{ + return NETC_MAX_FRAME_LEN - VLAN_ETH_HLEN - ETH_FCS_LEN; +} + +static struct net_device *netc_classify_db(struct dsa_db db) +{ + switch (db.type) { + case DSA_DB_PORT: + return NULL; + case DSA_DB_BRIDGE: + return db.bridge.dev; + default: + return ERR_PTR(-EOPNOTSUPP); + } +} + +static u16 netc_vlan_unaware_pvid(struct dsa_bridge *bridge) +{ + u32 br_num; + + if (!bridge) + return NETC_STANDALONE_PVID; + + br_num = bridge->num; + + /* The br_num is supposed to be 1 ~ ds->max_num_bridges, see + * dsa_bridge_num_get(). Since max_num_bridges is non-zero, + * so dsa_port_bridge_create() will return an error if + * dsa_bridge_num_get() returns 0. + */ + if (WARN_ON(!br_num)) + return NETC_STANDALONE_PVID; + + return NETC_VLAN_UNAWARE_PVID(br_num); +} + +static int netc_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + struct net_device *br_ndev = netc_classify_db(db); + struct netc_port *np = NETC_PORT(ds, port); + + if (IS_ERR(br_ndev)) + return PTR_ERR(br_ndev); + + if (!vid) + vid = netc_vlan_unaware_pvid(br_ndev ? &db.bridge : NULL); + + return netc_port_set_fdb_entry(np, addr, vid); +} + +static int netc_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + struct net_device *br_ndev = netc_classify_db(db); + struct netc_port *np = NETC_PORT(ds, port); + + if (IS_ERR(br_ndev)) + return PTR_ERR(br_ndev); + + if (!vid) + vid = netc_vlan_unaware_pvid(br_ndev ? &db.bridge : NULL); + + return netc_port_del_fdb_entry(np, addr, vid); +} + +static int netc_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct netc_switch *priv = ds->priv; + u32 resume_eid = NTMP_NULL_ENTRY_ID; + struct fdbt_entry_data *entry; + struct fdbt_keye_data *keye; + struct fdbt_cfge_data *cfge; + u32 cfg, cnt = 0; + bool is_static; + int err; + u16 vid; + + entry = kmalloc_obj(*entry); + if (!entry) + return -ENOMEM; + + keye = &entry->keye; + cfge = &entry->cfge; + mutex_lock(&priv->fdbt_lock); + + do { + memset(entry, 0, sizeof(*entry)); + err = ntmp_fdbt_search_port_entry(&priv->ntmp, port, + &resume_eid, entry); + if (err || entry->entry_id == NTMP_NULL_ENTRY_ID) + break; + + cfg = le32_to_cpu(cfge->cfg); + is_static = (cfg & FDBT_DYNAMIC) ? false : true; + vid = le16_to_cpu(keye->fid); + if (vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges)) + vid = 0; + + err = cb(keye->mac_addr, vid, is_static, data); + if (err) + break; + + /* To prevent hardware malfunctions from causing an + * infinite loop. + */ + if (++cnt >= priv->htmcapr_num_words) + break; + } while (resume_eid != NTMP_NULL_ENTRY_ID); + + mutex_unlock(&priv->fdbt_lock); + kfree(entry); + + return err; +} + +static int netc_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db) +{ + return netc_port_fdb_add(ds, port, mdb->addr, mdb->vid, db); +} + +static int netc_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db) +{ + return netc_port_fdb_del(ds, port, mdb->addr, mdb->vid, db); +} + +static int netc_port_add_host_flood_rule(struct netc_port *np, + bool uc, bool mc) +{ + const u8 dmac_mask[ETH_ALEN] = {0x1, 0, 0, 0, 0, 0}; + struct netc_switch *priv = np->switch_priv; + struct ipft_entry_data *host_flood; + struct ipft_keye_data *keye; + struct ipft_cfge_data *cfge; + u16 src_port; + u32 cfg; + int err; + + if (!uc && !mc) { + /* Disable ingress port filter table lookup */ + netc_port_wr(np, NETC_PIPFCR, 0); + np->uc = false; + np->mc = false; + + return 0; + } + + host_flood = kzalloc_obj(*host_flood); + if (!host_flood) + return -ENOMEM; + + keye = &host_flood->keye; + cfge = &host_flood->cfge; + + src_port = FIELD_PREP(IPFT_SRC_PORT, np->dp->index); + src_port |= IPFT_SRC_PORT_MASK; + keye->src_port = cpu_to_le16(src_port); + + /* If either only unicast or only multicast need to be flooded + * to the host, we always set the mask that tests the first MAC + * DA octet. The value should be 0 for the first bit (if unicast + * has to be flooded) or 1 (if multicast). If both unicast and + * multicast have to be flooded, we leave the key mask empty, so + * it matches everything. + */ + if (uc && !mc) + ether_addr_copy(keye->dmac_mask, dmac_mask); + + if (!uc && mc) { + ether_addr_copy(keye->dmac, dmac_mask); + ether_addr_copy(keye->dmac_mask, dmac_mask); + } + + cfg = FIELD_PREP(IPFT_FLTFA, IPFT_FLTFA_REDIRECT); + cfg |= FIELD_PREP(IPFT_HR, NETC_HR_HOST_FLOOD); + cfge->cfg = cpu_to_le32(cfg); + + err = ntmp_ipft_add_entry(&priv->ntmp, host_flood); + if (err) { + kfree(host_flood); + return err; + } + + np->uc = uc; + np->mc = mc; + np->host_flood = host_flood; + /* Enable ingress port filter table lookup */ + netc_port_wr(np, NETC_PIPFCR, PIPFCR_EN); + + return 0; +} + +static void netc_port_remove_host_flood(struct netc_port *np, + struct ipft_entry_data *host_flood) +{ + struct netc_switch *priv = np->switch_priv; + bool disable_host_flood = false; + + if (!host_flood) + return; + + if (np->host_flood == host_flood) + disable_host_flood = true; + + ntmp_ipft_delete_entry(&priv->ntmp, host_flood->entry_id); + kfree(host_flood); + + if (disable_host_flood) { + np->host_flood = NULL; + np->uc = false; + np->mc = false; + netc_port_wr(np, NETC_PIPFCR, 0); + } +} + +static void netc_port_set_host_flood(struct dsa_switch *ds, int port, + bool uc, bool mc) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct ipft_entry_data *old_host_flood; + + /* Do not add host flood rule to ingress port filter table when + * the port has joined a bridge. Otherwise, the ingress frames + * will bypass FDB table lookup and MAC learning, so the frames + * will be redirected directly to the CPU port. + */ + if (dsa_port_bridge_dev_get(np->dp)) { + netc_port_remove_host_flood(np, np->host_flood); + + return; + } + + if (np->uc == uc && np->mc == mc) + return; + + /* IPFT does not support in-place updates to the KEYE element, + * we need to add a new entry and then delete the old one. So + * save the old entry first. + */ + old_host_flood = np->host_flood; + np->host_flood = NULL; + + if (netc_port_add_host_flood_rule(np, uc, mc)) { + np->host_flood = old_host_flood; + dev_err(ds->dev, "Failed to add host flood rule on port %d\n", + port); + return; + } + + /* Remove the old host flood entry */ + netc_port_remove_host_flood(np, old_host_flood); +} + +static int netc_single_vlan_aware_bridge(struct dsa_switch *ds, + struct netlink_ext_ack *extack) +{ + struct net_device *br_ndev = NULL; + struct dsa_port *dp; + + dsa_switch_for_each_available_port(dp, ds) { + struct net_device *port_br = dsa_port_bridge_dev_get(dp); + + if (!port_br || !br_vlan_enabled(port_br)) + continue; + + if (!br_ndev) { + br_ndev = port_br; + continue; + } + + if (br_ndev == port_br) + continue; + + NL_SET_ERR_MSG_MOD(extack, + "Only one VLAN-aware bridge is supported"); + + return -EBUSY; + } + + return 0; +} + +static int netc_port_vlan_filtering(struct dsa_switch *ds, + int port, bool vlan_aware, + struct netlink_ext_ack *extack) +{ + struct netc_port *np = NETC_PORT(ds, port); + u16 pvid; + int err; + + /* Before calling port_vlan_filtering(), br_vlan_filter_toggle() has + * already updated the BROPT_VLAN_ENABLED bit of br->options. So the + * VLAN filtering status of the switch ports can be checked by the + * br_vlan_enabled() function. + */ + err = netc_single_vlan_aware_bridge(ds, extack); + if (err) + return err; + + pvid = netc_vlan_unaware_pvid(np->dp->bridge); + if (pvid == NETC_STANDALONE_PVID) { + vlan_aware = false; + goto bpdvr_config; + } + + if (vlan_aware) { + /* The FDB entries associated with unaware_pvid do not need + * to be deleted, so that when switching from VLAN-aware to + * VLAN-unaware mode, these FDB entries do not need to be + * re-added. + */ + err = netc_port_del_vlan_entry(np, pvid); + if (err) + return err; + + pvid = np->pvid; + } else { + err = netc_port_set_vlan_entry(np, pvid, false); + if (err) + return err; + } + +bpdvr_config: + netc_port_set_vlan_aware(np, vlan_aware); + netc_port_set_pvid(np, pvid); + + return 0; +} + +static int netc_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct dsa_port *dp = np->dp; + bool untagged; + int err; + + /* The 8021q layer may attempt to change NETC_STANDALONE_PVID + * (VID 0), so we need to ignore it. + */ + if (vlan->vid == NETC_STANDALONE_PVID) + return 0; + + if (vlan->vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges)) { + NL_SET_ERR_MSG_FMT_MOD(extack, + "VID %d~4095 reserved for VLAN-unaware bridge", + NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges)); + return -EINVAL; + } + + untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); + err = netc_port_set_vlan_entry(np, vlan->vid, untagged); + if (err) + return err; + + if (vlan->flags & BRIDGE_VLAN_INFO_PVID) { + np->pvid = vlan->vid; + if (dsa_port_is_vlan_filtering(dp)) + netc_port_set_pvid(np, vlan->vid); + + return 0; + } + + if (np->pvid != vlan->vid) + return 0; + + /* Delete PVID */ + np->pvid = NETC_STANDALONE_PVID; + if (dsa_port_is_vlan_filtering(dp)) + netc_port_set_pvid(np, NETC_STANDALONE_PVID); + + return 0; +} + +static int netc_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct netc_port *np = NETC_PORT(ds, port); + int err; + + if (vlan->vid == NETC_STANDALONE_PVID) + return 0; + + if (vlan->vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges)) + return -EINVAL; + + err = netc_port_del_vlan_entry(np, vlan->vid); + if (err) + return err; + + if (np->pvid == vlan->vid) { + np->pvid = NETC_STANDALONE_PVID; + + /* Set the port PVID to NETC_STANDALONE_PVID if the VLAN-aware + * bridge port has no PVID. The untagged frames will not be + * forwarded to other user ports, as NETC_STANDALONE_PVID VLAN + * entry has disabled MAC learning and flooding, and other user + * ports do not have FDB entries with NETC_STANDALONE_PVID. + */ + if (dsa_port_is_vlan_filtering(np->dp)) + netc_port_set_pvid(np, NETC_STANDALONE_PVID); + } + + return 0; +} + +static int netc_port_bridge_join(struct dsa_switch *ds, int port, + struct dsa_bridge bridge, + bool *tx_fwd_offload, + struct netlink_ext_ack *extack) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct netc_switch *priv = ds->priv; + u16 vlan_unaware_pvid; + int err; + + if (!bridge.num) { + NL_SET_ERR_MSG_MOD(extack, "Bridge number 0 is unsupported"); + return -EINVAL; + } + + err = netc_single_vlan_aware_bridge(ds, extack); + if (err) + return err; + + netc_port_set_mlo(np, MLO_NOT_OVERRIDE); + + if (br_vlan_enabled(bridge.dev)) + goto out; + + vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num); + err = netc_port_set_vlan_entry(np, vlan_unaware_pvid, false); + if (err) + goto disable_mlo; + + netc_port_set_pvid(np, vlan_unaware_pvid); + +out: + netc_port_remove_host_flood(np, np->host_flood); + + if (atomic_inc_return(&priv->br_cnt) == 1) + schedule_delayed_work(&priv->fdbt_ageing_work, + READ_ONCE(priv->fdbt_ageing_delay)); + + return 0; + +disable_mlo: + netc_port_set_mlo(np, MLO_DISABLE); + + return err; +} + +static void netc_port_remove_dynamic_entries(struct netc_port *np) +{ + struct netc_switch *priv = np->switch_priv; + + /* Return if the port is not available */ + if (!np->dp) + return; + + mutex_lock(&priv->fdbt_lock); + ntmp_fdbt_delete_port_dynamic_entries(&priv->ntmp, np->dp->index); + mutex_unlock(&priv->fdbt_lock); +} + +static void netc_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev = np->dp->user; + struct netc_switch *priv = ds->priv; + u16 vlan_unaware_pvid; + bool mc, uc; + + netc_port_set_mlo(np, MLO_DISABLE); + netc_port_set_pvid(np, NETC_STANDALONE_PVID); + np->pvid = NETC_STANDALONE_PVID; + + if (atomic_dec_and_test(&priv->br_cnt)) + cancel_delayed_work_sync(&priv->fdbt_ageing_work); + + netc_port_remove_dynamic_entries(np); + uc = ndev->flags & IFF_PROMISC; + mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI); + + if (netc_port_add_host_flood_rule(np, uc, mc)) + dev_warn(ds->dev, + "Failed to restore host flood rule on port %d\n", + port); + + /* When a port leaves a VLAN-aware bridge, dsa_port_bridge_leave() + * follows the sequence below: + * + * 1. dsa_port_bridge_destroy() is called to set dp->bridge to NULL. + * 2. dsa_broadcast() is called, which eventually invokes + * ds->ops->port_bridge_leave() + * 3. dsa_port_switchdev_unsync_attrs() is called, which triggers + * dsa_port_reset_vlan_filtering() and ultimately calls + * ds->ops->port_vlan_filtering() to transition the port from + * VLAN-aware mode to VLAN-unaware mode. + * + * At step 3, since dp->bridge has already been set to NULL in step 1, + * netc_port_vlan_filtering() will detect this and skip the creation + * of an unaware PVID entry in the VLAN filter table. Therefore, it is + * safe to return directly here. + */ + if (br_vlan_enabled(bridge.dev)) + return; + + vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num); + /* There is no need to check the return value even if it fails. + * Because the PVID has been set to NETC_STANDALONE_PVID, the + * frames will not match this VLAN entry. + */ + netc_port_del_vlan_entry(np, vlan_unaware_pvid); +} + +static int netc_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) +{ + struct netc_switch *priv = ds->priv; + unsigned long delay_jiffies; + + /* The dynamic FDB entry is deleted when its activity counter reaches + * NETC_FDBT_AGEING_THRESH (100). Each delayed_work tick increments + * the counter by 1 if the entry is inactive. + * + * Therefore: + * msecs (ms) = NETC_FDBT_AGEING_THRESH * delay_ms (ms) + * delay_ms = msecs / NETC_FDBT_AGEING_THRESH + * delay_jiffies = (delay_ms / 1000) * HZ + * = (msecs * HZ) / (1000 * NETC_FDBT_AGEING_THRESH) + * + * Use DIV_ROUND_CLOSEST_ULL to perform a single nearest-jiffy + * rounding, avoiding the two-step rounding error of the intermediate + * delay_ms approach. + * Maximum error = +/-0.5 jiffy * 100 = +/-50000/HZ ms. + */ + delay_jiffies = DIV_ROUND_CLOSEST_ULL((u64)msecs * HZ, + 1000 * NETC_FDBT_AGEING_THRESH); + WRITE_ONCE(priv->fdbt_ageing_delay, delay_jiffies); + + if (atomic_read(&priv->br_cnt)) + mod_delayed_work(system_percpu_wq, &priv->fdbt_ageing_work, + READ_ONCE(priv->fdbt_ageing_delay)); + + return 0; +} + +static void netc_port_fast_age(struct dsa_switch *ds, int port) +{ + struct netc_port *np = NETC_PORT(ds, port); + + netc_port_remove_dynamic_entries(np); +} + +static void netc_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + struct netc_switch *priv = ds->priv; + + priv->info->phylink_get_caps(port, config); +} + +static void netc_port_set_mac_mode(struct netc_port *np, + unsigned int mode, + phy_interface_t phy_mode) +{ + u32 mask = PM_IF_MODE_IFMODE | PM_IF_MODE_REVMII; + u32 val = 0; + + switch (phy_mode) { + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + val |= IFMODE_RGMII; + break; + case PHY_INTERFACE_MODE_RMII: + val |= IFMODE_RMII; + break; + case PHY_INTERFACE_MODE_REVMII: + val |= PM_IF_MODE_REVMII; + fallthrough; + case PHY_INTERFACE_MODE_MII: + val |= IFMODE_MII; + break; + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_2500BASEX: + val |= IFMODE_SGMII; + break; + default: + break; + } + + netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val); +} + +static void netc_mac_config(struct phylink_config *config, unsigned int mode, + const struct phylink_link_state *state) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + + netc_port_set_mac_mode(NETC_PORT(dp->ds, dp->index), mode, + state->interface); +} + +static void netc_port_set_speed(struct netc_port *np, int speed) +{ + netc_port_rmw(np, NETC_PCR, PCR_PSPEED, PSPEED_SET_VAL(speed)); +} + +static void netc_port_set_rgmii_mac(struct netc_port *np, + int speed, int duplex) +{ + u32 mask, val; + + mask = PM_IF_MODE_SSP | PM_IF_MODE_HD | PM_IF_MODE_M10; + + switch (speed) { + default: + case SPEED_1000: + val = FIELD_PREP(PM_IF_MODE_SSP, SSP_1G); + break; + case SPEED_100: + val = FIELD_PREP(PM_IF_MODE_SSP, SSP_100M); + break; + case SPEED_10: + val = FIELD_PREP(PM_IF_MODE_SSP, SSP_10M); + break; + } + + if (duplex != DUPLEX_FULL) + val |= PM_IF_MODE_HD; + + netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val); +} + +static void netc_port_set_rmii_mii_mac(struct netc_port *np, + int speed, int duplex) +{ + u32 mask, val = 0; + + mask = PM_IF_MODE_SSP | PM_IF_MODE_HD | PM_IF_MODE_M10; + + if (speed == SPEED_10) + val |= PM_IF_MODE_M10; + + if (duplex != DUPLEX_FULL) + val |= PM_IF_MODE_HD; + + netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val); +} + +static void netc_port_set_tx_pause(struct netc_port *np, bool tx_pause) +{ + struct netc_switch *priv = np->switch_priv; + int port = np->dp->index; + int i, j, num_bp; + + num_bp = priv->num_bp / priv->info->num_ports; + for (i = 0, j = port * num_bp; i < num_bp; i++, j++) { + struct bpt_cfge_data *cfge = &priv->bpt_list[j]; + struct bpt_cfge_data old_cfge = *cfge; + + if (tx_pause) { + cfge->fc_on_thresh = cpu_to_le16(NETC_FC_THRESH_ON); + cfge->fc_off_thresh = cpu_to_le16(NETC_FC_THRESH_OFF); + cfge->fccfg_sbpen = FIELD_PREP(BPT_FC_CFG, + BPT_FC_CFG_EN_BPFC); + cfge->fc_ports = cpu_to_le32(BIT(port)); + } else { + cfge->fc_on_thresh = cpu_to_le16(0); + cfge->fc_off_thresh = cpu_to_le16(0); + cfge->fccfg_sbpen = 0; + cfge->fc_ports = cpu_to_le32(0); + } + + if (ntmp_bpt_update_entry(&priv->ntmp, j, cfge)) { + *cfge = old_cfge; + dev_warn(priv->dev, + "Failed to %s TX pause of buffer pool %d (swp%d)\n", + tx_pause ? "enable" : "disable", j, port); + } + } +} + +static void netc_port_set_rx_pause(struct netc_port *np, bool rx_pause) +{ + netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_PAUSE_IGN, + rx_pause ? 0 : PM_CMD_CFG_PAUSE_IGN); +} + +static void netc_port_mac_rx_enable(struct netc_port *np) +{ + netc_port_rmw(np, NETC_POR, POR_RXDIS, 0); + netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN, + PM_CMD_CFG_RX_EN); +} + +static void netc_port_wait_rx_empty(struct netc_port *np, int mac) +{ + u32 val; + + /* PM_IEVENT_RX_EMPTY is a read-only bit, it is automatically set by + * hardware if RX FIFO is empty and no RX packet receive in process. + * And it is automatically cleared if RX FIFO is not empty or RX + * packet receive in process. + */ + if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_RX_EMPTY, + 100, 10000, false, np, NETC_PM_IEVENT(mac))) + dev_warn(np->switch_priv->dev, + "swp%d MAC%d: RX is not idle\n", np->dp->index, mac); +} + +static void netc_port_mac_rx_graceful_stop(struct netc_port *np) +{ + u32 val; + + if (is_netc_pseudo_port(np)) + goto rx_disable; + + if (np->caps.pmac) { + netc_port_rmw(np, NETC_PM_CMD_CFG(1), PM_CMD_CFG_RX_EN, 0); + netc_port_wait_rx_empty(np, 1); + } + + netc_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN, 0); + netc_port_wait_rx_empty(np, 0); + + if (read_poll_timeout(netc_port_rd, val, !(val & PSR_RX_BUSY), + 100, 10000, false, np, NETC_PSR)) + dev_warn(np->switch_priv->dev, "swp%d RX is busy\n", + np->dp->index); + +rx_disable: + netc_port_rmw(np, NETC_POR, POR_RXDIS, POR_RXDIS); +} + +static void netc_port_mac_tx_enable(struct netc_port *np) +{ + netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_TX_EN, + PM_CMD_CFG_TX_EN); + netc_port_rmw(np, NETC_POR, POR_TXDIS, 0); +} + +static void netc_port_wait_tx_empty(struct netc_port *np, int mac) +{ + u32 val; + + /* PM_IEVENT_TX_EMPTY is a read-only bit, it is automatically set by + * hardware if TX FIFO is empty. And it is automatically cleared if + * TX FIFO is not empty. + */ + if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_TX_EMPTY, + 100, 10000, false, np, NETC_PM_IEVENT(mac))) + dev_warn(np->switch_priv->dev, + "swp%d MAC%d: TX FIFO is not empty\n", + np->dp->index, mac); +} + +static void netc_port_mac_tx_graceful_stop(struct netc_port *np) +{ + netc_port_rmw(np, NETC_POR, POR_TXDIS, POR_TXDIS); + + if (is_netc_pseudo_port(np)) + return; + + netc_port_wait_tx_empty(np, 0); + if (np->caps.pmac) + netc_port_wait_tx_empty(np, 1); + + netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_TX_EN, 0); +} + +static void netc_mac_link_up(struct phylink_config *config, + struct phy_device *phy, unsigned int mode, + phy_interface_t interface, int speed, + int duplex, bool tx_pause, bool rx_pause) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct netc_port *np; + + np = NETC_PORT(dp->ds, dp->index); + netc_port_set_speed(np, speed); + + if (phy_interface_mode_is_rgmii(interface)) + netc_port_set_rgmii_mac(np, speed, duplex); + + if (interface == PHY_INTERFACE_MODE_RMII || + interface == PHY_INTERFACE_MODE_REVMII || + interface == PHY_INTERFACE_MODE_MII) + netc_port_set_rmii_mii_mac(np, speed, duplex); + + netc_port_set_tx_pause(np, tx_pause); + netc_port_set_rx_pause(np, rx_pause); + netc_port_mac_tx_enable(np); + netc_port_mac_rx_enable(np); +} + +static void netc_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct netc_port *np; + + np = NETC_PORT(dp->ds, dp->index); + netc_port_mac_rx_graceful_stop(np); + netc_port_mac_tx_graceful_stop(np); + netc_port_remove_dynamic_entries(np); +} + +static const struct phylink_mac_ops netc_phylink_mac_ops = { + .mac_config = netc_mac_config, + .mac_link_up = netc_mac_link_up, + .mac_link_down = netc_mac_link_down, +}; + +static const struct dsa_switch_ops netc_switch_ops = { + .get_tag_protocol = netc_get_tag_protocol, + .setup = netc_setup, + .teardown = netc_teardown, + .phylink_get_caps = netc_phylink_get_caps, + .port_enable = netc_port_enable, + .port_disable = netc_port_disable, + .port_stp_state_set = netc_port_stp_state_set, + .port_change_mtu = netc_port_change_mtu, + .port_max_mtu = netc_port_max_mtu, + .port_fdb_add = netc_port_fdb_add, + .port_fdb_del = netc_port_fdb_del, + .port_fdb_dump = netc_port_fdb_dump, + .port_mdb_add = netc_port_mdb_add, + .port_mdb_del = netc_port_mdb_del, + .port_set_host_flood = netc_port_set_host_flood, + .port_vlan_filtering = netc_port_vlan_filtering, + .port_vlan_add = netc_port_vlan_add, + .port_vlan_del = netc_port_vlan_del, + .port_bridge_join = netc_port_bridge_join, + .port_bridge_leave = netc_port_bridge_leave, + .set_ageing_time = netc_set_ageing_time, + .port_fast_age = netc_port_fast_age, + .get_pause_stats = netc_port_get_pause_stats, + .get_rmon_stats = netc_port_get_rmon_stats, + .get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats, + .get_eth_mac_stats = netc_port_get_eth_mac_stats, + .get_sset_count = netc_port_get_sset_count, + .get_strings = netc_port_get_strings, + .get_ethtool_stats = netc_port_get_ethtool_stats, +}; + +static int netc_switch_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct device_node *node = dev_of_node(&pdev->dev); + struct device *dev = &pdev->dev; + struct netc_switch *priv; + struct dsa_switch *ds; + int err; + + if (!node) + return dev_err_probe(dev, -ENODEV, + "No DT bindings, skipping\n"); + + err = netc_switch_check_emdio_is_ready(dev); + if (err) + return err; + + err = netc_switch_pci_init(pdev); + if (err) + return err; + + priv = pci_get_drvdata(pdev); + netc_switch_get_ip_revision(priv); + + err = netc_switch_platform_probe(priv); + if (err) + return err; + + ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL); + if (!ds) + return -ENOMEM; + + ds->dev = dev; + ds->num_ports = priv->info->num_ports; + ds->num_tx_queues = NETC_TC_NUM; + ds->ops = &netc_switch_ops; + ds->phylink_mac_ops = &netc_phylink_mac_ops; + ds->fdb_isolation = true; + ds->max_num_bridges = priv->info->num_ports - 1; + ds->ageing_time_min = 1000; + ds->ageing_time_max = U32_MAX; + ds->priv = priv; + priv->ds = ds; + + err = dsa_register_switch(ds); + if (err) + return dev_err_probe(dev, err, + "Failed to register DSA switch\n"); + + return 0; +} + +static void netc_switch_remove(struct pci_dev *pdev) +{ + struct netc_switch *priv = pci_get_drvdata(pdev); + + if (!priv) + return; + + dsa_unregister_switch(priv->ds); +} + +static void netc_switch_shutdown(struct pci_dev *pdev) +{ + struct netc_switch *priv = pci_get_drvdata(pdev); + + if (!priv) + return; + + dsa_switch_shutdown(priv->ds); + pci_set_drvdata(pdev, NULL); +} + +static const struct pci_device_id netc_switch_ids[] = { + { PCI_DEVICE(NETC_SWITCH_VENDOR_ID, NETC_SWITCH_DEVICE_ID) }, + { } +}; +MODULE_DEVICE_TABLE(pci, netc_switch_ids); + +static struct pci_driver netc_switch_driver = { + .name = KBUILD_MODNAME, + .id_table = netc_switch_ids, + .probe = netc_switch_probe, + .remove = netc_switch_remove, + .shutdown = netc_switch_shutdown, +}; +module_pci_driver(netc_switch_driver); + +MODULE_DESCRIPTION("NXP NETC Switch driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c new file mode 100644 index 000000000..34aeb6fce --- /dev/null +++ b/drivers/net/dsa/netc/netc_platform.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * NXP NETC switch driver + * Copyright 2025-2026 NXP + */ + +#include "netc_switch.h" + +struct netc_switch_platform { + u16 revision; + const struct netc_switch_info *info; +}; + +static void imx94_switch_phylink_get_caps(int port, + struct phylink_config *config) +{ + config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_1000FD; + + switch (port) { + case 0 ... 1: + __set_bit(PHY_INTERFACE_MODE_SGMII, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + config->supported_interfaces); + config->mac_capabilities |= MAC_2500FD; + fallthrough; + case 2: + config->mac_capabilities |= MAC_10 | MAC_100; + __set_bit(PHY_INTERFACE_MODE_MII, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_RMII, + config->supported_interfaces); + /* Port 0 and 1 do not support REVMII */ + if (port == 2) + __set_bit(PHY_INTERFACE_MODE_REVMII, + config->supported_interfaces); + + phy_interface_set_rgmii(config->supported_interfaces); + break; + case 3: /* CPU port */ + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + config->mac_capabilities |= MAC_10FD | MAC_100FD | + MAC_2500FD; + break; + default: + break; + } +} + +static const struct netc_switch_info imx94_info = { + .num_ports = 4, + .phylink_get_caps = imx94_switch_phylink_get_caps, +}; + +static const struct netc_switch_platform netc_platforms[] = { + { .revision = NETC_SWITCH_REV_4_3, .info = &imx94_info, }, + { } +}; + +static const struct netc_switch_info * +netc_switch_get_info(struct netc_switch *priv) +{ + int i; + + /* Matching based on IP revision */ + for (i = 0; i < ARRAY_SIZE(netc_platforms); i++) { + if (priv->revision == netc_platforms[i].revision) + return netc_platforms[i].info; + } + + return NULL; +} + +int netc_switch_platform_probe(struct netc_switch *priv) +{ + const struct netc_switch_info *info = netc_switch_get_info(priv); + + if (!info) { + dev_err(priv->dev, "Cannot find switch platform info\n"); + return -EINVAL; + } + + priv->info = info; + + return 0; +} diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h new file mode 100644 index 000000000..305f2a92e --- /dev/null +++ b/drivers/net/dsa/netc/netc_switch.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* + * Copyright 2025-2026 NXP + */ + +#ifndef _NETC_SWITCH_H +#define _NETC_SWITCH_H + +#include <linux/dsa/tag_netc.h> +#include <linux/fsl/netc_global.h> +#include <linux/fsl/ntmp.h> +#include <linux/of_device.h> +#include <linux/of_net.h> +#include <linux/pci.h> + +#include "netc_switch_hw.h" + +#define NETC_REGS_BAR 0 +#define NETC_REGS_SIZE 0x80000 +#define NETC_MSIX_TBL_BAR 2 +#define NETC_REGS_PORT_BASE 0x4000 +/* register block size per port */ +#define NETC_REGS_PORT_SIZE 0x4000 +#define PORT_IOBASE(p) (NETC_REGS_PORT_SIZE * (p)) +#define NETC_REGS_GLOBAL_BASE 0x70000 + +#define NETC_SWITCH_REV_4_3 0x0403 + +#define NETC_TC_NUM 8 +#define NETC_CBDR_NUM 2 +#define NETC_IPV_NUM 8 + +#define NETC_MAX_FRAME_LEN 9600 + +#define NETC_STANDALONE_PVID 0 +#define NETC_VLAN_UNAWARE_PVID(br_id) (4096 - (br_id)) + +/* Threshold format: MANT (bits 11:4) * 2^EXP (bits 3:0) + * Unit: Memory words (average of 20 bytes each) + * NETC_BP_THRESH = 0x8c3, MANT = 0x8c, EXP = 3. Threshold: 1120 words + * NETC_FC_THRESH_ON = 0x733, MANT = 0x73, EXP = 3. Threshold: 920 words + * NETC_FC_THRESH_OFF = 0x263, MANT = 0x26, EXP = 3. Threshold: 304 words + */ +#define NETC_BP_THRESH 0x8c3 +#define NETC_FC_THRESH_ON 0x733 +#define NETC_FC_THRESH_OFF 0x263 + +/* PAUSE quanta: 0xFFFF = 65535 quanta (each quanta = 512 bit times) */ +#define NETC_PAUSE_QUANTA 0xFFFF +/* PAUSE refresh threshold: send refresh when timer reaches this value */ +#define NETC_PAUSE_THRESH 0x7FFF + +#define NETC_FDBT_AGEING_DELAY (3 * HZ) +#define NETC_FDBT_AGEING_THRESH 100 + +struct netc_switch; + +struct netc_switch_info { + u32 num_ports; + void (*phylink_get_caps)(int port, struct phylink_config *config); +}; + +struct netc_port_caps { + u32 half_duplex:1; /* indicates whether the port support half-duplex */ + u32 pmac:1; /* indicates whether the port has preemption MAC */ + u32 pseudo_link:1; +}; + +enum netc_host_reason { + /* Software defined host reasons */ + NETC_HR_HOST_FLOOD = 8, +}; + +struct netc_port { + void __iomem *iobase; + struct netc_switch *switch_priv; + struct netc_port_caps caps; + struct dsa_port *dp; + struct clk *ref_clk; /* RGMII/RMII reference clock */ + struct mii_bus *emdio; + int ett_offset; + + u16 enable:1; + u16 uc:1; + u16 mc:1; + u16 pvid; + struct ipft_entry_data *host_flood; +}; + +struct netc_switch_regs { + void __iomem *base; + void __iomem *port; + void __iomem *global; +}; + +struct netc_fdb_entry { + u32 entry_id; + struct fdbt_cfge_data cfge; + struct fdbt_keye_data keye; + struct hlist_node node; +}; + +struct netc_vlan_entry { + u16 vid; + u32 ect_gid; + u32 untagged_port_bitmap; + struct vft_cfge_data cfge; + struct hlist_node node; +}; + +struct netc_port_stat { + int reg; + char name[ETH_GSTRING_LEN] __nonstring; +}; + +struct netc_switch { + struct pci_dev *pdev; + struct device *dev; + struct dsa_switch *ds; + u16 revision; + + const struct netc_switch_info *info; + struct netc_switch_regs regs; + struct netc_port **ports; + u32 port_bitmap; /* bitmap of available ports */ + + struct ntmp_user ntmp; + struct hlist_head fdb_list; + struct mutex fdbt_lock; /* FDB table lock */ + struct delayed_work fdbt_ageing_work; + /* (fdbt_ageing_delay * NETC_FDBT_AGEING_THRESH) is ageing time */ + unsigned long fdbt_ageing_delay; + atomic_t br_cnt; + struct hlist_head vlan_list; + struct mutex vft_lock; /* VLAN filter table lock */ + + /* Switch hardware capabilities */ + u32 htmcapr_num_words; + u32 num_bp; + + struct bpt_cfge_data *bpt_list; +}; + +#define NETC_PRIV(ds) ((struct netc_switch *)((ds)->priv)) +#define NETC_PORT(ds, port_id) (NETC_PRIV(ds)->ports[(port_id)]) + +/* Write/Read Switch base registers */ +#define netc_base_rd(r, o) netc_read((r)->base + (o)) +#define netc_base_wr(r, o, v) netc_write((r)->base + (o), v) + +/* Write/Read registers of Switch Port (including pseudo MAC port) */ +#define netc_port_rd(p, o) netc_read((p)->iobase + (o)) +#define netc_port_rd64(p, o) netc_read64((p)->iobase + (o)) +#define netc_port_wr(p, o, v) netc_write((p)->iobase + (o), v) + +/* Write/Read Switch global registers */ +#define netc_glb_rd(r, o) netc_read((r)->global + (o)) +#define netc_glb_wr(r, o, v) netc_write((r)->global + (o), v) + +static inline bool is_netc_pseudo_port(struct netc_port *np) +{ + return np->caps.pseudo_link; +} + +static inline void netc_add_fdb_entry(struct netc_switch *priv, + struct netc_fdb_entry *entry) +{ + hlist_add_head(&entry->node, &priv->fdb_list); +} + +static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry) +{ + hlist_del(&entry->node); + kfree(entry); +} + +static inline void netc_add_vlan_entry(struct netc_switch *priv, + struct netc_vlan_entry *entry) +{ + hlist_add_head(&entry->node, &priv->vlan_list); +} + +static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry) +{ + hlist_del(&entry->node); + kfree(entry); +} + +int netc_switch_platform_probe(struct netc_switch *priv); + +/* ethtool APIs */ +void netc_port_get_pause_stats(struct dsa_switch *ds, int port, + struct ethtool_pause_stats *pause_stats); +void netc_port_get_rmon_stats(struct dsa_switch *ds, int port, + struct ethtool_rmon_stats *rmon_stats, + const struct ethtool_rmon_hist_range **ranges); +void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port, + struct ethtool_eth_ctrl_stats *ctrl_stats); +void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port, + struct ethtool_eth_mac_stats *mac_stats); +int netc_port_get_sset_count(struct dsa_switch *ds, int port, int sset); +void netc_port_get_strings(struct dsa_switch *ds, int port, + u32 sset, u8 *data); +void netc_port_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data); + +#endif diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h new file mode 100644 index 000000000..1404ae41c --- /dev/null +++ b/drivers/net/dsa/netc/netc_switch_hw.h @@ -0,0 +1,372 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* + * Copyright 2025-2026 NXP + */ + +#ifndef _NETC_SWITCH_HW_H +#define _NETC_SWITCH_HW_H + +#include <linux/bitops.h> + +#define NETC_SWITCH_VENDOR_ID 0x1131 +#define NETC_SWITCH_DEVICE_ID 0xeef2 + +/* Definition of Switch base registers */ +#define NETC_BPCAPR 0x0008 +#define BPCAPR_NUM_BP GENMASK(7, 0) + +#define NETC_PBPMCR0 0x0400 +#define NETC_PBPMCR1 0x0404 + +#define NETC_CBDRMR(a) (0x0800 + (a) * 0x30) +#define NETC_CBDRBAR0(a) (0x0810 + (a) * 0x30) +#define NETC_CBDRBAR1(a) (0x0814 + (a) * 0x30) +#define NETC_CBDRPIR(a) (0x0818 + (a) * 0x30) +#define NETC_CBDRCIR(a) (0x081c + (a) * 0x30) +#define NETC_CBDRLENR(a) (0x0820 + (a) * 0x30) + +#define NETC_SWCR 0x1018 +#define SWCR_SWID GENMASK(2, 0) + +#define NETC_DOSL2CR 0x1220 +#define DOSL2CR_SAMEADDR BIT(0) +#define DOSL2CR_MSAMCC BIT(1) + +#define NETC_DOSL3CR 0x1224 +#define DOSL3CR_SAMEADDR BIT(0) +#define DOSL3CR_IPSAMCC BIT(1) + +#define NETC_ETTCAPR 0x18c4 +#define NETC_ECTCAPR 0x18ec +/* Index table NUM_ENTRIES mask */ +#define NETC_NUM_ENTRIES GENMASK(15, 0) +#define NETC_GET_NUM_ENTRIES(v) FIELD_GET(NETC_NUM_ENTRIES, (v)) + +/* Hash table memory capability register, the memory is shared by + * the following tables: + * + * - Ingress Stream Identification table + * - Ingress Stream Filter table + * - VLAN Filter table + * - FDB table + * - L2 IPv4 Multicast Filter table + * + * Each hash table entry is one word in size. + */ +#define NETC_HTMCAPR 0x1900 +#define HTMCAPR_NUM_WORDS GENMASK(15, 0) + +#define NETC_VFHTDECR1 0x2014 +#define NETC_VFHTDECR2 0x2018 +#define VFHTDECR2_ET_PORT(a) BIT((a)) +#define VFHTDECR2_MLO GENMASK(26, 24) +#define VFHTDECR2_MFO GENMASK(28, 27) + +/* Definition of Switch port registers */ +#define NETC_PCAPR 0x0000 +#define PCAPR_LINK_TYPE BIT(4) +#define PCAPR_NUM_TC GENMASK(15, 12) +#define PCAPR_NUM_Q GENMASK(19, 16) +#define PCAPR_NUM_CG GENMASK(27, 24) +#define PCAPR_TGS BIT(28) +#define PCAPR_CBS BIT(29) + +#define NETC_PMCAPR 0x0004 +#define PMCAPR_HD BIT(8) +#define PMCAPR_FP GENMASK(10, 9) +#define FP_SUPPORT 2 + +#define NETC_PCR 0x0010 +#define PCR_HDR_FMT BIT(0) +#define PCR_NS_TAG_PORT BIT(3) +#define PCR_L2DOSE BIT(4) +#define PCR_L3DOSE BIT(5) +#define PCR_TIMER_CS BIT(8) +#define PCR_PSPEED GENMASK(29, 16) +#define PSPEED_SET_VAL(s) FIELD_PREP(PCR_PSPEED, ((s) / 10 - 1)) + +#define NETC_PQOSMR 0x0054 +#define PQOSMR_VS BIT(0) +#define PQOSMR_VE BIT(1) +#define PQOSMR_DDR GENMASK(3, 2) +#define PQOSMR_DIPV GENMASK(6, 4) +#define PQOSMR_VQMP GENMASK(19, 16) +#define PQOSMR_QVMP GENMASK(23, 20) + +#define NETC_PIPFCR 0x0084 +#define PIPFCR_EN BIT(0) + +#define NETC_POR 0x100 +#define POR_TXDIS BIT(0) +#define POR_RXDIS BIT(1) + +#define NETC_PSR 0x104 +#define PSR_TX_BUSY BIT(0) +#define PSR_RX_BUSY BIT(1) + +#define NETC_PTGSLACR 0x130 + +#define NETC_PRXDCR 0x1c0 +#define NETC_PRXDCRRR 0x1c4 +#define NETC_PRXDCRR0 0x1c8 +#define NETC_PRXDCRR1 0x1cc +#define NETC_PTXDCR 0x1e0 + +#define NETC_PTCTMSDUR(a) (0x208 + (a) * 0x20) +#define PTCTMSDUR_MAXSDU GENMASK(15, 0) +#define PTCTMSDUR_SDU_TYPE GENMASK(17, 16) +#define SDU_TYPE_PPDU 0 +#define SDU_TYPE_MPDU 1 +#define SDU_TYPE_MSDU 2 + +#define NETC_PSDFTCR 0x4c4 +#define NETC_PSDFDDCR 0x4c8 + +#define NETC_BPCR 0x500 +#define BPCR_DYN_LIMIT GENMASK(15, 0) +#define BPCR_MLO GENMASK(22, 20) +#define BPCR_UUCASTE BIT(24) +#define BPCR_UMCASTE BIT(25) +#define BPCR_MCASTE BIT(26) +#define BPCR_BCASTE BIT(27) +#define BPCR_STAMVD BIT(28) +#define BPCR_SRCPRND BIT(29) + +/* MAC learning options, see BPCR[MLO], VFHTDECR2[MLO] and + * VLAN Filter Table CFGE_DATA[MLO] + */ +enum netc_mlo { + MLO_NOT_OVERRIDE = 0, + MLO_DISABLE, + MLO_HW, + MLO_SW_SEC, + MLO_SW_UNSEC, + MLO_DISABLE_SMAC, +}; + +/* MAC forwarding options, see VFHTDECR2[MFO] and VLAN + * Filter Table CFGE_DATA[MFO] + */ +enum netc_mfo { + MFO_NO_FDB_LOOKUP = 1, + MFO_NO_MATCH_FLOOD, + MFO_NO_MATCH_DISCARD, +}; + +#define NETC_BPDVR 0x510 +#define BPDVR_VID GENMASK(11, 0) +#define BPDVR_DEI BIT(12) +#define BPDVR_PCP GENMASK(15, 13) +#define BPDVR_TPID BIT(16) +#define BPDVR_RXTAGA GENMASK(23, 20) +#define BPDVR_RXVAM BIT(24) +#define BPDVR_TXTAGA GENMASK(26, 25) + +#define NETC_BPSTGSR 0x520 + +enum netc_stg_stage { + NETC_STG_STATE_DISABLED = 0, + NETC_STG_STATE_LEARNING, + NETC_STG_STATE_FORWARDING, +}; + +#define NETC_BPDCR 0x580 + +/* Definition of Switch ethernet MAC port registers */ +#define NETC_PMAC_OFFSET 0x400 +#define NETC_PM_CMD_CFG(a) (0x1008 + (a) * 0x400) +#define PM_CMD_CFG_TX_EN BIT(0) +#define PM_CMD_CFG_RX_EN BIT(1) +#define PM_CMD_CFG_PAUSE_IGN BIT(8) + +#define NETC_PM_MAXFRM(a) (0x1014 + (a) * 0x400) +#define PM_MAXFRAM GENMASK(15, 0) + +#define NETC_PM_IEVENT(a) (0x1040 + (a) * 0x400) +#define PM_IEVENT_TX_EMPTY BIT(5) +#define PM_IEVENT_RX_EMPTY BIT(6) + +#define NETC_PM_PAUSE_QUANTA(a) (0x1054 + (a) * 0x400) +#define NETC_PM_PAUSE_THRESH(a) (0x1064 + (a) * 0x400) + +#define NETC_PM_IF_MODE(a) (0x1300 + (a) * 0x400) +#define PM_IF_MODE_IFMODE GENMASK(2, 0) +#define IFMODE_MII 1 +#define IFMODE_RMII 3 +#define IFMODE_RGMII 4 +#define IFMODE_SGMII 5 +#define PM_IF_MODE_REVMII BIT(3) +#define PM_IF_MODE_M10 BIT(4) +#define PM_IF_MODE_HD BIT(6) +#define PM_IF_MODE_SSP GENMASK(14, 13) +#define SSP_100M 0 +#define SSP_10M 1 +#define SSP_1G 2 + +/* Port MAC 0/1 Receive Ethernet Octets Counter */ +#define NETC_PM_REOCT(a) (0x1100 + (a) * 0x400) + +/* Port MAC 0/1 Receive Octets Counter */ +#define NETC_PM_ROCT(a) (0x1108 + (a) * 0x400) + +/* Port MAC 0/1 Receive Alignment Error Counter Register */ +#define NETC_PM_RALN(a) (0x1110 + (a) * 0x400) + +/* Port MAC 0/1 Receive Valid Pause Frame Counter */ +#define NETC_PM_RXPF(a) (0x1118 + (a) * 0x400) + +/* Port MAC 0/1 Receive Frame Counter */ +#define NETC_PM_RFRM(a) (0x1120 + (a) * 0x400) + +/* Port MAC 0/1 Receive Frame Check Sequence Error Counter */ +#define NETC_PM_RFCS(a) (0x1128 + (a) * 0x400) + +/* Port MAC 0/1 Receive VLAN Frame Counter */ +#define NETC_PM_RVLAN(a) (0x1130 + (a) * 0x400) + +/* Port MAC 0/1 Receive Frame Error Counter */ +#define NETC_PM_RERR(a) (0x1138 + (a) * 0x400) + +/* Port MAC 0/1 Receive Unicast Frame Counter */ +#define NETC_PM_RUCA(a) (0x1140 + (a) * 0x400) + +/* Port MAC 0/1 Receive Multicast Frame Counter */ +#define NETC_PM_RMCA(a) (0x1148 + (a) * 0x400) + +/* Port MAC 0/1 Receive Broadcast Frame Counter */ +#define NETC_PM_RBCA(a) (0x1150 + (a) * 0x400) + +/* Port MAC 0/1 Receive Dropped Packets Counter */ +#define NETC_PM_RDRP(a) (0x1158 + (a) * 0x400) + +/* Port MAC 0/1 Receive Packets Counter */ +#define NETC_PM_RPKT(a) (0x1160 + (a) * 0x400) + +/* Port MAC 0/1 Receive Undersized Packet Counter */ +#define NETC_PM_RUND(a) (0x1168 + (a) * 0x400) + +/* Port MAC 0/1 Receive 64-Octet Packet Counter */ +#define NETC_PM_R64(a) (0x1170 + (a) * 0x400) + +/* Port MAC 0/1 Receive 65 to 127-Octet Packet Counter */ +#define NETC_PM_R127(a) (0x1178 + (a) * 0x400) + +/* Port MAC 0/1 Receive 128 to 255-Octet Packet Counter */ +#define NETC_PM_R255(a) (0x1180 + (a) * 0x400) + +/* Port MAC 0/1 Receive 256 to 511-Octet Packet Counter */ +#define NETC_PM_R511(a) (0x1188 + (a) * 0x400) + +/* Port MAC 0/1 Receive 512 to 1023-Octet Packet Counter */ +#define NETC_PM_R1023(a) (0x1190 + (a) * 0x400) + +/* Port MAC 0/1 Receive 1024 to 1522-Octet Packet Counter */ +#define NETC_PM_R1522(a) (0x1198 + (a) * 0x400) + +/* Port MAC 0/1 Receive 1523 to Max-Octet Packet Counter */ +#define NETC_PM_R1523X(a) (0x11a0 + (a) * 0x400) + +/* Port MAC 0/1 Receive Oversized Packet Counter */ +#define NETC_PM_ROVR(a) (0x11a8 + (a) * 0x400) + +/* Port MAC 0/1 Receive Jabber Packet Counter */ +#define NETC_PM_RJBR(a) (0x11b0 + (a) * 0x400) + +/* Port MAC 0/1 Receive Fragment Packet Counter */ +#define NETC_PM_RFRG(a) (0x11b8 + (a) * 0x400) + +/* Port MAC 0/1 Receive Control Packet Counter */ +#define NETC_PM_RCNP(a) (0x11c0 + (a) * 0x400) + +/* Port MAC 0/1 Receive Dropped Not Truncated Packets Counter */ +#define NETC_PM_RDRNTP(a) (0x11c8 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Ethernet Octets Counter */ +#define NETC_PM_TEOCT(a) (0x1200 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Octets Counter */ +#define NETC_PM_TOCT(a) (0x1208 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Excessive Deferral Packet Counter */ +#define NETC_PM_TEDFR(a) (0x1210 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Valid Pause Frame Counter */ +#define NETC_PM_TXPF(a) (0x1218 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Frame Counter */ +#define NETC_PM_TFRM(a) (0x1220 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Frame Check Sequence Error Counter */ +#define NETC_PM_TFCS(a) (0x1228 + (a) * 0x400) + +/* Port MAC 0/1 Transmit VLAN Frame Counter */ +#define NETC_PM_TVLAN(a) (0x1230 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Frame Error Counter */ +#define NETC_PM_TERR(a) (0x1238 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Unicast Frame Counter */ +#define NETC_PM_TUCA(a) (0x1240 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Multicast Frame Counter */ +#define NETC_PM_TMCA(a) (0x1248 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Broadcast Frame Counter */ +#define NETC_PM_TBCA(a) (0x1250 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Packets Counter */ +#define NETC_PM_TPKT(a) (0x1260 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Undersized Packet Counter */ +#define NETC_PM_TUND(a) (0x1268 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 64-Octet Packet Counter */ +#define NETC_PM_T64(a) (0x1270 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 65 to 127-Octet Packet Counter */ +#define NETC_PM_T127(a) (0x1278 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 128 to 255-Octet Packet Counter */ +#define NETC_PM_T255(a) (0x1280 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 256 to 511-Octet Packet Counter */ +#define NETC_PM_T511(a) (0x1288 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 512 to 1023-Octet Packet Counter */ +#define NETC_PM_T1023(a) (0x1290 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 1024 to 1522-Octet Packet Counter */ +#define NETC_PM_T1522(a) (0x1298 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 1523 to TX_MTU-Octet Packet Counter */ +#define NETC_PM_T1523X(a) (0x12a0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Control Packet Counter */ +#define NETC_PM_TCNP(a) (0x12c0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Deferred Packet Counter */ +#define NETC_PM_TDFR(a) (0x12d0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Multiple Collisions Counter */ +#define NETC_PM_TMCOL(a) (0x12d8 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Single Collision */ +#define NETC_PM_TSCOL(a) (0x12e0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Late Collision Counter */ +#define NETC_PM_TLCOL(a) (0x12e8 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Excessive Collisions Counter */ +#define NETC_PM_TECOL(a) (0x12f0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Invalid Octets Counter */ +#define NETC_PM_TIOCT(a) (0x12f8 + (a) * 0x400) + +#define NETC_PEMDIOCR 0x1c00 +#define NETC_EMDIO_BASE NETC_PEMDIOCR + +/* Definition of global registers (read only) */ +#define NETC_IPBRR0 0x0bf8 +#define IPBRR0_IP_REV GENMASK(15, 0) + +#endif |
