mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
net: dsa: netc: add FDB, STP, MTU, port setup and host flooding support
Expand the NETC switch driver with several foundational features: - FDB and MDB management - STP state handling - MTU configuration - Port setup/teardown - Host flooding support At this stage, the driver operates only in standalone port mode. Each port uses VLAN 0 as its PVID, meaning ingress frames are internally assigned VID 0 regardless of whether they arrive tagged or untagged. Note that this does not inject a VLAN 0 header into the frame, the VID is used purely for subsequent VLAN processing within the switch. Signed-off-by: Wei Fang <wei.fang@nxp.com> Link: https://patch.msgid.link/20260518082506.1318236-13-wei.fang@nxp.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
parent
bbe97e3472
commit
46d6407692
|
|
@ -4,13 +4,39 @@
|
|||
* 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 enum dsa_tag_protocol
|
||||
netc_get_tag_protocol(struct dsa_switch *ds, int port,
|
||||
enum dsa_tag_protocol mprot)
|
||||
|
|
@ -83,6 +109,22 @@ static void netc_port_get_capability(struct netc_port *np)
|
|||
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)
|
||||
{
|
||||
|
|
@ -163,6 +205,15 @@ static int netc_init_switch_id(struct netc_switch *priv)
|
|||
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);
|
||||
}
|
||||
|
||||
static int netc_init_all_ports(struct netc_switch *priv)
|
||||
{
|
||||
struct device *dev = priv->dev;
|
||||
|
|
@ -198,6 +249,10 @@ static int netc_init_all_ports(struct netc_switch *priv)
|
|||
np = priv->ports[dp->index];
|
||||
np->dp = dp;
|
||||
|
||||
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) {
|
||||
|
|
@ -367,6 +422,220 @@ static void netc_port_default_config(struct netc_port *np)
|
|||
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))
|
||||
/* 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.
|
||||
*/
|
||||
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 int netc_setup(struct dsa_switch *ds)
|
||||
{
|
||||
struct netc_switch *priv = ds->priv;
|
||||
|
|
@ -377,6 +646,8 @@ static int netc_setup(struct dsa_switch *ds)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
netc_get_switch_capabilities(priv);
|
||||
|
||||
err = netc_init_all_ports(priv);
|
||||
if (err)
|
||||
return err;
|
||||
|
|
@ -385,19 +656,65 @@ static int netc_setup(struct dsa_switch *ds)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
INIT_HLIST_HEAD(&priv->fdb_list);
|
||||
mutex_init(&priv->fdbt_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_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);
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
netc_destroy_all_lists(priv);
|
||||
netc_free_host_flood_rules(priv);
|
||||
netc_free_ntmp_user(priv);
|
||||
}
|
||||
|
||||
|
|
@ -531,6 +848,278 @@ static void netc_switch_get_ip_revision(struct netc_switch *priv)
|
|||
priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
|
||||
}
|
||||
|
||||
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 int netc_port_fdb_add(struct dsa_switch *ds, int port,
|
||||
const unsigned char *addr, u16 vid,
|
||||
struct dsa_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;
|
||||
|
||||
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 netc_port *np = NETC_PORT(ds, port);
|
||||
|
||||
if (vid != NETC_STANDALONE_PVID)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
if (!host_flood)
|
||||
return;
|
||||
|
||||
ntmp_ipft_delete_entry(&priv->ntmp, host_flood->entry_id);
|
||||
kfree(host_flood);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 void netc_phylink_get_caps(struct dsa_switch *ds, int port,
|
||||
struct phylink_config *config)
|
||||
{
|
||||
|
|
@ -757,6 +1346,17 @@ static const struct dsa_switch_ops netc_switch_ops = {
|
|||
.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,
|
||||
};
|
||||
|
||||
static int netc_switch_probe(struct pci_dev *pdev,
|
||||
|
|
@ -796,6 +1396,7 @@ static int netc_switch_probe(struct pci_dev *pdev,
|
|||
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->priv = priv;
|
||||
priv->ds = ds;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
#define NETC_MAX_FRAME_LEN 9600
|
||||
|
||||
#define NETC_STANDALONE_PVID 0
|
||||
|
||||
struct netc_switch;
|
||||
|
||||
struct netc_switch_info {
|
||||
|
|
@ -44,12 +46,23 @@ struct netc_port_caps {
|
|||
u32 pseudo_link:1;
|
||||
};
|
||||
|
||||
enum netc_host_reason {
|
||||
/* Software defined host reasons */
|
||||
NETC_HR_HOST_FLOOD = 8,
|
||||
};
|
||||
|
||||
struct netc_port {
|
||||
void __iomem *iobase;
|
||||
struct netc_switch *switch_priv;
|
||||
struct netc_port_caps caps;
|
||||
struct dsa_port *dp;
|
||||
struct clk *ref_clk; /* RGMII/RMII reference clock */
|
||||
struct mii_bus *emdio;
|
||||
|
||||
u16 enable:1;
|
||||
u16 uc:1;
|
||||
u16 mc:1;
|
||||
struct ipft_entry_data *host_flood;
|
||||
};
|
||||
|
||||
struct netc_switch_regs {
|
||||
|
|
@ -58,6 +71,13 @@ struct netc_switch_regs {
|
|||
void __iomem *global;
|
||||
};
|
||||
|
||||
struct netc_fdb_entry {
|
||||
u32 entry_id;
|
||||
struct fdbt_cfge_data cfge;
|
||||
struct fdbt_keye_data keye;
|
||||
struct hlist_node node;
|
||||
};
|
||||
|
||||
struct netc_switch {
|
||||
struct pci_dev *pdev;
|
||||
struct device *dev;
|
||||
|
|
@ -69,6 +89,11 @@ struct netc_switch {
|
|||
struct netc_port **ports;
|
||||
|
||||
struct ntmp_user ntmp;
|
||||
struct hlist_head fdb_list;
|
||||
struct mutex fdbt_lock; /* FDB table lock */
|
||||
|
||||
/* Switch hardware capabilities */
|
||||
u32 htmcapr_num_words;
|
||||
};
|
||||
|
||||
#define NETC_PRIV(ds) ((struct netc_switch *)((ds)->priv))
|
||||
|
|
@ -91,6 +116,18 @@ static inline bool is_netc_pseudo_port(struct netc_port *np)
|
|||
return np->caps.pseudo_link;
|
||||
}
|
||||
|
||||
static inline void netc_add_fdb_entry(struct netc_switch *priv,
|
||||
struct netc_fdb_entry *entry)
|
||||
{
|
||||
hlist_add_head(&entry->node, &priv->fdb_list);
|
||||
}
|
||||
|
||||
static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry)
|
||||
{
|
||||
hlist_del(&entry->node);
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
int netc_switch_platform_probe(struct netc_switch *priv);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -30,6 +30,20 @@
|
|||
#define DOSL3CR_SAMEADDR BIT(0)
|
||||
#define DOSL3CR_IPSAMCC BIT(1)
|
||||
|
||||
/* Hash table memory capability register, the memory is shared by
|
||||
* the following tables:
|
||||
*
|
||||
* - Ingress Stream Identification table
|
||||
* - Ingress Stream Filter table
|
||||
* - VLAN Filter table
|
||||
* - FDB table
|
||||
* - L2 IPv4 Multicast Filter table
|
||||
*
|
||||
* Each hash table entry is one word in size.
|
||||
*/
|
||||
#define NETC_HTMCAPR 0x1900
|
||||
#define HTMCAPR_NUM_WORDS GENMASK(15, 0)
|
||||
|
||||
#define NETC_VFHTDECR1 0x2014
|
||||
#define NETC_VFHTDECR2 0x2018
|
||||
#define VFHTDECR2_ET_PORT(a) BIT((a))
|
||||
|
|
@ -67,6 +81,9 @@
|
|||
#define PQOSMR_VQMP GENMASK(19, 16)
|
||||
#define PQOSMR_QVMP GENMASK(23, 20)
|
||||
|
||||
#define NETC_PIPFCR 0x0084
|
||||
#define PIPFCR_EN BIT(0)
|
||||
|
||||
#define NETC_POR 0x100
|
||||
#define POR_TXDIS BIT(0)
|
||||
#define POR_RXDIS BIT(1)
|
||||
|
|
@ -122,6 +139,14 @@ enum netc_mfo {
|
|||
#define BPDVR_RXVAM BIT(24)
|
||||
#define BPDVR_TXTAGA GENMASK(26, 25)
|
||||
|
||||
#define NETC_BPSTGSR 0x520
|
||||
|
||||
enum netc_stg_stage {
|
||||
NETC_STG_STATE_DISABLED = 0,
|
||||
NETC_STG_STATE_LEARNING,
|
||||
NETC_STG_STATE_FORWARDING,
|
||||
};
|
||||
|
||||
/* Definition of Switch ethernet MAC port registers */
|
||||
#define NETC_PMAC_OFFSET 0x400
|
||||
#define NETC_PM_CMD_CFG(a) (0x1008 + (a) * 0x400)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user