net: marvell: prestera: add LAG support
authorSerhiy Boiko <serhiy.boiko@plvision.eu>
Thu, 10 Jun 2021 15:43:11 +0000 (18:43 +0300)
committerDavid S. Miller <davem@davemloft.net>
Thu, 10 Jun 2021 21:20:44 +0000 (14:20 -0700)
The following features are supported:

    - LAG basic operations
        - create/delete LAG
        - add/remove a member to LAG
        - enable/disable member in LAG
    - LAG Bridge support
    - LAG VLAN support
    - LAG FDB support

Limitations:

    - Only HASH lag tx type is supported
    - The Hash parameters are not configurable. They are applied
      during the LAG creation stage.
    - Enslaving a port to the LAG device that already has an
      upper device is not supported.

Co-developed-by: Andrii Savka <andrii.savka@plvision.eu>
Signed-off-by: Andrii Savka <andrii.savka@plvision.eu>
Signed-off-by: Serhiy Boiko <serhiy.boiko@plvision.eu>
Co-developed-by: Vadym Kochan <vkochan@marvell.com>
Signed-off-by: Vadym Kochan <vkochan@marvell.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/marvell/prestera/prestera.h
drivers/net/ethernet/marvell/prestera/prestera_hw.c
drivers/net/ethernet/marvell/prestera/prestera_hw.h
drivers/net/ethernet/marvell/prestera/prestera_main.c
drivers/net/ethernet/marvell/prestera/prestera_switchdev.c

index 55aa4bf..ad0f33a 100644 (file)
@@ -60,10 +60,19 @@ struct prestera_port_caps {
        u8 transceiver;
 };
 
+struct prestera_lag {
+       struct net_device *dev;
+       struct list_head members;
+       u16 member_count;
+       u16 lag_id;
+};
+
 struct prestera_port {
        struct net_device *dev;
        struct prestera_switch *sw;
        struct devlink_port dl_port;
+       struct list_head lag_member;
+       struct prestera_lag *lag;
        u32 id;
        u32 hw_id;
        u32 dev_id;
@@ -127,6 +136,12 @@ struct prestera_port_event {
        } data;
 };
 
+enum prestera_fdb_entry_type {
+       PRESTERA_FDB_ENTRY_TYPE_REG_PORT,
+       PRESTERA_FDB_ENTRY_TYPE_LAG,
+       PRESTERA_FDB_ENTRY_TYPE_MAX
+};
+
 enum prestera_fdb_event_id {
        PRESTERA_FDB_EVENT_UNSPEC,
        PRESTERA_FDB_EVENT_LEARNED,
@@ -134,7 +149,11 @@ enum prestera_fdb_event_id {
 };
 
 struct prestera_fdb_event {
-       u32 port_id;
+       enum prestera_fdb_entry_type type;
+       union {
+               u32 port_id;
+               u16 lag_id;
+       } dest;
        u32 vid;
        union {
                u8 mac[ETH_ALEN];
@@ -165,6 +184,9 @@ struct prestera_switch {
        u32 mtu_min;
        u32 mtu_max;
        u8 id;
+       struct prestera_lag *lags;
+       u8 lag_member_max;
+       u8 lag_max;
 };
 
 struct prestera_rxtx_params {
@@ -203,4 +225,10 @@ int prestera_port_pvid_set(struct prestera_port *port, u16 vid);
 
 bool prestera_netdev_check(const struct net_device *dev);
 
+bool prestera_port_is_lag_member(const struct prestera_port *port);
+
+struct prestera_lag *prestera_lag_by_id(struct prestera_switch *sw, u16 id);
+
+u16 prestera_port_lag_id(const struct prestera_port *port);
+
 #endif /* _PRESTERA_H_ */
index 96ce73b..886ce25 100644 (file)
@@ -40,6 +40,11 @@ enum prestera_cmd_type_t {
        PRESTERA_CMD_TYPE_RXTX_INIT = 0x800,
        PRESTERA_CMD_TYPE_RXTX_PORT_INIT = 0x801,
 
+       PRESTERA_CMD_TYPE_LAG_MEMBER_ADD = 0x900,
+       PRESTERA_CMD_TYPE_LAG_MEMBER_DELETE = 0x901,
+       PRESTERA_CMD_TYPE_LAG_MEMBER_ENABLE = 0x902,
+       PRESTERA_CMD_TYPE_LAG_MEMBER_DISABLE = 0x903,
+
        PRESTERA_CMD_TYPE_STP_PORT_SET = 0x1000,
 
        PRESTERA_CMD_TYPE_ACK = 0x10000,
@@ -133,6 +138,12 @@ enum {
        PRESTERA_FC_SYMM_ASYMM,
 };
 
+enum {
+       PRESTERA_HW_FDB_ENTRY_TYPE_REG_PORT = 0,
+       PRESTERA_HW_FDB_ENTRY_TYPE_LAG = 1,
+       PRESTERA_HW_FDB_ENTRY_TYPE_MAX = 2,
+};
+
 struct prestera_fw_event_handler {
        struct list_head list;
        struct rcu_head rcu;
@@ -174,6 +185,8 @@ struct prestera_msg_switch_init_resp {
        u32 port_count;
        u32 mtu_max;
        u8  switch_id;
+       u8  lag_max;
+       u8  lag_member_max;
 };
 
 struct prestera_msg_port_autoneg_param {
@@ -261,8 +274,13 @@ struct prestera_msg_vlan_req {
 struct prestera_msg_fdb_req {
        struct prestera_msg_cmd cmd;
        u8 dest_type;
-       u32 port;
-       u32 dev;
+       union {
+               struct {
+                       u32 port;
+                       u32 dev;
+               };
+               u16 lag_id;
+       } dest;
        u8  mac[ETH_ALEN];
        u16 vid;
        u8  dynamic;
@@ -305,6 +323,13 @@ struct prestera_msg_rxtx_port_req {
        u32 dev;
 };
 
+struct prestera_msg_lag_req {
+       struct prestera_msg_cmd cmd;
+       u32 port;
+       u32 dev;
+       u16 lag_id;
+};
+
 struct prestera_msg_event {
        u16 type;
        u16 id;
@@ -327,7 +352,10 @@ union prestera_msg_event_fdb_param {
 struct prestera_msg_event_fdb {
        struct prestera_msg_event id;
        u8 dest_type;
-       u32 port_id;
+       union {
+               u32 port_id;
+               u16 lag_id;
+       } dest;
        u32 vid;
        union prestera_msg_event_fdb_param param;
 };
@@ -398,7 +426,19 @@ static int prestera_fw_parse_fdb_evt(void *msg, struct prestera_event *evt)
 {
        struct prestera_msg_event_fdb *hw_evt = msg;
 
-       evt->fdb_evt.port_id = hw_evt->port_id;
+       switch (hw_evt->dest_type) {
+       case PRESTERA_HW_FDB_ENTRY_TYPE_REG_PORT:
+               evt->fdb_evt.type = PRESTERA_FDB_ENTRY_TYPE_REG_PORT;
+               evt->fdb_evt.dest.port_id = hw_evt->dest.port_id;
+               break;
+       case PRESTERA_HW_FDB_ENTRY_TYPE_LAG:
+               evt->fdb_evt.type = PRESTERA_FDB_ENTRY_TYPE_LAG;
+               evt->fdb_evt.dest.lag_id = hw_evt->dest.lag_id;
+               break;
+       default:
+               return -EINVAL;
+       }
+
        evt->fdb_evt.vid = hw_evt->vid;
 
        ether_addr_copy(evt->fdb_evt.data.mac, hw_evt->param.mac);
@@ -543,6 +583,8 @@ int prestera_hw_switch_init(struct prestera_switch *sw)
        sw->mtu_min = PRESTERA_MIN_MTU;
        sw->mtu_max = resp.mtu_max;
        sw->id = resp.switch_id;
+       sw->lag_member_max = resp.lag_member_max;
+       sw->lag_max = resp.lag_max;
 
        return 0;
 }
@@ -1150,8 +1192,10 @@ int prestera_hw_fdb_add(struct prestera_port *port, const unsigned char *mac,
                        u16 vid, bool dynamic)
 {
        struct prestera_msg_fdb_req req = {
-               .port = port->hw_id,
-               .dev = port->dev_id,
+               .dest = {
+                       .dev = port->dev_id,
+                       .port = port->hw_id,
+               },
                .vid = vid,
                .dynamic = dynamic,
        };
@@ -1166,8 +1210,10 @@ int prestera_hw_fdb_del(struct prestera_port *port, const unsigned char *mac,
                        u16 vid)
 {
        struct prestera_msg_fdb_req req = {
-               .port = port->hw_id,
-               .dev = port->dev_id,
+               .dest = {
+                       .dev = port->dev_id,
+                       .port = port->hw_id,
+               },
                .vid = vid,
        };
 
@@ -1177,11 +1223,48 @@ int prestera_hw_fdb_del(struct prestera_port *port, const unsigned char *mac,
                            &req.cmd, sizeof(req));
 }
 
+int prestera_hw_lag_fdb_add(struct prestera_switch *sw, u16 lag_id,
+                           const unsigned char *mac, u16 vid, bool dynamic)
+{
+       struct prestera_msg_fdb_req req = {
+               .dest_type = PRESTERA_HW_FDB_ENTRY_TYPE_LAG,
+               .dest = {
+                       .lag_id = lag_id,
+               },
+               .vid = vid,
+               .dynamic = dynamic,
+       };
+
+       ether_addr_copy(req.mac, mac);
+
+       return prestera_cmd(sw, PRESTERA_CMD_TYPE_FDB_ADD,
+                           &req.cmd, sizeof(req));
+}
+
+int prestera_hw_lag_fdb_del(struct prestera_switch *sw, u16 lag_id,
+                           const unsigned char *mac, u16 vid)
+{
+       struct prestera_msg_fdb_req req = {
+               .dest_type = PRESTERA_HW_FDB_ENTRY_TYPE_LAG,
+               .dest = {
+                       .lag_id = lag_id,
+               },
+               .vid = vid,
+       };
+
+       ether_addr_copy(req.mac, mac);
+
+       return prestera_cmd(sw, PRESTERA_CMD_TYPE_FDB_DELETE,
+                           &req.cmd, sizeof(req));
+}
+
 int prestera_hw_fdb_flush_port(struct prestera_port *port, u32 mode)
 {
        struct prestera_msg_fdb_req req = {
-               .port = port->hw_id,
-               .dev = port->dev_id,
+               .dest = {
+                       .dev = port->dev_id,
+                       .port = port->hw_id,
+               },
                .flush_mode = mode,
        };
 
@@ -1204,8 +1287,10 @@ int prestera_hw_fdb_flush_port_vlan(struct prestera_port *port, u16 vid,
                                    u32 mode)
 {
        struct prestera_msg_fdb_req req = {
-               .port = port->hw_id,
-               .dev = port->dev_id,
+               .dest = {
+                       .dev = port->dev_id,
+                       .port = port->hw_id,
+               },
                .vid = vid,
                .flush_mode = mode,
        };
@@ -1214,6 +1299,37 @@ int prestera_hw_fdb_flush_port_vlan(struct prestera_port *port, u16 vid,
                            &req.cmd, sizeof(req));
 }
 
+int prestera_hw_fdb_flush_lag(struct prestera_switch *sw, u16 lag_id,
+                             u32 mode)
+{
+       struct prestera_msg_fdb_req req = {
+               .dest_type = PRESTERA_HW_FDB_ENTRY_TYPE_LAG,
+               .dest = {
+                       .lag_id = lag_id,
+               },
+               .flush_mode = mode,
+       };
+
+       return prestera_cmd(sw, PRESTERA_CMD_TYPE_FDB_FLUSH_PORT,
+                           &req.cmd, sizeof(req));
+}
+
+int prestera_hw_fdb_flush_lag_vlan(struct prestera_switch *sw,
+                                  u16 lag_id, u16 vid, u32 mode)
+{
+       struct prestera_msg_fdb_req req = {
+               .dest_type = PRESTERA_HW_FDB_ENTRY_TYPE_LAG,
+               .dest = {
+                       .lag_id = lag_id,
+               },
+               .vid = vid,
+               .flush_mode = mode,
+       };
+
+       return prestera_cmd(sw, PRESTERA_CMD_TYPE_FDB_FLUSH_PORT_VLAN,
+                           &req.cmd, sizeof(req));
+}
+
 int prestera_hw_bridge_create(struct prestera_switch *sw, u16 *bridge_id)
 {
        struct prestera_msg_bridge_resp resp;
@@ -1295,6 +1411,46 @@ int prestera_hw_rxtx_port_init(struct prestera_port *port)
                            &req.cmd, sizeof(req));
 }
 
+int prestera_hw_lag_member_add(struct prestera_port *port, u16 lag_id)
+{
+       struct prestera_msg_lag_req req = {
+               .port = port->hw_id,
+               .dev = port->dev_id,
+               .lag_id = lag_id,
+       };
+
+       return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_LAG_MEMBER_ADD,
+                           &req.cmd, sizeof(req));
+}
+
+int prestera_hw_lag_member_del(struct prestera_port *port, u16 lag_id)
+{
+       struct prestera_msg_lag_req req = {
+               .port = port->hw_id,
+               .dev = port->dev_id,
+               .lag_id = lag_id,
+       };
+
+       return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_LAG_MEMBER_DELETE,
+                           &req.cmd, sizeof(req));
+}
+
+int prestera_hw_lag_member_enable(struct prestera_port *port, u16 lag_id,
+                                 bool enable)
+{
+       struct prestera_msg_lag_req req = {
+               .port = port->hw_id,
+               .dev = port->dev_id,
+               .lag_id = lag_id,
+       };
+       u32 cmd;
+
+       cmd = enable ? PRESTERA_CMD_TYPE_LAG_MEMBER_ENABLE :
+                       PRESTERA_CMD_TYPE_LAG_MEMBER_DISABLE;
+
+       return prestera_cmd(port->sw, cmd, &req.cmd, sizeof(req));
+}
+
 int prestera_hw_event_handler_register(struct prestera_switch *sw,
                                       enum prestera_event_type type,
                                       prestera_event_cb_t fn,
index e8dd0e2..846bdc0 100644 (file)
@@ -180,4 +180,18 @@ int prestera_hw_rxtx_init(struct prestera_switch *sw,
                          struct prestera_rxtx_params *params);
 int prestera_hw_rxtx_port_init(struct prestera_port *port);
 
+/* LAG API */
+int prestera_hw_lag_member_add(struct prestera_port *port, u16 lag_id);
+int prestera_hw_lag_member_del(struct prestera_port *port, u16 lag_id);
+int prestera_hw_lag_member_enable(struct prestera_port *port, u16 lag_id,
+                                 bool enable);
+int prestera_hw_lag_fdb_add(struct prestera_switch *sw, u16 lag_id,
+                           const unsigned char *mac, u16 vid, bool dynamic);
+int prestera_hw_lag_fdb_del(struct prestera_switch *sw, u16 lag_id,
+                           const unsigned char *mac, u16 vid);
+int prestera_hw_fdb_flush_lag(struct prestera_switch *sw, u16 lag_id,
+                             u32 mode);
+int prestera_hw_fdb_flush_lag_vlan(struct prestera_switch *sw,
+                                  u16 lag_id, u16 vid, u32 mode);
+
 #endif /* _PRESTERA_HW_H_ */
index bee477f..d825fbd 100644 (file)
@@ -8,6 +8,7 @@
 #include <linux/netdev_features.h>
 #include <linux/of.h>
 #include <linux/of_net.h>
+#include <linux/if_vlan.h>
 
 #include "prestera.h"
 #include "prestera_hw.h"
@@ -281,6 +282,7 @@ static int prestera_port_create(struct prestera_switch *sw, u32 id)
 
        INIT_LIST_HEAD(&port->vlans_list);
        port->pvid = PRESTERA_DEFAULT_VID;
+       port->lag = NULL;
        port->dev = dev;
        port->id = id;
        port->sw = sw;
@@ -472,6 +474,149 @@ static int prestera_switch_set_base_mac_addr(struct prestera_switch *sw)
        return prestera_hw_switch_mac_set(sw, sw->base_mac);
 }
 
+struct prestera_lag *prestera_lag_by_id(struct prestera_switch *sw, u16 id)
+{
+       return id < sw->lag_max ? &sw->lags[id] : NULL;
+}
+
+static struct prestera_lag *prestera_lag_by_dev(struct prestera_switch *sw,
+                                               struct net_device *dev)
+{
+       struct prestera_lag *lag;
+       u16 id;
+
+       for (id = 0; id < sw->lag_max; id++) {
+               lag = &sw->lags[id];
+               if (lag->dev == dev)
+                       return lag;
+       }
+
+       return NULL;
+}
+
+static struct prestera_lag *prestera_lag_create(struct prestera_switch *sw,
+                                               struct net_device *lag_dev)
+{
+       struct prestera_lag *lag = NULL;
+       u16 id;
+
+       for (id = 0; id < sw->lag_max; id++) {
+               lag = &sw->lags[id];
+               if (!lag->dev)
+                       break;
+       }
+       if (lag) {
+               INIT_LIST_HEAD(&lag->members);
+               lag->dev = lag_dev;
+       }
+
+       return lag;
+}
+
+static void prestera_lag_destroy(struct prestera_switch *sw,
+                                struct prestera_lag *lag)
+{
+       WARN_ON(!list_empty(&lag->members));
+       lag->member_count = 0;
+       lag->dev = NULL;
+}
+
+static int prestera_lag_port_add(struct prestera_port *port,
+                                struct net_device *lag_dev)
+{
+       struct prestera_switch *sw = port->sw;
+       struct prestera_lag *lag;
+       int err;
+
+       lag = prestera_lag_by_dev(sw, lag_dev);
+       if (!lag) {
+               lag = prestera_lag_create(sw, lag_dev);
+               if (!lag)
+                       return -ENOSPC;
+       }
+
+       if (lag->member_count >= sw->lag_member_max)
+               return -ENOSPC;
+
+       err = prestera_hw_lag_member_add(port, lag->lag_id);
+       if (err) {
+               if (!lag->member_count)
+                       prestera_lag_destroy(sw, lag);
+               return err;
+       }
+
+       list_add(&port->lag_member, &lag->members);
+       lag->member_count++;
+       port->lag = lag;
+
+       return 0;
+}
+
+static int prestera_lag_port_del(struct prestera_port *port)
+{
+       struct prestera_switch *sw = port->sw;
+       struct prestera_lag *lag = port->lag;
+       int err;
+
+       if (!lag || !lag->member_count)
+               return -EINVAL;
+
+       err = prestera_hw_lag_member_del(port, lag->lag_id);
+       if (err)
+               return err;
+
+       list_del(&port->lag_member);
+       lag->member_count--;
+       port->lag = NULL;
+
+       if (netif_is_bridge_port(lag->dev)) {
+               struct net_device *br_dev;
+
+               br_dev = netdev_master_upper_dev_get(lag->dev);
+
+               prestera_bridge_port_leave(br_dev, port);
+       }
+
+       if (!lag->member_count)
+               prestera_lag_destroy(sw, lag);
+
+       return 0;
+}
+
+bool prestera_port_is_lag_member(const struct prestera_port *port)
+{
+       return !!port->lag;
+}
+
+u16 prestera_port_lag_id(const struct prestera_port *port)
+{
+       return port->lag->lag_id;
+}
+
+static int prestera_lag_init(struct prestera_switch *sw)
+{
+       u16 id;
+
+       sw->lags = kcalloc(sw->lag_max, sizeof(*sw->lags), GFP_KERNEL);
+       if (!sw->lags)
+               return -ENOMEM;
+
+       for (id = 0; id < sw->lag_max; id++)
+               sw->lags[id].lag_id = id;
+
+       return 0;
+}
+
+static void prestera_lag_fini(struct prestera_switch *sw)
+{
+       u8 idx;
+
+       for (idx = 0; idx < sw->lag_max; idx++)
+               WARN_ON(sw->lags[idx].member_count);
+
+       kfree(sw->lags);
+}
+
 bool prestera_netdev_check(const struct net_device *dev)
 {
        return dev->netdev_ops == &prestera_netdev_ops;
@@ -505,7 +650,39 @@ struct prestera_port *prestera_port_dev_lower_find(struct net_device *dev)
        return port;
 }
 
-static int prestera_netdev_port_event(struct net_device *dev,
+static int prestera_netdev_port_lower_event(struct net_device *dev,
+                                           unsigned long event, void *ptr)
+{
+       struct netdev_notifier_changelowerstate_info *info = ptr;
+       struct netdev_lag_lower_state_info *lower_state_info;
+       struct prestera_port *port = netdev_priv(dev);
+       bool enabled;
+
+       if (!netif_is_lag_port(dev))
+               return 0;
+       if (!prestera_port_is_lag_member(port))
+               return 0;
+
+       lower_state_info = info->lower_state_info;
+       enabled = lower_state_info->link_up && lower_state_info->tx_enabled;
+
+       return prestera_hw_lag_member_enable(port, port->lag->lag_id, enabled);
+}
+
+static bool prestera_lag_master_check(struct net_device *lag_dev,
+                                     struct netdev_lag_upper_info *info,
+                                     struct netlink_ext_ack *ext_ack)
+{
+       if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) {
+               NL_SET_ERR_MSG_MOD(ext_ack, "Unsupported LAG Tx type");
+               return false;
+       }
+
+       return true;
+}
+
+static int prestera_netdev_port_event(struct net_device *lower,
+                                     struct net_device *dev,
                                      unsigned long event, void *ptr)
 {
        struct netdev_notifier_changeupper_info *info = ptr;
@@ -518,7 +695,8 @@ static int prestera_netdev_port_event(struct net_device *dev,
 
        switch (event) {
        case NETDEV_PRECHANGEUPPER:
-               if (!netif_is_bridge_master(upper)) {
+               if (!netif_is_bridge_master(upper) &&
+                   !netif_is_lag_master(upper)) {
                        NL_SET_ERR_MSG_MOD(extack, "Unknown upper device type");
                        return -EINVAL;
                }
@@ -530,6 +708,21 @@ static int prestera_netdev_port_event(struct net_device *dev,
                        NL_SET_ERR_MSG_MOD(extack, "Upper device is already enslaved");
                        return -EINVAL;
                }
+
+               if (netif_is_lag_master(upper) &&
+                   !prestera_lag_master_check(upper, info->upper_info, extack))
+                       return -EOPNOTSUPP;
+               if (netif_is_lag_master(upper) && vlan_uses_dev(dev)) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "Master device is a LAG master and port has a VLAN");
+                       return -EINVAL;
+               }
+               if (netif_is_lag_port(dev) && is_vlan_dev(upper) &&
+                   !netif_is_lag_master(vlan_dev_real_dev(upper))) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "Can not put a VLAN on a LAG port");
+                       return -EINVAL;
+               }
                break;
 
        case NETDEV_CHANGEUPPER:
@@ -538,8 +731,35 @@ static int prestera_netdev_port_event(struct net_device *dev,
                                return prestera_bridge_port_join(upper, port);
                        else
                                prestera_bridge_port_leave(upper, port);
+               } else if (netif_is_lag_master(upper)) {
+                       if (info->linking)
+                               return prestera_lag_port_add(port, upper);
+                       else
+                               prestera_lag_port_del(port);
                }
                break;
+
+       case NETDEV_CHANGELOWERSTATE:
+               return prestera_netdev_port_lower_event(dev, event, ptr);
+       }
+
+       return 0;
+}
+
+static int prestera_netdevice_lag_event(struct net_device *lag_dev,
+                                       unsigned long event, void *ptr)
+{
+       struct net_device *dev;
+       struct list_head *iter;
+       int err;
+
+       netdev_for_each_lower_dev(lag_dev, dev, iter) {
+               if (prestera_netdev_check(dev)) {
+                       err = prestera_netdev_port_event(lag_dev, dev, event,
+                                                        ptr);
+                       if (err)
+                               return err;
+               }
        }
 
        return 0;
@@ -552,7 +772,9 @@ static int prestera_netdev_event_handler(struct notifier_block *nb,
        int err = 0;
 
        if (prestera_netdev_check(dev))
-               err = prestera_netdev_port_event(dev, event, ptr);
+               err = prestera_netdev_port_event(dev, dev, event, ptr);
+       else if (netif_is_lag_master(dev))
+               err = prestera_netdevice_lag_event(dev, event, ptr);
 
        return notifier_from_errno(err);
 }
@@ -606,6 +828,10 @@ static int prestera_switch_init(struct prestera_switch *sw)
        if (err)
                goto err_dl_register;
 
+       err = prestera_lag_init(sw);
+       if (err)
+               goto err_lag_init;
+
        err = prestera_create_ports(sw);
        if (err)
                goto err_ports_create;
@@ -613,6 +839,8 @@ static int prestera_switch_init(struct prestera_switch *sw)
        return 0;
 
 err_ports_create:
+       prestera_lag_fini(sw);
+err_lag_init:
        prestera_devlink_unregister(sw);
 err_dl_register:
        prestera_event_handlers_unregister(sw);
@@ -630,6 +858,7 @@ err_swdev_register:
 static void prestera_switch_fini(struct prestera_switch *sw)
 {
        prestera_destroy_ports(sw);
+       prestera_lag_fini(sw);
        prestera_devlink_unregister(sw);
        prestera_event_handlers_unregister(sw);
        prestera_rxtx_switch_fini(sw);
index 0afbd48..74b81b4 100644 (file)
@@ -180,6 +180,45 @@ err_port_vlan_alloc:
        return ERR_PTR(err);
 }
 
+static int prestera_fdb_add(struct prestera_port *port,
+                           const unsigned char *mac, u16 vid, bool dynamic)
+{
+       if (prestera_port_is_lag_member(port))
+               return prestera_hw_lag_fdb_add(port->sw, prestera_port_lag_id(port),
+                                             mac, vid, dynamic);
+
+       return prestera_hw_fdb_add(port, mac, vid, dynamic);
+}
+
+static int prestera_fdb_del(struct prestera_port *port,
+                           const unsigned char *mac, u16 vid)
+{
+       if (prestera_port_is_lag_member(port))
+               return prestera_hw_lag_fdb_del(port->sw, prestera_port_lag_id(port),
+                                             mac, vid);
+       else
+               return prestera_hw_fdb_del(port, mac, vid);
+}
+
+static int prestera_fdb_flush_port_vlan(struct prestera_port *port, u16 vid,
+                                       u32 mode)
+{
+       if (prestera_port_is_lag_member(port))
+               return prestera_hw_fdb_flush_lag_vlan(port->sw, prestera_port_lag_id(port),
+                                                     vid, mode);
+       else
+               return prestera_hw_fdb_flush_port_vlan(port, vid, mode);
+}
+
+static int prestera_fdb_flush_port(struct prestera_port *port, u32 mode)
+{
+       if (prestera_port_is_lag_member(port))
+               return prestera_hw_fdb_flush_lag(port->sw, prestera_port_lag_id(port),
+                                                mode);
+       else
+               return prestera_hw_fdb_flush_port(port, mode);
+}
+
 static void
 prestera_port_vlan_bridge_leave(struct prestera_port_vlan *port_vlan)
 {
@@ -199,11 +238,11 @@ prestera_port_vlan_bridge_leave(struct prestera_port_vlan *port_vlan)
        last_port = port_count == 1;
 
        if (last_vlan)
-               prestera_hw_fdb_flush_port(port, fdb_flush_mode);
+               prestera_fdb_flush_port(port, fdb_flush_mode);
        else if (last_port)
                prestera_hw_fdb_flush_vlan(port->sw, vid, fdb_flush_mode);
        else
-               prestera_hw_fdb_flush_port_vlan(port, vid, fdb_flush_mode);
+               prestera_fdb_flush_port_vlan(port, vid, fdb_flush_mode);
 
        list_del(&port_vlan->br_vlan_head);
        prestera_bridge_vlan_put(br_vlan);
@@ -312,11 +351,29 @@ __prestera_bridge_port_by_dev(struct prestera_bridge *bridge,
        return NULL;
 }
 
+static int prestera_match_upper_bridge_dev(struct net_device *dev,
+                                          struct netdev_nested_priv *priv)
+{
+       if (netif_is_bridge_master(dev))
+               priv->data = dev;
+
+       return 0;
+}
+
+static struct net_device *prestera_get_upper_bridge_dev(struct net_device *dev)
+{
+       struct netdev_nested_priv priv = { };
+
+       netdev_walk_all_upper_dev_rcu(dev, prestera_match_upper_bridge_dev,
+                                     &priv);
+       return priv.data;
+}
+
 static struct prestera_bridge_port *
 prestera_bridge_port_by_dev(struct prestera_switchdev *swdev,
                            struct net_device *dev)
 {
-       struct net_device *br_dev = netdev_master_upper_dev_get(dev);
+       struct net_device *br_dev = prestera_get_upper_bridge_dev(dev);
        struct prestera_bridge *bridge;
 
        if (!br_dev)
@@ -723,9 +780,9 @@ static int prestera_port_fdb_set(struct prestera_port *port,
                vid = bridge->bridge_id;
 
        if (adding)
-               err = prestera_hw_fdb_add(port, fdb_info->addr, vid, false);
+               err = prestera_fdb_add(port, fdb_info->addr, vid, false);
        else
-               err = prestera_hw_fdb_del(port, fdb_info->addr, vid);
+               err = prestera_fdb_del(port, fdb_info->addr, vid);
 
        return err;
 }
@@ -962,15 +1019,15 @@ static int prestera_port_vlans_add(struct prestera_port *port,
 {
        bool flag_untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
        bool flag_pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
-       struct net_device *dev = vlan->obj.orig_dev;
+       struct net_device *orig_dev = vlan->obj.orig_dev;
        struct prestera_bridge_port *br_port;
        struct prestera_switch *sw = port->sw;
        struct prestera_bridge *bridge;
 
-       if (netif_is_bridge_master(dev))
+       if (netif_is_bridge_master(orig_dev))
                return 0;
 
-       br_port = prestera_bridge_port_by_dev(sw->swdev, dev);
+       br_port = prestera_bridge_port_by_dev(sw->swdev, port->dev);
        if (WARN_ON(!br_port))
                return -EINVAL;
 
@@ -1002,14 +1059,14 @@ static int prestera_port_obj_add(struct net_device *dev,
 static int prestera_port_vlans_del(struct prestera_port *port,
                                   const struct switchdev_obj_port_vlan *vlan)
 {
-       struct net_device *dev = vlan->obj.orig_dev;
+       struct net_device *orig_dev = vlan->obj.orig_dev;
        struct prestera_bridge_port *br_port;
        struct prestera_switch *sw = port->sw;
 
-       if (netif_is_bridge_master(dev))
+       if (netif_is_bridge_master(orig_dev))
                return -EOPNOTSUPP;
 
-       br_port = prestera_bridge_port_by_dev(sw->swdev, dev);
+       br_port = prestera_bridge_port_by_dev(sw->swdev, port->dev);
        if (WARN_ON(!br_port))
                return -EINVAL;
 
@@ -1067,10 +1124,26 @@ static void prestera_fdb_event(struct prestera_switch *sw,
                               struct prestera_event *evt, void *arg)
 {
        struct switchdev_notifier_fdb_info info;
+       struct net_device *dev = NULL;
        struct prestera_port *port;
+       struct prestera_lag *lag;
 
-       port = prestera_find_port(sw, evt->fdb_evt.port_id);
-       if (!port)
+       switch (evt->fdb_evt.type) {
+       case PRESTERA_FDB_ENTRY_TYPE_REG_PORT:
+               port = prestera_find_port(sw, evt->fdb_evt.dest.port_id);
+               if (port)
+                       dev = port->dev;
+               break;
+       case PRESTERA_FDB_ENTRY_TYPE_LAG:
+               lag = prestera_lag_by_id(sw, evt->fdb_evt.dest.lag_id);
+               if (lag)
+                       dev = lag->dev;
+               break;
+       default:
+               return;
+       }
+
+       if (!dev)
                return;
 
        info.addr = evt->fdb_evt.data.mac;
@@ -1082,11 +1155,11 @@ static void prestera_fdb_event(struct prestera_switch *sw,
        switch (evt->id) {
        case PRESTERA_FDB_EVENT_LEARNED:
                call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
-                                        port->dev, &info.info, NULL);
+                                        dev, &info.info, NULL);
                break;
        case PRESTERA_FDB_EVENT_AGED:
                call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
-                                        port->dev, &info.info, NULL);
+                                        dev, &info.info, NULL);
                break;
        }