summaryrefslogtreecommitdiff
path: root/drivers/net/dsa/realtek/rtl83xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa/realtek/rtl83xx.c')
-rw-r--r--drivers/net/dsa/realtek/rtl83xx.c563
1 files changed, 563 insertions, 0 deletions
diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
index 2b9bd4462714..71124ecca92f 100644
--- a/drivers/net/dsa/realtek/rtl83xx.c
+++ b/drivers/net/dsa/realtek/rtl83xx.c
@@ -3,6 +3,8 @@
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/of_mdio.h>
+#include <linux/if_bridge.h>
+#include <linux/etherdevice.h>
#include "realtek.h"
#include "rtl83xx.h"
@@ -155,6 +157,8 @@ rtl83xx_probe(struct device *dev,
return ERR_PTR(-ENOMEM);
mutex_init(&priv->map_lock);
+ mutex_init(&priv->vlan_lock);
+ mutex_init(&priv->l2_lock);
rc.lock_arg = priv;
priv->map = devm_regmap_init(dev, NULL, priv, &rc);
@@ -325,6 +329,565 @@ void rtl83xx_reset_deassert(struct realtek_priv *priv)
gpiod_set_value(priv->reset, false);
}
+/**
+ * rtl83xx_port_bridge_join() - join a port to a bridge
+ * @ds: DSA switch instance
+ * @port: port index
+ * @bridge: bridge being joined
+ * @tx_forward_offload: if the switch can offload TX forwarding
+ * @extack: netlink extended ack for reporting errors
+ *
+ * This function handles joining a port to a bridge. It updates the port
+ * isolation masks and EFID.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_forward_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct dsa_port *dp;
+ u32 mask = 0;
+ int ret;
+
+ if (!priv->ops->port_add_isolation)
+ return -EOPNOTSUPP;
+
+ if (!priv->ops->port_set_learning)
+ return -EOPNOTSUPP;
+
+ dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port);
+
+ /* Add this port to the isolation group of every other port
+ * offloading this bridge.
+ */
+ dsa_switch_for_each_user_port(dp, ds) {
+ /* Handle this port after */
+ if (dp->index == port)
+ continue;
+
+ /* Skip ports that are not in this bridge */
+ if (!dsa_port_offloads_bridge(dp, &bridge))
+ continue;
+
+ ret = priv->ops->port_add_isolation(priv, dp->index, BIT(port));
+ if (ret)
+ goto undo_isolation;
+
+ mask |= BIT(dp->index);
+ }
+
+ /* If we support cascade switches, it should also include the
+ * downstream DSA ports to the isolation group.
+ */
+
+ /* Add those ports to the isolation group of this port */
+ ret = priv->ops->port_add_isolation(priv, port, mask);
+ if (ret)
+ goto undo_isolation;
+
+ /* Use the bridge number as the EFID for this port */
+ if (priv->ops->port_set_efid) {
+ ret = priv->ops->port_set_efid(priv, port, bridge.num);
+ if (ret)
+ goto undo_self_isolation;
+ }
+
+ ret = priv->ops->port_set_learning(priv, port, true);
+ if (ret)
+ goto undo_efid;
+
+ return 0;
+
+undo_efid:
+ if (priv->ops->port_set_efid)
+ priv->ops->port_set_efid(priv, port, 0);
+
+undo_self_isolation:
+ priv->ops->port_remove_isolation(priv, port, mask);
+
+undo_isolation:
+ dsa_switch_for_each_port(dp, ds) {
+ if (mask & BIT(dp->index))
+ priv->ops->port_remove_isolation(priv, dp->index,
+ BIT(port));
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_join, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_bridge_leave() - leave a bridge
+ * @ds: DSA switch instance
+ * @port: port index
+ * @bridge: bridge being left
+ *
+ * This function handles removing a port from a bridge. It updates the port
+ * isolation masks and EFID.
+ *
+ * Context: Can sleep.
+ * Return: nothing
+ */
+void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct dsa_port *dp;
+ u32 mask = 0;
+ int ret;
+
+ if (!priv->ops->port_remove_isolation)
+ return;
+
+ if (!priv->ops->port_set_learning)
+ return;
+
+ dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port);
+
+ /* Remove this port from the isolation group of every other
+ * port offloading this bridge.
+ */
+ dsa_switch_for_each_user_port(dp, ds) {
+ /* Handle this port after */
+ if (dp->index == port)
+ continue;
+
+ /* Skip ports that are not in this bridge */
+ if (!dsa_port_offloads_bridge(dp, &bridge))
+ continue;
+
+ ret = priv->ops->port_remove_isolation(priv, dp->index,
+ BIT(port));
+ if (ret)
+ dev_err(priv->dev,
+ "failed to isolate port %d from port %d: %pe\n",
+ port, dp->index, ERR_PTR(ret));
+
+ mask |= BIT(dp->index);
+ }
+
+ /* If we support cascade switches, it should also exclude the
+ * downstream DSA ports from the isolation group.
+ */
+
+ ret = priv->ops->port_set_learning(priv, port, false);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to disable learning on port %d: %pe\n",
+ port, ERR_PTR(ret));
+
+ /* Remove those ports from the isolation group of this port */
+ ret = priv->ops->port_remove_isolation(priv, port, mask);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to remove isolation mask from port %d: %pe\n",
+ port, ERR_PTR(ret));
+
+ /* Revert to the default EFID 0 for standalone mode */
+ if (priv->ops->port_set_efid) {
+ ret = priv->ops->port_set_efid(priv, port, 0);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to clear EFID on port %d: %pe\n",
+ port, ERR_PTR(ret));
+ }
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port
+ * @ds: DSA switch instance
+ * @port: port index
+ *
+ * This function requests the switch to age out dynamic FDB entries learned on
+ * @port.
+ *
+ * Context: Can sleep.
+ * Return: Nothing.
+ */
+void rtl83xx_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct realtek_priv *priv = ds->priv;
+ int ret;
+
+ if (!priv->ops->l2_flush) {
+ dev_warn_once(priv->dev, "l2_flush op not defined\n");
+ return;
+ }
+
+ dev_dbg(priv->dev, "fast_age port %d\n", port);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_flush(priv, port, 0);
+ mutex_unlock(&priv->l2_lock);
+ if (ret)
+ dev_err(priv->dev, "failed to fast age on port %d: %d\n", port,
+ ret);
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fast_age, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_add() - add a static FDB entry to a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @addr: MAC address to add
+ * @vid: VLAN ID associated with @addr
+ * @db: database where the entry should be added
+ *
+ * This function adds a static unicast FDB entry to the standalone port
+ * database or to a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ int efid;
+ int ret;
+
+ if (is_multicast_ether_addr(addr))
+ return -EOPNOTSUPP;
+
+ if (!priv->ops->l2_add_uc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /* Bridge ports use bridge.num as EFID, while standalone ports use
+ * EFID 0. FDB entries for the CPU port follow the bridge EFID due
+ * to assisted learning.
+ */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
+ __func__, port, addr, efid, vid, db.type);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_add_uc(priv, port, addr, efid, vid);
+
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "fdb_add ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_add, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_del() - delete a static FDB entry from a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @addr: MAC address to delete
+ * @vid: VLAN ID associated with @addr
+ * @db: database where the entry should be removed
+ *
+ * This function deletes a static unicast FDB entry from the standalone port
+ * database or from a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ int efid;
+ int ret;
+
+ if (is_multicast_ether_addr(addr))
+ return -EOPNOTSUPP;
+
+ if (!priv->ops->l2_del_uc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /*
+ * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while
+ * DSA_DB_PORT use the default EFID (0), not used by any bridge.
+ */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
+ __func__, port, addr, efid, vid, db.type);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_del_uc(priv, port, addr, efid, vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "fdb_del ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_del, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_dump() - iterate over FDB entries associated with a port
+ * @ds: DSA switch instance
+ * @port: port index
+ * @cb: callback invoked for each entry
+ * @data: opaque pointer passed to @cb
+ *
+ * This function walks the unicast FDB entries associated with @port and calls
+ * @cb for each matching entry.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, or negative value for failure.
+ */
+int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ struct realtek_fdb_entry entry = { 0 };
+ struct realtek_priv *priv = ds->priv;
+ u16 start_addr, addr = 0;
+ int ret = 0;
+
+ if (!priv->ops->l2_get_next_uc)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&priv->l2_lock);
+ while (true) {
+ start_addr = addr;
+
+ dev_dbg(priv->dev, "l2_get_next_uc, addr:%d, port:%d\n",
+ addr, port);
+ ret = priv->ops->l2_get_next_uc(priv, &addr, port, &entry);
+ dev_dbg(priv->dev,
+ "%s addr:%d mac:%pM vid:%d static:%d ret:%pe\n",
+ __func__, addr, entry.mac_addr, entry.vid,
+ entry.is_static, ERR_PTR(ret));
+
+ if (ret == -ENOENT) {
+ /* If the table is empty, returns without errors. Note
+ * that the l2_get_next_uc overflow to the first match
+ * when it reaches the end of the table.
+ */
+ ret = 0;
+ break;
+ }
+
+ if (ret)
+ break;
+
+ /* When the addr returned is before the requested one, it
+ * indicates that we reached the end.
+ */
+ if (addr < start_addr)
+ break;
+
+ ret = cb(entry.mac_addr, entry.vid, entry.is_static, data);
+ if (ret)
+ break;
+
+ addr++;
+ }
+ mutex_unlock(&priv->l2_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_dump, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_mdb_add() - add a multicast database entry to a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @mdb: multicast database entry to add
+ * @db: database where the entry should be added
+ *
+ * This function adds a multicast database entry to the standalone port
+ * database or to a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ const unsigned char *addr = mdb->addr;
+ u16 vid = mdb->vid;
+ int efid;
+ int ret;
+
+ if (!priv->ops->l2_add_mc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /* EFID is not used by hardware MDB entries; debugging only */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
+ __func__, port, addr, efid, vid, db.type);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_add_mc(priv, port, addr, vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "mdb_add ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_add, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_mdb_del() - delete a multicast database entry from a port
+ * database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @mdb: multicast database entry to delete
+ * @db: database where the entry should be removed
+ *
+ * This function deletes a multicast database entry from the standalone port
+ * database or from a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ const unsigned char *addr = mdb->addr;
+ u16 vid = mdb->vid;
+ int efid;
+ int ret;
+
+ if (!priv->ops->l2_del_mc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /* EFID is not used by hardware MDB entries; debugging only */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
+ __func__, port, addr, efid, vid, db.type);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_del_mc(priv, port, addr, vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "mdb_del ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_bridge_flags() - set port bridge flags
+ * @ds: DSA switch instance
+ * @port: port index
+ * @flags: bridge port flags
+ * @extack: netlink extended ack for reporting errors
+ *
+ * This function handles setting bridge port flags like learning and flooding.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+{
+ struct realtek_priv *priv = ds->priv;
+ bool enable;
+ int ret;
+
+ if (flags.mask & BR_LEARNING) {
+ if (!priv->ops->port_set_learning)
+ return -EOPNOTSUPP;
+
+ enable = !!(flags.val & BR_LEARNING);
+ ret = priv->ops->port_set_learning(priv, port, enable);
+ if (ret)
+ return ret;
+ }
+
+ if (flags.mask & BR_FLOOD) {
+ if (!priv->ops->port_set_ucast_flood)
+ return -EOPNOTSUPP;
+
+ enable = !!(flags.val & BR_FLOOD);
+ ret = priv->ops->port_set_ucast_flood(priv, port, enable);
+ if (ret)
+ return ret;
+ }
+
+ if (flags.mask & BR_MCAST_FLOOD) {
+ if (!priv->ops->port_set_mcast_flood)
+ return -EOPNOTSUPP;
+
+ enable = !!(flags.val & BR_MCAST_FLOOD);
+ ret = priv->ops->port_set_mcast_flood(priv, port, enable);
+ if (ret)
+ return ret;
+ }
+
+ if (flags.mask & BR_BCAST_FLOOD) {
+ if (!priv->ops->port_set_bcast_flood)
+ return -EOPNOTSUPP;
+
+ enable = !!(flags.val & BR_BCAST_FLOOD);
+ ret = priv->ops->port_set_bcast_flood(priv, port, enable);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, "REALTEK_DSA");
+
+/**
+ * rtl83xx_setup_port_flood_control() - setup default flood control for a port
+ * @priv: realtek_priv pointer
+ * @port: port index
+ *
+ * This function enables flooding for a given port.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port)
+{
+ int ret;
+
+ if (priv->ops->port_set_ucast_flood) {
+ ret = priv->ops->port_set_ucast_flood(priv, port, true);
+ if (ret)
+ return ret;
+ }
+
+ if (priv->ops->port_set_mcast_flood) {
+ ret = priv->ops->port_set_mcast_flood(priv, port, true);
+ if (ret)
+ return ret;
+ }
+
+ if (priv->ops->port_set_bcast_flood) {
+ ret = priv->ops->port_set_bcast_flood(priv, port, true);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, "REALTEK_DSA");
+
MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
MODULE_DESCRIPTION("Realtek DSA switches common module");