[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20211213101432.2668820-8-horatiu.vultur@microchip.com>
Date: Mon, 13 Dec 2021 11:14:29 +0100
From: Horatiu Vultur <horatiu.vultur@...rochip.com>
To: <netdev@...r.kernel.org>, <devicetree@...r.kernel.org>,
<linux-kernel@...r.kernel.org>
CC: <davem@...emloft.net>, <kuba@...nel.org>, <robh+dt@...nel.org>,
<UNGLinuxDriver@...rochip.com>, <linux@...linux.org.uk>,
<f.fainelli@...il.com>, <vivien.didelot@...il.com>,
<vladimir.oltean@....com>, <andrew@...n.ch>,
Horatiu Vultur <horatiu.vultur@...rochip.com>
Subject: [PATCH net-next v4 07/10] net: lan966x: Add support to offload the forwarding.
This patch adds basic support to offload in the HW the forwarding of the
frames. The driver registers to the switchdev callbacks and implements
the callbacks for attributes SWITCHDEV_ATTR_ID_PORT_STP_STATE and
SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME.
It is not allowed to add a lan966x port to a bridge that contains a
different interface than lan966x.
Signed-off-by: Horatiu Vultur <horatiu.vultur@...rochip.com>
---
.../net/ethernet/microchip/lan966x/Kconfig | 1 +
.../net/ethernet/microchip/lan966x/Makefile | 3 +-
.../ethernet/microchip/lan966x/lan966x_main.c | 19 +-
.../ethernet/microchip/lan966x/lan966x_main.h | 19 +
.../microchip/lan966x/lan966x_switchdev.c | 398 ++++++++++++++++++
.../ethernet/microchip/lan966x/lan966x_vlan.c | 14 +-
6 files changed, 449 insertions(+), 5 deletions(-)
create mode 100644 drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
diff --git a/drivers/net/ethernet/microchip/lan966x/Kconfig b/drivers/net/ethernet/microchip/lan966x/Kconfig
index 2860a8c9923d..ac273f84b69e 100644
--- a/drivers/net/ethernet/microchip/lan966x/Kconfig
+++ b/drivers/net/ethernet/microchip/lan966x/Kconfig
@@ -2,6 +2,7 @@ config LAN966X_SWITCH
tristate "Lan966x switch driver"
depends on HAS_IOMEM
depends on OF
+ depends on NET_SWITCHDEV
select PHYLINK
select PACKING
help
diff --git a/drivers/net/ethernet/microchip/lan966x/Makefile b/drivers/net/ethernet/microchip/lan966x/Makefile
index f7e6068a91cb..d489a34fc643 100644
--- a/drivers/net/ethernet/microchip/lan966x/Makefile
+++ b/drivers/net/ethernet/microchip/lan966x/Makefile
@@ -6,4 +6,5 @@
obj-$(CONFIG_LAN966X_SWITCH) += lan966x-switch.o
lan966x-switch-objs := lan966x_main.o lan966x_phylink.o lan966x_port.o \
- lan966x_mac.o lan966x_ethtool.o lan966x_vlan.o
+ lan966x_mac.o lan966x_ethtool.o lan966x_vlan.o \
+ lan966x_switchdev.o
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
index 22bb2e4dfdb2..08d8b230548b 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
@@ -378,6 +378,11 @@ static const struct net_device_ops lan966x_port_netdev_ops = {
.ndo_vlan_rx_kill_vid = lan966x_vlan_rx_kill_vid,
};
+bool lan966x_netdevice_check(const struct net_device *dev)
+{
+ return dev->netdev_ops == &lan966x_port_netdev_ops;
+}
+
static int lan966x_port_xtr_status(struct lan966x *lan966x, u8 grp)
{
return lan_rd(lan966x, QS_XTR_RD(grp));
@@ -514,6 +519,9 @@ static irqreturn_t lan966x_xtr_irq_handler(int irq, void *args)
skb->protocol = eth_type_trans(skb, dev);
+ if (lan966x->bridge_mask & BIT(src_port))
+ skb->offload_fwd_mark = 1;
+
netif_rx_ni(skb);
dev->stats.rx_bytes += len;
dev->stats.rx_packets++;
@@ -604,9 +612,6 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
eth_hw_addr_gen(dev, lan966x->base_mac, p + 1);
- lan966x_mac_learn(lan966x, PGID_CPU, dev->dev_addr, port->pvid,
- ENTRYTYPE_LOCKED);
-
port->phylink_config.dev = &port->dev->dev;
port->phylink_config.type = PHYLINK_NETDEV;
port->phylink_pcs.poll = true;
@@ -930,6 +935,11 @@ static int lan966x_probe(struct platform_device *pdev)
lan966x_port_init(lan966x->ports[p]);
}
+ lan966x_ext_init(lan966x);
+ err = lan966x_register_notifier_blocks(lan966x);
+ if (err)
+ goto cleanup_ports;
+
return 0;
cleanup_ports:
@@ -948,6 +958,8 @@ static int lan966x_remove(struct platform_device *pdev)
{
struct lan966x *lan966x = platform_get_drvdata(pdev);
+ lan966x_unregister_notifier_blocks(lan966x);
+
lan966x_cleanup_ports(lan966x);
cancel_delayed_work_sync(&lan966x->stats_work);
@@ -955,6 +967,7 @@ static int lan966x_remove(struct platform_device *pdev)
mutex_destroy(&lan966x->stats_lock);
lan966x_mac_purge_entries(lan966x);
+ lan966x_ext_purge_entries(lan966x);
return 0;
}
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
index 306d52ed140d..ac5ae30468ff 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
@@ -77,9 +77,20 @@ struct lan966x {
u8 base_mac[ETH_ALEN];
+ struct net_device *bridge;
+ u16 bridge_mask;
+ u16 bridge_fwd_mask;
+
struct list_head mac_entries;
spinlock_t mac_lock; /* lock for mac_entries list */
+ struct list_head ext_entries;
+
+ /* Notifiers */
+ struct notifier_block netdevice_nb;
+ struct notifier_block switchdev_nb;
+ struct notifier_block switchdev_blocking_nb;
+
u16 vlan_mask[VLAN_N_VID];
DECLARE_BITMAP(cpu_vlan_mask, VLAN_N_VID);
@@ -129,6 +140,11 @@ extern const struct phylink_mac_ops lan966x_phylink_mac_ops;
extern const struct phylink_pcs_ops lan966x_phylink_pcs_ops;
extern const struct ethtool_ops lan966x_ethtool_ops;
+bool lan966x_netdevice_check(const struct net_device *dev);
+
+int lan966x_register_notifier_blocks(struct lan966x *lan966x);
+void lan966x_unregister_notifier_blocks(struct lan966x *lan966x);
+
void lan966x_stats_get(struct net_device *dev,
struct rtnl_link_stats64 *stats);
int lan966x_stats_init(struct lan966x *lan966x);
@@ -194,6 +210,9 @@ int lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x,
struct net_device *dev,
u16 vid);
+void lan966x_ext_purge_entries(struct lan966x *lan966x);
+void lan966x_ext_init(struct lan966x *lan966x);
+
static inline void __iomem *lan_addr(void __iomem *base[],
int id, int tinst, int tcnt,
int gbase, int ginst,
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
new file mode 100644
index 000000000000..3f6d361ad65b
--- /dev/null
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/if_bridge.h>
+#include <net/switchdev.h>
+
+#include "lan966x_main.h"
+
+struct lan966x_ext_entry {
+ struct list_head list;
+ struct net_device *dev;
+ u32 ports;
+};
+
+static void lan966x_update_fwd_mask(struct lan966x *lan966x)
+{
+ int i;
+
+ for (i = 0; i < lan966x->num_phys_ports; i++) {
+ struct lan966x_port *port = lan966x->ports[i];
+ unsigned long mask = 0;
+
+ if (port && lan966x->bridge_fwd_mask & BIT(i))
+ mask = lan966x->bridge_fwd_mask & ~BIT(i);
+
+ mask |= BIT(CPU_PORT);
+
+ lan_wr(ANA_PGID_PGID_SET(mask),
+ lan966x, ANA_PGID(PGID_SRC + i));
+ }
+}
+
+static void lan966x_port_stp_state_set(struct lan966x_port *port, u8 state)
+{
+ struct lan966x *lan966x = port->lan966x;
+ bool learn_ena = false;
+
+ if (state == BR_STATE_FORWARDING || state == BR_STATE_LEARNING)
+ learn_ena = true;
+
+ if (state == BR_STATE_FORWARDING)
+ lan966x->bridge_fwd_mask |= BIT(port->chip_port);
+ else
+ lan966x->bridge_fwd_mask &= ~BIT(port->chip_port);
+
+ lan_rmw(ANA_PORT_CFG_LEARN_ENA_SET(learn_ena),
+ ANA_PORT_CFG_LEARN_ENA,
+ lan966x, ANA_PORT_CFG(port->chip_port));
+
+ lan966x_update_fwd_mask(lan966x);
+}
+
+static void lan966x_port_ageing_set(struct lan966x_port *port,
+ unsigned long ageing_clock_t)
+{
+ unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock_t);
+ u32 ageing_time = jiffies_to_msecs(ageing_jiffies) / 1000;
+
+ lan966x_mac_set_ageing(port->lan966x, ageing_time);
+}
+
+static int lan966x_port_attr_set(struct net_device *dev, const void *ctx,
+ const struct switchdev_attr *attr,
+ struct netlink_ext_ack *extack)
+{
+ struct lan966x_port *port = netdev_priv(dev);
+ int err = 0;
+
+ switch (attr->id) {
+ case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
+ lan966x_port_stp_state_set(port, attr->u.stp_state);
+ break;
+ case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
+ lan966x_port_ageing_set(port, attr->u.ageing_time);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+static int lan966x_port_bridge_join(struct lan966x_port *port,
+ struct net_device *bridge,
+ struct netlink_ext_ack *extack)
+{
+ struct lan966x *lan966x = port->lan966x;
+ struct net_device *dev = port->dev;
+ int err;
+
+ if (!lan966x->bridge_mask) {
+ lan966x->bridge = bridge;
+ } else {
+ if (lan966x->bridge != bridge)
+ return -ENODEV;
+ }
+
+ err = switchdev_bridge_port_offload(dev, dev, NULL, NULL, NULL,
+ false, extack);
+ if (err)
+ return err;
+
+ lan966x->bridge_mask |= BIT(port->chip_port);
+
+ return 0;
+}
+
+static void lan966x_port_bridge_leave(struct lan966x_port *port,
+ struct net_device *bridge)
+{
+ struct lan966x *lan966x = port->lan966x;
+
+ lan966x->bridge_mask &= ~BIT(port->chip_port);
+
+ if (!lan966x->bridge_mask)
+ lan966x->bridge = NULL;
+
+ /* Set the port back to host mode */
+ lan966x_vlan_port_set_vlan_aware(port, false);
+ lan966x_vlan_port_set_vid(port, HOST_PVID, false, false);
+ lan966x_vlan_port_apply(port);
+
+ lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, HOST_PVID);
+}
+
+static int lan966x_port_changeupper(struct net_device *dev,
+ struct netdev_notifier_changeupper_info *info)
+{
+ struct lan966x_port *port = netdev_priv(dev);
+ struct netlink_ext_ack *extack;
+ int err = 0;
+
+ extack = netdev_notifier_info_to_extack(&info->info);
+
+ if (netif_is_bridge_master(info->upper_dev)) {
+ if (info->linking)
+ err = lan966x_port_bridge_join(port, info->upper_dev,
+ extack);
+ else
+ lan966x_port_bridge_leave(port, info->upper_dev);
+ }
+
+ return err;
+}
+
+static int lan966x_port_prechangeupper(struct net_device *dev,
+ struct netdev_notifier_changeupper_info *info)
+{
+ struct lan966x_port *port = netdev_priv(dev);
+ struct lan966x *lan966x = port->lan966x;
+
+ if (netif_is_bridge_master(info->upper_dev) && !info->linking)
+ switchdev_bridge_port_unoffload(port->dev, port,
+ &lan966x->netdevice_nb,
+ &lan966x->switchdev_blocking_nb);
+
+ return NOTIFY_DONE;
+}
+
+static int lan966x_port_add_addr(struct net_device *dev, bool up)
+{
+ struct lan966x_port *port = netdev_priv(dev);
+ struct lan966x *lan966x = port->lan966x;
+ u16 vid;
+
+ vid = lan966x_vlan_port_get_pvid(port);
+
+ if (up)
+ lan966x_mac_cpu_learn(lan966x, dev->dev_addr, vid);
+ else
+ lan966x_mac_cpu_forget(lan966x, dev->dev_addr, vid);
+
+ return 0;
+}
+
+static struct lan966x_ext_entry *lan966x_ext_find_entry(struct lan966x *lan966x,
+ struct net_device *dev)
+{
+ struct lan966x_ext_entry *ext_entry;
+
+ list_for_each_entry(ext_entry, &lan966x->ext_entries, list) {
+ if (ext_entry->dev == dev)
+ return ext_entry;
+ }
+
+ return NULL;
+}
+
+static bool lan966x_ext_add_entry(struct lan966x *lan966x,
+ struct net_device *dev)
+{
+ struct lan966x_ext_entry *ext_entry;
+
+ ext_entry = lan966x_ext_find_entry(lan966x, dev);
+ if (ext_entry) {
+ ext_entry->ports++;
+ return true;
+ }
+
+ ext_entry = kzalloc(sizeof(*ext_entry), GFP_KERNEL);
+ if (!ext_entry)
+ return false;
+
+ ext_entry->dev = dev;
+ ext_entry->ports = 1;
+ list_add_tail(&ext_entry->list, &lan966x->ext_entries);
+ return true;
+}
+
+static void lan966x_ext_remove_entry(struct lan966x *lan966x,
+ struct net_device *dev)
+{
+ struct lan966x_ext_entry *ext_entry;
+
+ ext_entry = lan966x_ext_find_entry(lan966x, dev);
+ if (!ext_entry)
+ return;
+
+ ext_entry->ports--;
+ if (!ext_entry->ports) {
+ list_del(&ext_entry->list);
+ kfree(ext_entry);
+ }
+}
+
+void lan966x_ext_init(struct lan966x *lan966x)
+{
+ INIT_LIST_HEAD(&lan966x->ext_entries);
+}
+
+void lan966x_ext_purge_entries(struct lan966x *lan966x)
+{
+ struct lan966x_ext_entry *ext_entry, *tmp;
+
+ list_for_each_entry_safe(ext_entry, tmp, &lan966x->ext_entries, list) {
+ list_del(&ext_entry->list);
+ kfree(ext_entry);
+ }
+}
+
+static int lan966x_ext_check_entry(struct lan966x *lan966x,
+ struct net_device *dev,
+ unsigned long event,
+ void *ptr)
+{
+ struct netdev_notifier_changeupper_info *info;
+
+ if (event != NETDEV_PRECHANGEUPPER)
+ return 0;
+
+ info = ptr;
+ if (!netif_is_bridge_master(info->upper_dev))
+ return 0;
+
+ /* If the master is used by lan966x, then don't allow the foreign
+ * interfaces to be added
+ */
+ if (lan966x->bridge == info->upper_dev)
+ return -EOPNOTSUPP;
+
+ /* The master is not used by lan966x so return OK, but keep the
+ * master in a cache in case at a later point one of the lan966x ports
+ * is added to it. In that case don't allow it.
+ */
+ if (info->linking) {
+ if (!lan966x_ext_add_entry(lan966x, info->upper_dev))
+ return -EOPNOTSUPP;
+ } else {
+ lan966x_ext_remove_entry(lan966x, info->upper_dev);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static bool lan966x_port_ext_check_entry(struct net_device *dev,
+ struct netdev_notifier_changeupper_info *info)
+{
+ struct lan966x_port *port = netdev_priv(dev);
+ struct lan966x *lan966x = port->lan966x;
+
+ if (!netif_is_bridge_master(info->upper_dev) || !info->linking)
+ return true;
+
+ return lan966x_ext_find_entry(lan966x, info->upper_dev) ? false : true;
+}
+
+static int lan966x_netdevice_port_event(struct net_device *dev,
+ struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct lan966x *lan966x = container_of(nb, struct lan966x, netdevice_nb);
+ int err = 0;
+
+ if (!lan966x_netdevice_check(dev))
+ return lan966x_ext_check_entry(lan966x, dev, event, ptr);
+
+ switch (event) {
+ case NETDEV_PRECHANGEUPPER:
+ if (!lan966x_port_ext_check_entry(dev, ptr))
+ return -EOPNOTSUPP;
+
+ err = lan966x_port_prechangeupper(dev, ptr);
+ break;
+ case NETDEV_CHANGEUPPER:
+ err = lan966x_port_changeupper(dev, ptr);
+ break;
+ case NETDEV_PRE_UP:
+ err = lan966x_port_add_addr(dev, true);
+ break;
+ case NETDEV_DOWN:
+ err = lan966x_port_add_addr(dev, false);
+ break;
+ }
+
+ return err;
+}
+
+static int lan966x_netdevice_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ int ret;
+
+ ret = lan966x_netdevice_port_event(dev, nb, event, ptr);
+
+ return notifier_from_errno(ret);
+}
+
+static int lan966x_switchdev_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ int err;
+
+ switch (event) {
+ case SWITCHDEV_PORT_ATTR_SET:
+ err = switchdev_handle_port_attr_set(dev, ptr,
+ lan966x_netdevice_check,
+ lan966x_port_attr_set);
+ return notifier_from_errno(err);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int lan966x_switchdev_blocking_event(struct notifier_block *nb,
+ unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ int err;
+
+ switch (event) {
+ case SWITCHDEV_PORT_ATTR_SET:
+ err = switchdev_handle_port_attr_set(dev, ptr,
+ lan966x_netdevice_check,
+ lan966x_port_attr_set);
+ return notifier_from_errno(err);
+ }
+
+ return NOTIFY_DONE;
+}
+
+int lan966x_register_notifier_blocks(struct lan966x *lan966x)
+{
+ int err;
+
+ lan966x->netdevice_nb.notifier_call = lan966x_netdevice_event;
+ err = register_netdevice_notifier(&lan966x->netdevice_nb);
+ if (err)
+ return err;
+
+ lan966x->switchdev_nb.notifier_call = lan966x_switchdev_event;
+ err = register_switchdev_notifier(&lan966x->switchdev_nb);
+ if (err)
+ goto err_switchdev_nb;
+
+ lan966x->switchdev_blocking_nb.notifier_call = lan966x_switchdev_blocking_event;
+ err = register_switchdev_blocking_notifier(&lan966x->switchdev_blocking_nb);
+ if (err)
+ goto err_switchdev_blocking_nb;
+
+ return 0;
+
+err_switchdev_blocking_nb:
+ unregister_switchdev_notifier(&lan966x->switchdev_nb);
+err_switchdev_nb:
+ unregister_netdevice_notifier(&lan966x->netdevice_nb);
+
+ return err;
+}
+
+void lan966x_unregister_notifier_blocks(struct lan966x *lan966x)
+{
+ unregister_switchdev_blocking_notifier(&lan966x->switchdev_blocking_nb);
+ unregister_switchdev_notifier(&lan966x->switchdev_nb);
+ unregister_netdevice_notifier(&lan966x->netdevice_nb);
+}
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c b/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
index 78d18ab00d81..268e93328a08 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
@@ -150,6 +150,11 @@ bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
u16 lan966x_vlan_port_get_pvid(struct lan966x_port *port)
{
+ struct lan966x *lan966x = port->lan966x;
+
+ if (!(lan966x->bridge_mask & BIT(port->chip_port)))
+ return HOST_PVID;
+
return port->vlan_aware ? port->pvid : UNAWARE_PVID;
}
@@ -205,6 +210,8 @@ void lan966x_vlan_cpu_set_vlan_aware(struct lan966x_port *port)
* table for the front port and the CPU
*/
lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, UNAWARE_PVID);
+ lan966x_mac_cpu_learn(lan966x, lan966x->bridge->dev_addr,
+ UNAWARE_PVID);
lan966x_vlan_port_add_vlan_mask(port, UNAWARE_PVID);
lan966x_vlan_port_apply(port);
@@ -213,6 +220,8 @@ void lan966x_vlan_cpu_set_vlan_aware(struct lan966x_port *port)
* to vlan unaware
*/
lan966x_mac_cpu_forget(lan966x, port->dev->dev_addr, UNAWARE_PVID);
+ lan966x_mac_cpu_forget(lan966x, lan966x->bridge->dev_addr,
+ UNAWARE_PVID);
lan966x_vlan_port_del_vlan_mask(port, UNAWARE_PVID);
lan966x_vlan_port_apply(port);
@@ -288,6 +297,7 @@ int lan966x_vlan_port_add_vlan(struct lan966x_port *port,
*/
if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, vid)) {
lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, vid);
+ lan966x_mac_cpu_learn(lan966x, lan966x->bridge->dev_addr, vid);
lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
}
@@ -317,8 +327,10 @@ int lan966x_vlan_port_del_vlan(struct lan966x_port *port,
* that vlan but still keep it in the mask because it may be needed
* again then another port gets added in tha vlan
*/
- if (!lan966x_vlan_port_any_vlan_mask(lan966x, vid))
+ if (!lan966x_vlan_port_any_vlan_mask(lan966x, vid)) {
+ lan966x_mac_cpu_forget(lan966x, lan966x->bridge->dev_addr, vid);
lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
+ }
return 0;
}
--
2.33.0
Powered by blists - more mailing lists