blob: 842b8433d231336c8b49d1d405dd2c8db2c5c6de [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2017-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.
**************************************************************************
*/
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <linux/version.h>
#include <net/switchdev.h>
#include "nss_dp_dev.h"
#include "fal/fal_stp.h"
#include "fal/fal_ctrlpkt.h"
#define NSS_DP_SWITCH_ID 0
#define NSS_DP_SW_ETHTYPE_PID 0 /* PPE ethtype profile ID for slow protocols */
#define ETH_P_NONE 0
/*
* nss_dp_set_slow_proto_filter()
* Enable/Disable filter to allow Ethernet slow-protocol
*/
static void nss_dp_set_slow_proto_filter(struct nss_dp_dev *dp_priv, bool filter_enable)
{
sw_error_t ret = 0;
fal_ctrlpkt_profile_t profile;
fal_ctrlpkt_action_t action;
memset(&profile, 0, sizeof(profile));
/*
* Action is redirect cpu
*/
action.action = FAL_MAC_RDT_TO_CPU;
action.sg_bypass = A_FALSE;
/*
* Bypass stp
*/
action.in_stp_bypass = A_TRUE;
action.in_vlan_fltr_bypass = A_FALSE;
action.l2_filter_bypass = A_FALSE;
profile.action = action;
profile.ethtype_profile_bitmap = 0x1;
/*
* Set port map
*/
profile.port_map = (1 << dp_priv->macid);
if (filter_enable) {
ret = fal_mgmtctrl_ctrlpkt_profile_add(NSS_DP_SWITCH_ID, &profile);
if (ret != SW_OK) {
netdev_dbg(dp_priv->netdev, "failed to add profile for port_map: 0x%x, ret: %d\n", profile.port_map, ret);
return;
}
/*
* Enable filter to allow ethernet slow-protocol,
* if this is the first port being disabled by STP
*/
if (!dp_priv->ctx->slowproto_acl_bm) {
ret = fal_mgmtctrl_ethtype_profile_set(NSS_DP_SWITCH_ID, NSS_DP_SW_ETHTYPE_PID, ETH_P_SLOW);
if (ret != SW_OK) {
netdev_dbg(dp_priv->netdev, "failed to set ethertype profile: 0x%x, ret: %d\n", ETH_P_SLOW, ret);
ret = fal_mgmtctrl_ctrlpkt_profile_del(NSS_DP_SWITCH_ID, &profile);
if (ret != SW_OK) {
netdev_dbg(dp_priv->netdev, "failed to delete profile for port_map: 0x%x, ret: %d\n", profile.port_map, ret);
}
return;
}
}
/*
* Add port to port bitmap
*/
dp_priv->ctx->slowproto_acl_bm = dp_priv->ctx->slowproto_acl_bm | (1 << dp_priv->macid);
} else {
ret = fal_mgmtctrl_ctrlpkt_profile_del(NSS_DP_SWITCH_ID, &profile);
if (ret != SW_OK) {
netdev_dbg(dp_priv->netdev, "failed to delete profile for port_map: 0x%x, ret: %d\n", profile.port_map, ret);
return;
}
/*
* Delete port from port bitmap
*/
dp_priv->ctx->slowproto_acl_bm = dp_priv->ctx->slowproto_acl_bm & (~(1 << dp_priv->macid));
/*
* If all ports are in STP-enabled state, then we do not need
* the filter to allow ethernet slow protocol packets
*/
if (!dp_priv->ctx->slowproto_acl_bm) {
ret = fal_mgmtctrl_ethtype_profile_set(NSS_DP_SWITCH_ID, NSS_DP_SW_ETHTYPE_PID, ETH_P_NONE);
if (ret != SW_OK) {
netdev_dbg(dp_priv->netdev, "failed to reset ethertype profile: 0x%x ret: %d\n", ETH_P_NONE, ret);
}
}
}
}
/*
* nss_dp_stp_state_set()
* Set bridge port STP state to the port of NSS data plane.
*/
static int nss_dp_stp_state_set(struct nss_dp_dev *dp_priv, u8 state)
{
sw_error_t err;
fal_stp_state_t stp_state;
switch (state) {
case BR_STATE_DISABLED:
stp_state = FAL_STP_DISABLED;
/*
* Dynamic bond interfaces which are bridge slaves need to receive
* ethernet slow protocol packets for LACP protocol even in STP
* disabled state
*/
nss_dp_set_slow_proto_filter(dp_priv, true);
break;
case BR_STATE_LISTENING:
stp_state = FAL_STP_LISTENING;
break;
case BR_STATE_BLOCKING:
stp_state = FAL_STP_BLOCKING;
break;
case BR_STATE_LEARNING:
stp_state = FAL_STP_LEARNING;
break;
case BR_STATE_FORWARDING:
stp_state = FAL_STP_FORWARDING;
/*
* Remove the filter for allowing ethernet slow protocol packets
* for bond interfaces
*/
nss_dp_set_slow_proto_filter(dp_priv, false);
break;
default:
return -EOPNOTSUPP;
}
err = fal_stp_port_state_set(NSS_DP_SWITCH_ID, 0, dp_priv->macid,
stp_state);
if (err) {
netdev_dbg(dp_priv->netdev, "failed to set ftp state\n");
/*
* Restore the slow proto filters
*/
if (state == BR_STATE_DISABLED)
nss_dp_set_slow_proto_filter(dp_priv, false);
else if (state == BR_STATE_FORWARDING)
nss_dp_set_slow_proto_filter(dp_priv, true);
return -EINVAL;
}
return 0;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
/*
* nss_dp_attr_get()
* Get port information to update switchdev attribute for NSS data plane.
*/
static int nss_dp_attr_get(struct net_device *dev, struct switchdev_attr *attr)
{
struct nss_dp_dev *dp_priv = (struct nss_dp_dev *)netdev_priv(dev);
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_PARENT_ID:
attr->u.ppid.id_len = 1;
attr->u.ppid.id[0] = NSS_DP_SWITCH_ID;
break;
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
attr->u.brport_flags = dp_priv->brport_flags;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
/*
* nss_dp_attr_set()
* Get switchdev attribute and set to the device of NSS data plane.
*/
static int nss_dp_attr_set(struct net_device *dev,
const struct switchdev_attr *attr,
struct switchdev_trans *trans)
{
struct nss_dp_dev *dp_priv = (struct nss_dp_dev *)netdev_priv(dev);
struct net_device *upper_dev;
struct vlan_dev_priv *vlan;
struct list_head *iter;
uint32_t stp_state = attr->u.stp_state;
if (switchdev_trans_ph_prepare(trans))
return 0;
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
dp_priv->brport_flags = attr->u.brport_flags;
netdev_dbg(dev, "set brport_flags %lu\n", attr->u.brport_flags);
return 0;
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
/*
* The stp state is not changed to FAL_STP_DISABLED if
* the net_device (dev) has any vlan configured. Otherwise
* traffic on other vlan(s) will not work.
*
* Note: STP for VLANs is not supported by PPE.
*/
if ((stp_state == BR_STATE_DISABLED) ||
(stp_state == BR_STATE_BLOCKING)) {
rcu_read_lock();
netdev_for_each_upper_dev_rcu(dev, upper_dev, iter) {
if (!is_vlan_dev(upper_dev))
continue;
vlan = vlan_dev_priv(upper_dev);
if (vlan->real_dev == dev) {
rcu_read_unlock();
netdev_dbg(dev, "Do not update stp state to: %u since vlan id: %d is configured on netdevice: %s\n",
stp_state, vlan->vlan_id, vlan->real_dev->name);
return 0;
}
}
rcu_read_unlock();
}
return nss_dp_stp_state_set(dp_priv, stp_state);
default:
return -EOPNOTSUPP;
}
}
/*
* nss_dp_switchdev_ops
* Switchdev operations of NSS data plane.
*/
static const struct switchdev_ops nss_dp_switchdev_ops = {
.switchdev_port_attr_get = nss_dp_attr_get,
.switchdev_port_attr_set = nss_dp_attr_set,
};
/*
* nss_dp_switchdev_setup()
* Set up NSS data plane switchdev operations.
*/
void nss_dp_switchdev_setup(struct net_device *dev)
{
dev->switchdev_ops = &nss_dp_switchdev_ops;
switchdev_port_fwd_mark_set(dev, NULL, false);
}
#else
/*
* nss_dp_port_attr_set()
* Sets attributes
*/
static int nss_dp_port_attr_set(struct net_device *dev,
const struct switchdev_attr *attr,
struct switchdev_trans *trans)
{
struct nss_dp_dev *dp_priv = (struct nss_dp_dev *)netdev_priv(dev);
if (switchdev_trans_ph_prepare(trans))
return 0;
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
dp_priv->brport_flags = attr->u.brport_flags;
netdev_dbg(dev, "set brport_flags %lu\n", attr->u.brport_flags);
return 0;
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
return nss_dp_stp_state_set(dp_priv, attr->u.stp_state);
default:
return -EOPNOTSUPP;
}
}
/*
* nss_dp_switchdev_port_attr_set_event()
* Attribute set event
*/
static int nss_dp_switchdev_port_attr_set_event(struct net_device *netdev,
struct switchdev_notifier_port_attr_info *port_attr_info)
{
int err;
err = nss_dp_port_attr_set(netdev, port_attr_info->attr,
port_attr_info->trans);
port_attr_info->handled = true;
return notifier_from_errno(err);
}
/*
* nss_dp_switchdev_event()
* Switch dev event on netdevice
*/
static int nss_dp_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
/*
* Handle switchdev event only for physical devices
*/
if (!nss_dp_is_phy_dev(dev)) {
return NOTIFY_DONE;
}
if (event == SWITCHDEV_PORT_ATTR_SET)
nss_dp_switchdev_port_attr_set_event(dev, ptr);
return NOTIFY_DONE;
}
static struct notifier_block nss_dp_switchdev_notifier = {
.notifier_call = nss_dp_switchdev_event,
};
static bool switch_init_done;
/*
* nss_dp_switchdev_setup()
* Setup switch dev
*/
void nss_dp_switchdev_setup(struct net_device *dev)
{
int err;
if (switch_init_done) {
return;
}
err = register_switchdev_blocking_notifier(&nss_dp_switchdev_notifier);
if (err) {
netdev_dbg(dev, "%px:Failed to register switchdev notifier\n", dev);
}
switch_init_done = true;
}
#endif