diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c index fa7dd307ce13..77077352c1a5 100644 --- a/drivers/net/dsa/netc/netc_main.c +++ b/drivers/net/dsa/netc/netc_main.c @@ -37,6 +37,27 @@ static void netc_destroy_fdb_list(struct netc_switch *priv) 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) @@ -222,6 +243,7 @@ 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, @@ -251,6 +273,8 @@ static int netc_init_all_ports(struct netc_switch *priv) 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) @@ -323,16 +347,123 @@ static void netc_remove_all_cbdrs(struct netc_switch *priv) 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); - return netc_init_all_cbdrs(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) @@ -397,6 +528,17 @@ 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 */ @@ -422,7 +564,7 @@ static void netc_port_default_config(struct netc_port *np) netc_port_fixed_config(np); /* Default VLAN unaware */ - netc_port_rmw(np, NETC_BPDVR, BPDVR_RXVAM, BPDVR_RXVAM); + netc_port_set_vlan_aware(np, false); if (dsa_port_is_cpu(np->dp)) /* For CPU port, source port pruning is disabled */ @@ -583,10 +725,16 @@ static int netc_port_del_fdb_entry(struct netc_port *np, entry = netc_lookup_fdb_entry(priv, addr, vid); if (unlikely(!entry)) - /* Currently only single port mode is supported, MAC learning - * is disabled, so there is no dynamically learned FDB entry. - * We need to support deleting dynamically FDB entry when the - * bridge mode is supported. + /* 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; @@ -743,6 +891,12 @@ static int netc_setup(struct dsa_switch *ds) 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); @@ -770,6 +924,7 @@ static int netc_setup(struct dsa_switch *ds) * hardware state. */ mutex_destroy(&priv->fdbt_lock); + mutex_destroy(&priv->vft_lock); netc_free_ntmp_user(priv); return err; @@ -779,6 +934,8 @@ 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) @@ -802,6 +959,7 @@ 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); @@ -937,6 +1095,412 @@ static void netc_switch_get_ip_revision(struct netc_switch *priv) 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) { @@ -1014,17 +1578,50 @@ 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); - /* Currently, only support standalone port mode, so only - * NETC_STANDALONE_PVID (= 0) is supported here. - */ - if (vid != NETC_STANDALONE_PVID) - return -EOPNOTSUPP; + 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); } @@ -1033,10 +1630,14 @@ 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 (vid != NETC_STANDALONE_PVID) - return -EOPNOTSUPP; + 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); } @@ -1072,6 +1673,8 @@ static int netc_port_fdb_dump(struct dsa_switch *ds, int port, 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) @@ -1174,12 +1777,23 @@ 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, @@ -1188,6 +1802,17 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port, 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; @@ -1209,6 +1834,309 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port, 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) { @@ -1463,6 +2391,7 @@ static void netc_mac_link_down(struct phylink_config *config, 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 = { @@ -1487,6 +2416,13 @@ static const struct dsa_switch_ops netc_switch_ops = { .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, @@ -1534,6 +2470,9 @@ static int netc_switch_probe(struct pci_dev *pdev, 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; diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h index 4fbd12825b67..305f2a92e2f9 100644 --- a/drivers/net/dsa/netc/netc_switch.h +++ b/drivers/net/dsa/netc/netc_switch.h @@ -33,6 +33,7 @@ #define NETC_MAX_FRAME_LEN 9600 #define NETC_STANDALONE_PVID 0 +#define NETC_VLAN_UNAWARE_PVID(br_id) (4096 - (br_id)) /* Threshold format: MANT (bits 11:4) * 2^EXP (bits 3:0) * Unit: Memory words (average of 20 bytes each) @@ -49,6 +50,9 @@ /* PAUSE refresh threshold: send refresh when timer reaches this value */ #define NETC_PAUSE_THRESH 0x7FFF +#define NETC_FDBT_AGEING_DELAY (3 * HZ) +#define NETC_FDBT_AGEING_THRESH 100 + struct netc_switch; struct netc_switch_info { @@ -74,10 +78,12 @@ struct netc_port { struct dsa_port *dp; struct clk *ref_clk; /* RGMII/RMII reference clock */ struct mii_bus *emdio; + int ett_offset; u16 enable:1; u16 uc:1; u16 mc:1; + u16 pvid; struct ipft_entry_data *host_flood; }; @@ -94,6 +100,14 @@ struct netc_fdb_entry { struct hlist_node node; }; +struct netc_vlan_entry { + u16 vid; + u32 ect_gid; + u32 untagged_port_bitmap; + struct vft_cfge_data cfge; + struct hlist_node node; +}; + struct netc_port_stat { int reg; char name[ETH_GSTRING_LEN] __nonstring; @@ -108,10 +122,17 @@ struct netc_switch { const struct netc_switch_info *info; struct netc_switch_regs regs; struct netc_port **ports; + u32 port_bitmap; /* bitmap of available ports */ struct ntmp_user ntmp; struct hlist_head fdb_list; struct mutex fdbt_lock; /* FDB table lock */ + struct delayed_work fdbt_ageing_work; + /* (fdbt_ageing_delay * NETC_FDBT_AGEING_THRESH) is ageing time */ + unsigned long fdbt_ageing_delay; + atomic_t br_cnt; + struct hlist_head vlan_list; + struct mutex vft_lock; /* VLAN filter table lock */ /* Switch hardware capabilities */ u32 htmcapr_num_words; @@ -153,6 +174,18 @@ static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry) kfree(entry); } +static inline void netc_add_vlan_entry(struct netc_switch *priv, + struct netc_vlan_entry *entry) +{ + hlist_add_head(&entry->node, &priv->vlan_list); +} + +static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry) +{ + hlist_del(&entry->node); + kfree(entry); +} + int netc_switch_platform_probe(struct netc_switch *priv); /* ethtool APIs */ diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h index 1d976882a6cc..1404ae41c7bc 100644 --- a/drivers/net/dsa/netc/netc_switch_hw.h +++ b/drivers/net/dsa/netc/netc_switch_hw.h @@ -36,6 +36,12 @@ #define DOSL3CR_SAMEADDR BIT(0) #define DOSL3CR_IPSAMCC BIT(1) +#define NETC_ETTCAPR 0x18c4 +#define NETC_ECTCAPR 0x18ec +/* Index table NUM_ENTRIES mask */ +#define NETC_NUM_ENTRIES GENMASK(15, 0) +#define NETC_GET_NUM_ENTRIES(v) FIELD_GET(NETC_NUM_ENTRIES, (v)) + /* Hash table memory capability register, the memory is shared by * the following tables: * diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index f71cad943424..0d83f0c9dc6e 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -24,6 +24,8 @@ #define NTMP_IPFT_ID 13 #define NTMP_FDBT_ID 15 #define NTMP_VFT_ID 18 +#define NTMP_ETT_ID 33 +#define NTMP_ECT_ID 39 #define NTMP_BPT_ID 41 /* Generic Update Actions for most tables */ @@ -31,6 +33,8 @@ #define NTMP_GEN_UA_STSEU BIT(1) /* Specific Update Actions for some tables */ +#define FDBT_UA_ACTEU BIT(1) +#define ECT_UA_STSEU BIT(0) #define BPT_UA_BPSEU BIT(1) /* Query Action: 0: Full query. 1: Query entry ID, the fields after entry @@ -43,6 +47,30 @@ #define RSST_STSE_DATA_SIZE(n) ((n) * 8) #define RSST_CFGE_DATA_SIZE(n) (n) +u32 ntmp_lookup_free_eid(unsigned long *bitmap, u32 size) +{ + u32 entry_id; + + entry_id = find_first_zero_bit(bitmap, size); + if (entry_id == size) + return NTMP_NULL_ENTRY_ID; + + /* Set the bit once we found it */ + __set_bit(entry_id, bitmap); + + return entry_id; +} +EXPORT_SYMBOL_GPL(ntmp_lookup_free_eid); + +void ntmp_clear_eid_bitmap(unsigned long *bitmap, u32 entry_id) +{ + if (entry_id == NTMP_NULL_ENTRY_ID) + return; + + __clear_bit(entry_id, bitmap); +} +EXPORT_SYMBOL_GPL(ntmp_clear_eid_bitmap); + int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs) { @@ -283,6 +311,10 @@ static const char *ntmp_table_name(int tbl_id) return "FDB Table"; case NTMP_VFT_ID: return "VLAN Filter Table"; + case NTMP_ETT_ID: + return "Egress Treatment Table"; + case NTMP_ECT_ID: + return "Egress Count Table"; case NTMP_BPT_ID: return "Buffer Pool Table"; default: @@ -794,18 +826,26 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port, EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry); /** - * ntmp_vft_add_entry - add an entry into the VLAN filter table + * ntmp_fdbt_update_activity_element - update the activity element of all + * the dynamic entries in the FDB table. * @user: target ntmp_user struct - * @vid: VLAN ID - * @cfge: configuration element data + * + * A single activity update management could be used to process all the + * dynamic entries in the FDB table. When hardware process an activity + * update management command for an entry in the FDB table and the entry + * does not have its activity flag set, the activity counter is incremented. + * However, if the activity flag is set, then both the activity flag and + * activity counter are reset. Software can issue the activity update + * management commands at predefined times and the value of the activity + * counter can then be used to estimate the period of how long an FDB + * entry has been inactive. * * Return: 0 on success, otherwise a negative error code */ -int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, - const struct vft_cfge_data *cfge) +int ntmp_fdbt_update_activity_element(struct ntmp_user *user) { + struct fdbt_req_ua *req; struct netc_swcbd swcbd; - struct vft_req_ua *req; struct netc_cbdr *cbdr; union netc_cbd cbd; u32 len; @@ -817,28 +857,414 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, return err; /* Request data */ - ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, - NTMP_GEN_UA_CFGEU); + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, FDBT_UA_ACTEU); + req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC); + req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC; + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + /* For activity update, the access method must be search */ + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_UPDATE, NTMP_AM_SEARCH); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to update activity of %s, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_update_activity_element); + +/** + * ntmp_fdbt_delete_ageing_entries - delete all the ageing dynamic entries + * in the FDB table + * @user: target ntmp_user struct + * @act_cnt: the target value of the activity counter + * + * The matching rule is that the activity flag is not set and the activity + * counter is greater than or equal to act_cnt + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_delete_ageing_entries(struct ntmp_user *user, u8 act_cnt) +{ + struct fdbt_req_qd *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + if (act_cnt > FDBT_ACT_CNT) + return -EINVAL; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0); + req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC); + req->ak.search.acte = act_cnt; + /* Exact match with ACTE_DATA[ACT_FLAG] AND + * match >= ACTE_DATA[ACT_CNT] + */ + req->ak.search.acte_mc = FDBT_ACTE_MC; + req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC; + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_DELETE, NTMP_AM_SEARCH); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to delete ageing entries of %s, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_ageing_entries); + +/** + * ntmp_fdbt_delete_port_dynamic_entries - delete all dynamic FDB entries + * associated with the specified switch port + * @user: target ntmp_user struct + * @port: the specified switch port ID + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port) +{ + struct fdbt_req_qd *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0); + req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + req->ak.search.cfge.port_bitmap = cpu_to_le32(BIT(port)); + req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC); + /* Match CFGE_DATA[DYNAMIC & PORT_BITMAP] field */ + req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP; + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_DELETE, NTMP_AM_SEARCH); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to delete dynamic %s entries on port %d, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), port, ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_port_dynamic_entries); + +/** + * ntmp_vft_set_entry - add an entry into the VLAN filter table or update + * the configuration element data of the specified VLAN filter entry + * @user: target ntmp_user struct + * @vid: VLAN ID + * @cmd: command type, NTMP_CMD_ADD or NTMP_CMD_UPDATE + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +static int ntmp_vft_set_entry(struct ntmp_user *user, u16 vid, int cmd, + const struct vft_cfge_data *cfge) +{ + struct netc_swcbd swcbd; + struct vft_req_ua *req; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + if (cmd != NTMP_CMD_ADD && cmd != NTMP_CMD_UPDATE) + return -EINVAL; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, NTMP_GEN_UA_CFGEU); req->ak.exact.vid = cpu_to_le16(vid); req->cfge = *cfge; /* Request header */ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID, - NTMP_CMD_ADD, NTMP_AM_EXACT_KEY); + cmd, NTMP_AM_EXACT_KEY); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + ntmp_unlock_cbdr(cbdr); + + return err; +} + +/** + * ntmp_vft_add_entry - add an entry into the VLAN filter table + * @user: target ntmp_user struct + * @vid: VLAN ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, + const struct vft_cfge_data *cfge) +{ + int err; + + err = ntmp_vft_set_entry(user, vid, NTMP_CMD_ADD, cfge); + if (err) + dev_err(user->dev, + "Failed to add %s entry, vid: %u, err: %pe\n", + ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err)); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_vft_add_entry); + +/** + * ntmp_vft_update_entry - update the configuration element data of the + * specified VLAN filter entry + * @user: target ntmp_user struct + * @vid: VLAN ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid, + const struct vft_cfge_data *cfge) +{ + int err; + + err = ntmp_vft_set_entry(user, vid, NTMP_CMD_UPDATE, cfge); + if (err) + dev_err(user->dev, + "Failed to update %s entry, vid: %u, err: %pe\n", + ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err)); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_vft_update_entry); + +/** + * ntmp_vft_delete_entry - delete the VLAN filter entry based on the + * specified VLAN ID + * @user: target ntmp_user struct + * @vid: VLAN ID + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid) +{ + struct netc_swcbd swcbd; + struct vft_req_qd *req; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, 0); + req->ak.exact.vid = cpu_to_le16(vid); + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID, + NTMP_CMD_DELETE, NTMP_AM_EXACT_KEY); ntmp_select_and_lock_cbdr(user, &cbdr); err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); if (err) dev_err(user->dev, - "Failed to add %s entry, vid: %u, err: %pe\n", + "Failed to delete %s entry, vid: %u, err: %pe\n", ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err)); ntmp_unlock_cbdr(cbdr); return err; } -EXPORT_SYMBOL_GPL(ntmp_vft_add_entry); +EXPORT_SYMBOL_GPL(ntmp_vft_delete_entry); + +/** + * ntmp_ett_set_entry - add a new entry to the egress treatment table or + * update the configuration element data of the specified entry + * @user: target ntmp_user struct + * @entry_id: entry ID + * @cmd: command type, NTMP_CMD_ADD or NTMP_CMD_UPDATE + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +static int ntmp_ett_set_entry(struct ntmp_user *user, u32 entry_id, + int cmd, const struct ett_cfge_data *cfge) +{ + struct netc_swcbd swcbd; + struct ett_req_ua *req; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + int err; + + if (cmd != NTMP_CMD_ADD && cmd != NTMP_CMD_UPDATE) + return -EINVAL; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd_eid(&req->rbe, user->tbl.ett_ver, 0, + NTMP_GEN_UA_CFGEU, entry_id); + req->cfge = *cfge; + + /* Request header */ + ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0), + NTMP_ETT_ID, cmd, NTMP_AM_ENTRY_ID); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + ntmp_unlock_cbdr(cbdr); + + return err; +} + +/** + * ntmp_ett_add_entry - add a new entry to the egress treatment table + * @user: target ntmp_user struct + * @entry_id: entry ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ett_add_entry(struct ntmp_user *user, u32 entry_id, + const struct ett_cfge_data *cfge) +{ + int err; + + err = ntmp_ett_set_entry(user, entry_id, NTMP_CMD_ADD, cfge); + if (err) + dev_err(user->dev, "Failed to add %s entry 0x%x, err: %pe\n", + ntmp_table_name(NTMP_ETT_ID), entry_id, ERR_PTR(err)); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_ett_add_entry); + +/** + * ntmp_ett_update_entry - update the configuration element data of the + * specified entry + * @user: target ntmp_user struct + * @entry_id: entry ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ett_update_entry(struct ntmp_user *user, u32 entry_id, + const struct ett_cfge_data *cfge) +{ + int err; + + err = ntmp_ett_set_entry(user, entry_id, NTMP_CMD_UPDATE, cfge); + if (err) + dev_err(user->dev, + "Failed to update %s entry 0x%x, err: %pe\n", + ntmp_table_name(NTMP_ETT_ID), entry_id, ERR_PTR(err)); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_ett_update_entry); + +/** + * ntmp_ett_delete_entry - delete the specified egress treatment table entry + * @user: target ntmp_user struct + * @entry_id: entry ID + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id) +{ + return ntmp_delete_entry_by_id(user, NTMP_ETT_ID, user->tbl.ett_ver, + entry_id, NTMP_EID_REQ_LEN, 0); +} +EXPORT_SYMBOL_GPL(ntmp_ett_delete_entry); + +/** + * ntmp_ect_update_entry - reset the statistics element data of the + * specified egress counter table entry + * @user: target ntmp_user struct + * @entry_id: entry ID + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ect_update_entry(struct ntmp_user *user, u32 entry_id) +{ + struct ntmp_req_by_eid *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd_eid(req, user->tbl.ect_ver, 0, ECT_UA_STSEU, entry_id); + + /* Request header */ + ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0), + NTMP_ECT_ID, NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to update %s entry 0x%x, err: %pe\n", + ntmp_table_name(NTMP_ECT_ID), entry_id, ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_ect_update_entry); int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, const struct bpt_cfge_data *cfge) diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h index 0a9b87286105..531ea7ddd145 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h +++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h @@ -155,8 +155,8 @@ struct fdbt_ak_search { #define FDBT_KEYE_MAC GENMASK(1, 0) u8 cfge_mc; #define FDBT_CFGE_MC GENMASK(2, 0) -#define FDBT_CFGE_MC_ANY 0 -#define FDBT_CFGE_MC_DYNAMIC 1 +#define FDBT_CFGE_MC_ANY 0 +#define FDBT_CFGE_MC_DYNAMIC 1 #define FDBT_CFGE_MC_PORT_BITMAP 2 #define FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP 3 u8 acte_mc; @@ -211,6 +211,20 @@ struct vft_req_ua { struct vft_cfge_data cfge; }; +/* VLAN Filter Table Request Data Buffer Format of Query and Delete actions */ +struct vft_req_qd { + struct ntmp_cmn_req_data crd; + union vft_access_key ak; +}; + +/* Egress Treatment Table Request Data Buffer Format of Update and Add + * actions + */ +struct ett_req_ua { + struct ntmp_req_by_eid rbe; + struct ett_cfge_data cfge; +}; + /* Buffer Pool Table Request Data Buffer Format of Update action */ struct bpt_req_update { struct ntmp_req_by_eid rbe; diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 88166f9ad3a2..d3b6c476b91a 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -3,6 +3,7 @@ #ifndef __NETC_NTMP_H #define __NETC_NTMP_H +#include #include #include @@ -36,6 +37,8 @@ struct netc_tbl_vers { u8 vft_ver; u8 bpt_ver; u8 ipft_ver; + u8 ett_ver; + u8 ect_ver; }; struct netc_swcbd { @@ -68,6 +71,12 @@ struct ntmp_user { struct device *dev; struct netc_cbdr *ring; struct netc_tbl_vers tbl; + + /* NTMP table bitmaps for resource management */ + u32 ett_bitmap_size; + u32 ect_bitmap_size; + unsigned long *ett_gid_bitmap; /* only valid for switch */ + unsigned long *ect_gid_bitmap; /* only valid for switch */ }; struct maft_entry_data { @@ -214,6 +223,23 @@ struct vft_cfge_data { __le32 et_eid; }; +struct ett_cfge_data { + __le16 efm_cfg; +#define ETT_EFM_MODE GENMASK(1, 0) +#define ETT_ESQA GENMASK(5, 4) +#define ETT_ECA GENMASK(8, 6) +#define ETT_ECA_INC 1 +#define ETT_EFM_LEN_CHANGE GENMASK(15, 9) +#define ETT_FRM_LEN_DEL_VLAN 0x7c +#define ETT_FRM_LEN_DEL_RTAG 0x7a +#define ETT_FRM_LEN_DEL_VLAN_RTAG 0x76 + __le16 efm_data_len; +#define ETT_EFM_DATA_LEN GENMASK(10, 0) + __le32 efm_eid; + __le32 ec_eid; + __le32 esqa_tgt_eid; +}; + struct bpt_bpse_data { __le32 amount_used; __le32 amount_used_hwm; @@ -236,10 +262,27 @@ struct bpt_cfge_data { __le32 fc_ports; }; +union ntmp_fmt_eid { + __le32 index; +#define FMTEID_INDEX GENMASK(12, 0) + __le32 vuda_sqta; +#define FMTEID_VUDA GENMASK(1, 0) +#define FMTEID_VUDA_DEL_OTAG 2 +#define FMTEID_SQTA GENMASK(4, 2) +#define FMTEID_SQTA_DEL 2 +#define FMTEID_VUDA_SQTA BIT(13) + __le32 vara_vid; +#define FMTEID_VID GENMASK(11, 0) +#define FMTEID_VARA GENMASK(13, 12) +#define FMTEID_VARA_VID BIT(14) +}; + #if IS_ENABLED(CONFIG_NXP_NETC_LIB) int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs); void ntmp_free_cbdr(struct netc_cbdr *cbdr); +u32 ntmp_lookup_free_eid(unsigned long *bitmap, u32 size); +void ntmp_clear_eid_bitmap(unsigned long *bitmap, u32 entry_id); /* NTMP APIs */ int ntmp_maft_add_entry(struct ntmp_user *user, u32 entry_id, @@ -263,8 +306,20 @@ int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id); int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port, u32 *resume_entry_id, struct fdbt_entry_data *entry); +int ntmp_fdbt_update_activity_element(struct ntmp_user *user); +int ntmp_fdbt_delete_ageing_entries(struct ntmp_user *user, u8 act_cnt); +int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port); int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, const struct vft_cfge_data *cfge); +int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid, + const struct vft_cfge_data *cfge); +int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid); +int ntmp_ett_add_entry(struct ntmp_user *user, u32 entry_id, + const struct ett_cfge_data *cfge); +int ntmp_ett_update_entry(struct ntmp_user *user, u32 entry_id, + const struct ett_cfge_data *cfge); +int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id); +int ntmp_ect_update_entry(struct ntmp_user *user, u32 entry_id); int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, const struct bpt_cfge_data *cfge); #else