blob: 57f1e2dbba6c4b3a36ed90d8bcec0edc3404f74c [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
/*
* nss_bridge_mgr.c
* NSS to HLOS Bridge Interface manager
*/
#include <linux/sysctl.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/of.h>
#include <linux/if_bridge.h>
#include <net/bonding.h>
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
#include <ref/ref_vsi.h>
#include <nss_vlan_mgr.h>
#include <fal/fal_fdb.h>
#include <fal/fal_stp.h>
#include <fal/fal_acl.h>
#include <fal/fal_api.h>
#include <fal/fal_port_ctrl.h>
#endif
#include <nss_api_if.h>
#if defined(NSS_BRIDGE_MGR_OVS_ENABLE)
#include <ovsmgr.h>
#endif
#include "nss_bridge_mgr_priv.h"
/*
* Module parameter to enable/disable OVS bridge.
*/
static bool ovs_enabled = false;
static struct nss_bridge_mgr_context br_mgr_ctx;
/*
* nss_bridge_mgr_create_instance()
* Create a bridge instance.
*/
static struct nss_bridge_pvt *nss_bridge_mgr_create_instance(struct net_device *dev)
{
struct nss_bridge_pvt *br;
#if !defined(NSS_BRIDGE_MGR_OVS_ENABLE)
if (!netif_is_bridge_master(dev))
return NULL;
#else
/*
* When OVS is enabled, we have to check for both bridge master
* and OVS master.
*/
if (!netif_is_bridge_master(dev) && !ovsmgr_is_ovs_master(dev))
return NULL;
#endif
br = kzalloc(sizeof(*br), GFP_KERNEL);
if (!br)
return NULL;
INIT_LIST_HEAD(&br->list);
return br;
}
/*
* nss_bridge_mgr_delete_instance()
* Delete a bridge instance from bridge list and free the bridge instance.
*/
static void nss_bridge_mgr_delete_instance(struct nss_bridge_pvt *br)
{
spin_lock(&br_mgr_ctx.lock);
if (!list_empty(&br->list))
list_del(&br->list);
spin_unlock(&br_mgr_ctx.lock);
kfree(br);
}
/*
* nss_bridge_mgr_find_instance()
* Find a bridge instance from bridge list.
*/
struct nss_bridge_pvt *nss_bridge_mgr_find_instance(struct net_device *dev)
{
struct nss_bridge_pvt *br;
#if !defined(NSS_BRIDGE_MGR_OVS_ENABLE)
if (!netif_is_bridge_master(dev))
return NULL;
#else
/*
* When OVS is enabled, we have to check for both bridge master
* and OVS master.
*/
if (!netif_is_bridge_master(dev) && !ovsmgr_is_ovs_master(dev))
return NULL;
#endif
/*
* Do we have it on record?
*/
spin_lock(&br_mgr_ctx.lock);
list_for_each_entry(br, &br_mgr_ctx.list, list) {
if (br->dev == dev) {
spin_unlock(&br_mgr_ctx.lock);
return br;
}
}
spin_unlock(&br_mgr_ctx.lock);
return NULL;
}
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
/*
* nss_bridge_mgr_enable_fdb_learning()
* Enable fdb learning in PPE.
*/
static int nss_bridge_mgr_enable_fdb_learning(struct nss_bridge_pvt *br)
{
fal_vsi_newaddr_lrn_t newaddr_lrn;
fal_vsi_stamove_t sta_move;
/*
* Enable station move
*/
sta_move.stamove_en = 1;
sta_move.action = FAL_MAC_FRWRD;
if (fal_vsi_stamove_set(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, &sta_move)) {
nss_bridge_mgr_warn("%px: Failed to enable station move for Bridge vsi\n", br);
return -1;
}
/*
* Enable FDB learning in PPE
*/
newaddr_lrn.lrn_en = 1;
newaddr_lrn.action = FAL_MAC_FRWRD;
if (fal_vsi_newaddr_lrn_set(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, &newaddr_lrn)) {
nss_bridge_mgr_warn("%px: Failed to enable FDB learning for Bridge vsi\n", br);
goto disable_sta_move;
}
/*
* Send a notification to NSS for FDB learning enable.
*/
if (nss_bridge_tx_set_fdb_learn_msg(br->ifnum, NSS_BRIDGE_FDB_LEARN_ENABLE) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: Tx message failed for FDB learning status\n", br);
goto disable_fdb_learning;
}
return 0;
disable_fdb_learning:
newaddr_lrn.lrn_en = 0;
newaddr_lrn.action = FAL_MAC_FRWRD;
if (fal_vsi_newaddr_lrn_set(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, &newaddr_lrn))
nss_bridge_mgr_warn("%px: Failed to disable FDB learning for Bridge vsi\n", br);
disable_sta_move:
sta_move.stamove_en = 0;
sta_move.action = FAL_MAC_FRWRD;
if (fal_vsi_stamove_set(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, &sta_move))
nss_bridge_mgr_warn("%px: Failed to disable station move for Bridge vsi\n", br);
return -1;
}
/*
* nss_bridge_mgr_disable_fdb_learning()
* Disable fdb learning in PPE
*
* For the first time a bond interface join bridge, we need to use flow based rule.
* FDB learing/station move need to be disabled.
*/
static int nss_bridge_mgr_disable_fdb_learning(struct nss_bridge_pvt *br)
{
fal_vsi_newaddr_lrn_t newaddr_lrn;
fal_vsi_stamove_t sta_move;
/*
* Disable station move
*/
sta_move.stamove_en = 0;
sta_move.action = FAL_MAC_FRWRD;
if (fal_vsi_stamove_set(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, &sta_move)) {
nss_bridge_mgr_warn("%px: Failed to disable station move for Bridge vsi\n", br);
return -1;
}
/*
* Disable FDB learning in PPE
*/
newaddr_lrn.lrn_en = 0;
newaddr_lrn.action = FAL_MAC_FRWRD;
if (fal_vsi_newaddr_lrn_set(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, &newaddr_lrn)) {
nss_bridge_mgr_warn("%px: Failed to disable FDB learning for Bridge vsi\n", br);
goto enable_sta_move;
}
/*
* Flush FDB table for the bridge vsi
*/
if (fal_fdb_entry_del_byfid(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, FAL_FDB_DEL_STATIC)) {
nss_bridge_mgr_warn("%px: Failed to flush FDB table for vsi:%d in PPE\n", br, br->vsi);
goto enable_fdb_learning;
}
/*
* Send a notification to NSS for FDB learning disable.
*/
if (nss_bridge_tx_set_fdb_learn_msg(br->ifnum, NSS_BRIDGE_FDB_LEARN_DISABLE) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: Tx message failed for FDB learning status\n", br);
goto enable_fdb_learning;
}
return 0;
enable_fdb_learning:
newaddr_lrn.lrn_en = 1;
newaddr_lrn.action = FAL_MAC_FRWRD;
if (fal_vsi_newaddr_lrn_set(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, &newaddr_lrn))
nss_bridge_mgr_warn("%px: Failed to enable FDB learning for Bridge vsi\n", br);
enable_sta_move:
sta_move.stamove_en = 1;
sta_move.action = FAL_MAC_FRWRD;
if (fal_vsi_stamove_set(NSS_BRIDGE_MGR_SWITCH_ID, br->vsi, &sta_move))
nss_bridge_mgr_warn("%px: Failed to enable station move for Bridge vsi\n", br);
return -1;
}
/*
* nss_bridge_mgr_add_bond_slave()
* A slave interface being added to a bond master that belongs to a bridge.
*/
static int nss_bridge_mgr_add_bond_slave(struct net_device *bond_master,
struct net_device *slave, struct nss_bridge_pvt *b_pvt)
{
uint32_t *port_vsi;
int32_t port_id;
int32_t ifnum;
int32_t lagid = 0;
int32_t bondid = -1;
/*
* Figure out the aggregation id of this slave
*/
#if defined(BONDING_SUPPORT)
bondid = bond_get_id(bond_master);
#endif
if (bondid < 0) {
nss_bridge_mgr_warn("%px: Invalid LAG group id 0x%x\n",
b_pvt, bondid);
return -1;
}
lagid = bondid + NSS_LAG0_INTERFACE_NUM;
nss_bridge_mgr_trace("%px: Bond Slave %s is added bridge\n",
b_pvt, slave->name);
ifnum = nss_cmn_get_interface_number_by_dev(slave);
/*
* Hardware supports only PHYSICAL Ports as trunk ports
*/
if (!NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(ifnum)) {
nss_bridge_mgr_warn("%px: Interface %s is not Physical Interface\n",
b_pvt, slave->name);
return -1;
}
nss_bridge_mgr_trace("%px: Interface %s adding into bridge\n",
b_pvt, slave->name);
port_id = ifnum;
/*
* Take bridge lock as we are updating vsi and port forwarding
* details in PPE Hardware
*/
spin_lock(&br_mgr_ctx.lock);
port_vsi = &b_pvt->port_vsi[port_id - 1];
if (ppe_port_vsi_get(NSS_BRIDGE_MGR_SWITCH_ID, port_id, port_vsi)) {
spin_unlock(&br_mgr_ctx.lock);
nss_bridge_mgr_warn("%px: Couldn't get VSI for port %d\n",
b_pvt, port_id);
return -1;
}
if (ppe_port_vsi_set(NSS_BRIDGE_MGR_SWITCH_ID, port_id, b_pvt->vsi)) {
spin_unlock(&br_mgr_ctx.lock);
nss_bridge_mgr_warn("%px: Couldn't set bridge VSI for port %d\n",
b_pvt, port_id);
return -1;
}
spin_unlock(&br_mgr_ctx.lock);
if (nss_bridge_tx_join_msg(b_pvt->ifnum,
slave) != NSS_TX_SUCCESS) {
if (ppe_port_vsi_set(NSS_BRIDGE_MGR_SWITCH_ID, port_id, *port_vsi))
nss_bridge_mgr_warn("%px: Couldn't set bridge VSI for port %d\n", b_pvt, port_id);
nss_bridge_mgr_warn("%px: Couldn't add port %d in bridge",
b_pvt, port_id);
return -1;
}
spin_lock(&br_mgr_ctx.lock);
b_pvt->lag_ports[port_id - 1] = lagid;
spin_unlock(&br_mgr_ctx.lock);
return 0;
}
/*
* nss_bridge_mgr_del_bond_slave()
* A slave interface being removed from a bond master that belongs to a bridge.
*/
static int nss_bridge_mgr_del_bond_slave(struct net_device *bond_master,
struct net_device *slave, struct nss_bridge_pvt *b_pvt)
{
uint32_t *port_vsi;
int32_t port_id;
int32_t ifnum;
int32_t lagid = 0;
int32_t bondid = -1;
/*
* Figure out the aggregation id of this slave
*/
#if defined(BONDING_SUPPORT)
bondid = bond_get_id(bond_master);
#endif
if (bondid < 0) {
nss_bridge_mgr_warn("%px: Invalid LAG group id 0x%x\n",
b_pvt, bondid);
return -1;
}
lagid = bondid + NSS_LAG0_INTERFACE_NUM;
nss_bridge_mgr_trace("%px: Bond Slave %s leaving bridge\n",
b_pvt, slave->name);
ifnum = nss_cmn_get_interface_number_by_dev(slave);
/*
* Hardware supports only PHYSICAL Ports as trunk ports
*/
if (!NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(ifnum)) {
nss_bridge_mgr_warn("%px: Interface %s is not Physical Interface\n",
b_pvt, slave->name);
return -1;
}
nss_bridge_mgr_trace("%px: Interface %s leaving from bridge\n",
b_pvt, slave->name);
port_id = (fal_port_t)ifnum;
/*
* Take bridge lock as we are updating vsi and port forwarding
* details in PPE Hardware
*/
spin_lock(&br_mgr_ctx.lock);
port_vsi = &b_pvt->port_vsi[port_id - 1];
if (b_pvt->lag_ports[port_id - 1] != lagid) {
spin_unlock(&br_mgr_ctx.lock);
return -1;
}
if (ppe_port_vsi_set(NSS_BRIDGE_MGR_SWITCH_ID, port_id, *port_vsi)) {
spin_unlock(&br_mgr_ctx.lock);
nss_bridge_mgr_warn("%px: failed to restore port VSI for port %d\n", b_pvt, port_id);
return -1;
}
spin_unlock(&br_mgr_ctx.lock);
if (nss_bridge_tx_leave_msg(b_pvt->ifnum,
slave) != NSS_TX_SUCCESS) {
ppe_port_vsi_set(NSS_BRIDGE_MGR_SWITCH_ID, port_id, b_pvt->vsi);
nss_bridge_mgr_trace("%px: Failed to remove port %d from bridge\n",
b_pvt, port_id);
return -1;
}
spin_lock(&br_mgr_ctx.lock);
b_pvt->lag_ports[port_id - 1] = 0;
spin_unlock(&br_mgr_ctx.lock);
/*
* Set STP state to forwarding after bond physical port leaves bridge
*/
fal_stp_port_state_set(NSS_BRIDGE_MGR_SWITCH_ID, NSS_BRIDGE_MGR_SPANNING_TREE_ID,
port_id, FAL_STP_FORWARDING);
return 0;
}
/*
* nss_bridge_mgr_bond_master_join()
* Add a bond interface to bridge
*/
static int nss_bridge_mgr_bond_master_join(struct net_device *bond_master,
struct nss_bridge_pvt *b_pvt)
{
struct net_device *slave;
/*
* bond enslave/release path is protected by rtnl lock
*/
ASSERT_RTNL();
/*
* Wait for RCU QS
*/
synchronize_rcu();
/*
* Join each of the bonded slaves to the VSI group
*/
for_each_netdev(&init_net, slave) {
if (netdev_master_upper_dev_get(slave) != bond_master) {
continue;
}
if (nss_bridge_mgr_add_bond_slave(bond_master, slave, b_pvt)) {
nss_bridge_mgr_warn("%px: Failed to add slave (%s) state in Bridge\n", b_pvt, slave->name);
goto cleanup;
}
}
/*
* If already other bond devices are attached to bridge,
* only increment bond_slave_num,
*/
spin_lock(&br_mgr_ctx.lock);
if (b_pvt->bond_slave_num) {
b_pvt->bond_slave_num++;
spin_unlock(&br_mgr_ctx.lock);
return NOTIFY_DONE;
}
spin_unlock(&br_mgr_ctx.lock);
/*
* This is the first bond device being attached to bridge. In order to enforce Linux
* bond slave selection in bridge flows involving bond interfaces, we need to disable
* fdb learning on this bridge master to allow flow based bridging.
*/
if (!nss_bridge_mgr_disable_fdb_learning(b_pvt)) {
spin_lock(&br_mgr_ctx.lock);
b_pvt->bond_slave_num = 1;
spin_unlock(&br_mgr_ctx.lock);
return NOTIFY_DONE;
}
cleanup:
for_each_netdev(&init_net, slave) {
if (netdev_master_upper_dev_get(slave) != bond_master) {
continue;
}
if (nss_bridge_mgr_del_bond_slave(bond_master, slave, b_pvt)) {
nss_bridge_mgr_warn("%px: Failed to remove slave (%s) from Bridge\n", b_pvt, slave->name);
}
}
return NOTIFY_BAD;
}
/*
* nss_bridge_mgr_bond_master_leave()
* Remove a bond interface from bridge
*/
static int nss_bridge_mgr_bond_master_leave(struct net_device *bond_master,
struct nss_bridge_pvt *b_pvt)
{
struct net_device *slave;
nss_bridge_mgr_assert(b_pvt->bond_slave_num == 0);
ASSERT_RTNL();
synchronize_rcu();
/*
* Remove each of the bonded slaves from the VSI group
*/
for_each_netdev(&init_net, slave) {
if (netdev_master_upper_dev_get(slave) != bond_master) {
continue;
}
if (nss_bridge_mgr_del_bond_slave(bond_master, slave, b_pvt)) {
nss_bridge_mgr_warn("%px: Failed to remove slave (%s) from Bridge\n", b_pvt, slave->name);
goto cleanup;
}
}
/*
* If more than one bond devices are attached to bridge,
* only decrement the bond_slave_num
*/
spin_lock(&br_mgr_ctx.lock);
if (b_pvt->bond_slave_num > 1) {
b_pvt->bond_slave_num--;
spin_unlock(&br_mgr_ctx.lock);
return NOTIFY_DONE;
}
spin_unlock(&br_mgr_ctx.lock);
/*
* The last bond interface is removed from bridge, we can switch back to FDB
* learning mode.
*/
if (!nss_bridge_mgr_enable_fdb_learning(b_pvt)) {
spin_lock(&br_mgr_ctx.lock);
b_pvt->bond_slave_num = 0;
spin_unlock(&br_mgr_ctx.lock);
return NOTIFY_DONE;
}
cleanup:
for_each_netdev(&init_net, slave) {
if (netdev_master_upper_dev_get(slave) != bond_master) {
continue;
}
if (nss_bridge_mgr_add_bond_slave(bond_master, slave, b_pvt)) {
nss_bridge_mgr_warn("%px: Failed to add slave (%s) state in Bridge\n", b_pvt, slave->name);
}
}
return NOTIFY_BAD;
}
/*
* nss_bridge_mgr_l2_exception_acl_enable()
* Create ACL rule to enable L2 exception.
*/
static bool nss_bridge_mgr_l2_exception_acl_enable(void)
{
sw_error_t error;
fal_acl_rule_t rule;
memset(&rule, 0, sizeof(rule));
error = fal_acl_list_creat(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_LIST_PRIORITY);
if (error != SW_OK) {
nss_bridge_mgr_warn("List creation failed with error = %d\n", error);
return false;
}
/*
* Enable excpetion for packets with fragments.
*/
rule.rule_type = FAL_ACL_RULE_IP4;
rule.is_fragement_mask = 1;
rule.is_fragement_val = A_TRUE;
FAL_FIELD_FLG_SET(rule.field_flg, FAL_ACL_FIELD_L3_FRAGMENT);
FAL_ACTION_FLG_SET(rule.action_flg, FAL_ACL_ACTION_RDTCPU);
error = fal_acl_rule_add(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_FRAG_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR, &rule);
if (error != SW_OK) {
nss_bridge_mgr_warn("Could not add fragment acl rule, error = %d\n", error);
goto frag_fail;
}
/*
* Enable excpetion for TCP FIN.
*/
memset(&rule, 0, sizeof(rule));
rule.rule_type = FAL_ACL_RULE_IP4;
rule.tcp_flag_val = 0x1 & 0x3f;
rule.tcp_flag_mask = 0x1 & 0x3f;
FAL_FIELD_FLG_SET(rule.field_flg, FAL_ACL_FIELD_TCP_FLAG);
FAL_ACTION_FLG_SET(rule.action_flg, FAL_ACL_ACTION_RDTCPU);
error = fal_acl_rule_add(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_FIN_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR, &rule);
if (error != SW_OK) {
nss_bridge_mgr_warn("Could not add TCP FIN rule, error = %d\n", error);
goto fin_fail;
}
/*
* Enable excpetion for TCP SYN.
*/
memset(&rule, 0, sizeof(rule));
rule.rule_type = FAL_ACL_RULE_IP4;
rule.tcp_flag_val = 0x2 & 0x3f;
rule.tcp_flag_mask = 0x2 & 0x3f;
FAL_FIELD_FLG_SET(rule.field_flg, FAL_ACL_FIELD_TCP_FLAG);
FAL_ACTION_FLG_SET(rule.action_flg, FAL_ACL_ACTION_RDTCPU);
error = fal_acl_rule_add(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_SYN_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR, &rule);
if (error != SW_OK) {
nss_bridge_mgr_warn("Could not add TCP SYN rule, error = %d\n", error);
goto syn_fail;
}
/*
* Enable excpetion for TCP RST.
*/
memset(&rule, 0, sizeof(rule));
rule.rule_type = FAL_ACL_RULE_IP4;
rule.tcp_flag_val = 0x4 & 0x3f;
rule.tcp_flag_mask = 0x4 & 0x3f;
FAL_FIELD_FLG_SET(rule.field_flg, FAL_ACL_FIELD_TCP_FLAG);
FAL_ACTION_FLG_SET(rule.action_flg, FAL_ACL_ACTION_RDTCPU);
error = fal_acl_rule_add(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_RST_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR, &rule);
if (error != SW_OK) {
nss_bridge_mgr_warn("Could not add TCP RST rule, error = %d\n", error);
goto rst_fail;
}
/*
* Bind ACL list with service code
*/
error = fal_acl_list_bind(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
FAL_ACL_DIREC_IN, FAL_ACL_BIND_SERVICE_CODE, NSS_PPE_SC_VLAN_FILTER_BYPASS);
if (error != SW_OK) {
nss_bridge_mgr_warn("Could not bind ACL list, error = %d\n", error);
goto bind_fail;
}
nss_bridge_mgr_info("Created ACL rule\n");
return true;
bind_fail:
error = fal_acl_rule_delete(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_RST_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR);
if (error != SW_OK) {
nss_bridge_mgr_warn("TCP RST rule deletion failed, error %d\n", error);
}
rst_fail:
error = fal_acl_rule_delete(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_SYN_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR);
if (error != SW_OK) {
nss_bridge_mgr_warn("TCP SYN rule deletion failed, error %d\n", error);
}
syn_fail:
error = fal_acl_rule_delete(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_FIN_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR);
if (error != SW_OK) {
nss_bridge_mgr_warn("TCP FIN rule deletion failed, error %d\n", error);
}
fin_fail:
error = fal_acl_rule_delete(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_FRAG_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR);
if (error != SW_OK) {
nss_bridge_mgr_warn("IP fragmentation rule deletion failed, error %d\n", error);
}
frag_fail:
error = fal_acl_list_destroy(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID);
if (error != SW_OK) {
nss_bridge_mgr_warn("ACL list destroy failed, error %d\n", error);
}
return false;
}
/*
* nss_bridge_mgr_l2_exception_acl_disable()
* Destroy ACL list and rule created by the driver.
*/
static void nss_bridge_mgr_l2_exception_acl_disable(void)
{
sw_error_t error;
error = fal_acl_rule_delete(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_SYN_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR);
if (error != SW_OK) {
nss_bridge_mgr_warn("TCP SYN rule deletion failed, error %d\n", error);
}
error = fal_acl_rule_delete(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_FIN_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR);
if (error != SW_OK) {
nss_bridge_mgr_warn("TCP FIN rule deletion failed, error %d\n", error);
}
error = fal_acl_rule_delete(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_RST_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR);
if (error != SW_OK) {
nss_bridge_mgr_warn("TCP RST rule deletion failed, error %d\n", error);
}
error = fal_acl_rule_delete(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID,
NSS_BRIDGE_MGR_ACL_FRAG_RULE_ID, NSS_BRIDGE_MGR_ACL_RULE_NR);
if (error != SW_OK) {
nss_bridge_mgr_warn("IP fragmentation rule deletion failed, error %d\n", error);
}
error = fal_acl_list_destroy(NSS_BRIDGE_MGR_ACL_DEV_ID, NSS_BRIDGE_MGR_ACL_LIST_ID);
if (error != SW_OK) {
nss_bridge_mgr_warn("ACL list destroy failed, error %d\n", error);
}
}
#endif
/*
* nss_bridge_mgr_join_bridge()
* Netdevice join bridge and send netdevice joining bridge message to NSS FW.
*/
int nss_bridge_mgr_join_bridge(struct net_device *dev, struct nss_bridge_pvt *br)
{
int32_t ifnum;
ifnum = nss_cmn_get_interface_number_by_dev(dev);
if (ifnum < 0) {
nss_bridge_mgr_warn("%s: failed to find interface number\n", dev->name);
return -EINVAL;
}
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
if (NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(ifnum)) {
fal_port_t port_num = (fal_port_t)ifnum;
/*
* If there is a wan interface added in bridge, create a
* separate VSI for it, hence avoiding FDB based forwarding.
* This is done by not sending join message to the bridge in NSS.
*/
if (br_mgr_ctx.wan_if_num == ifnum) {
br->wan_if_enabled = true;
br->wan_if_num = ifnum;
nss_bridge_mgr_info("if_num %d is added as WAN interface \n", ifnum);
return 0;
}
if (ppe_port_vsi_get(NSS_BRIDGE_MGR_SWITCH_ID, port_num, &br->port_vsi[port_num - 1])) {
nss_bridge_mgr_warn("%px: failed to save port VSI of physical interface\n", br);
return -EIO;
}
if (ppe_port_vsi_set(NSS_BRIDGE_MGR_SWITCH_ID, port_num, br->vsi)) {
nss_bridge_mgr_warn("%px: failed to set bridge VSI for physical interface\n", br);
return -EIO;
}
} else if (is_vlan_dev(dev)) {
struct net_device *real_dev;
/*
* Find real_dev associated with the VLAN
*/
real_dev = nss_vlan_mgr_get_real_dev(dev);
if (real_dev && is_vlan_dev(real_dev))
real_dev = nss_vlan_mgr_get_real_dev(real_dev);
if (real_dev == NULL) {
nss_bridge_mgr_warn("%px: real dev for the vlan: %s in NULL\n", br, dev->name);
return -EINVAL;
}
/*
* This is a valid vlan dev, add the vlan dev to bridge
*/
if (nss_vlan_mgr_join_bridge(dev, br->vsi)) {
nss_bridge_mgr_warn("%px: vlan device failed to join bridge\n", br);
return -ENODEV;
}
/*
* dev is a bond with VLAN and VLAN is added to bridge
*/
if (netif_is_bond_master(real_dev)) {
if (nss_bridge_tx_join_msg(br->ifnum, dev) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: Interface %s join bridge failed\n", br, dev->name);
nss_vlan_mgr_leave_bridge(dev, br->vsi);
return -ENOENT;
}
/*
* Add the bond_master to bridge.
*/
if (nss_bridge_mgr_bond_master_join(real_dev, br) != NOTIFY_DONE) {
nss_bridge_mgr_warn("%px: Slaves of bond interface %s join bridge failed\n", br, real_dev->name);
nss_bridge_tx_leave_msg(br->ifnum, dev);
nss_vlan_mgr_leave_bridge(dev, br->vsi);
return -EINVAL;
}
return 0;
}
}
#endif
if (nss_bridge_tx_join_msg(br->ifnum, dev) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: Interface %s join bridge failed\n", br, dev->name);
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
if (NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(ifnum)) {
fal_port_t port_num = (fal_port_t)ifnum;
ppe_port_vsi_set(NSS_BRIDGE_MGR_SWITCH_ID, port_num, br->port_vsi[port_num - 1]);
}
#endif
return -EIO;
}
return 0;
}
/*
* nss_bridge_mgr_leave_bridge()
* Netdevice leave bridge and send netdevice leaving bridge message to NSS FW.
*/
int nss_bridge_mgr_leave_bridge(struct net_device *dev, struct nss_bridge_pvt *br)
{
int32_t ifnum;
ifnum = nss_cmn_get_interface_number_by_dev(dev);
if (ifnum < 0) {
nss_bridge_mgr_warn("%s: failed to find interface number\n", dev->name);
return -1;
}
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
if (NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(ifnum)) {
fal_port_t port_num = (fal_port_t)ifnum;
if (fal_stp_port_state_set(NSS_BRIDGE_MGR_SWITCH_ID, NSS_BRIDGE_MGR_SPANNING_TREE_ID, port_num, FAL_STP_FORWARDING)) {
nss_bridge_mgr_warn("%px: faied to set the STP state to forwarding\n", br);
return -1;
}
/*
* If there is a wan interface added in bridge, a separate
* VSI is created for it by not sending join message to NSS.
* Hence a leave message should also be avaoided.
*/
if ((br->wan_if_enabled) && (br->wan_if_num == ifnum)) {
br->wan_if_enabled = false;
br->wan_if_num = -1;
nss_bridge_mgr_info("if_num %d is added as WAN interface\n", ifnum);
return 0;
}
if (ppe_port_vsi_set(NSS_BRIDGE_MGR_SWITCH_ID, port_num, br->port_vsi[port_num - 1])) {
nss_bridge_mgr_warn("%px: failed to restore port VSI of physical interface\n", br);
fal_stp_port_state_set(NSS_BRIDGE_MGR_SWITCH_ID, NSS_BRIDGE_MGR_SPANNING_TREE_ID, port_num, FAL_STP_DISABLED);
return -1;
}
} else if (is_vlan_dev(dev)) {
struct net_device *real_dev;
/*
* Find real_dev associated with the VLAN.
*/
real_dev = nss_vlan_mgr_get_real_dev(dev);
if (real_dev && is_vlan_dev(real_dev))
real_dev = nss_vlan_mgr_get_real_dev(real_dev);
if (real_dev == NULL) {
nss_bridge_mgr_warn("%px: real dev for the vlan: %s in NULL\n", br, dev->name);
return -1;
}
/*
* This is a valid vlan dev, remove the vlan dev from bridge.
*/
if (nss_vlan_mgr_leave_bridge(dev, br->vsi)) {
nss_bridge_mgr_warn("%px: vlan device failed to leave bridge\n", br);
return -1;
}
/*
* dev is a bond with VLAN and VLAN is removed from bridge
*/
if (netif_is_bond_master(real_dev)) {
if (nss_bridge_tx_leave_msg(br->ifnum, dev) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: Interface %s leave bridge failed\n", br, dev->name);
nss_vlan_mgr_join_bridge(dev, br->vsi);
nss_bridge_tx_join_msg(br->ifnum, dev);
return -1;
}
/*
* Remove the bond_master from bridge.
*/
if (nss_bridge_mgr_bond_master_leave(real_dev, br) != NOTIFY_DONE) {
nss_bridge_mgr_warn("%px: Slaves of bond interface %s leave bridge failed\n", br, real_dev->name);
nss_vlan_mgr_join_bridge(dev, br->vsi);
nss_bridge_tx_join_msg(br->ifnum, dev);
return -1;
}
return 0;
}
}
#endif
if (nss_bridge_tx_leave_msg(br->ifnum, dev) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: Interface %s leave bridge failed\n", br, dev->name);
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
if (is_vlan_dev(dev)) {
nss_vlan_mgr_join_bridge(dev, br->vsi);
nss_bridge_tx_join_msg(br->ifnum, dev);
} else if (NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(ifnum)) {
fal_port_t port_num = (fal_port_t)ifnum;
fal_stp_port_state_set(NSS_BRIDGE_MGR_SWITCH_ID, NSS_BRIDGE_MGR_SPANNING_TREE_ID, port_num, FAL_STP_DISABLED);
ppe_port_vsi_set(NSS_BRIDGE_MGR_SWITCH_ID, port_num, br->vsi);
}
#endif
return -1;
}
return 0;
}
/*
* nss_bridge_mgr_unregister_br()
* Unregister bridge device, dev, from bridge manager database.
*/
int nss_bridge_mgr_unregister_br(struct net_device *dev)
{
struct nss_bridge_pvt *b_pvt;
/*
* Do we have it on record?
*/
b_pvt = nss_bridge_mgr_find_instance(dev);
if (!b_pvt)
return -1;
/*
* sequence of free:
* 1. issue VSI unassign to NSS
* 2. free VSI
* 3. flush bridge FDB table
* 4. unregister bridge netdevice from data plane
* 5. deallocate dynamic interface associated with bridge netdevice
* 6. free bridge netdevice
*/
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
/*
* VSI unassign function in NSS firmware only returns
* CNODE_SEND_NACK in the beginning of the function when it
* detects that bridge VSI is not assigned for the bridge.
* Please refer to the function bridge_configure_vsi_unassign
* in NSS firmware for detailed operation.
*/
if (nss_bridge_tx_vsi_unassign_msg(b_pvt->ifnum, b_pvt->vsi) != NSS_TX_SUCCESS)
nss_bridge_mgr_warn("%px: failed to unassign vsi\n", b_pvt);
ppe_vsi_free(NSS_BRIDGE_MGR_SWITCH_ID, b_pvt->vsi);
/*
* It may happen that the same VSI is allocated again,
* so there is a need to flush bridge FDB table.
*/
if (fal_fdb_entry_del_byfid(NSS_BRIDGE_MGR_SWITCH_ID, b_pvt->vsi, FAL_FDB_DEL_STATIC)) {
nss_bridge_mgr_warn("%px: Failed to flush FDB table for vsi:%d in PPE\n", b_pvt, b_pvt->vsi);
}
#endif
nss_bridge_mgr_trace("%px: Bridge %s unregsitered. Freeing bridge di %d\n", b_pvt, dev->name, b_pvt->ifnum);
nss_bridge_unregister(b_pvt->ifnum);
if (nss_dynamic_interface_dealloc_node(b_pvt->ifnum, NSS_DYNAMIC_INTERFACE_TYPE_BRIDGE) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: dealloc bridge di failed\n", b_pvt);
}
nss_bridge_mgr_delete_instance(b_pvt);
return 0;
}
/*
* nss_bridge_mgr_register_br()
* Register new bridge instance in bridge manager database.
*/
int nss_bridge_mgr_register_br(struct net_device *dev)
{
struct nss_bridge_pvt *b_pvt;
int ifnum;
int err;
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
uint32_t vsi_id = 0;
#endif
nss_bridge_mgr_info("%px: Bridge register: %s\n", dev, dev->name);
b_pvt = nss_bridge_mgr_create_instance(dev);
if (!b_pvt)
return -EINVAL;
b_pvt->dev = dev;
ifnum = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_BRIDGE);
if (ifnum < 0) {
nss_bridge_mgr_warn("%px: failed to alloc bridge di\n", b_pvt);
nss_bridge_mgr_delete_instance(b_pvt);
return -EFAULT;
}
if (!nss_bridge_register(ifnum, dev, NULL, NULL, 0, b_pvt)) {
nss_bridge_mgr_warn("%px: failed to register bridge di to NSS\n", b_pvt);
goto fail;
}
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
err = ppe_vsi_alloc(NSS_BRIDGE_MGR_SWITCH_ID, &vsi_id);
if (err) {
nss_bridge_mgr_warn("%px: failed to alloc bridge vsi, error = %d\n", b_pvt, err);
goto fail_1;
}
b_pvt->vsi = vsi_id;
err = nss_bridge_tx_vsi_assign_msg(ifnum, vsi_id);
if (err != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: failed to assign vsi msg, error = %d\n", b_pvt, err);
goto fail_2;
}
#endif
err = nss_bridge_tx_set_mac_addr_msg(ifnum, dev->dev_addr);
if (err != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: failed to set mac_addr msg, error = %d\n", b_pvt, err);
goto fail_3;
}
err = nss_bridge_tx_set_mtu_msg(ifnum, dev->mtu);
if (err != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: failed to set mtu msg, error = %d\n", b_pvt, err);
goto fail_3;
}
/*
* All done, take a snapshot of the current mtu and mac addrees
*/
b_pvt->ifnum = ifnum;
b_pvt->mtu = dev->mtu;
b_pvt->wan_if_num = -1;
b_pvt->wan_if_enabled = false;
ether_addr_copy(b_pvt->dev_addr, dev->dev_addr);
spin_lock(&br_mgr_ctx.lock);
list_add(&b_pvt->list, &br_mgr_ctx.list);
spin_unlock(&br_mgr_ctx.lock);
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
/*
* Disable FDB learning if OVS is enabled for
* all bridges (including Linux bridge).
*/
if (ovs_enabled) {
nss_bridge_mgr_disable_fdb_learning(b_pvt);
}
#endif
return 0;
fail_3:
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
if (nss_bridge_tx_vsi_unassign_msg(ifnum, vsi_id) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: failed to unassign vsi\n", b_pvt);
}
fail_2:
ppe_vsi_free(NSS_BRIDGE_MGR_SWITCH_ID, vsi_id);
fail_1:
#endif
nss_bridge_unregister(ifnum);
fail:
if (nss_dynamic_interface_dealloc_node(ifnum, NSS_DYNAMIC_INTERFACE_TYPE_BRIDGE) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: failed to dealloc bridge di\n", b_pvt);
}
nss_bridge_mgr_delete_instance(b_pvt);
return -EFAULT;
}
/*
* nss_bridge_mgr_bond_slave_changeupper()
* Add bond slave to bridge VSI
*/
static int nss_bridge_mgr_bond_slave_changeupper(struct netdev_notifier_changeupper_info *cu_info,
struct net_device *bond_slave)
{
struct net_device *master;
struct nss_bridge_pvt *b_pvt;
/*
* Checking if our bond master is part of a bridge
*/
master = netdev_master_upper_dev_get(cu_info->upper_dev);
if (!master)
return NOTIFY_DONE;
b_pvt = nss_bridge_mgr_find_instance(master);
if (!b_pvt) {
nss_bridge_mgr_warn("The bond master is not part of Bridge dev:%s\n", master->name);
return NOTIFY_DONE;
}
/*
* Add or remove the slave based based on linking event
*/
if (cu_info->linking) {
if (nss_bridge_mgr_add_bond_slave(cu_info->upper_dev, bond_slave, b_pvt)) {
nss_bridge_mgr_warn("%px: Failed to add slave (%s) state in Bridge %s\n", b_pvt,
cu_info->upper_dev->name, master->name);
}
} else {
if (nss_bridge_mgr_del_bond_slave(cu_info->upper_dev, bond_slave, b_pvt)) {
nss_bridge_mgr_warn("%px: Failed to remove slave (%s) state in Bridge %s\n", b_pvt,
cu_info->upper_dev->name, master->name);
}
}
return NOTIFY_DONE;
}
/*
* nss_bridge_mgr_changemtu_event()
* Change bridge MTU and send change bridge MTU message to NSS FW.
*/
static int nss_bridge_mgr_changemtu_event(struct netdev_notifier_info *info)
{
struct net_device *dev = netdev_notifier_info_to_dev(info);
struct nss_bridge_pvt *b_pvt = nss_bridge_mgr_find_instance(dev);
if (!b_pvt)
return NOTIFY_DONE;
spin_lock(&br_mgr_ctx.lock);
if (b_pvt->mtu == dev->mtu) {
spin_unlock(&br_mgr_ctx.lock);
return NOTIFY_DONE;
}
spin_unlock(&br_mgr_ctx.lock);
nss_bridge_mgr_trace("%px: MTU changed to %d, send message to NSS\n", b_pvt, dev->mtu);
if (nss_bridge_tx_set_mtu_msg(b_pvt->ifnum, dev->mtu) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: Failed to send change MTU message to NSS\n", b_pvt);
return NOTIFY_DONE;
}
spin_lock(&br_mgr_ctx.lock);
b_pvt->mtu = dev->mtu;
spin_unlock(&br_mgr_ctx.lock);
return NOTIFY_DONE;
}
/*
* nss_bridge_mgr_changeaddr_event()
* Change bridge MAC address and send change bridge address message to NSS FW.
*/
static int nss_bridge_mgr_changeaddr_event(struct netdev_notifier_info *info)
{
struct net_device *dev = netdev_notifier_info_to_dev(info);
struct nss_bridge_pvt *b_pvt = nss_bridge_mgr_find_instance(dev);
if (!b_pvt)
return NOTIFY_DONE;
spin_lock(&br_mgr_ctx.lock);
if (!memcmp(b_pvt->dev_addr, dev->dev_addr, ETH_ALEN)) {
spin_unlock(&br_mgr_ctx.lock);
nss_bridge_mgr_trace("%px: MAC are the same..skip processing it\n", b_pvt);
return NOTIFY_DONE;
}
spin_unlock(&br_mgr_ctx.lock);
nss_bridge_mgr_trace("%px: MAC changed to %pM, update NSS\n", b_pvt, dev->dev_addr);
if (nss_bridge_tx_set_mac_addr_msg(b_pvt->ifnum, dev->dev_addr) != NSS_TX_SUCCESS) {
nss_bridge_mgr_warn("%px: Failed to send change MAC address message to NSS\n", b_pvt);
return NOTIFY_DONE;
}
spin_lock(&br_mgr_ctx.lock);
ether_addr_copy(b_pvt->dev_addr, dev->dev_addr);
spin_unlock(&br_mgr_ctx.lock);
return NOTIFY_DONE;
}
/*
* nss_bridge_mgr_changeupper_event()
* Bridge manager handles netdevice joining or leaving bridge notification.
*/
static int nss_bridge_mgr_changeupper_event(struct netdev_notifier_info *info)
{
struct net_device *dev = netdev_notifier_info_to_dev(info);
struct net_device *master_dev;
struct netdev_notifier_changeupper_info *cu_info;
struct nss_bridge_pvt *b_pvt;
cu_info = (struct netdev_notifier_changeupper_info *)info;
/*
* Check if the master pointer is valid
*/
if (!cu_info->master)
return NOTIFY_DONE;
/*
* The master is a bond that we don't need to process, but the bond might be part of a bridge.
*/
if (netif_is_bond_slave(dev))
return nss_bridge_mgr_bond_slave_changeupper(cu_info, dev);
master_dev = cu_info->upper_dev;
/*
* Check if upper_dev is a known bridge.
*/
b_pvt = nss_bridge_mgr_find_instance(master_dev);
if (!b_pvt)
return NOTIFY_DONE;
/*
* Slave device is bond master and it is added/removed to/from bridge
*/
if (netif_is_bond_master(dev)) {
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
if (cu_info->linking)
return nss_bridge_mgr_bond_master_join(dev, b_pvt);
else
return nss_bridge_mgr_bond_master_leave(dev, b_pvt);
#endif
}
if (cu_info->linking) {
nss_bridge_mgr_trace("%px: Interface %s joining bridge %s\n", b_pvt, dev->name, master_dev->name);
if (nss_bridge_mgr_join_bridge(dev, b_pvt)) {
nss_bridge_mgr_warn("%px: Interface %s failed to join bridge %s\n", b_pvt, dev->name, master_dev->name);
}
return NOTIFY_DONE;
}
nss_bridge_mgr_trace("%px: Interface %s leaving bridge %s\n", b_pvt, dev->name, master_dev->name);
if (nss_bridge_mgr_leave_bridge(dev, b_pvt)) {
nss_bridge_mgr_warn("%px: Interface %s failed to leave bridge %s\n", b_pvt, dev->name, master_dev->name);
}
return NOTIFY_DONE;
}
/*
* nss_bridge_mgr_register_event()
* Bridge manager handles bridge registration notification.
*/
static int nss_bridge_mgr_register_event(struct netdev_notifier_info *info)
{
nss_bridge_mgr_register_br(netdev_notifier_info_to_dev(info));
return NOTIFY_DONE;
}
/*
* nss_bridge_mgr_unregister_event()
* Bridge manager handles bridge unregistration notification.
*/
static int nss_bridge_mgr_unregister_event(struct netdev_notifier_info *info)
{
nss_bridge_mgr_unregister_br(netdev_notifier_info_to_dev(info));
return NOTIFY_DONE;
}
/*
* nss_bridge_mgr_netdevice_event()
* Bridge manager handles bridge operation notifications.
*/
static int nss_bridge_mgr_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct netdev_notifier_info *info = (struct netdev_notifier_info *)ptr;
switch (event) {
case NETDEV_CHANGEUPPER:
return nss_bridge_mgr_changeupper_event(info);
case NETDEV_CHANGEADDR:
return nss_bridge_mgr_changeaddr_event(info);
case NETDEV_CHANGEMTU:
return nss_bridge_mgr_changemtu_event(info);
case NETDEV_REGISTER:
return nss_bridge_mgr_register_event(info);
case NETDEV_UNREGISTER:
return nss_bridge_mgr_unregister_event(info);
}
/*
* Notify done for all the events we don't care
*/
return NOTIFY_DONE;
}
static struct notifier_block nss_bridge_mgr_netdevice_nb __read_mostly = {
.notifier_call = nss_bridge_mgr_netdevice_event,
};
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
/*
* nss_bridge_mgr_is_physical_dev()
* Check if the device is on physical device.
*/
static bool nss_bridge_mgr_is_physical_dev(struct net_device *dev)
{
struct net_device *root_dev = dev;
uint32_t ifnum;
if (!dev)
return false;
/*
* Check if it is VLAN first because VLAN can be over bond interface.
* However, the bond over VLAN is not supported in our driver.
*/
if (is_vlan_dev(dev)) {
root_dev = nss_vlan_mgr_get_real_dev(dev);
if (!root_dev)
goto error;
if (is_vlan_dev(root_dev))
root_dev = nss_vlan_mgr_get_real_dev(root_dev);
if (!root_dev)
goto error;
}
/*
* Don't consider bond interface because FDB learning is disabled.
*/
if (netif_is_bond_master(root_dev))
return false;
ifnum = nss_cmn_get_interface_number_by_dev(root_dev);
if (!NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(ifnum)) {
nss_bridge_mgr_warn("%px: interface %s is not physical interface\n",
root_dev, root_dev->name);
return false;
}
return true;
error:
nss_bridge_mgr_warn("%px: cannot find the real device for VLAN %s\n", dev, dev->name);
return false;
}
/*
* nss_bridge_mgr_fdb_update_callback()
* Get invoked when there is a FDB update.
*/
static int nss_bridge_mgr_fdb_update_callback(struct notifier_block *notifier,
unsigned long val, void *ctx)
{
struct br_fdb_event *event = (struct br_fdb_event *)ctx;
struct nss_bridge_pvt *b_pvt = NULL;
struct net_device *br_dev = NULL;
fal_fdb_entry_t entry;
if (!event->br)
return NOTIFY_DONE;
br_dev = br_fdb_bridge_dev_get_and_hold(event->br);
if (!br_dev) {
nss_bridge_mgr_warn("%px: bridge device not found\n", event->br);
return NOTIFY_DONE;
}
nss_bridge_mgr_trace("%px: MAC: %pM, original source: %s, new source: %s, bridge: %s\n",
event, event->addr, event->orig_dev->name, event->dev->name, br_dev->name);
/*
* When a MAC address move from a physical interface to a non-physical
* interface, the FDB entry in the PPE needs to be flushed.
*/
if (!nss_bridge_mgr_is_physical_dev(event->orig_dev)) {
nss_bridge_mgr_trace("%px: original source is not a physical interface\n", event->orig_dev);
dev_put(br_dev);
return NOTIFY_DONE;
}
if (nss_bridge_mgr_is_physical_dev(event->dev)) {
nss_bridge_mgr_trace("%px: new source is not a non-physical interface\n", event->dev);
dev_put(br_dev);
return NOTIFY_DONE;
}
b_pvt = nss_bridge_mgr_find_instance(br_dev);
dev_put(br_dev);
if (!b_pvt) {
nss_bridge_mgr_warn("%px: bridge instance not found\n", event->br);
return NOTIFY_DONE;
}
memset(&entry, 0, sizeof(entry));
memcpy(&entry.addr, event->addr, ETH_ALEN);
entry.fid = b_pvt->vsi;
if (SW_OK != fal_fdb_entry_del_bymac(NSS_BRIDGE_MGR_SWITCH_ID, &entry)) {
nss_bridge_mgr_warn("%px: FDB entry delete failed with MAC %pM and fid %d\n",
b_pvt, &entry.addr, entry.fid);
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
/*
* Notifier block for FDB update
*/
static struct notifier_block nss_bridge_mgr_fdb_update_notifier = {
.notifier_call = nss_bridge_mgr_fdb_update_callback,
};
/*
* nss_bridge_mgr_wan_inf_add_handler
* Marks an interface as a WAN interface for special handling by bridge.
*/
static int nss_bridge_mgr_wan_intf_add_handler(struct ctl_table *table,
int write, void __user *buffer,
size_t *lenp, loff_t *ppos)
{
struct net_device *dev;
char *dev_name;
char *if_name;
int32_t if_num;
int ret;
/*
* Find the string, return an error if not found
*/
ret = proc_dostring(table, write, buffer, lenp, ppos);
if (ret || !write) {
return ret;
}
if_name = br_mgr_ctx.wan_ifname;
dev_name = strsep(&if_name, " ");
dev = dev_get_by_name(&init_net, dev_name);
if (!dev) {
nss_bridge_mgr_warn("Cannot find the net device associated with %s\n", dev_name);
return -ENODEV;
}
if_num = nss_cmn_get_interface_number_by_dev(dev);
if (!NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(if_num)) {
dev_put(dev);
nss_bridge_mgr_warn("Only physical interfaces can be marked as WAN interface: if_num %d\n", if_num);
return -ENOMSG;
}
if (br_mgr_ctx.wan_if_num != -1) {
dev_put(dev);
nss_bridge_mgr_warn("Cannot overwrite a pre-existing wan interface\n");
return -ENOMSG;
}
br_mgr_ctx.wan_if_num = if_num;
dev_put(dev);
nss_bridge_mgr_always("For adding if_num: %d as WAN interface, do a network restart\n", if_num);
return ret;
}
/*
* nss_bridge_mgr_wan_inf_del_handler
* Un-marks an interface as a WAN interface.
*/
static int nss_bridge_mgr_wan_intf_del_handler(struct ctl_table *table,
int write, void __user *buffer,
size_t *lenp, loff_t *ppos)
{
struct net_device *dev;
char *dev_name;
char *if_name;
int32_t if_num;
int ret;
ret = proc_dostring(table, write, buffer, lenp, ppos);
if (ret)
return ret;
if (!write)
return ret;
if_name = br_mgr_ctx.wan_ifname;
dev_name = strsep(&if_name, " ");
dev = dev_get_by_name(&init_net, dev_name);
if (!dev) {
nss_bridge_mgr_warn("Cannot find the net device associated with %s\n", dev_name);
return -ENODEV;
}
if_num = nss_cmn_get_interface_number_by_dev(dev);
if (!NSS_BRIDGE_MGR_IF_IS_TYPE_PHYSICAL(if_num)) {
dev_put(dev);
nss_bridge_mgr_warn("Only physical interfaces can be marked/unmarked, if_num: %d\n", if_num);
return -ENOMSG;
}
if (br_mgr_ctx.wan_if_num != if_num) {
dev_put(dev);
nss_bridge_mgr_warn("This interface is not marked as a WAN interface\n");
return -ENOMSG;
}
br_mgr_ctx.wan_if_num = -1;
dev_put(dev);
nss_bridge_mgr_always("For deleting if_num: %d as WAN interface, do a network restart\n", if_num);
return ret;
}
static struct ctl_table nss_bridge_mgr_table[] = {
{
.procname = "add_wanif",
.data = &br_mgr_ctx.wan_ifname,
.maxlen = sizeof(char) * IFNAMSIZ,
.mode = 0644,
.proc_handler = &nss_bridge_mgr_wan_intf_add_handler,
},
{
.procname = "del_wanif",
.data = &br_mgr_ctx.wan_ifname,
.maxlen = sizeof(char) * IFNAMSIZ,
.mode = 0644,
.proc_handler = &nss_bridge_mgr_wan_intf_del_handler,
},
{ }
};
static struct ctl_table nss_bridge_mgr_dir[] = {
{
.procname = "bridge_mgr",
.mode = 0555,
.child = nss_bridge_mgr_table,
},
{ }
};
static struct ctl_table nss_bridge_mgr_root_dir[] = {
{
.procname = "nss",
.mode = 0555,
.child = nss_bridge_mgr_dir,
},
{ }
};
#endif
/*
* nss_bridge_mgr_init_module()
* bridge_mgr module init function
*/
int __init nss_bridge_mgr_init_module(void)
{
/*
* Monitor bridge activity only on supported platform
*/
if (!of_machine_is_compatible("qcom,ipq807x") && !of_machine_is_compatible("qcom,ipq6018") && !of_machine_is_compatible("qcom,ipq8074"))
return 0;
INIT_LIST_HEAD(&br_mgr_ctx.list);
spin_lock_init(&br_mgr_ctx.lock);
register_netdevice_notifier(&nss_bridge_mgr_netdevice_nb);
nss_bridge_mgr_info("Module (Build %s) loaded\n", NSS_CLIENT_BUILD_ID);
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
br_mgr_ctx.wan_if_num = -1;
br_fdb_update_register_notify(&nss_bridge_mgr_fdb_update_notifier);
br_mgr_ctx.nss_bridge_mgr_header = register_sysctl_table(nss_bridge_mgr_root_dir);
/*
* Enable ACL rule to enable L2 exception. This is needed if PPE Virtual ports is added to bridge.
* It is assumed that VP is using flow based bridging, hence L2 exceptions will need to be enabled on PPE bridge.
*/
if (!nss_bridge_mgr_l2_exception_acl_enable()) {
nss_bridge_mgr_warn("Failed to enable ACL\n");
}
#endif
#if defined (NSS_BRIDGE_MGR_OVS_ENABLE)
nss_bridge_mgr_ovs_init();
#endif
return 0;
}
/*
* nss_bridge_mgr_exit_module()
* bridge_mgr module exit function
*/
void __exit nss_bridge_mgr_exit_module(void)
{
unregister_netdevice_notifier(&nss_bridge_mgr_netdevice_nb);
nss_bridge_mgr_info("Module unloaded\n");
#if defined(NSS_BRIDGE_MGR_PPE_SUPPORT)
br_fdb_update_unregister_notify(&nss_bridge_mgr_fdb_update_notifier);
if (br_mgr_ctx.nss_bridge_mgr_header) {
unregister_sysctl_table(br_mgr_ctx.nss_bridge_mgr_header);
}
/*
* Disable the PPE L2 exceptions which were enabled during module init for PPE virtual ports.
*/
nss_bridge_mgr_l2_exception_acl_disable();
#endif
#if defined (NSS_BRIDGE_MGR_OVS_ENABLE)
nss_bridge_mgr_ovs_exit();
#endif
}
module_init(nss_bridge_mgr_init_module);
module_exit(nss_bridge_mgr_exit_module);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("NSS bridge manager");
module_param(ovs_enabled, bool, 0644);
MODULE_PARM_DESC(ovs_enabled, "OVS bridge is enabled");