diff options
author | David S. Miller <davem@davemloft.net> | 2022-07-13 12:14:05 +0100 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2022-07-13 12:14:05 +0100 |
commit | dd517237c3197428336ec12637831e8473865874 (patch) | |
tree | 55a41dd41676b25a47a7f1ef1fb9f8c323a021f8 | |
parent | b09ab9c92e5077a00602dbff606ea518f379303c (diff) | |
parent | deef0d6afe84eb16a30adfa5356ddcc5db204f13 (diff) |
Merge branch 'prestera-mdb-offload'
Oleksandr Mazur says:
====================
net: marvell: prestera: add MDB offloading support
This patch series adds support for the MDB handling for the marvell
Prestera Driver. It's used to propagate IGMP groups registered within
the Kernel to the underlying HW (offload registered groups).
Features:
- define (and implement) main internal MDB-related objects;
- define (and implement) main HW APIs for MDB handling;
- add MDB handling support for both regular ports as well as Bond
interfaces;
- Mirrored behavior of Bridge driver upon multicast router appearance
(all traffic flooded when there's no mcast router; mcast router
receives all mcast traffic, and only group-specific registered mcast
traffic is being received by ports who've explicitly joined any group
when there's a registered mcast router);
- Reworked prestera driver bridge flags (especially multicast)
setting - thus making it possible to react over mcast disabled messages
properly by offloading this state to the underlying HW
(disabling multicast flooding);
Limitations:
- Not full (partial) IGMPv3 support (due to limited switchdev
notification capabilities:
MDB events are being propagated only with a single MAC entry,
while IGMPv3 has Group-Specific requests and responses);
- It's possible that multiple IP groups would receive traffic from
other groups, as MDB events are being propagated with a single MAC
entry, which makes it possible to map a few IPs over same MAC;
Co-developed-by: Yevhen Orlov <yevhen.orlov@plvision.eu>
Signed-off-by: Yevhen Orlov <yevhen.orlov@plvision.eu>
Signed-off-by: Oleksandr Mazur <oleksandr.mazur@plvision.eu>
PATCH V5:
- fix checkpatch errors (4/4).
- remove function forward declarations, and move
function implementations to match the removed
forward declarations (4/4).
- rebased changes on top of latest master.
PATCH V4:
- fix clang warning - var uninitialized when used.
PATCH V3:
- add missing function implementations to 2/4 (HW API implementation),
only definitions were added int V1, V2, and implementation waas missed
by mistake.
Reported-by: kernel test robot <lkp@intel.com>
- fix compiletime warning (unused variable)
PATCH V2:
- include all the patches of patch series (V1's been sent to
closed net-next, also had not all patches included);
====================
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/ethernet/marvell/prestera/prestera.h | 47 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/prestera/prestera_hw.c | 256 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/prestera/prestera_hw.h | 15 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/prestera/prestera_main.c | 191 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/prestera/prestera_switchdev.c | 706 |
5 files changed, 1124 insertions, 91 deletions
diff --git a/drivers/net/ethernet/marvell/prestera/prestera.h b/drivers/net/ethernet/marvell/prestera/prestera.h index 0bb46eee46b4..bff9651f0a89 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera.h +++ b/drivers/net/ethernet/marvell/prestera/prestera.h @@ -20,6 +20,26 @@ struct prestera_fw_rev { u16 sub; }; +struct prestera_flood_domain { + struct prestera_switch *sw; + struct list_head flood_domain_port_list; + u32 idx; +}; + +struct prestera_mdb_entry { + struct prestera_switch *sw; + struct prestera_flood_domain *flood_domain; + unsigned char addr[ETH_ALEN]; + u16 vid; +}; + +struct prestera_flood_domain_port { + struct prestera_flood_domain *flood_domain; + struct net_device *dev; + struct list_head flood_domain_port_node; + u16 vid; +}; + struct prestera_port_stats { u64 good_octets_received; u64 bad_octets_received; @@ -321,6 +341,8 @@ void prestera_router_fini(struct prestera_switch *sw); struct prestera_port *prestera_find_port(struct prestera_switch *sw, u32 id); +struct prestera_switch *prestera_switch_get(struct net_device *dev); + int prestera_port_cfg_mac_read(struct prestera_port *port, struct prestera_port_mac_config *cfg); @@ -331,6 +353,10 @@ struct prestera_port *prestera_port_dev_lower_find(struct net_device *dev); void prestera_queue_work(struct work_struct *work); +int prestera_port_learning_set(struct prestera_port *port, bool learn_enable); +int prestera_port_uc_flood_set(struct prestera_port *port, bool flood); +int prestera_port_mc_flood_set(struct prestera_port *port, bool flood); + int prestera_port_pvid_set(struct prestera_port *port, u16 vid); bool prestera_netdev_check(const struct net_device *dev); @@ -338,9 +364,30 @@ bool prestera_netdev_check(const struct net_device *dev); int prestera_is_valid_mac_addr(struct prestera_port *port, const u8 *addr); bool prestera_port_is_lag_member(const struct prestera_port *port); +int prestera_lag_id(struct prestera_switch *sw, + struct net_device *lag_dev, u16 *lag_id); struct prestera_lag *prestera_lag_by_id(struct prestera_switch *sw, u16 id); u16 prestera_port_lag_id(const struct prestera_port *port); +struct prestera_mdb_entry * +prestera_mdb_entry_create(struct prestera_switch *sw, + const unsigned char *addr, u16 vid); +void prestera_mdb_entry_destroy(struct prestera_mdb_entry *mdb_entry); + +struct prestera_flood_domain * +prestera_flood_domain_create(struct prestera_switch *sw); +void prestera_flood_domain_destroy(struct prestera_flood_domain *flood_domain); + +int +prestera_flood_domain_port_create(struct prestera_flood_domain *flood_domain, + struct net_device *dev, + u16 vid); +void +prestera_flood_domain_port_destroy(struct prestera_flood_domain_port *port); +struct prestera_flood_domain_port * +prestera_flood_domain_port_find(struct prestera_flood_domain *flood_domain, + struct net_device *dev, u16 vid); + #endif /* _PRESTERA_H_ */ diff --git a/drivers/net/ethernet/marvell/prestera/prestera_hw.c b/drivers/net/ethernet/marvell/prestera/prestera_hw.c index 79fd3cac539d..962d7e0c0cb5 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera_hw.c +++ b/drivers/net/ethernet/marvell/prestera/prestera_hw.c @@ -60,6 +60,14 @@ enum prestera_cmd_type_t { PRESTERA_CMD_TYPE_ROUTER_VR_CREATE = 0x630, PRESTERA_CMD_TYPE_ROUTER_VR_DELETE = 0x631, + PRESTERA_CMD_TYPE_FLOOD_DOMAIN_CREATE = 0x700, + PRESTERA_CMD_TYPE_FLOOD_DOMAIN_DESTROY = 0x701, + PRESTERA_CMD_TYPE_FLOOD_DOMAIN_PORTS_SET = 0x702, + PRESTERA_CMD_TYPE_FLOOD_DOMAIN_PORTS_RESET = 0x703, + + PRESTERA_CMD_TYPE_MDB_CREATE = 0x704, + PRESTERA_CMD_TYPE_MDB_DESTROY = 0x705, + PRESTERA_CMD_TYPE_RXTX_INIT = 0x800, PRESTERA_CMD_TYPE_LAG_MEMBER_ADD = 0x900, @@ -185,6 +193,12 @@ struct prestera_fw_event_handler { void *arg; }; +enum { + PRESTERA_HW_FLOOD_DOMAIN_PORT_TYPE_REG_PORT = 0, + PRESTERA_HW_FLOOD_DOMAIN_PORT_TYPE_LAG = 1, + PRESTERA_HW_FLOOD_DOMAIN_PORT_TYPE_MAX = 2, +}; + struct prestera_msg_cmd { __le32 type; }; @@ -627,6 +641,57 @@ struct prestera_msg_event_fdb { u8 dest_type; }; +struct prestera_msg_flood_domain_create_req { + struct prestera_msg_cmd cmd; +}; + +struct prestera_msg_flood_domain_create_resp { + struct prestera_msg_ret ret; + __le32 flood_domain_idx; +}; + +struct prestera_msg_flood_domain_destroy_req { + struct prestera_msg_cmd cmd; + __le32 flood_domain_idx; +}; + +struct prestera_msg_flood_domain_ports_set_req { + struct prestera_msg_cmd cmd; + __le32 flood_domain_idx; + __le32 ports_num; +}; + +struct prestera_msg_flood_domain_ports_reset_req { + struct prestera_msg_cmd cmd; + __le32 flood_domain_idx; +}; + +struct prestera_msg_flood_domain_port { + union { + struct { + __le32 port_num; + __le32 dev_num; + }; + __le16 lag_id; + }; + __le16 vid; + __le16 port_type; +}; + +struct prestera_msg_mdb_create_req { + struct prestera_msg_cmd cmd; + __le32 flood_domain_idx; + __le16 vid; + u8 mac[ETH_ALEN]; +}; + +struct prestera_msg_mdb_destroy_req { + struct prestera_msg_cmd cmd; + __le32 flood_domain_idx; + __le16 vid; + u8 mac[ETH_ALEN]; +}; + static void prestera_hw_build_tests(void) { /* check requests */ @@ -654,10 +719,17 @@ static void prestera_hw_build_tests(void) BUILD_BUG_ON(sizeof(struct prestera_msg_vr_req) != 8); BUILD_BUG_ON(sizeof(struct prestera_msg_lpm_req) != 36); BUILD_BUG_ON(sizeof(struct prestera_msg_policer_req) != 36); + BUILD_BUG_ON(sizeof(struct prestera_msg_flood_domain_create_req) != 4); + BUILD_BUG_ON(sizeof(struct prestera_msg_flood_domain_destroy_req) != 8); + BUILD_BUG_ON(sizeof(struct prestera_msg_flood_domain_ports_set_req) != 12); + BUILD_BUG_ON(sizeof(struct prestera_msg_flood_domain_ports_reset_req) != 8); + BUILD_BUG_ON(sizeof(struct prestera_msg_mdb_create_req) != 16); + BUILD_BUG_ON(sizeof(struct prestera_msg_mdb_destroy_req) != 16); /* structure that are part of req/resp fw messages */ BUILD_BUG_ON(sizeof(struct prestera_msg_iface) != 16); BUILD_BUG_ON(sizeof(struct prestera_msg_ip_addr) != 20); + BUILD_BUG_ON(sizeof(struct prestera_msg_flood_domain_port) != 12); /* check responses */ BUILD_BUG_ON(sizeof(struct prestera_msg_common_resp) != 8); @@ -1531,7 +1603,7 @@ int prestera_hw_port_learning_set(struct prestera_port *port, bool enable) &req.cmd, sizeof(req)); } -static int prestera_hw_port_uc_flood_set(struct prestera_port *port, bool flood) +int prestera_hw_port_uc_flood_set(const struct prestera_port *port, bool flood) { struct prestera_msg_port_attr_req req = { .attr = __cpu_to_le32(PRESTERA_CMD_PORT_ATTR_FLOOD), @@ -1549,7 +1621,7 @@ static int prestera_hw_port_uc_flood_set(struct prestera_port *port, bool flood) &req.cmd, sizeof(req)); } -static int prestera_hw_port_mc_flood_set(struct prestera_port *port, bool flood) +int prestera_hw_port_mc_flood_set(const struct prestera_port *port, bool flood) { struct prestera_msg_port_attr_req req = { .attr = __cpu_to_le32(PRESTERA_CMD_PORT_ATTR_FLOOD), @@ -1567,56 +1639,6 @@ static int prestera_hw_port_mc_flood_set(struct prestera_port *port, bool flood) &req.cmd, sizeof(req)); } -static int prestera_hw_port_flood_set_v2(struct prestera_port *port, bool flood) -{ - struct prestera_msg_port_attr_req req = { - .attr = __cpu_to_le32(PRESTERA_CMD_PORT_ATTR_FLOOD), - .port = __cpu_to_le32(port->hw_id), - .dev = __cpu_to_le32(port->dev_id), - .param = { - .flood = flood, - } - }; - - return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET, - &req.cmd, sizeof(req)); -} - -int prestera_hw_port_flood_set(struct prestera_port *port, unsigned long mask, - unsigned long val) -{ - int err; - - if (port->sw->dev->fw_rev.maj <= 2) { - if (!(mask & BR_FLOOD)) - return 0; - - return prestera_hw_port_flood_set_v2(port, val & BR_FLOOD); - } - - if (mask & BR_FLOOD) { - err = prestera_hw_port_uc_flood_set(port, val & BR_FLOOD); - if (err) - goto err_uc_flood; - } - - if (mask & BR_MCAST_FLOOD) { - err = prestera_hw_port_mc_flood_set(port, val & BR_MCAST_FLOOD); - if (err) - goto err_mc_flood; - } - - return 0; - -err_mc_flood: - prestera_hw_port_mc_flood_set(port, 0); -err_uc_flood: - if (mask & BR_FLOOD) - prestera_hw_port_uc_flood_set(port, 0); - - return err; -} - int prestera_hw_vlan_create(struct prestera_switch *sw, u16 vid) { struct prestera_msg_vlan_req req = { @@ -2244,3 +2266,133 @@ int prestera_hw_policer_sr_tcm_set(struct prestera_switch *sw, return prestera_cmd(sw, PRESTERA_CMD_TYPE_POLICER_SET, &req.cmd, sizeof(req)); } + +int prestera_hw_flood_domain_create(struct prestera_flood_domain *domain) +{ + struct prestera_msg_flood_domain_create_resp resp; + struct prestera_msg_flood_domain_create_req req; + int err; + + err = prestera_cmd_ret(domain->sw, + PRESTERA_CMD_TYPE_FLOOD_DOMAIN_CREATE, &req.cmd, + sizeof(req), &resp.ret, sizeof(resp)); + if (err) + return err; + + domain->idx = __le32_to_cpu(resp.flood_domain_idx); + + return 0; +} + +int prestera_hw_flood_domain_destroy(struct prestera_flood_domain *domain) +{ + struct prestera_msg_flood_domain_destroy_req req = { + .flood_domain_idx = __cpu_to_le32(domain->idx), + }; + + return prestera_cmd(domain->sw, PRESTERA_CMD_TYPE_FLOOD_DOMAIN_DESTROY, + &req.cmd, sizeof(req)); +} + +int prestera_hw_flood_domain_ports_set(struct prestera_flood_domain *domain) +{ + struct prestera_flood_domain_port *flood_domain_port; + struct prestera_msg_flood_domain_ports_set_req *req; + struct prestera_msg_flood_domain_port *ports; + struct prestera_switch *sw = domain->sw; + struct prestera_port *port; + u32 ports_num = 0; + int buf_size; + void *buff; + u16 lag_id; + int err; + + list_for_each_entry(flood_domain_port, &domain->flood_domain_port_list, + flood_domain_port_node) + ports_num++; + + if (!ports_num) + return -EINVAL; + + buf_size = sizeof(*req) + sizeof(*ports) * ports_num; + + buff = kmalloc(buf_size, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + req = buff; + ports = buff + sizeof(*req); + + req->flood_domain_idx = __cpu_to_le32(domain->idx); + req->ports_num = __cpu_to_le32(ports_num); + + list_for_each_entry(flood_domain_port, &domain->flood_domain_port_list, + flood_domain_port_node) { + if (netif_is_lag_master(flood_domain_port->dev)) { + if (prestera_lag_id(sw, flood_domain_port->dev, + &lag_id)) { + kfree(buff); + return -EINVAL; + } + + ports->port_type = + __cpu_to_le16(PRESTERA_HW_FLOOD_DOMAIN_PORT_TYPE_LAG); + ports->lag_id = __cpu_to_le16(lag_id); + } else { + port = prestera_port_dev_lower_find(flood_domain_port->dev); + + ports->port_type = + __cpu_to_le16(PRESTERA_HW_FDB_ENTRY_TYPE_REG_PORT); + ports->dev_num = __cpu_to_le32(port->dev_id); + ports->port_num = __cpu_to_le32(port->hw_id); + } + + ports->vid = __cpu_to_le16(flood_domain_port->vid); + + ports++; + } + + err = prestera_cmd(sw, PRESTERA_CMD_TYPE_FLOOD_DOMAIN_PORTS_SET, + &req->cmd, buf_size); + + kfree(buff); + + return err; +} + +int prestera_hw_flood_domain_ports_reset(struct prestera_flood_domain *domain) +{ + struct prestera_msg_flood_domain_ports_reset_req req = { + .flood_domain_idx = __cpu_to_le32(domain->idx), + }; + + return prestera_cmd(domain->sw, + PRESTERA_CMD_TYPE_FLOOD_DOMAIN_PORTS_RESET, &req.cmd, + sizeof(req)); +} + +int prestera_hw_mdb_create(struct prestera_mdb_entry *mdb) +{ + struct prestera_msg_mdb_create_req req = { + .flood_domain_idx = __cpu_to_le32(mdb->flood_domain->idx), + .vid = __cpu_to_le16(mdb->vid), + }; + + memcpy(req.mac, mdb->addr, ETH_ALEN); + + return prestera_cmd(mdb->sw, PRESTERA_CMD_TYPE_MDB_CREATE, &req.cmd, + sizeof(req)); +} + +int prestera_hw_mdb_destroy(struct prestera_mdb_entry *mdb) +{ + struct prestera_msg_mdb_destroy_req req = { + .flood_domain_idx = __cpu_to_le32(mdb->flood_domain->idx), + .vid = __cpu_to_le16(mdb->vid), + }; + + memcpy(req.mac, mdb->addr, ETH_ALEN); + + return prestera_cmd(mdb->sw, PRESTERA_CMD_TYPE_MDB_DESTROY, &req.cmd, + sizeof(req)); +} diff --git a/drivers/net/ethernet/marvell/prestera/prestera_hw.h b/drivers/net/ethernet/marvell/prestera/prestera_hw.h index aa74f668aa3c..56e043146dd2 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera_hw.h +++ b/drivers/net/ethernet/marvell/prestera/prestera_hw.h @@ -144,6 +144,8 @@ struct prestera_acl_hw_action_info; struct prestera_acl_iface; struct prestera_counter_stats; struct prestera_iface; +struct prestera_flood_domain; +struct prestera_mdb_entry; /* Switch API */ int prestera_hw_switch_init(struct prestera_switch *sw); @@ -179,8 +181,8 @@ int prestera_hw_port_stats_get(const struct prestera_port *port, struct prestera_port_stats *stats); int prestera_hw_port_speed_get(const struct prestera_port *port, u32 *speed); int prestera_hw_port_learning_set(struct prestera_port *port, bool enable); -int prestera_hw_port_flood_set(struct prestera_port *port, unsigned long mask, - unsigned long val); +int prestera_hw_port_uc_flood_set(const struct prestera_port *port, bool flood); +int prestera_hw_port_mc_flood_set(const struct prestera_port *port, bool flood); int prestera_hw_port_accept_frm_type(struct prestera_port *port, enum prestera_accept_frm_type type); /* Vlan API */ @@ -302,4 +304,13 @@ int prestera_hw_policer_release(struct prestera_switch *sw, int prestera_hw_policer_sr_tcm_set(struct prestera_switch *sw, u32 policer_id, u64 cir, u32 cbs); +/* Flood domain / MDB API */ +int prestera_hw_flood_domain_create(struct prestera_flood_domain *domain); +int prestera_hw_flood_domain_destroy(struct prestera_flood_domain *domain); +int prestera_hw_flood_domain_ports_set(struct prestera_flood_domain *domain); +int prestera_hw_flood_domain_ports_reset(struct prestera_flood_domain *domain); + +int prestera_hw_mdb_create(struct prestera_mdb_entry *mdb); +int prestera_hw_mdb_destroy(struct prestera_mdb_entry *mdb); + #endif /* _PRESTERA_HW_H_ */ diff --git a/drivers/net/ethernet/marvell/prestera/prestera_main.c b/drivers/net/ethernet/marvell/prestera/prestera_main.c index 3952fdcc9240..ea5bd5069826 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera_main.c +++ b/drivers/net/ethernet/marvell/prestera/prestera_main.c @@ -35,6 +35,21 @@ void prestera_queue_work(struct work_struct *work) queue_work(prestera_owq, work); } +int prestera_port_learning_set(struct prestera_port *port, bool learn) +{ + return prestera_hw_port_learning_set(port, learn); +} + +int prestera_port_uc_flood_set(struct prestera_port *port, bool flood) +{ + return prestera_hw_port_uc_flood_set(port, flood); +} + +int prestera_port_mc_flood_set(struct prestera_port *port, bool flood) +{ + return prestera_hw_port_mc_flood_set(port, flood); +} + int prestera_port_pvid_set(struct prestera_port *port, u16 vid) { enum prestera_accept_frm_type frm_type; @@ -91,6 +106,14 @@ struct prestera_port *prestera_find_port(struct prestera_switch *sw, u32 id) return port; } +struct prestera_switch *prestera_switch_get(struct net_device *dev) +{ + struct prestera_port *port; + + port = prestera_port_dev_lower_find(dev); + return port ? port->sw : NULL; +} + int prestera_port_cfg_mac_read(struct prestera_port *port, struct prestera_port_mac_config *cfg) { @@ -585,6 +608,30 @@ static struct prestera_lag *prestera_lag_by_dev(struct prestera_switch *sw, return NULL; } +int prestera_lag_id(struct prestera_switch *sw, + struct net_device *lag_dev, u16 *lag_id) +{ + struct prestera_lag *lag; + int free_id = -1; + int id; + + for (id = 0; id < sw->lag_max; id++) { + lag = prestera_lag_by_id(sw, id); + if (lag->member_count) { + if (lag->dev == lag_dev) { + *lag_id = id; + return 0; + } + } else if (free_id < 0) { + free_id = id; + } + } + if (free_id < 0) + return -ENOSPC; + *lag_id = free_id; + return 0; +} + static struct prestera_lag *prestera_lag_create(struct prestera_switch *sw, struct net_device *lag_dev) { @@ -876,6 +923,150 @@ static int prestera_netdev_event_handler(struct notifier_block *nb, return notifier_from_errno(err); } +struct prestera_mdb_entry * +prestera_mdb_entry_create(struct prestera_switch *sw, + const unsigned char *addr, u16 vid) +{ + struct prestera_flood_domain *flood_domain; + struct prestera_mdb_entry *mdb_entry; + + mdb_entry = kzalloc(sizeof(*mdb_entry), GFP_KERNEL); + if (!mdb_entry) + goto err_mdb_alloc; + + flood_domain = prestera_flood_domain_create(sw); + if (!flood_domain) + goto err_flood_domain_create; + + mdb_entry->sw = sw; + mdb_entry->vid = vid; + mdb_entry->flood_domain = flood_domain; + ether_addr_copy(mdb_entry->addr, addr); + + if (prestera_hw_mdb_create(mdb_entry)) + goto err_mdb_hw_create; + + return mdb_entry; + +err_mdb_hw_create: + prestera_flood_domain_destroy(flood_domain); +err_flood_domain_create: + kfree(mdb_entry); +err_mdb_alloc: + return NULL; +} + +void prestera_mdb_entry_destroy(struct prestera_mdb_entry *mdb_entry) +{ + prestera_hw_mdb_destroy(mdb_entry); + prestera_flood_domain_destroy(mdb_entry->flood_domain); + kfree(mdb_entry); +} + +struct prestera_flood_domain * +prestera_flood_domain_create(struct prestera_switch *sw) +{ + struct prestera_flood_domain *domain; + + domain = kzalloc(sizeof(*domain), GFP_KERNEL); + if (!domain) + return NULL; + + domain->sw = sw; + + if (prestera_hw_flood_domain_create(domain)) { + kfree(domain); + return NULL; + } + + INIT_LIST_HEAD(&domain->flood_domain_port_list); + + return domain; +} + +void prestera_flood_domain_destroy(struct prestera_flood_domain *flood_domain) +{ + WARN_ON(!list_empty(&flood_domain->flood_domain_port_list)); + WARN_ON_ONCE(prestera_hw_flood_domain_destroy(flood_domain)); + kfree(flood_domain); +} + +int +prestera_flood_domain_port_create(struct prestera_flood_domain *flood_domain, + struct net_device *dev, + u16 vid) +{ + struct prestera_flood_domain_port *flood_domain_port; + bool is_first_port_in_list = false; + int err; + + flood_domain_port = kzalloc(sizeof(*flood_domain_port), GFP_KERNEL); + if (!flood_domain_port) { + err = -ENOMEM; + goto err_port_alloc; + } + + flood_domain_port->vid = vid; + + if (list_empty(&flood_domain->flood_domain_port_list)) + is_first_port_in_list = true; + + list_add(&flood_domain_port->flood_domain_port_node, + &flood_domain->flood_domain_port_list); + + flood_domain_port->flood_domain = flood_domain; + flood_domain_port->dev = dev; + + if (!is_first_port_in_list) { + err = prestera_hw_flood_domain_ports_reset(flood_domain); + if (err) + goto err_prestera_mdb_port_create_hw; + } + + err = prestera_hw_flood_domain_ports_set(flood_domain); + if (err) + goto err_prestera_mdb_port_create_hw; + + return 0; + +err_prestera_mdb_port_create_hw: + list_del(&flood_domain_port->flood_domain_port_node); + kfree(flood_domain_port); +err_port_alloc: + return err; +} + +void +prestera_flood_domain_port_destroy(struct prestera_flood_domain_port *port) +{ + struct prestera_flood_domain *flood_domain = port->flood_domain; + + list_del(&port->flood_domain_port_node); + + WARN_ON_ONCE(prestera_hw_flood_domain_ports_reset(flood_domain)); + + if (!list_empty(&flood_domain->flood_domain_port_list)) + WARN_ON_ONCE(prestera_hw_flood_domain_ports_set(flood_domain)); + + kfree(port); +} + +struct prestera_flood_domain_port * +prestera_flood_domain_port_find(struct prestera_flood_domain *flood_domain, + struct net_device *dev, u16 vid) +{ + struct prestera_flood_domain_port *flood_domain_port; + + list_for_each_entry(flood_domain_port, + &flood_domain->flood_domain_port_list, + flood_domain_port_node) + if (flood_domain_port->dev == dev && + vid == flood_domain_port->vid) + return flood_domain_port; + + return NULL; +} + static int prestera_netdev_event_handler_register(struct prestera_switch *sw) { sw->netdev_nb.notifier_call = prestera_netdev_event_handler; diff --git a/drivers/net/ethernet/marvell/prestera/prestera_switchdev.c b/drivers/net/ethernet/marvell/prestera/prestera_switchdev.c index b4599fe4ca8d..71cde97d85c8 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera_switchdev.c +++ b/drivers/net/ethernet/marvell/prestera/prestera_switchdev.c @@ -39,7 +39,10 @@ struct prestera_bridge { struct net_device *dev; struct prestera_switchdev *swdev; struct list_head port_list; + struct list_head br_mdb_entry_list; + bool mrouter_exist; bool vlan_enabled; + bool multicast_enabled; u16 bridge_id; }; @@ -48,8 +51,10 @@ struct prestera_bridge_port { struct net_device *dev; struct prestera_bridge *bridge; struct list_head vlan_list; + struct list_head br_mdb_port_list; refcount_t ref_count; unsigned long flags; + bool mrouter; u8 stp_state; }; @@ -67,6 +72,20 @@ struct prestera_port_vlan { u16 vid; }; +struct prestera_br_mdb_port { + struct prestera_bridge_port *br_port; + struct list_head br_mdb_port_node; +}; + +/* Software representation of MDB table. */ +struct prestera_br_mdb_entry { + struct prestera_bridge *bridge; + struct prestera_mdb_entry *mdb; + struct list_head br_mdb_port_list; + struct list_head br_mdb_entry_node; + bool enabled; +}; + static struct workqueue_struct *swdev_wq; static void prestera_bridge_port_put(struct prestera_bridge_port *br_port); @@ -74,6 +93,82 @@ static void prestera_bridge_port_put(struct prestera_bridge_port *br_port); static int prestera_port_vid_stp_set(struct prestera_port *port, u16 vid, u8 state); +static struct prestera_bridge * +prestera_bridge_find(const struct prestera_switch *sw, + const struct net_device *br_dev) +{ + struct prestera_bridge *bridge; + + list_for_each_entry(bridge, &sw->swdev->bridge_list, head) + if (bridge->dev == br_dev) + return bridge; + + return NULL; +} + +static struct prestera_bridge_port * +__prestera_bridge_port_find(const struct prestera_bridge *bridge, + const struct net_device *brport_dev) +{ + struct prestera_bridge_port *br_port; + + list_for_each_entry(br_port, &bridge->port_list, head) + if (br_port->dev == brport_dev) + return br_port; + + return NULL; +} + +static struct prestera_bridge_port * +prestera_bridge_port_find(struct prestera_switch *sw, + struct net_device *brport_dev) +{ + struct net_device *br_dev = netdev_master_upper_dev_get(brport_dev); + struct prestera_bridge *bridge; + + if (!br_dev) + return NULL; + + bridge = prestera_bridge_find(sw, br_dev); + if (!bridge) + return NULL; + + return __prestera_bridge_port_find(bridge, brport_dev); +} + +static void +prestera_br_port_flags_reset(struct prestera_bridge_port *br_port, + struct prestera_port *port) +{ + prestera_port_uc_flood_set(port, false); + prestera_port_mc_flood_set(port, false); + prestera_port_learning_set(port, false); +} + +static int prestera_br_port_flags_set(struct prestera_bridge_port *br_port, + struct prestera_port *port) +{ + int err; + + err = prestera_port_uc_flood_set(port, br_port->flags & BR_FLOOD); + if (err) + goto err_out; + + err = prestera_port_mc_flood_set(port, br_port->flags & BR_MCAST_FLOOD); + if (err) + goto err_out; + + err = prestera_port_learning_set(port, br_port->flags & BR_LEARNING); + if (err) + goto err_out; + + return 0; + +err_out: + prestera_br_port_flags_reset(br_port, port); + return err; +} + static struct prestera_bridge_vlan * prestera_bridge_vlan_create(struct prestera_bridge_port *br_port, u16 vid) { @@ -220,6 +315,70 @@ static int prestera_fdb_flush_port(struct prestera_port *port, u32 mode) } static void +prestera_mdb_port_del(struct prestera_mdb_entry *mdb, + struct net_device *orig_dev) +{ + struct prestera_flood_domain *fl_domain = mdb->flood_domain; + struct prestera_flood_domain_port *flood_domain_port; + + flood_domain_port = prestera_flood_domain_port_find(fl_domain, + orig_dev, + mdb->vid); + if (flood_domain_port) + prestera_flood_domain_port_destroy(flood_domain_port); +} + +static void +prestera_br_mdb_entry_put(struct prestera_br_mdb_entry *br_mdb) +{ + struct prestera_bridge_port *br_port; + + if (list_empty(&br_mdb->br_mdb_port_list)) { + list_for_each_entry(br_port, &br_mdb->bridge->port_list, head) + prestera_mdb_port_del(br_mdb->mdb, br_port->dev); + + prestera_mdb_entry_destroy(br_mdb->mdb); + list_del(&br_mdb->br_mdb_entry_node); + kfree(br_mdb); + } +} + +static void +prestera_br_mdb_port_del(struct prestera_br_mdb_entry *br_mdb, + struct prestera_bridge_port *br_port) +{ + struct prestera_br_mdb_port *br_mdb_port, *tmp; + + list_for_each_entry_safe(br_mdb_port, tmp, &br_mdb->br_mdb_port_list, + br_mdb_port_node) { + if (br_mdb_port->br_port == br_port) { + list_del(&br_mdb_port->br_mdb_port_node); + kfree(br_mdb_port); + } + } +} + +static void +prestera_mdb_flush_bridge_port(struct prestera_bridge_port *br_port) +{ + struct prestera_br_mdb_port *br_mdb_port, *tmp_port; + struct prestera_br_mdb_entry *br_mdb, *br_mdb_tmp; + struct prestera_bridge *br_dev = br_port->bridge; + + list_for_each_entry_safe(br_mdb, br_mdb_tmp, &br_dev->br_mdb_entry_list, + br_mdb_entry_node) { + list_for_each_entry_safe(br_mdb_port, tmp_port, + &br_mdb->br_mdb_port_list, + br_mdb_port_node) { + prestera_mdb_port_del(br_mdb->mdb, + br_mdb_port->br_port->dev); + prestera_br_mdb_port_del(br_mdb, br_mdb_port->br_port); + } + prestera_br_mdb_entry_put(br_mdb); + } +} + +static void prestera_port_vlan_bridge_leave(struct prestera_port_vlan *port_vlan) { u32 fdb_flush_mode = PRESTERA_FDB_FLUSH_MODE_DYNAMIC; @@ -244,6 +403,8 @@ prestera_port_vlan_bridge_leave(struct prestera_port_vlan *port_vlan) else prestera_fdb_flush_port_vlan(port, vid, fdb_flush_mode); + prestera_mdb_flush_bridge_port(br_port); + list_del(&port_vlan->br_vlan_head); prestera_bridge_vlan_put(br_vlan); prestera_bridge_port_put(br_port); @@ -295,8 +456,10 @@ prestera_bridge_create(struct prestera_switchdev *swdev, struct net_device *dev) bridge->vlan_enabled = vlan_enabled; bridge->swdev = swdev; bridge->dev = dev; + bridge->multicast_enabled = br_multicast_enabled(dev); INIT_LIST_HEAD(&bridge->port_list); + INIT_LIST_HEAD(&bridge->br_mdb_entry_list); list_add(&bridge->head, &swdev->bridge_list); @@ -314,6 +477,7 @@ static void prestera_bridge_destroy(struct prestera_bridge *bridge) else prestera_hw_bridge_delete(swdev->sw, bridge->bridge_id); + WARN_ON(!list_empty(&bridge->br_mdb_entry_list)); WARN_ON(!list_empty(&bridge->port_list)); kfree(bridge); } @@ -405,6 +569,7 @@ prestera_bridge_port_create(struct prestera_bridge *bridge, INIT_LIST_HEAD(&br_port->vlan_list); list_add(&br_port->head, &bridge->port_list); + INIT_LIST_HEAD(&br_port->br_mdb_port_list); return br_port; } @@ -414,6 +579,7 @@ prestera_bridge_port_destroy(struct prestera_bridge_port *br_port) { list_del(&br_port->head); WARN_ON(!list_empty(&br_port->vlan_list)); + WARN_ON(!list_empty(&br_port->br_mdb_port_list)); kfree(br_port); } @@ -461,19 +627,13 @@ prestera_bridge_1d_port_join(struct prestera_bridge_port *br_port) if (err) return err; - err = prestera_hw_port_flood_set(port, BR_FLOOD | BR_MCAST_FLOOD, - br_port->flags); + err = prestera_br_port_flags_set(br_port, port); if (err) - goto err_port_flood_set; - - err = prestera_hw_port_learning_set(port, br_port->flags & BR_LEARNING); - if (err) - goto err_port_learning_set; + goto err_flags2port_set; return 0; -err_port_learning_set: -err_port_flood_set: +err_flags2port_set: prestera_hw_bridge_port_delete(port, bridge->bridge_id); return err; @@ -592,8 +752,9 @@ void prestera_bridge_port_leave(struct net_device *br_dev, switchdev_bridge_port_unoffload(br_port->dev, NULL, NULL, NULL); - prestera_hw_port_learning_set(port, false); - prestera_hw_port_flood_set(port, BR_FLOOD | BR_MCAST_FLOOD, 0); + prestera_mdb_flush_bridge_port(br_port); + + prestera_br_port_flags_reset(br_port, port); prestera_port_vid_stp_set(port, PRESTERA_VID_ALL, BR_STATE_FORWARDING); prestera_bridge_port_put(br_port); } @@ -603,26 +764,14 @@ static int prestera_port_attr_br_flags_set(struct prestera_port *port, struct switchdev_brport_flags flags) { struct prestera_bridge_port *br_port; - int err; br_port = prestera_bridge_port_by_dev(port->sw->swdev, dev); if (!br_port) return 0; - err = prestera_hw_port_flood_set(port, flags.mask, flags.val); - if (err) - return err; - - if (flags.mask & BR_LEARNING) { - err = prestera_hw_port_learning_set(port, - flags.val & BR_LEARNING); - if (err) - return err; - } - - memcpy(&br_port->flags, &flags.val, sizeof(flags.val)); - - return 0; + br_port->flags &= ~flags.mask; + br_port->flags |= flags.val & flags.mask; + return prestera_br_port_flags_set(br_port, port); } static int prestera_port_attr_br_ageing_set(struct prestera_port *port, @@ -716,6 +865,290 @@ err_port_stp_set: return err; } +static int +prestera_br_port_lag_mdb_mc_enable_sync(struct prestera_bridge_port *br_port, + bool enabled) +{ + struct prestera_port *pr_port; + struct prestera_switch *sw; + u16 lag_id; + int err; + + pr_port = prestera_port_dev_lower_find(br_port->dev); + if (!pr_port) + return 0; + + sw = pr_port->sw; + err = prestera_lag_id(sw, br_port->dev, &lag_id); + if (err) + return err; + + list_for_each_entry(pr_port, &sw->port_list, list) { + if (pr_port->lag->lag_id == lag_id) { + err = prestera_port_mc_flood_set(pr_port, enabled); + if (err) + return err; + } + } + + return 0; +} + +static int prestera_br_mdb_mc_enable_sync(struct prestera_bridge *br_dev) +{ + struct prestera_bridge_port *br_port; + struct prestera_port *port; + bool enabled; + int err; + + /* if mrouter exists: + * - make sure every mrouter receives unreg mcast traffic; + * if mrouter doesn't exists: + * - make sure every port receives unreg mcast traffic; + */ + list_for_each_entry(br_port, &br_dev->port_list, head) { + if (br_dev->multicast_enabled && br_dev->mrouter_exist) + enabled = br_port->mrouter; + else + enabled = br_port->flags & BR_MCAST_FLOOD; + + if (netif_is_lag_master(br_port->dev)) { + err = prestera_br_port_lag_mdb_mc_enable_sync(br_port, + enabled); + if (err) + return err; + continue; + } + + port = prestera_port_dev_lower_find(br_port->dev); + if (!port) + continue; + + err = prestera_port_mc_flood_set(port, enabled); + if (err) + return err; + } + + return 0; +} + +static bool +prestera_br_mdb_port_is_member(struct prestera_br_mdb_entry *br_mdb, + struct net_device *orig_dev) +{ + struct prestera_br_mdb_port *tmp_port; + + list_for_each_entry(tmp_port, &br_mdb->br_mdb_port_list, + br_mdb_port_node) + if (tmp_port->br_port->dev == orig_dev) + return true; + + return false; +} + +static int +prestera_mdb_port_add(struct prestera_mdb_entry *mdb, + struct net_device *orig_dev, + const unsigned char addr[ETH_ALEN], u16 vid) +{ + struct prestera_flood_domain *flood_domain = mdb->flood_domain; + int err; + + if (!prestera_flood_domain_port_find(flood_domain, + orig_dev, vid)) { + err = prestera_flood_domain_port_create(flood_domain, orig_dev, + vid); + if (err) + return err; + } + + return 0; +} + +/* Sync bridge mdb (software table) with HW table (if MC is enabled). */ +static int prestera_br_mdb_sync(struct prestera_bridge *br_dev) +{ + struct prestera_br_mdb_port *br_mdb_port; + struct prestera_bridge_port *br_port; + struct prestera_br_mdb_entry *br_mdb; + struct prestera_mdb_entry *mdb; + struct prestera_port *pr_port; + int err = 0; + + if (!br_dev->multicast_enabled) + return 0; + + list_for_each_entry(br_mdb, &br_dev->br_mdb_entry_list, + br_mdb_entry_node) { + mdb = br_mdb->mdb; + /* Make sure every port that explicitly been added to the mdb + * joins the specified group. + */ + list_for_each_entry(br_mdb_port, &br_mdb->br_mdb_port_list, + br_mdb_port_node) { + br_port = br_mdb_port->br_port; + pr_port = prestera_port_dev_lower_find(br_port->dev); + + /* Match only mdb and br_mdb ports that belong to the + * same broadcast domain. + */ + if (br_dev->vlan_enabled && + !prestera_port_vlan_by_vid(pr_port, + mdb->vid)) + continue; + + /* If port is not in MDB or there's no Mrouter + * clear HW mdb. + */ + if (prestera_br_mdb_port_is_member(br_mdb, + br_mdb_port->br_port->dev) && + br_dev->mrouter_exist) + err = prestera_mdb_port_add(mdb, br_port->dev, + mdb->addr, + mdb->vid); + else + prestera_mdb_port_del(mdb, br_port->dev); + + if (err) + return err; + } + + /* Make sure that every mrouter port joins every MC group int + * broadcast domain. If it's not an mrouter - it should leave + */ + list_for_each_entry(br_port, &br_dev->port_list, head) { + pr_port = prestera_port_dev_lower_find(br_port->dev); + + /* Make sure mrouter woudln't receive traffci from + * another broadcast domain (e.g. from a vlan, which + * mrouter port is not a member of). + */ + if (br_dev->vlan_enabled && + !prestera_port_vlan_by_vid(pr_port, + mdb->vid)) + continue; + + if (br_port->mrouter) { + err = prestera_mdb_port_add(mdb, br_port->dev, + mdb->addr, + mdb->vid); + if (err) + return err; + } else if (!br_port->mrouter && + !prestera_br_mdb_port_is_member + (br_mdb, br_port->dev)) { + prestera_mdb_port_del(mdb, br_port->dev); + } + } + } + + return 0; +} + +static int +prestera_mdb_enable_set(struct prestera_br_mdb_entry *br_mdb, bool enable) +{ + int err; + + if (enable != br_mdb->enabled) { + if (enable) + err = prestera_hw_mdb_create(br_mdb->mdb); + else + err = prestera_hw_mdb_destroy(br_mdb->mdb); + + if (err) + return err; + + br_mdb->enabled = enable; + } + + return 0; +} + +static int +prestera_br_mdb_enable_set(struct prestera_bridge *br_dev, bool enable) +{ + struct prestera_br_mdb_entry *br_mdb; + int err; + + list_for_each_entry(br_mdb, &br_dev->br_mdb_entry_list, + br_mdb_entry_node) { + err = prestera_mdb_enable_set(br_mdb, enable); + if (err) + return err; + } + + return 0; +} + +static int prestera_port_attr_br_mc_disabled_set(struct prestera_port *port, + struct net_device *orig_dev, + bool mc_disabled) +{ + struct prestera_switch *sw = port->sw; + struct prestera_bridge *br_dev; + + br_dev = prestera_bridge_find(sw, orig_dev); + if (!br_dev) + return 0; + + br_dev->multicast_enabled = !mc_disabled; + + /* There's no point in enabling mdb back if router is missing. */ + WARN_ON(prestera_br_mdb_enable_set(br_dev, br_dev->multicast_enabled && + br_dev->mrouter_exist)); + + WARN_ON(prestera_br_mdb_sync(br_dev)); + + WARN_ON(prestera_br_mdb_mc_enable_sync(br_dev)); + + return 0; +} + +static bool +prestera_bridge_mdb_mc_mrouter_exists(struct prestera_bridge *br_dev) +{ + struct prestera_bridge_port *br_port; + + list_for_each_entry(br_port, &br_dev->port_list, head) + if (br_port->mrouter) + return true; + + return false; +} + +static int +prestera_port_attr_mrouter_set(struct prestera_port *port, + struct net_device *orig_dev, + bool is_port_mrouter) +{ + struct prestera_bridge_port *br_port; + struct prestera_bridge *br_dev; + + br_port = prestera_bridge_port_find(port->sw, orig_dev); + if (!br_port) + return 0; + + br_dev = br_port->bridge; + br_port->mrouter = is_port_mrouter; + + br_dev->mrouter_exist = prestera_bridge_mdb_mc_mrouter_exists(br_dev); + + /* Enable MDB processing if both mrouter exists and mc is enabled. + * In case if MC enabled, but there is no mrouter, device would flood + * all multicast traffic (even if MDB table is not empty) with the use + * of bridge's flood capabilities (without the use of flood_domain). + */ + WARN_ON(prestera_br_mdb_enable_set(br_dev, br_dev->multicast_enabled && + br_dev->mrouter_exist)); + + WARN_ON(prestera_br_mdb_sync(br_dev)); + + WARN_ON(prestera_br_mdb_mc_enable_sync(br_dev)); + + return 0; +} + static int prestera_port_obj_attr_set(struct net_device *dev, const void *ctx, const struct switchdev_attr *attr, struct netlink_ext_ack *extack) @@ -745,6 +1178,14 @@ static int prestera_port_obj_attr_set(struct net_device *dev, const void *ctx, err = prestera_port_attr_br_vlan_set(port, attr->orig_dev, attr->u.vlan_filtering); break; + case SWITCHDEV_ATTR_ID_PORT_MROUTER: + err = prestera_port_attr_mrouter_set(port, attr->orig_dev, + attr->u.mrouter); + break; + case SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED: + err = prestera_port_attr_br_mc_disabled_set(port, attr->orig_dev, + attr->u.mc_disabled); + break; default: err = -EOPNOTSUPP; } @@ -918,14 +1359,9 @@ prestera_port_vlan_bridge_join(struct prestera_port_vlan *port_vlan, if (port_vlan->br_port) return 0; - err = prestera_hw_port_flood_set(port, BR_FLOOD | BR_MCAST_FLOOD, - br_port->flags); + err = prestera_br_port_flags_set(br_port, port); if (err) - return err; - - err = prestera_hw_port_learning_set(port, br_port->flags & BR_LEARNING); - if (err) - goto err_port_learning_set; + goto err_flags2port_set; err = prestera_port_vid_stp_set(port, vid, br_port->stp_state); if (err) @@ -950,8 +1386,8 @@ prestera_port_vlan_bridge_join(struct prestera_port_vlan *port_vlan, err_bridge_vlan_get: prestera_port_vid_stp_set(port, vid, BR_STATE_FORWARDING); err_port_vid_stp_set: - prestera_hw_port_learning_set(port, false); -err_port_learning_set: + prestera_br_port_flags_reset(br_port, port); +err_flags2port_set: return err; } @@ -1048,20 +1484,162 @@ static int prestera_port_vlans_add(struct prestera_port *port, flag_pvid, extack); } +static struct prestera_br_mdb_entry * +prestera_br_mdb_entry_create(struct prestera_switch *sw, + struct prestera_bridge *br_dev, + const unsigned char *addr, u16 vid) +{ + struct prestera_br_mdb_entry *br_mdb_entry; + struct prestera_mdb_entry *mdb_entry; + + br_mdb_entry = kzalloc(sizeof(*br_mdb_entry), GFP_KERNEL); + if (!br_mdb_entry) + return NULL; + + mdb_entry = prestera_mdb_entry_create(sw, addr, vid); + if (!mdb_entry) + goto err_mdb_alloc; + + br_mdb_entry->mdb = mdb_entry; + br_mdb_entry->bridge = br_dev; + br_mdb_entry->enabled = true; + INIT_LIST_HEAD(&br_mdb_entry->br_mdb_port_list); + + list_add(&br_mdb_entry->br_mdb_entry_node, &br_dev->br_mdb_entry_list); + + return br_mdb_entry; + +err_mdb_alloc: + kfree(br_mdb_entry); + return NULL; +} + +static int prestera_br_mdb_port_add(struct prestera_br_mdb_entry *br_mdb, + struct prestera_bridge_port *br_port) +{ + struct prestera_br_mdb_port *br_mdb_port; + + list_for_each_entry(br_mdb_port, &br_mdb->br_mdb_port_list, + br_mdb_port_node) + if (br_mdb_port->br_port == br_port) + return 0; + + br_mdb_port = kzalloc(sizeof(*br_mdb_port), GFP_KERNEL); + if (!br_mdb_port) + return -ENOMEM; + + br_mdb_port->br_port = br_port; + list_add(&br_mdb_port->br_mdb_port_node, + &br_mdb->br_mdb_port_list); + + return 0; +} + +static struct prestera_br_mdb_entry * +prestera_br_mdb_entry_find(struct prestera_bridge *br_dev, + const unsigned char *addr, u16 vid) +{ + struct prestera_br_mdb_entry *br_mdb; + + list_for_each_entry(br_mdb, &br_dev->br_mdb_entry_list, + br_mdb_entry_node) + if (ether_addr_equal(&br_mdb->mdb->addr[0], addr) && + vid == br_mdb->mdb->vid) + return br_mdb; + + return NULL; +} + +static struct prestera_br_mdb_entry * +prestera_br_mdb_entry_get(struct prestera_switch *sw, + struct prestera_bridge *br_dev, + const unsigned char *addr, u16 vid) +{ + struct prestera_br_mdb_entry *br_mdb; + + br_mdb = prestera_br_mdb_entry_find(br_dev, addr, vid); + if (br_mdb) + return br_mdb; + + return prestera_br_mdb_entry_create(sw, br_dev, addr, vid); +} + +static int +prestera_mdb_port_addr_obj_add(const struct switchdev_obj_port_mdb *mdb) +{ + struct prestera_br_mdb_entry *br_mdb; + struct prestera_bridge_port *br_port; + struct prestera_bridge *br_dev; + struct prestera_switch *sw; + struct prestera_port *port; + int err; + + sw = prestera_switch_get(mdb->obj.orig_dev); + port = prestera_port_dev_lower_find(mdb->obj.orig_dev); + + br_port = prestera_bridge_port_find(sw, mdb->obj.orig_dev); + if (!br_port) + return 0; + + br_dev = br_port->bridge; + + if (mdb->vid && !prestera_port_vlan_by_vid(port, mdb->vid)) + return 0; + + if (mdb->vid) + br_mdb = prestera_br_mdb_entry_get(sw, br_dev, &mdb->addr[0], + mdb->vid); + else + br_mdb = prestera_br_mdb_entry_get(sw, br_dev, &mdb->addr[0], + br_dev->bridge_id); + + if (!br_mdb) + return -ENOMEM; + + /* Make sure newly allocated MDB entry gets disabled if either MC is + * disabled, or the mrouter does not exist. + */ + WARN_ON(prestera_mdb_enable_set(br_mdb, br_dev->multicast_enabled && + br_dev->mrouter_exist)); + + err = prestera_br_mdb_port_add(br_mdb, br_port); + if (err) { + prestera_br_mdb_entry_put(br_mdb); + return err; + } + + err = prestera_br_mdb_sync(br_dev); + if (err) + return err; + + return 0; +} + static int prestera_port_obj_add(struct net_device *dev, const void *ctx, const struct switchdev_obj *obj, struct netlink_ext_ack *extack) { struct prestera_port *port = netdev_priv(dev); const struct switchdev_obj_port_vlan *vlan; + const struct switchdev_obj_port_mdb *mdb; + int err = 0; switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: vlan = SWITCHDEV_OBJ_PORT_VLAN(obj); return prestera_port_vlans_add(port, vlan, extack); + case SWITCHDEV_OBJ_ID_PORT_MDB: + mdb = SWITCHDEV_OBJ_PORT_MDB(obj); + err = prestera_mdb_port_addr_obj_add(mdb); + break; + case SWITCHDEV_OBJ_ID_HOST_MDB: + fallthrough; default: - return -EOPNOTSUPP; + err = -EOPNOTSUPP; + break; } + + return err; } static int prestera_port_vlans_del(struct prestera_port *port, @@ -1086,17 +1664,71 @@ static int prestera_port_vlans_del(struct prestera_port *port, return 0; } +static int +prestera_mdb_port_addr_obj_del(struct prestera_port *port, + const struct switchdev_obj_port_mdb *mdb) +{ + struct prestera_br_mdb_entry *br_mdb; + struct prestera_bridge_port *br_port; + struct prestera_bridge *br_dev; + int err; + + /* Bridge port no longer exists - and so does this MDB entry */ + br_port = prestera_bridge_port_find(port->sw, mdb->obj.orig_dev); + if (!br_port) + return 0; + + /* Removing MDB with non-existing VLAN - not supported; */ + if (mdb->vid && !prestera_port_vlan_by_vid(port, mdb->vid)) + return 0; + + br_dev = br_port->bridge; + + if (br_port->bridge->vlan_enabled) + br_mdb = prestera_br_mdb_entry_find(br_dev, &mdb->addr[0], + mdb->vid); + else + br_mdb = prestera_br_mdb_entry_find(br_dev, &mdb->addr[0], + br_port->bridge->bridge_id); + + if (!br_mdb) + return 0; + + /* Since there might be a situation that this port was the last in the + * MDB group, we have to both remove this port from software and HW MDB, + * sync MDB table, and then destroy software MDB (if needed). + */ + prestera_br_mdb_port_del(br_mdb, br_port); + + prestera_br_mdb_entry_put(br_mdb); + + err = prestera_br_mdb_sync(br_dev); + if (err) + return err; + + return 0; +} + static int prestera_port_obj_del(struct net_device *dev, const void *ctx, const struct switchdev_obj *obj) { struct prestera_port *port = netdev_priv(dev); + const struct switchdev_obj_port_mdb *mdb; + int err = 0; switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: return prestera_port_vlans_del(port, SWITCHDEV_OBJ_PORT_VLAN(obj)); + case SWITCHDEV_OBJ_ID_PORT_MDB: + mdb = SWITCHDEV_OBJ_PORT_MDB(obj); + err = prestera_mdb_port_addr_obj_del(port, mdb); + break; default: - return -EOPNOTSUPP; + err = -EOPNOTSUPP; + break; } + + return err; } static int prestera_switchdev_blk_event(struct notifier_block *unused, |