blob: 4797264f17ef920ed61687f5da6b744c9ce1ffd3 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2017-2020 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_connnmgr_gre.c
* This file implements client for GRE.
*/
#include <linux/version.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <net/ip_tunnels.h>
#include <net/ip6_tunnel.h>
#include <linux/if_ether.h>
#include <net/gre.h>
#include <net/ip.h>
#include <nss_api_if.h>
#include <nss_dynamic_interface.h>
#include <nss_cmn.h>
#include "nss_connmgr_gre_public.h"
#include "nss_connmgr_gre.h"
#define MAX_RETRY_COUNT 100
#define MAX_WIFI_HEADROOM 66
/*
* GRE connection manager context structure
*/
struct nss_connmgr_gre_context {
struct list_head list; /* List of GRE interface instances */
spinlock_t lock; /* Lock to protect list */
} gre_connmgr_ctx;
/*
* GRE interface instance
*/
struct nss_gre_iface_instance {
struct list_head list; /* List of GRE interface instances */
struct net_device *dev; /* GRE netdevice */
struct nss_connmgr_gre_cfg gre_cfg; /* GRE configuration */
enum nss_connmgr_gre_iftype gre_iftype; /* GRE interface type */
uint32_t inner_ifnum; /* GRE inner dynamic interface */
uint32_t outer_ifnum; /* GRE outer dynamic interface */
};
/*
* Unaligned infra in nss is disabled by default
*/
static bool enable_unalign = 1;
module_param(enable_unalign, bool, 0);
/*
* nss_connmgr_gre_is_gre()
* Check whether device is of type GRE Tap or GRE Tun.
*/
static bool nss_connmgr_gre_is_gre(struct net_device *dev)
{
if ((dev->type == ARPHRD_IPGRE) ||
(dev->type == ARPHRD_IP6GRE) || ((dev->type == ARPHRD_ETHER) &&
(dev->priv_flags_ext & (IFF_EXT_GRE_V4_TAP | IFF_EXT_GRE_V6_TAP)))) {
return true;
}
return false;
}
/*
* nss_connmgr_gre_alloc_instance()
* Allocate GRE interface instance.
*/
static struct nss_gre_iface_instance *nss_connmgr_gre_alloc_instance(struct net_device *dev)
{
struct nss_gre_iface_instance *ngii;
if (!nss_connmgr_gre_is_gre(dev)) {
nss_connmgr_gre_warning("%px: dev is not a GRE interface\n", dev);
return NULL;
}
ngii = kzalloc(sizeof(*ngii), GFP_KERNEL);
if (!ngii)
return NULL;
INIT_LIST_HEAD(&ngii->list);
ngii->dev = dev;
return ngii;
}
/*
* nss_connmgr_gre_free_instance()
* Free GRE interface instance.
*/
static void nss_connmgr_gre_free_instance(struct nss_gre_iface_instance *ngii)
{
spin_lock(&gre_connmgr_ctx.lock);
ngii->dev = NULL;
if (!list_empty(&ngii->list))
list_del(&ngii->list);
spin_unlock(&gre_connmgr_ctx.lock);
kfree(ngii);
}
/*
* nss_connmgr_gre_find_instance()
* Find GRE interface instance from list.
*/
static struct nss_gre_iface_instance *nss_connmgr_gre_find_instance(struct net_device *dev)
{
struct nss_gre_iface_instance *ngii;
if (!nss_connmgr_gre_is_gre(dev)) {
nss_connmgr_gre_warning("%px: dev is not a GRE interface\n", dev);
return NULL;
}
/*
* Check if dev instance is in the list
*/
spin_lock(&gre_connmgr_ctx.lock);
list_for_each_entry(ngii, &gre_connmgr_ctx.list, list) {
if (ngii->dev == dev) {
spin_unlock(&gre_connmgr_ctx.lock);
return ngii;
}
}
spin_unlock(&gre_connmgr_ctx.lock);
return NULL;
}
/*
* nss_connmgr_gre_dev_change_mtu()
* Netdev ops function to modify MTU of netdevice.
*/
static int nss_connmgr_gre_dev_change_mtu(struct net_device *dev, int new_mtu)
{
if (new_mtu < 68 ||
new_mtu > 0xFFF8 - dev->needed_headroom) {
return -EINVAL;
}
dev->mtu = new_mtu;
return 0;
}
/*
* nss_connmgr_gre_dev_init()
* Netdev ops function to intialize netdevice.
*/
static int nss_connmgr_gre_dev_init(struct net_device *dev)
{
int i;
nss_connmgr_gre_priv_t *priv = netdev_priv(dev);
int32_t append = priv->pad_len + priv->gre_hlen;
dev->tstats = alloc_percpu(struct pcpu_sw_netstats);
if (!dev->tstats) {
return -ENOMEM;
}
for_each_possible_cpu(i) {
struct pcpu_sw_netstats *stats;
stats = per_cpu_ptr(dev->tstats, i);
u64_stats_init(&stats->syncp);
}
if ((dev->priv_flags_ext & IFF_EXT_GRE_V4_TAP) || (dev->type == ARPHRD_IPGRE)) {
dev->needed_headroom = sizeof(struct iphdr) + sizeof(struct ethhdr) + MAX_WIFI_HEADROOM + append;
dev->mtu = ETH_DATA_LEN - sizeof(struct iphdr) - append;
dev->features |= NETIF_F_NETNS_LOCAL | NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA;
dev->hw_features |= NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA;
return 0;
}
/*
* Ipv6
*/
dev->needed_headroom = sizeof(struct ipv6hdr) + sizeof(struct ethhdr) + MAX_WIFI_HEADROOM + append;
dev->mtu = ETH_DATA_LEN - sizeof(struct ipv6hdr) - append;
if (dev->mtu < IPV6_MIN_MTU) {
dev->mtu = IPV6_MIN_MTU;
}
dev->features |= NETIF_F_NETNS_LOCAL;
return 0;
}
/*
* nss_connmgr_gre_dev_uninit()
* Netdev ops function to unintialize netdevice.
*/
static void nss_connmgr_gre_dev_uninit(struct net_device *dev)
{
free_percpu(dev->tstats);
return;
}
/*
* nss_connmgr_gre_dev_xmit()
* Netdev ops function to send packet to NSS.
*/
static netdev_tx_t nss_connmgr_gre_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
nss_tx_status_t status;
int if_number;
struct nss_ctx_instance *gre_ctx;
nss_connmgr_gre_priv_t *priv = netdev_priv(dev);
if_number = priv->nss_if_number_inner;
if (unlikely(if_number <= 0)) {
nss_connmgr_gre_info("%px: GRE dev is not registered with nss\n", dev);
goto fail;
}
gre_ctx = nss_gre_get_context();
if (unlikely(!gre_ctx)) {
nss_connmgr_gre_info("%px: NSS GRE context not found for if_number %d\n", dev, if_number);
goto fail;
}
/*
* Make room for needed headroom and un-share
* the SKB if it is cloned.
*/
if (skb_cow_head(skb, dev->needed_headroom)) {
nss_connmgr_gre_info("%px: NSS GRE insufficient headroom\n", dev);
goto fail;
}
status = nss_gre_tx_buf(gre_ctx, if_number, skb);
if (unlikely(status != NSS_TX_SUCCESS)) {
nss_connmgr_gre_info("%px: NSS GRE could not send packet to NSS %d\n", dev, if_number);
goto fail;
}
return NETDEV_TX_OK;
fail:
dev->stats.tx_dropped++;
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
}
/*
* nss_connmgr_gre_get_dev_stats64()
* To get the netdev stats
*/
static struct rtnl_link_stats64 *nss_connmgr_gre_get_dev_stats64(struct net_device *dev,
struct rtnl_link_stats64 *tot)
{
uint64_t rx_packets, rx_bytes, tx_packets, tx_bytes;
unsigned int start;
int i;
for_each_possible_cpu(i) {
const struct pcpu_sw_netstats *tstats = per_cpu_ptr(dev->tstats, i);
do {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0))
start = u64_stats_fetch_begin_bh(&tstats->syncp);
#else
start = u64_stats_fetch_begin_irq(&tstats->syncp);
#endif
rx_packets = tstats->rx_packets;
tx_packets = tstats->tx_packets;
rx_bytes = tstats->rx_bytes;
tx_bytes = tstats->tx_bytes;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0))
} while (u64_stats_fetch_retry_bh(&tstats->syncp, start));
#else
} while (u64_stats_fetch_retry_irq(&tstats->syncp, start));
#endif
tot->rx_packets += rx_packets;
tot->tx_packets += tx_packets;
tot->rx_bytes += rx_bytes;
tot->tx_bytes += tx_bytes;
tot->rx_dropped = dev->stats.rx_dropped;
tot->tx_dropped = dev->stats.tx_dropped;
}
return tot;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0))
/*
* nss_connmgr_gre_dev_stats64()
* Netdev ops function to retrieve stats for kernel version < 4.6
*/
static struct rtnl_link_stats64 *nss_connmgr_gre_dev_stats64(struct net_device *dev,
struct rtnl_link_stats64 *tot)
{
return nss_connmgr_gre_get_dev_stats64(dev, tot);
}
#else
/*
* nss_connmgr_gre_dev_stats64()
* Netdev ops function to retrieve stats
*/
static void nss_connmgr_gre_dev_stats64(struct net_device *dev,
struct rtnl_link_stats64 *tot)
{
nss_connmgr_gre_get_dev_stats64(dev, tot);
}
#endif
/*
* nss_connmgr_gre_dev_open()
* Netdev ops function to open netdevice.
*/
int nss_connmgr_gre_dev_open(struct net_device *dev)
{
struct nss_ctx_instance *nss_ctx;
struct nss_gre_msg req;
struct nss_gre_linkup_msg *linkup = &req.msg.linkup;
struct nss_gre_linkdown_msg *linkdown = &req.msg.linkdown;
nss_tx_status_t status;
int32_t inner_if, outer_if;
inner_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (inner_if < 0) {
return -EINVAL;
}
outer_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (outer_if < 0) {
return -EINVAL;
}
nss_ctx = nss_gre_get_context();
memset(&req, 0, sizeof(struct nss_gre_msg));
/*
* Open inner interface
*/
linkup->if_number = inner_if;
nss_gre_msg_init(&req, inner_if, NSS_IF_OPEN, sizeof(struct nss_gre_linkup_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: open failed for inner interface %s", dev, dev->name);
return -EFAULT;
}
/*
* Open outer interface
*/
linkup->if_number = outer_if;
nss_gre_msg_init(&req, outer_if, NSS_IF_OPEN, sizeof(struct nss_gre_linkup_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: open failed for outer interface %s", dev, dev->name);
linkdown->if_number = inner_if;
nss_gre_msg_init(&req, inner_if, NSS_IF_CLOSE, sizeof(struct nss_gre_linkdown_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: close failed for inner interface %s", dev, dev->name);
}
return -EFAULT;
}
netif_start_queue(dev);
return 0;
}
EXPORT_SYMBOL(nss_connmgr_gre_dev_open);
/*
* nss_connmgr_gre_dev_close()
* Netdevice ops function to close netdevice.
*/
int nss_connmgr_gre_dev_close(struct net_device *dev)
{
struct nss_ctx_instance *nss_ctx;
struct nss_gre_msg req;
struct nss_gre_linkdown_msg *linkdown = &req.msg.linkdown;
struct nss_gre_linkup_msg *linkup = &req.msg.linkup;
nss_tx_status_t status;
int32_t inner_if, outer_if;
netif_stop_queue(dev);
inner_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (inner_if < 0) {
nss_connmgr_gre_info("%px: close failed for interface %s, inner interface: %d not valid", dev, dev->name, inner_if);
return -EINVAL;
}
outer_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (outer_if < 0) {
nss_connmgr_gre_info("%px: close failed for interface %s, outer interface: %d not valid", dev, dev->name, inner_if);
return -EINVAL;
}
nss_ctx = nss_gre_get_context();
memset(&req, 0, sizeof(struct nss_gre_msg));
/*
* Close inner interface
*/
linkdown->if_number = inner_if;
nss_gre_msg_init(&req, inner_if, NSS_IF_CLOSE, sizeof(struct nss_gre_linkdown_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: close failed for inner interface %s", dev, dev->name);
return -EFAULT;
}
/*
* Close outer interface
*/
linkdown->if_number = outer_if;
nss_gre_msg_init(&req, outer_if, NSS_IF_CLOSE, sizeof(struct nss_gre_linkdown_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: close failed for outer interface %s", dev, dev->name);
linkup->if_number = inner_if;
nss_gre_msg_init(&req, inner_if, NSS_IF_OPEN, sizeof(struct nss_gre_linkup_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: open failed for inner interface %s", dev, dev->name);
}
return -EFAULT;
}
return 0;
}
EXPORT_SYMBOL(nss_connmgr_gre_dev_close);
/*
* Tap net device ops
*/
static const struct net_device_ops nss_connmgr_gre_tap_ops = {
.ndo_init = nss_connmgr_gre_dev_init,
.ndo_uninit = nss_connmgr_gre_dev_uninit,
.ndo_open = nss_connmgr_gre_dev_open,
.ndo_stop = nss_connmgr_gre_dev_close,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_change_mtu = nss_connmgr_gre_dev_change_mtu,
.ndo_start_xmit = nss_connmgr_gre_dev_xmit,
.ndo_get_stats64 = nss_connmgr_gre_dev_stats64,
};
/*
* Tun netdevice ops
*/
static const struct net_device_ops nss_connmgr_gre_tun_ops = {
.ndo_init = nss_connmgr_gre_dev_init,
.ndo_uninit = nss_connmgr_gre_dev_uninit,
.ndo_open = nss_connmgr_gre_dev_open,
.ndo_stop = nss_connmgr_gre_dev_close,
.ndo_change_mtu = nss_connmgr_gre_dev_change_mtu,
.ndo_start_xmit = nss_connmgr_gre_dev_xmit,
.ndo_get_stats64 = nss_connmgr_gre_dev_stats64,
};
/*
* nss_connmgr_gre_tun_setup()
*/
static void nss_connmgr_gre_tun_setup(struct net_device *dev)
{
dev->addr_len = 4;
dev->flags = IFF_NOARP | IFF_POINTOPOINT;
dev->priv_flags &= ~IFF_XMIT_DST_RELEASE;
dev->netdev_ops = &nss_connmgr_gre_tun_ops;
}
/*
* nss_connmgr_gre_tap_setup()
*/
static void nss_connmgr_gre_tap_setup(struct net_device *dev)
{
dev->netdev_ops = &nss_connmgr_gre_tap_ops;
eth_hw_addr_random(dev);
}
/*
* nss_connmgr_gre_prepare_config_cmd()
* Retrieve info from netdevie and fill it in config message to NSS.
*/
static int32_t nss_connmgr_gre_prepare_config_cmd(struct net_device *dev,
struct nss_gre_msg *req,
struct net_device **next_dev,
bool hold)
{
struct nss_gre_config_msg *cmsg = &req->msg.cmsg;
if ((dev->type == ARPHRD_ETHER) && (dev->priv_flags_ext & IFF_EXT_GRE_V4_TAP)) {
cmsg->mode = NSS_GRE_MODE_TAP;
cmsg->ip_type = NSS_GRE_IP_IPV4;
if (enable_unalign) {
cmsg->flags |= NSS_GRE_CONFIG_USE_UNALIGNED;
}
return nss_connmgr_gre_v4_get_config(dev, req, next_dev, hold);
}
if ((dev->type == ARPHRD_ETHER) && (dev->priv_flags_ext & IFF_EXT_GRE_V6_TAP)) {
cmsg->mode = NSS_GRE_MODE_TAP;
cmsg->ip_type = NSS_GRE_IP_IPV6;
if (enable_unalign) {
cmsg->flags |= NSS_GRE_CONFIG_USE_UNALIGNED;
}
return nss_connmgr_gre_v6_get_config(dev, req, next_dev, hold);
}
if (dev->type == ARPHRD_IPGRE) {
cmsg->mode = NSS_GRE_MODE_TUN;
cmsg->ip_type = NSS_GRE_IP_IPV4;
return nss_connmgr_gre_v4_get_config(dev, req, next_dev, hold);
}
if (dev->type == ARPHRD_IP6GRE) {
cmsg->mode = NSS_GRE_MODE_TUN;
cmsg->ip_type = NSS_GRE_IP_IPV6;
return nss_connmgr_gre_v6_get_config(dev, req, next_dev, hold);
}
return GRE_ERR_NOT_GRE_NETDEV;
}
/*
* nss_connmgr_gre_tap_inner_exception()
* Exception handler for GRETAP inner device
*
* These are the packets exceptioned after decapsulation,
* and we can send the packet to linux after modifying
* some skb fields.
*/
static void nss_connmgr_gre_tap_inner_exception(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct ethhdr *eth_hdr = (struct ethhdr *)skb->data;
nss_connmgr_gre_trace("%px: eth_hdr->h_proto: %d\n", dev, eth_hdr->h_proto);
if (likely(ntohs(eth_hdr->h_proto) >= ETH_P_802_3_MIN)) {
switch (ntohs(eth_hdr->h_proto)) {
case ETH_P_IP:
case ETH_P_IPV6:
/*
* These are decapped packets.
*/
skb->protocol = eth_type_trans(skb, dev);
netif_receive_skb(skb);
return;
default:
break;
}
}
/*
* These are decapped and exceptioned non IP packets.
*/
skb->protocol = eth_type_trans(skb, dev);
netif_receive_skb(skb);
return;
}
/*
* nss_connmgr_gre_tap_outer_exception()
* Exception handler for GRETAP outer device
*
* These are the packets exceptioned after encapsulation,
* and we need to remove the GRE header of the packet and
* pass it to linux.
*/
static void nss_connmgr_gre_tap_outer_exception(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct ethhdr *eth_hdr;
eth_hdr = (struct ethhdr *)skb->data;
nss_connmgr_gre_trace("%px: eth_hdr->h_proto: %d\n", dev, eth_hdr->h_proto);
if (likely(ntohs(eth_hdr->h_proto) >= ETH_P_802_3_MIN)) {
switch (ntohs(eth_hdr->h_proto)) {
case ETH_P_IP:
return nss_connmgr_gre_tap_v4_outer_exception(dev, skb);
case ETH_P_IPV6:
return nss_connmgr_gre_tap_v6_outer_exception(dev, skb);
default:
nss_connmgr_gre_warning("%px: invalid skb received:%px with protocol: %d. Freeing the skb.\n", dev, skb, ntohs(eth_hdr->h_proto));
dev_kfree_skb_any(skb);
}
}
}
/*
* nss_connmgr_gre_tun_inner_exception()
* Exception handler for GRETUN inner device
*
* These are the packets exceptioned after decapsulation,
* and we can send the packet to linux after modifying
* some skb fields.
*/
static void nss_connmgr_gre_tun_inner_exception(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct iphdr *iph;
iph = (struct iphdr *)skb->data;
switch (iph->version) {
case 4:
skb->protocol = htons(ETH_P_IP);
break;
case 6:
skb->protocol = htons(ETH_P_IPV6);
break;
default:
nss_connmgr_gre_warning("%px: wrong IP version set to skb:%px\n", dev, skb);
dev_kfree_skb_any(skb);
return;
}
skb->pkt_type = PACKET_HOST;
skb->dev = dev;
netif_receive_skb(skb);
}
/*
* nss_connmgr_gre_tun_outer_exception()
* Exception handler for GRETUN outer device
*
* These are the packets exceptioned after encapsulation,
* and we need to remove the GRE header of the packet and
* pass it to linux.
*/
static void nss_connmgr_gre_tun_outer_exception(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct iphdr *iph;
iph = (struct iphdr *)skb->data;
switch (iph->version) {
case 4:
return nss_connmgr_gre_tun_v4_outer_exception(dev, skb);
case 6:
return nss_connmgr_gre_tun_v6_outer_exception(dev, skb);
default:
nss_connmgr_gre_warning("%px: wrong IP version set to skb:%px\n", dev, skb);
dev_kfree_skb_any(skb);
break;
}
}
/*
* nss_connmgr_gre_rx_pkt()
* GRE Tap function to receive packet from NSS.
*/
static void nss_connmgr_gre_rx_pkt(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
netif_receive_skb(skb);
}
/*
* nss_connmgr_gre_event_receive()
* Event Callback to receive events from NSS
*/
static void nss_connmgr_gre_event_receive(void *if_ctx, struct nss_gre_msg *tnlmsg)
{
struct net_device *dev = if_ctx;
struct nss_cmn_node_stats *stats = &tnlmsg->msg.sstats.node_stats;
struct pcpu_sw_netstats *tstats;
enum nss_dynamic_interface_type interface_type;
switch (tnlmsg->cm.type) {
case NSS_GRE_MSG_SESSION_STATS:
interface_type = nss_dynamic_interface_get_type(nss_gre_get_context(), tnlmsg->cm.interface);
tstats = this_cpu_ptr(dev->tstats);
u64_stats_update_begin(&tstats->syncp);
if (interface_type == NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER) {
tstats->tx_packets += stats->tx_packets;
tstats->tx_bytes += stats->tx_bytes;
} else if (interface_type == NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER) {
tstats->rx_packets += stats->rx_packets;
tstats->rx_bytes += stats->rx_bytes;
}
u64_stats_update_end(&tstats->syncp);
dev->stats.rx_dropped += nss_cmn_rx_dropped_sum(stats);
break;
case NSS_GRE_MSG_BASE_STATS:
break;
default:
nss_connmgr_gre_info("%px: Unknown Event from NSS\n", dev);
break;
}
}
/*
* nss_connmgr_gre_make_name()
* Generate a name for netdevice if user does not provide one.
*/
static void nss_connmgr_gre_make_name(struct nss_connmgr_gre_cfg *cfg, char *name)
{
switch (cfg->mode) {
case GRE_MODE_TUN:
strlcpy(name, "tun-%d", IFNAMSIZ);
break;
case GRE_MODE_TAP:
strlcpy(name, "tap-%d", IFNAMSIZ);
break;
default:
break;
}
}
/*
* __nss_connmgr_gre_create_interface()
* Creates GRE Tap/Tun netdevice and configure GRE node in NSS.
* This should be called after acquiring rtnl_lock().
*/
static struct net_device *__nss_connmgr_gre_create_interface(struct nss_connmgr_gre_cfg *cfg,
enum nss_connmgr_gre_err_codes *err_code)
{
struct nss_ctx_instance *nss_ctx;
struct net_device *dev = NULL;
struct net_device *next_dev_inner = NULL;
struct net_device *next_dev_outer = NULL;
struct nss_gre_iface_instance *ngii;
struct nss_gre_msg req;
struct nss_gre_config_msg *cmsg = &req.msg.cmsg;
nss_connmgr_gre_priv_t *priv;
nss_tx_status_t status;
uint32_t features = 0;
int32_t inner_if, outer_if;
char name[IFNAMSIZ] = {0};
int ret = -1, retry, next_if_num_inner = 0, next_if_num_outer = 0;
if (cfg->name) {
strlcpy(name, cfg->name, IFNAMSIZ);
} else {
nss_connmgr_gre_make_name(cfg, name);
}
switch (cfg->mode) {
case GRE_MODE_TUN:
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
dev = alloc_netdev(sizeof(nss_connmgr_gre_priv_t), name, nss_connmgr_gre_tun_setup);
#else
dev = alloc_netdev(sizeof(nss_connmgr_gre_priv_t), name, NET_NAME_UNKNOWN, nss_connmgr_gre_tun_setup);
#endif
if (!dev) {
nss_connmgr_gre_warning("Allocation of netdev failed\n");
*err_code = GRE_ERR_ALLOC_NETDEV;
return NULL;
}
if (cfg->is_ipv6) {
dev->type = ARPHRD_IP6GRE;
ret = nss_connmgr_gre_v6_set_config(dev, cfg);
} else {
dev->type = ARPHRD_IPGRE;
ret = nss_connmgr_gre_v4_set_config(dev, cfg);
}
break;
case GRE_MODE_TAP:
dev = alloc_etherdev(sizeof(nss_connmgr_gre_priv_t));
if (!dev) {
nss_connmgr_gre_warning("Allocation of netdev failed\n");
*err_code = GRE_ERR_ALLOC_NETDEV;
return NULL;
}
nss_connmgr_gre_tap_setup(dev);
if (cfg->is_ipv6) {
dev->priv_flags_ext |= IFF_EXT_GRE_V6_TAP;
ret = nss_connmgr_gre_v6_set_config(dev, cfg);
} else {
dev->priv_flags_ext |= IFF_EXT_GRE_V4_TAP;
ret = nss_connmgr_gre_v4_set_config(dev, cfg);
}
break;
default:
nss_connmgr_gre_warning("Please Specify gre mode\n");
*err_code = GRE_ERR_INVALID_MODE;
goto release_ref;
}
if (ret) {
nss_connmgr_gre_warning("%px: gre interface configuration failed\n", dev);
*err_code = ret;
goto release_ref;
}
/*
* Set name
*/
memcpy(dev->name, name, IFNAMSIZ);
/*
* Create config cmd for acceleration engine
*/
memset(&req, 0, sizeof(struct nss_gre_msg));
ret = nss_connmgr_gre_prepare_config_cmd(dev, &req, &next_dev_inner, true);
if (ret) {
nss_connmgr_gre_warning("%px: gre get config failed\n", dev);
*err_code = ret;
goto release_ref;
}
/*
* Replace MAC addr and next node with user provided entries.
*/
if (cfg->use_mac_hdr) {
memcpy(cmsg->src_mac, cfg->src_mac, ETH_ALEN);
memcpy(cmsg->dest_mac, cfg->dest_mac, ETH_ALEN);
cmsg->flags |= NSS_GRE_CONFIG_SET_MAC;
}
/*
* If next_dev is NULL, then the set MAC
* flag can not be set. Because the packet
* will be forwarded to ipv4_rx/ipv6_rx
*/
if (!cfg->next_dev) {
cmsg->flags &= ~NSS_GRE_CONFIG_SET_MAC;
}
/*
* By now, we should have valid MAC addresses
*/
if (!is_valid_ether_addr((const u8 *)cmsg->src_mac) ||
!is_valid_ether_addr((const u8 *)cmsg->dest_mac)) {
nss_connmgr_gre_warning("%px: Could not find MAC address for src/dest IP\n", dev);
*err_code = GRE_ERR_INVALID_MAC;
goto release_ref;
}
/*
* Configure the inner nexthop ifnum
*/
if (cfg->next_dev) {
/*
* Release hold on GRE inner's nexthop that we have found,
* since it has been passed explicitly via the cfg
*/
if (next_dev_inner) {
dev_put(next_dev_inner);
}
dev_hold(cfg->next_dev);
/*
* TODO: Use the dynamic interface type for the inner's nexthop, in addition to the next_dev.
*/
next_if_num_inner = nss_cmn_get_interface_number_by_dev(cfg->next_dev);
next_dev_inner = cfg->next_dev;
if (next_if_num_inner < 0) {
nss_connmgr_gre_warning("%px: Next dev inner device= %s is not registered with ae engine\n",
dev, cfg->next_dev->name);
*err_code = GRE_ERR_NEXT_NODE_UNREG_IN_AE;
goto release_ref;
}
cmsg->flags |= NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE;
}
/*
* Configure the outer nexthop ifnum
*/
if (cfg->next_dev_outer) {
dev_hold(cfg->next_dev_outer);
/*
* Verify if dynamic interface type is in range.
*/
if (cfg->outer_nss_if_type >= NSS_DYNAMIC_INTERFACE_TYPE_MAX) {
nss_connmgr_gre_warning("%px: invalid cfg, outer nexthop type %d is not in range\n",
dev, cfg->outer_nss_if_type);
goto release_ref;
}
next_if_num_outer = nss_cmn_get_interface_number_by_dev_and_type(cfg->next_dev_outer, cfg->outer_nss_if_type);
next_dev_outer = cfg->next_dev_outer;
if (next_if_num_outer < 0) {
nss_connmgr_gre_warning("%px: Next dev outer device %s with dynamic if num %d not registered with NSS\n",
dev, cfg->next_dev_outer->name, next_if_num_outer);
*err_code = GRE_ERR_NEXT_NODE_UNREG_IN_AE;
goto release_ref;
}
/*
* Get the NSS ctx for the outer next hop
*/
nss_ctx = nss_dynamic_interface_get_nss_ctx_by_type(cfg->outer_nss_if_type);
if (!nss_ctx) {
nss_connmgr_gre_warning("Could not get NSS context for type : %d\n", cfg->outer_nss_if_type);
goto release_ref;
}
/*
* Append the core-id for the outer next hop ifnum
*/
next_if_num_outer = nss_cmn_append_core_id(nss_ctx, next_if_num_outer);
if (!next_if_num_outer) {
nss_connmgr_gre_warning("%px: Could not get interface number with core ID for outer nexthop device %s with core ID.\n",
nss_ctx, next_dev_outer->name);
goto release_ref;
}
cmsg->flags |= NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE;
}
/*
* By now, we should have a valid next node for either inner or outer
*/
if (!(cmsg->flags & NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE)) {
nss_connmgr_gre_warning("%px: Next dev is not available\n", dev);
*err_code = GRE_ERR_NO_NEXT_NETDEV;
goto release_ref;
}
if (cfg->add_padding) {
cmsg->flags |= NSS_GRE_CONFIG_SET_PADDING;
}
if (cfg->copy_metadata) {
cmsg->flags |= NSS_GRE_CONFIG_COPY_METADATA;
cmsg->metadata_size = sizeof(struct nss_wifi_append_statsv2_metahdr);
}
/*
* Set per packet DSCP configuration if needed
*/
if (cfg->dscp_valid) {
cmsg->flags |= NSS_GRE_CONFIG_DSCP_VALID;
}
/*
* Register net_device
*/
ret = register_netdevice(dev);
if (ret) {
*err_code = GRE_ERR_NETDEV_REG_FAILED;
nss_connmgr_gre_warning("%px: Netdevice registration failed\n", dev);
goto release_ref;
}
/*
* Create GRE interface instance
*/
ngii = nss_connmgr_gre_alloc_instance(dev);
if (!ngii) {
nss_connmgr_gre_warning("%px: GRE interfacen intance creation failed\n", dev);
*err_code = GRE_ERR_ALLOC_GRE_INSTANCE;
goto unregister_netdev;
}
/*
* Create nss outer dynamic interface
*/
outer_if = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (outer_if < 0) {
nss_connmgr_gre_warning("%px: Request interface number failed\n", dev);
*err_code = GRE_ERR_DYNMAIC_IFACE_CREATE;
goto free_gre_instance;
}
ngii->outer_ifnum = outer_if;
priv = (nss_connmgr_gre_priv_t *)netdev_priv(dev);
priv->next_dev_outer = next_dev_outer;
/*
* Create nss inner dynamic interface
*/
inner_if = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (inner_if < 0) {
nss_connmgr_gre_warning("%px: Request interface number failed\n", dev);
*err_code = GRE_ERR_DYNMAIC_IFACE_CREATE;
goto free_gre_instance;
}
ngii->inner_ifnum = inner_if;
priv->next_dev_inner = next_dev_inner;
priv->nss_if_number_inner = inner_if;
/*
* Register outer gre tunnel with NSS
*/
nss_ctx = nss_gre_register_if(outer_if,
NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER,
nss_connmgr_gre_rx_pkt,
nss_connmgr_gre_event_receive,
dev,
features);
if (!nss_ctx) {
nss_connmgr_gre_info("%px: nss_register_gre_if failed\n", dev);
*err_code = GRE_ERR_GRE_IFACE_REG;
goto dealloc_inner_node;
}
/*
* Register inner gre tunnel with NSS
*/
nss_ctx = nss_gre_register_if(inner_if,
NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER,
nss_connmgr_gre_rx_pkt,
nss_connmgr_gre_event_receive,
dev,
features);
if (!nss_ctx) {
nss_connmgr_gre_info("%px: nss_register_gre_if failed\n", dev);
*err_code = GRE_ERR_GRE_IFACE_REG;
goto dealloc_inner_node;
}
nss_connmgr_gre_info("%px: nss_register_gre_if() custom successful. nss_ctx = %px. inner_if: %d, outer_if: %d\n", dev, nss_ctx, inner_if, outer_if);
/*
* Send encap config to AE
*/
cmsg->flags &= ~NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE;
/*
* Configure nexthop to inner node if available
*/
if (next_if_num_inner) {
cmsg->next_node_if_num = next_if_num_inner;
cmsg->flags |= NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE;
}
cmsg->sibling_if_num = outer_if;
nss_gre_msg_init(&req, inner_if, NSS_GRE_MSG_ENCAP_CONFIGURE, sizeof(struct nss_gre_config_msg), NULL, NULL);
nss_connmgr_gre_info("%px: NSS_GRE_MSG_ENCAP_CONFIGURE:: nss_ctx = %px, flags:0x%x, ikey:0x%x, okey:0x%x, mode:%x, next_node_if_num:%u, sibling_if_num:%u, ttl:%u, tos:%u, metadata_size:%u\n",
dev, nss_ctx, cmsg->flags, cmsg->ikey, cmsg->okey, cmsg->mode, cmsg->next_node_if_num, cmsg->sibling_if_num, cmsg->ttl, cmsg->tos, cmsg->metadata_size);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
*err_code = GRE_ERR_AE_CONFIG_FAILED;
nss_connmgr_gre_info("%px: Send Encap config to AE failed\n", next_dev_inner);
goto unregister_nss_interface;
}
/*
* Send decap config to AE
*/
cmsg->flags &= ~NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE;
/*
* Configure nexthop to outer node if available
*/
if (next_if_num_outer) {
cmsg->next_node_if_num = next_if_num_outer;
cmsg->flags |= NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE;
}
cmsg->sibling_if_num = inner_if;
nss_gre_msg_init(&req, outer_if, NSS_GRE_MSG_DECAP_CONFIGURE, sizeof(struct nss_gre_config_msg), NULL, NULL);
nss_connmgr_gre_info("%px: NSS_GRE_MSG_DECAP_CONFIGURE:: nss_ctx = %px, flags:0x%x, ikey:0x%x, okey:0x%x, mode:%x, next_node_if_num:%u, sibling_if_num:%u, ttl:%u, tos:%u, metadata_size:%u\n",
dev, nss_ctx, cmsg->flags, cmsg->ikey, cmsg->okey, cmsg->mode, cmsg->next_node_if_num, cmsg->sibling_if_num, cmsg->ttl, cmsg->tos, cmsg->metadata_size);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
*err_code = GRE_ERR_AE_CONFIG_FAILED;
nss_connmgr_gre_info("%px: Send decap config to AE failed\n", next_dev_outer);
goto unregister_nss_interface;
}
/*
* Set vap next hop if next_dev is configured
* TODO: Do this in a more generic manner by checking the dynamic interface type of next_dev
*/
if (cfg->next_dev) {
ret = nss_connmgr_gre_set_wifi_next_hop(cfg->next_dev);
if (ret) {
nss_connmgr_gre_info("%px: Setting next hop of wifi vdev failed\n", dev);
*err_code = ret;
goto unregister_nss_interface;
}
}
memcpy(&ngii->gre_cfg, cfg, sizeof(*cfg));
ngii->gre_iftype = NSS_CONNMGR_GRE_IFTYPE_CUSTOM_GRE;
spin_lock(&gre_connmgr_ctx.lock);
list_add(&ngii->list, &gre_connmgr_ctx.list);
spin_unlock(&gre_connmgr_ctx.lock);
/*
* Success
*/
*err_code = GRE_SUCCESS;
return dev;
unregister_nss_interface:
nss_gre_unregister_if(inner_if);
nss_gre_unregister_if(outer_if);
retry = 0;
dealloc_inner_node:
status = nss_dynamic_interface_dealloc_node(inner_if, NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (status != NSS_TX_SUCCESS) {
if (++retry <= MAX_RETRY_COUNT) {
goto dealloc_inner_node;
}
nss_connmgr_gre_error("%px: Fatal Error, Unable to dealloc the node[%d] in the NSS FW!\n", dev, inner_if);
}
retry = 0;
dealloc_outer_node:
status = nss_dynamic_interface_dealloc_node(outer_if, NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (status != NSS_TX_SUCCESS) {
if (++retry <= MAX_RETRY_COUNT) {
goto dealloc_outer_node;
}
nss_connmgr_gre_error("%px: Fatal Error, Unable to dealloc the node[%d] in the NSS FW!\n", dev, outer_if);
}
free_gre_instance:
nss_connmgr_gre_free_instance(ngii);
unregister_netdev:
unregister_netdevice(dev);
release_ref:
if (next_dev_inner) {
dev_put(next_dev_inner);
}
if (next_dev_outer) {
dev_put(next_dev_outer);
}
return dev;
}
/*
* nss_connmgr_gre_destroy_inner_interface()
* Destroy inner GRE Tap/Tun interface.
*/
static enum nss_connmgr_gre_err_codes nss_connmgr_gre_destroy_inner_interface(struct net_device *dev, int interface_num)
{
struct nss_gre_msg req;
struct nss_gre_deconfig_msg *dmsg;
nss_tx_status_t status;
int retry = 0;
memset(&req, 0, sizeof(struct nss_gre_msg));
dmsg = &req.msg.dmsg;
dmsg->if_number = interface_num;
deconfig_inner:
nss_gre_msg_init(&req, interface_num, NSS_GRE_MSG_ENCAP_DECONFIGURE, sizeof(struct nss_gre_deconfig_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_gre_get_context(), &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: gre instance deconfigure command failed, interface_num = %d\n", dev, interface_num);
if (++retry <= MAX_RETRY_COUNT) {
goto deconfig_inner;
}
nss_connmgr_gre_error("%px: Fatal Error, failed to send GRE deconfig command to NSS\n", dev);
return GRE_ERR_AE_DECONFIG_FAILED;
}
retry = 0;
nss_gre_unregister_if(interface_num);
dealloc_inner:
status = nss_dynamic_interface_dealloc_node(interface_num, NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: gre dealloc node failure for interface_num = %d\n", dev, interface_num);
if (++retry <= MAX_RETRY_COUNT) {
goto dealloc_inner;
}
nss_connmgr_gre_error("%px: Fatal Error, failed to send GRE dealloc command to NSS\n", dev);
return GRE_ERR_DYNMAIC_IFACE_DESTROY;
}
return GRE_SUCCESS;
}
/*
* nss_connmgr_gre_destroy_outer_interface()
* Destroy outer GRE Tap/Tun interface.
*/
static enum nss_connmgr_gre_err_codes nss_connmgr_gre_destroy_outer_interface(struct net_device *dev, int interface_num)
{
struct nss_gre_msg req;
struct nss_gre_deconfig_msg *dmsg;
nss_tx_status_t status;
int retry = 0;
memset(&req, 0, sizeof(struct nss_gre_msg));
dmsg = &req.msg.dmsg;
dmsg->if_number = interface_num;
deconfig_outer:
nss_gre_msg_init(&req, interface_num, NSS_GRE_MSG_DECAP_DECONFIGURE, sizeof(struct nss_gre_deconfig_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_gre_get_context(), &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: gre instance deconfigure command failed, interface_num = %d\n", dev, interface_num);
if (++retry <= MAX_RETRY_COUNT) {
goto deconfig_outer;
}
nss_connmgr_gre_error("%px: Fatal Error, failed to send GRE deconfig command to NSS\n", dev);
return GRE_ERR_AE_DECONFIG_FAILED;
}
nss_gre_unregister_if(interface_num);
retry = 0;
dealloc_outer:
status = nss_dynamic_interface_dealloc_node(interface_num, NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: gre dealloc node failure for inner_if = %d\n", dev, interface_num);
if (++retry <= MAX_RETRY_COUNT) {
goto dealloc_outer;
}
nss_connmgr_gre_error("%px: Fatal Error, failed to send GRE dealloc command to NSS\n", dev);
return GRE_ERR_DYNMAIC_IFACE_DESTROY;
}
return GRE_SUCCESS;
}
/*
* __nss_connmgr_gre_destroy_interface()
* Destroy GRE Tap/Tun netdevice. Acquire rtnl_lock() before calling this
* function.
*/
static enum nss_connmgr_gre_err_codes __nss_connmgr_gre_destroy_interface(struct net_device *dev)
{
struct nss_gre_iface_instance *ngii;
nss_connmgr_gre_priv_t *priv;
enum nss_connmgr_gre_err_codes ret;
netif_tx_disable(dev);
ngii = nss_connmgr_gre_find_instance(dev);
if(!ngii) {
nss_connmgr_gre_warning("%px: GRE interface instance is not found.\n", dev);
return GRE_ERR_NO_GRE_INSTANCE;
}
/*
* Decrement ref to next_dev
*/
priv = (nss_connmgr_gre_priv_t *)netdev_priv(dev);
if (priv->next_dev_inner) {
dev_put(priv->next_dev_inner);
}
if (priv->next_dev_outer) {
dev_put(priv->next_dev_outer);
}
ret = nss_connmgr_gre_destroy_inner_interface(dev, ngii->inner_ifnum);
if (ret != GRE_SUCCESS) {
nss_connmgr_gre_warning("%px: failed to destroy inner interface: %d\n", dev, ngii->inner_ifnum);
return ret;
}
ret = nss_connmgr_gre_destroy_outer_interface(dev, ngii->outer_ifnum);
if (ret != GRE_SUCCESS) {
nss_connmgr_gre_warning("%px: failed to destroy outer interface: %d\n", dev, ngii->outer_ifnum);
return ret;
}
nss_connmgr_gre_info("%px: deleted gre instance, inner_if = %d outer_if = %d\n",
dev, ngii->inner_ifnum, ngii->outer_ifnum);
nss_connmgr_gre_free_instance(ngii);
unregister_netdevice(dev);
return GRE_SUCCESS;
}
/*
* nss_connmgr_gre_validate_config()
* No support for CSUM, SEQ number.
*/
static bool nss_connmgr_gre_validate_config(struct nss_connmgr_gre_cfg *cfg)
{
/*
* TODO:Disallow key for standard GRE TAP/TUN.
*/
if (cfg->iseq_valid || cfg->oseq_valid || cfg->icsum_valid || cfg->ocsum_valid) {
nss_connmgr_gre_info("Bad config, seq and csum are not supported.\n");
return false;
}
/*
* Validate DSCP and ToS inherit flags.
*/
if (cfg->dscp_valid && cfg->tos_inherit) {
nss_connmgr_gre_info("Bad config, Both DSCP and ToS inherit flags are set.\n");
return false;
}
return true;
}
/*
* nss_connmgr_gre_dev_up()
* Netdevice notifier call back to configure NSS for GRE Tap/Tun device.
*/
static int nss_connmgr_gre_dev_up(struct net_device *dev)
{
struct nss_gre_msg req;
struct nss_gre_config_msg *cmsg = &req.msg.cmsg;
int inner_if, outer_if;
uint32_t features = 0;
struct nss_ctx_instance *nss_ctx = NULL;
nss_tx_status_t status;
struct net_device *next_dev = NULL;
/*
* If GRE interface instance is found return, dev is Custom GRE interface type.
*/
if (nss_connmgr_gre_find_instance(dev)) {
nss_connmgr_gre_info("%px: Custom GRE interface is up.\n", dev);
return NOTIFY_DONE;
}
/*
* Create config cmd for acceleration engine
*/
memset(&req, 0, sizeof(struct nss_gre_msg));
if (nss_connmgr_gre_prepare_config_cmd(dev, &req, &next_dev, false)) {
nss_connmgr_gre_info("%px: gre tunnel get config failed\n", dev);
return NOTIFY_DONE;
}
/*
* Ignore set_mac and next_dev flags for generic GRE
*/
cmsg->flags &= ~(NSS_GRE_CONFIG_SET_MAC | NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE);
/*
* Create nss outer dynamic interface
*/
outer_if = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (outer_if < 0) {
nss_connmgr_gre_warning("%px: Request interface number failed\n", dev);
return NOTIFY_DONE;
}
/*
* Create nss inner dynamic interface
*/
inner_if = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (inner_if < 0) {
nss_connmgr_gre_warning("%px: Request interface number failed\n", dev);
goto dealloc_outer;
}
/*
* Register gre inner/outer interface with NSS
*/
if ((dev->type == ARPHRD_IPGRE) || (dev->type == ARPHRD_IP6GRE)) {
/*
* GRE Tunnel mode
*/
nss_ctx = nss_gre_register_if(inner_if,
NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER,
nss_connmgr_gre_tun_inner_exception,
nss_connmgr_gre_event_receive,
dev,
features);
if (!nss_ctx) {
nss_connmgr_gre_info("%px: nss_register_gre_if failed\n", dev);
goto dealloc_inner;
}
nss_ctx = nss_gre_register_if(outer_if,
NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER,
nss_connmgr_gre_tun_outer_exception,
nss_connmgr_gre_event_receive,
dev,
features);
} else {
/*
* GRE Tap mode
*/
nss_ctx = nss_gre_register_if(inner_if,
NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER,
nss_connmgr_gre_tap_inner_exception,
nss_connmgr_gre_event_receive,
dev,
features);
if (!nss_ctx) {
nss_connmgr_gre_info("%px: nss_register_gre_if failed\n", dev);
goto dealloc_inner;
}
nss_ctx = nss_gre_register_if(outer_if,
NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER,
nss_connmgr_gre_tap_outer_exception,
nss_connmgr_gre_event_receive,
dev,
features);
}
if (!nss_ctx) {
nss_connmgr_gre_info("%px: nss_register_gre_if failed\n", dev);
goto unregister_inner;
}
nss_connmgr_gre_info("%px: nss_register_gre_if() standard successful. nss_ctx = %px. inner_if: %d, outer_if: %d\n", dev, nss_ctx, inner_if, outer_if);
/*
* Send configure command for inner interface
*/
cmsg->sibling_if_num = outer_if;
nss_gre_msg_init(&req, inner_if, NSS_GRE_MSG_ENCAP_CONFIGURE, sizeof(struct nss_gre_config_msg), NULL, NULL);
nss_connmgr_gre_info("%px: NSS_GRE_MSG_ENCAP_CONFIGURE:: nss_ctx = %px, flags:0x%x, ikey:0x%x, okey:0x%x, mode:%x, next_node_if_num:%u, sibling_if_num:%u, ttl:%u, tos:%u, metadata_size:%u\n",
dev, nss_ctx, cmsg->flags, cmsg->ikey, cmsg->okey, cmsg->mode, cmsg->next_node_if_num, cmsg->sibling_if_num, cmsg->ttl, cmsg->tos, cmsg->metadata_size);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_warning("%px: nss gre std configure command error %d\n", dev, status);
goto unregister_outer;
}
/*
* Send configure command for outer interface
*/
cmsg->sibling_if_num = inner_if;
nss_gre_msg_init(&req, outer_if, NSS_GRE_MSG_DECAP_CONFIGURE, sizeof(struct nss_gre_config_msg), NULL, NULL);
nss_connmgr_gre_info("%px: NSS_GRE_MSG_DECAP_CONFIGURE:: nss_ctx = %px, flags:0x%x, ikey:0x%x, okey:0x%x, mode:%x, next_node_if_num:%u, sibling_if_num:%u, ttl:%u, tos:%u, metadata_size:%u\n",
dev, nss_ctx, cmsg->flags, cmsg->ikey, cmsg->okey, cmsg->mode, cmsg->next_node_if_num, cmsg->sibling_if_num, cmsg->ttl, cmsg->tos, cmsg->metadata_size);
status = nss_gre_tx_msg_sync(nss_ctx, &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_warning("%px: nss gre std configure command error %d\n", dev, status);
goto unregister_outer;
}
/*
* Open the netdev to accept packets
*/
if (nss_connmgr_gre_dev_open(dev)) {
nss_connmgr_gre_warning("%px: nss gre std device up command failed %d\n", dev, status);
goto unregister_outer;
}
return NOTIFY_DONE;
unregister_outer:
nss_gre_unregister_if(outer_if);
unregister_inner:
nss_gre_unregister_if(inner_if);
dealloc_inner:
status = nss_dynamic_interface_dealloc_node(inner_if, NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_warning("%px: Unable to dealloc the node[%d] in the NSS fw!\n", dev, inner_if);
}
dealloc_outer:
status = nss_dynamic_interface_dealloc_node(outer_if, NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_warning("%px: Unable to dealloc the node[%d] in the NSS fw!\n", dev, outer_if);
}
return NOTIFY_DONE;
}
/*
* nss_connmgr_gre_dev_down()
* Netdevice notifier call back to destroy GRE interface in NSS.
*/
static int nss_connmgr_gre_dev_down(struct net_device *dev)
{
struct nss_gre_msg req;
struct nss_gre_deconfig_msg *dmsg;
int inner_if, outer_if;
nss_tx_status_t status;
/*
* If GRE interface instance is found return, dev is Custom GRE interface type.
*/
if (nss_connmgr_gre_find_instance(dev)) {
nss_connmgr_gre_info("%px: Custom GRE interface is down.\n", dev);
return NOTIFY_DONE;
}
/*
* Check if gre inner interface is registered with NSS
*/
inner_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (inner_if < 0) {
nss_connmgr_gre_info("%px: Net device is not registered with nss\n", dev);
return NOTIFY_DONE;
}
outer_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (outer_if < 0) {
nss_connmgr_gre_info("%px: Net device is not registered with nss\n", dev);
return NOTIFY_DONE;
}
memset(&req, 0, sizeof(struct nss_gre_msg));
dmsg = &req.msg.dmsg;
dmsg->if_number = inner_if;
nss_gre_msg_init(&req, inner_if, NSS_GRE_MSG_ENCAP_DECONFIGURE, sizeof(struct nss_gre_deconfig_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_gre_get_context(), &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: gre instance deconfigure command fo encap failed, inner_if = %d\n", dev, inner_if);
return NOTIFY_DONE;
}
dmsg->if_number = outer_if;
nss_gre_msg_init(&req, outer_if, NSS_GRE_MSG_DECAP_DECONFIGURE, sizeof(struct nss_gre_deconfig_msg), NULL, NULL);
status = nss_gre_tx_msg_sync(nss_gre_get_context(), &req);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: gre instance deconfigure command for decap failed, inner_if = %d\n", dev, inner_if);
return NOTIFY_DONE;
}
if (nss_connmgr_gre_dev_close(dev)) {
nss_connmgr_gre_info("%px: gre instance device close command failed, inner_if = %d\n", dev, inner_if);
return NOTIFY_DONE;
}
nss_gre_unregister_if(inner_if);
status = nss_dynamic_interface_dealloc_node(inner_if, NSS_DYNAMIC_INTERFACE_TYPE_GRE_INNER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: gre dealloc node failure for inner_if = %d\n", dev, inner_if);
return NOTIFY_DONE;
}
nss_connmgr_gre_info("%px: deleting gre instance, inner_if = %d\n", dev, inner_if);
nss_gre_unregister_if(outer_if);
status = nss_dynamic_interface_dealloc_node(outer_if, NSS_DYNAMIC_INTERFACE_TYPE_GRE_OUTER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: gre dealloc node failure for outer_if = %d\n", dev, outer_if);
return NOTIFY_DONE;
}
nss_connmgr_gre_info("%px: deleted gre instance, outer_if = %d\n", dev, outer_if);
return NOTIFY_DONE;
}
/*
* nss_connmgr_gre_dev_event()
* Netdevice notifier call back function.
*/
static int nss_connmgr_gre_dev_event(struct notifier_block *nb,
unsigned long event, void *dev)
{
struct net_device *netdev;
netdev = netdev_notifier_info_to_dev(dev);
switch (event) {
case NETDEV_UP:
return nss_connmgr_gre_dev_up(netdev);
case NETDEV_DOWN:
return nss_connmgr_gre_dev_down(netdev);
default:
break;
}
return NOTIFY_DONE;
}
/*
* Linux Net device Notifier
*/
static struct notifier_block nss_connmgr_gre_notifier = {
.notifier_call = nss_connmgr_gre_dev_event,
};
/*
* nss_connmgr_gre_dev_init_module()
* Tunnel gre module init function
*/
static int __init nss_connmgr_gre_dev_init_module(void)
{
#ifdef CONFIG_OF
/*
* If the node is not compatible, don't do anything.
*/
if (!of_find_node_by_name(NULL, "nss-common")) {
return 0;
}
#endif
INIT_LIST_HEAD(&gre_connmgr_ctx.list);
spin_lock_init(&gre_connmgr_ctx.lock);
register_netdevice_notifier(&nss_connmgr_gre_notifier);
return 0;
}
/*
* nss_connmgr_gre_exit_module
* Tunnel gre module exit function
*/
static void __exit nss_connmgr_gre_exit_module(void)
{
struct nss_gre_iface_instance *ngii, *n;
#ifdef CONFIG_OF
/*
* If the node is not compatible, don't do anything.
*/
if (!of_find_node_by_name(NULL, "nss-common")) {
return;
}
#endif
/*
* Unregister GRE interfaces created and delete interface
* instances.
*/
list_for_each_entry_safe(ngii, n, &gre_connmgr_ctx.list, list) {
rtnl_lock();
__nss_connmgr_gre_destroy_interface(ngii->dev);
rtnl_unlock();
}
unregister_netdevice_notifier(&nss_connmgr_gre_notifier);
}
/*
* nss_connmgr_gre_set_next_hop()
* Function changes next hop of wifi_vdev to NSS_GRE_INTERFACE
*/
int nss_connmgr_gre_set_wifi_next_hop(struct net_device *wifi_vdev)
{
nss_tx_status_t status;
void *ctx;
int ifnumber;
if (!wifi_vdev) {
nss_connmgr_gre_info("wifi interface is NULL\n");
return GRE_ERR_NO_NEXT_NETDEV;
}
ifnumber = nss_cmn_get_interface_number_by_dev(wifi_vdev);
if (ifnumber < 0) {
nss_connmgr_gre_info("%px: wifi interface is not recognized by NSS\n", wifi_vdev);
return GRE_ERR_NEXT_NODE_UNREG_IN_AE;
}
ctx = nss_wifi_get_context();
status = nss_wifi_vdev_set_next_hop(ctx, ifnumber, NSS_GRE_INTERFACE);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_gre_info("%px: wifi drv api failed to set next hop\n", wifi_vdev);
return GRE_ERR_AE_SET_NEXT_HOP;
}
return GRE_SUCCESS;
}
/*
* nss_connmgr_gre_get_hlen()
* Calculates GRE header length
*/
uint32_t nss_connmgr_gre_get_hlen(struct nss_connmgr_gre_cfg *cfg)
{
uint32_t size = 4; /* minimum size of GRE packet */
if (cfg->okey_valid) {
size += 4;
}
if (cfg->oseq_valid) {
size += 4;
}
if (cfg->ocsum_valid) {
size += 4;
}
return size;
}
/*
* nss_connmgr_gre_set_gre_flags()
* Map User configured flags to tunnel specific flag.
*/
void nss_connmgr_gre_set_gre_flags(struct nss_connmgr_gre_cfg *cfg,
uint16_t *o_flags, uint16_t *i_flags)
{
if (cfg->ikey_valid) {
*i_flags |= TUNNEL_KEY;
}
if (cfg->okey_valid) {
*o_flags |= TUNNEL_KEY;
}
if (cfg->iseq_valid) {
*i_flags |= TUNNEL_SEQ;
}
if (cfg->oseq_valid) {
*o_flags |= TUNNEL_SEQ;
}
if (cfg->icsum_valid) {
*i_flags |= TUNNEL_CSUM;
}
if (cfg->ocsum_valid) {
*o_flags |= TUNNEL_CSUM;
}
}
/*
* nss_connmgr_gre_get_nss_config_flags()
* Map tunnel specific flags to NSS flags.
*/
uint32_t nss_connmgr_gre_get_nss_config_flags(uint16_t o_flags, uint16_t i_flags,
uint8_t tos, uint8_t ttl,
uint16_t frag_off)
{
uint32_t gre_flags = 0;
if (i_flags & TUNNEL_KEY) {
gre_flags |= NSS_GRE_CONFIG_IKEY_VALID;
}
if (o_flags & TUNNEL_KEY) {
gre_flags |= NSS_GRE_CONFIG_OKEY_VALID;
}
if (i_flags & TUNNEL_SEQ) {
gre_flags |= NSS_GRE_CONFIG_ISEQ_VALID;
}
if (o_flags & TUNNEL_SEQ) {
gre_flags |= NSS_GRE_CONFIG_OSEQ_VALID;
}
if (i_flags & TUNNEL_CSUM) {
gre_flags |= NSS_GRE_CONFIG_ICSUM_VALID;
}
if (o_flags & TUNNEL_CSUM) {
gre_flags |= NSS_GRE_CONFIG_OCSUM_VALID;
}
if (tos & 0x1) {
gre_flags |= NSS_GRE_CONFIG_TOS_INHERIT;
}
if (!ttl) {
gre_flags |= NSS_GRE_CONFIG_TTL_INHERIT;
}
if (frag_off == htons(IP_DF)) {
gre_flags |= NSS_GRE_CONFIG_SET_DF;
}
return gre_flags;
}
/*
* nss_connmgr_gre_destroy_interface()
* User API to delete interface
*/
enum nss_connmgr_gre_err_codes nss_connmgr_gre_destroy_interface(struct net_device *dev)
{
enum nss_connmgr_gre_err_codes ret;
if (!dev) {
nss_connmgr_gre_info("Please specifiy valid interface to be deleted\n");
return GRE_ERR_NO_NETDEV;
}
if (in_interrupt()) {
nss_connmgr_gre_info("%px: nss_connmgr_gre_destroy_interface() called in interrupt context\n", dev);
return GRE_ERR_IN_INTERRUPT_CTX;
}
rtnl_lock();
ret = __nss_connmgr_gre_destroy_interface(dev);
rtnl_unlock();
/*
* free_netdev() should be called outside of rtnl lock
*/
if (!ret) {
free_netdev(dev);
}
return ret;
}
EXPORT_SYMBOL(nss_connmgr_gre_destroy_interface);
/*
* nss_connmgr_gre_create_interface()
* User API to create gre standard interface
*/
struct net_device *nss_connmgr_gre_create_interface(struct nss_connmgr_gre_cfg *cfg,
enum nss_connmgr_gre_err_codes *err_code)
{
struct net_device *dev;
if ((!cfg) || (!err_code)) {
nss_connmgr_gre_info("parameter to this function should not be NULL\n");
return NULL;
}
if (in_interrupt()) {
nss_connmgr_gre_info("nss_connmgr_gre_create_interface() called in interrupt context\n");
*err_code = GRE_ERR_IN_INTERRUPT_CTX;
return NULL;
}
if (!nss_connmgr_gre_validate_config(cfg)) {
*err_code = GRE_ERR_UNSUPPORTED_CFG;
return NULL;
}
rtnl_lock();
dev = __nss_connmgr_gre_create_interface(cfg, err_code);
rtnl_unlock();
if (*err_code == GRE_SUCCESS) {
return dev;
}
if (dev) {
free_netdev(dev);
}
return NULL;
}
EXPORT_SYMBOL(nss_connmgr_gre_create_interface);
module_init(nss_connmgr_gre_dev_init_module);
module_exit(nss_connmgr_gre_exit_module);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("NSS gre offload manager");