| // 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"); |