mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
The NETC switch does not age out dynamic FDB entries automatically. Without software management, stale entries persist after topology changes and cause incorrect forwarding. Add a delayed work that periodically removes entries that have not been refreshed within the specified cycles. The effective ageing time is: ageing_time = fdbt_ageing_delay * 100 Default values are 3s interval and 100 cycles (300s total), matching the IEEE 802.1Q default ageing time. The work starts when the first port joins a bridge (tracked via br_cnt) and is cancelled when the last port leaves. All FDB operations are serialized under fdbt_lock. Implement .set_ageing_time() to allow the bridge layer to reconfigure ageing parameters on demand. Signed-off-by: Wei Fang <wei.fang@nxp.com> Link: https://patch.msgid.link/20260611021458.2629145-10-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2525 lines
63 KiB
C
2525 lines
63 KiB
C
// 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");
|