Commit 255213ca authored by Serhiy Boiko's avatar Serhiy Boiko Committed by David S. Miller

net: marvell: prestera: add LAG support

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: default avatarAndrii Savka <andrii.savka@plvision.eu>
Signed-off-by: default avatarAndrii Savka <andrii.savka@plvision.eu>
Signed-off-by: default avatarSerhiy Boiko <serhiy.boiko@plvision.eu>
Co-developed-by: default avatarVadym Kochan <vkochan@marvell.com>
Signed-off-by: default avatarVadym Kochan <vkochan@marvell.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 82bbaa05
...@@ -60,10 +60,19 @@ struct prestera_port_caps { ...@@ -60,10 +60,19 @@ struct prestera_port_caps {
u8 transceiver; u8 transceiver;
}; };
struct prestera_lag {
struct net_device *dev;
struct list_head members;
u16 member_count;
u16 lag_id;
};
struct prestera_port { struct prestera_port {
struct net_device *dev; struct net_device *dev;
struct prestera_switch *sw; struct prestera_switch *sw;
struct devlink_port dl_port; struct devlink_port dl_port;
struct list_head lag_member;
struct prestera_lag *lag;
u32 id; u32 id;
u32 hw_id; u32 hw_id;
u32 dev_id; u32 dev_id;
...@@ -127,6 +136,12 @@ struct prestera_port_event { ...@@ -127,6 +136,12 @@ struct prestera_port_event {
} data; } 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 { enum prestera_fdb_event_id {
PRESTERA_FDB_EVENT_UNSPEC, PRESTERA_FDB_EVENT_UNSPEC,
PRESTERA_FDB_EVENT_LEARNED, PRESTERA_FDB_EVENT_LEARNED,
...@@ -134,7 +149,11 @@ enum prestera_fdb_event_id { ...@@ -134,7 +149,11 @@ enum prestera_fdb_event_id {
}; };
struct prestera_fdb_event { struct prestera_fdb_event {
enum prestera_fdb_entry_type type;
union {
u32 port_id; u32 port_id;
u16 lag_id;
} dest;
u32 vid; u32 vid;
union { union {
u8 mac[ETH_ALEN]; u8 mac[ETH_ALEN];
...@@ -165,6 +184,9 @@ struct prestera_switch { ...@@ -165,6 +184,9 @@ struct prestera_switch {
u32 mtu_min; u32 mtu_min;
u32 mtu_max; u32 mtu_max;
u8 id; u8 id;
struct prestera_lag *lags;
u8 lag_member_max;
u8 lag_max;
}; };
struct prestera_rxtx_params { struct prestera_rxtx_params {
...@@ -203,4 +225,10 @@ int prestera_port_pvid_set(struct prestera_port *port, u16 vid); ...@@ -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_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_ */ #endif /* _PRESTERA_H_ */
...@@ -40,6 +40,11 @@ enum prestera_cmd_type_t { ...@@ -40,6 +40,11 @@ enum prestera_cmd_type_t {
PRESTERA_CMD_TYPE_RXTX_INIT = 0x800, PRESTERA_CMD_TYPE_RXTX_INIT = 0x800,
PRESTERA_CMD_TYPE_RXTX_PORT_INIT = 0x801, 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_STP_PORT_SET = 0x1000,
PRESTERA_CMD_TYPE_ACK = 0x10000, PRESTERA_CMD_TYPE_ACK = 0x10000,
...@@ -133,6 +138,12 @@ enum { ...@@ -133,6 +138,12 @@ enum {
PRESTERA_FC_SYMM_ASYMM, 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 prestera_fw_event_handler {
struct list_head list; struct list_head list;
struct rcu_head rcu; struct rcu_head rcu;
...@@ -174,6 +185,8 @@ struct prestera_msg_switch_init_resp { ...@@ -174,6 +185,8 @@ struct prestera_msg_switch_init_resp {
u32 port_count; u32 port_count;
u32 mtu_max; u32 mtu_max;
u8 switch_id; u8 switch_id;
u8 lag_max;
u8 lag_member_max;
}; };
struct prestera_msg_port_autoneg_param { struct prestera_msg_port_autoneg_param {
...@@ -261,8 +274,13 @@ struct prestera_msg_vlan_req { ...@@ -261,8 +274,13 @@ struct prestera_msg_vlan_req {
struct prestera_msg_fdb_req { struct prestera_msg_fdb_req {
struct prestera_msg_cmd cmd; struct prestera_msg_cmd cmd;
u8 dest_type; u8 dest_type;
union {
struct {
u32 port; u32 port;
u32 dev; u32 dev;
};
u16 lag_id;
} dest;
u8 mac[ETH_ALEN]; u8 mac[ETH_ALEN];
u16 vid; u16 vid;
u8 dynamic; u8 dynamic;
...@@ -305,6 +323,13 @@ struct prestera_msg_rxtx_port_req { ...@@ -305,6 +323,13 @@ struct prestera_msg_rxtx_port_req {
u32 dev; u32 dev;
}; };
struct prestera_msg_lag_req {
struct prestera_msg_cmd cmd;
u32 port;
u32 dev;
u16 lag_id;
};
struct prestera_msg_event { struct prestera_msg_event {
u16 type; u16 type;
u16 id; u16 id;
...@@ -327,7 +352,10 @@ union prestera_msg_event_fdb_param { ...@@ -327,7 +352,10 @@ union prestera_msg_event_fdb_param {
struct prestera_msg_event_fdb { struct prestera_msg_event_fdb {
struct prestera_msg_event id; struct prestera_msg_event id;
u8 dest_type; u8 dest_type;
union {
u32 port_id; u32 port_id;
u16 lag_id;
} dest;
u32 vid; u32 vid;
union prestera_msg_event_fdb_param param; union prestera_msg_event_fdb_param param;
}; };
...@@ -398,7 +426,19 @@ static int prestera_fw_parse_fdb_evt(void *msg, struct prestera_event *evt) ...@@ -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; 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; evt->fdb_evt.vid = hw_evt->vid;
ether_addr_copy(evt->fdb_evt.data.mac, hw_evt->param.mac); 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) ...@@ -543,6 +583,8 @@ int prestera_hw_switch_init(struct prestera_switch *sw)
sw->mtu_min = PRESTERA_MIN_MTU; sw->mtu_min = PRESTERA_MIN_MTU;
sw->mtu_max = resp.mtu_max; sw->mtu_max = resp.mtu_max;
sw->id = resp.switch_id; sw->id = resp.switch_id;
sw->lag_member_max = resp.lag_member_max;
sw->lag_max = resp.lag_max;
return 0; return 0;
} }
...@@ -1150,8 +1192,10 @@ int prestera_hw_fdb_add(struct prestera_port *port, const unsigned char *mac, ...@@ -1150,8 +1192,10 @@ int prestera_hw_fdb_add(struct prestera_port *port, const unsigned char *mac,
u16 vid, bool dynamic) u16 vid, bool dynamic)
{ {
struct prestera_msg_fdb_req req = { struct prestera_msg_fdb_req req = {
.port = port->hw_id, .dest = {
.dev = port->dev_id, .dev = port->dev_id,
.port = port->hw_id,
},
.vid = vid, .vid = vid,
.dynamic = dynamic, .dynamic = dynamic,
}; };
...@@ -1166,8 +1210,10 @@ int prestera_hw_fdb_del(struct prestera_port *port, const unsigned char *mac, ...@@ -1166,8 +1210,10 @@ int prestera_hw_fdb_del(struct prestera_port *port, const unsigned char *mac,
u16 vid) u16 vid)
{ {
struct prestera_msg_fdb_req req = { struct prestera_msg_fdb_req req = {
.port = port->hw_id, .dest = {
.dev = port->dev_id, .dev = port->dev_id,
.port = port->hw_id,
},
.vid = vid, .vid = vid,
}; };
...@@ -1177,11 +1223,48 @@ int prestera_hw_fdb_del(struct prestera_port *port, const unsigned char *mac, ...@@ -1177,11 +1223,48 @@ int prestera_hw_fdb_del(struct prestera_port *port, const unsigned char *mac,
&req.cmd, sizeof(req)); &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) int prestera_hw_fdb_flush_port(struct prestera_port *port, u32 mode)
{ {
struct prestera_msg_fdb_req req = { struct prestera_msg_fdb_req req = {
.port = port->hw_id, .dest = {
.dev = port->dev_id, .dev = port->dev_id,
.port = port->hw_id,
},
.flush_mode = mode, .flush_mode = mode,
}; };
...@@ -1204,8 +1287,10 @@ int prestera_hw_fdb_flush_port_vlan(struct prestera_port *port, u16 vid, ...@@ -1204,8 +1287,10 @@ int prestera_hw_fdb_flush_port_vlan(struct prestera_port *port, u16 vid,
u32 mode) u32 mode)
{ {
struct prestera_msg_fdb_req req = { struct prestera_msg_fdb_req req = {
.port = port->hw_id, .dest = {
.dev = port->dev_id, .dev = port->dev_id,
.port = port->hw_id,
},
.vid = vid, .vid = vid,
.flush_mode = mode, .flush_mode = mode,
}; };
...@@ -1214,6 +1299,37 @@ int prestera_hw_fdb_flush_port_vlan(struct prestera_port *port, u16 vid, ...@@ -1214,6 +1299,37 @@ int prestera_hw_fdb_flush_port_vlan(struct prestera_port *port, u16 vid,
&req.cmd, sizeof(req)); &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) int prestera_hw_bridge_create(struct prestera_switch *sw, u16 *bridge_id)
{ {
struct prestera_msg_bridge_resp resp; struct prestera_msg_bridge_resp resp;
...@@ -1295,6 +1411,46 @@ int prestera_hw_rxtx_port_init(struct prestera_port *port) ...@@ -1295,6 +1411,46 @@ int prestera_hw_rxtx_port_init(struct prestera_port *port)
&req.cmd, sizeof(req)); &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, int prestera_hw_event_handler_register(struct prestera_switch *sw,
enum prestera_event_type type, enum prestera_event_type type,
prestera_event_cb_t fn, prestera_event_cb_t fn,
......
...@@ -180,4 +180,18 @@ int prestera_hw_rxtx_init(struct prestera_switch *sw, ...@@ -180,4 +180,18 @@ int prestera_hw_rxtx_init(struct prestera_switch *sw,
struct prestera_rxtx_params *params); struct prestera_rxtx_params *params);
int prestera_hw_rxtx_port_init(struct prestera_port *port); 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_ */ #endif /* _PRESTERA_HW_H_ */
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <linux/netdev_features.h> #include <linux/netdev_features.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_net.h> #include <linux/of_net.h>
#include <linux/if_vlan.h>
#include "prestera.h" #include "prestera.h"
#include "prestera_hw.h" #include "prestera_hw.h"
...@@ -281,6 +282,7 @@ static int prestera_port_create(struct prestera_switch *sw, u32 id) ...@@ -281,6 +282,7 @@ static int prestera_port_create(struct prestera_switch *sw, u32 id)
INIT_LIST_HEAD(&port->vlans_list); INIT_LIST_HEAD(&port->vlans_list);
port->pvid = PRESTERA_DEFAULT_VID; port->pvid = PRESTERA_DEFAULT_VID;
port->lag = NULL;
port->dev = dev; port->dev = dev;
port->id = id; port->id = id;
port->sw = sw; port->sw = sw;
...@@ -472,6 +474,149 @@ static int prestera_switch_set_base_mac_addr(struct prestera_switch *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); 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) bool prestera_netdev_check(const struct net_device *dev)
{ {
return dev->netdev_ops == &prestera_netdev_ops; return dev->netdev_ops == &prestera_netdev_ops;
...@@ -505,7 +650,39 @@ struct prestera_port *prestera_port_dev_lower_find(struct net_device *dev) ...@@ -505,7 +650,39 @@ struct prestera_port *prestera_port_dev_lower_find(struct net_device *dev)
return port; 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) unsigned long event, void *ptr)
{ {
struct netdev_notifier_changeupper_info *info = ptr; struct netdev_notifier_changeupper_info *info = ptr;
...@@ -518,7 +695,8 @@ static int prestera_netdev_port_event(struct net_device *dev, ...@@ -518,7 +695,8 @@ static int prestera_netdev_port_event(struct net_device *dev,
switch (event) { switch (event) {
case NETDEV_PRECHANGEUPPER: 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"); NL_SET_ERR_MSG_MOD(extack, "Unknown upper device type");
return -EINVAL; return -EINVAL;
} }
...@@ -530,6 +708,21 @@ static int prestera_netdev_port_event(struct net_device *dev, ...@@ -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"); NL_SET_ERR_MSG_MOD(extack, "Upper device is already enslaved");
return -EINVAL; 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; break;
case NETDEV_CHANGEUPPER: case NETDEV_CHANGEUPPER:
...@@ -538,8 +731,35 @@ static int prestera_netdev_port_event(struct net_device *dev, ...@@ -538,8 +731,35 @@ static int prestera_netdev_port_event(struct net_device *dev,
return prestera_bridge_port_join(upper, port); return prestera_bridge_port_join(upper, port);
else else
prestera_bridge_port_leave(upper, port); 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; 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; return 0;
...@@ -552,7 +772,9 @@ static int prestera_netdev_event_handler(struct notifier_block *nb, ...@@ -552,7 +772,9 @@ static int prestera_netdev_event_handler(struct notifier_block *nb,
int err = 0; int err = 0;
if (prestera_netdev_check(dev)) 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); return notifier_from_errno(err);
} }
...@@ -606,6 +828,10 @@ static int prestera_switch_init(struct prestera_switch *sw) ...@@ -606,6 +828,10 @@ static int prestera_switch_init(struct prestera_switch *sw)
if (err) if (err)
goto err_dl_register; goto err_dl_register;
err = prestera_lag_init(sw);
if (err)
goto err_lag_init;
err = prestera_create_ports(sw); err = prestera_create_ports(sw);
if (err) if (err)
goto err_ports_create; goto err_ports_create;
...@@ -613,6 +839,8 @@ static int prestera_switch_init(struct prestera_switch *sw) ...@@ -613,6 +839,8 @@ static int prestera_switch_init(struct prestera_switch *sw)
return 0; return 0;
err_ports_create: err_ports_create:
prestera_lag_fini(sw);
err_lag_init:
prestera_devlink_unregister(sw); prestera_devlink_unregister(sw);
err_dl_register: err_dl_register:
prestera_event_handlers_unregister(sw); prestera_event_handlers_unregister(sw);
...@@ -630,6 +858,7 @@ static int prestera_switch_init(struct prestera_switch *sw) ...@@ -630,6 +858,7 @@ static int prestera_switch_init(struct prestera_switch *sw)
static void prestera_switch_fini(struct prestera_switch *sw) static void prestera_switch_fini(struct prestera_switch *sw)
{ {
prestera_destroy_ports(sw); prestera_destroy_ports(sw);
prestera_lag_fini(sw);
prestera_devlink_unregister(sw); prestera_devlink_unregister(sw);
prestera_event_handlers_unregister(sw); prestera_event_handlers_unregister(sw);
prestera_rxtx_switch_fini(sw); prestera_rxtx_switch_fini(sw);
......
...@@ -180,6 +180,45 @@ prestera_port_vlan_create(struct prestera_port *port, u16 vid, bool untagged) ...@@ -180,6 +180,45 @@ prestera_port_vlan_create(struct prestera_port *port, u16 vid, bool untagged)
return ERR_PTR(err); 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 static void
prestera_port_vlan_bridge_leave(struct prestera_port_vlan *port_vlan) 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) ...@@ -199,11 +238,11 @@ prestera_port_vlan_bridge_leave(struct prestera_port_vlan *port_vlan)
last_port = port_count == 1; last_port = port_count == 1;
if (last_vlan) if (last_vlan)
prestera_hw_fdb_flush_port(port, fdb_flush_mode); prestera_fdb_flush_port(port, fdb_flush_mode);
else if (last_port) else if (last_port)
prestera_hw_fdb_flush_vlan(port->sw, vid, fdb_flush_mode); prestera_hw_fdb_flush_vlan(port->sw, vid, fdb_flush_mode);
else 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); list_del(&port_vlan->br_vlan_head);
prestera_bridge_vlan_put(br_vlan); prestera_bridge_vlan_put(br_vlan);
...@@ -312,11 +351,29 @@ __prestera_bridge_port_by_dev(struct prestera_bridge *bridge, ...@@ -312,11 +351,29 @@ __prestera_bridge_port_by_dev(struct prestera_bridge *bridge,
return NULL; 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 * static struct prestera_bridge_port *
prestera_bridge_port_by_dev(struct prestera_switchdev *swdev, prestera_bridge_port_by_dev(struct prestera_switchdev *swdev,
struct net_device *dev) 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; struct prestera_bridge *bridge;
if (!br_dev) if (!br_dev)
...@@ -723,9 +780,9 @@ static int prestera_port_fdb_set(struct prestera_port *port, ...@@ -723,9 +780,9 @@ static int prestera_port_fdb_set(struct prestera_port *port,
vid = bridge->bridge_id; vid = bridge->bridge_id;
if (adding) if (adding)
err = prestera_hw_fdb_add(port, fdb_info->addr, vid, false); err = prestera_fdb_add(port, fdb_info->addr, vid, false);
else else
err = prestera_hw_fdb_del(port, fdb_info->addr, vid); err = prestera_fdb_del(port, fdb_info->addr, vid);
return err; return err;
} }
...@@ -962,15 +1019,15 @@ static int prestera_port_vlans_add(struct prestera_port *port, ...@@ -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_untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
bool flag_pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; 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_bridge_port *br_port;
struct prestera_switch *sw = port->sw; struct prestera_switch *sw = port->sw;
struct prestera_bridge *bridge; struct prestera_bridge *bridge;
if (netif_is_bridge_master(dev)) if (netif_is_bridge_master(orig_dev))
return 0; 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)) if (WARN_ON(!br_port))
return -EINVAL; return -EINVAL;
...@@ -1002,14 +1059,14 @@ static int prestera_port_obj_add(struct net_device *dev, ...@@ -1002,14 +1059,14 @@ static int prestera_port_obj_add(struct net_device *dev,
static int prestera_port_vlans_del(struct prestera_port *port, static int prestera_port_vlans_del(struct prestera_port *port,
const struct switchdev_obj_port_vlan *vlan) 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_bridge_port *br_port;
struct prestera_switch *sw = port->sw; struct prestera_switch *sw = port->sw;
if (netif_is_bridge_master(dev)) if (netif_is_bridge_master(orig_dev))
return -EOPNOTSUPP; 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)) if (WARN_ON(!br_port))
return -EINVAL; return -EINVAL;
...@@ -1067,10 +1124,26 @@ static void prestera_fdb_event(struct prestera_switch *sw, ...@@ -1067,10 +1124,26 @@ static void prestera_fdb_event(struct prestera_switch *sw,
struct prestera_event *evt, void *arg) struct prestera_event *evt, void *arg)
{ {
struct switchdev_notifier_fdb_info info; struct switchdev_notifier_fdb_info info;
struct net_device *dev = NULL;
struct prestera_port *port; struct prestera_port *port;
struct prestera_lag *lag;
port = prestera_find_port(sw, evt->fdb_evt.port_id); switch (evt->fdb_evt.type) {
if (!port) 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; return;
info.addr = evt->fdb_evt.data.mac; info.addr = evt->fdb_evt.data.mac;
...@@ -1082,11 +1155,11 @@ static void prestera_fdb_event(struct prestera_switch *sw, ...@@ -1082,11 +1155,11 @@ static void prestera_fdb_event(struct prestera_switch *sw,
switch (evt->id) { switch (evt->id) {
case PRESTERA_FDB_EVENT_LEARNED: case PRESTERA_FDB_EVENT_LEARNED:
call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
port->dev, &info.info, NULL); dev, &info.info, NULL);
break; break;
case PRESTERA_FDB_EVENT_AGED: case PRESTERA_FDB_EVENT_AGED:
call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE, call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
port->dev, &info.info, NULL); dev, &info.info, NULL);
break; break;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment