blob: 7e20f23fe91e6ea8cf4e2fa01d65fad5e3d8f61e [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2019-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.
**************************************************************************
*/
#include <nss_api_if.h>
#include <nss_cmn.h>
#include <net/netfilter/nf_conntrack_dscpremark_ext.h>
#include "nss_mirred.h"
#include "nss_igs.h"
#include "nss_ifb.h"
/*
* TODO: Current implementation only supports one IFB interface to be mapped with
* any one interface at a time. This has to be changed to support the mapping of
* an IFB device to multiple interfaces.
*/
static LIST_HEAD(nss_ifb_list); /* List of IFB and its mapped interface */
static DEFINE_SPINLOCK(nss_ifb_list_lock); /* Lock for the ifb list */
/*
* nss_ifb_msg_response
* NSS firmware message response structure.
*/
static struct nss_ifb_msg_response {
struct semaphore sem;
wait_queue_head_t wq;
enum nss_cmn_response response;
bool cond;
} msg_response;
/*
* nss_ifb_igs_ip_pre_routing_hook()
* Copy class-id to Linux CT structure.
*
* Copy class-id from tc_index field of skb in ingress QoS fields inside
* DSCP CT extention structure.
*/
unsigned int nss_ifb_igs_ip_pre_routing_hook(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct nf_conn *ct;
struct nf_ct_dscpremark_ext *dscpcte;
enum ip_conntrack_info ctinfo;
if (unlikely(!skb))
return NF_ACCEPT;
/*
* Return if ingress qostag value (saved in tc_index field) is 0.
*/
if (likely(!skb->tc_index))
return NF_ACCEPT;
ct = nf_ct_get(skb, &ctinfo);
if (!ct)
return NF_ACCEPT;
spin_lock_bh(&ct->lock);
dscpcte = nf_ct_dscpremark_ext_find(ct);
if (!dscpcte) {
spin_unlock_bh(&ct->lock);
return NF_ACCEPT;
}
/*
* Copy ingress qostag value (saved in tc_index) to ingress
* qostag fields of DSCP CT extension structure.
*/
if (IP_CT_DIR_ORIGINAL == CTINFO2DIR(ctinfo)) {
dscpcte->igs_flow_qos_tag = skb->tc_index;
} else {
dscpcte->igs_reply_qos_tag = skb->tc_index;
}
spin_unlock_bh(&ct->lock);
/*
* Reset the tc_index field as it no longer required.
*/
skb->tc_index = 0;
return NF_ACCEPT;
}
/*
* nss_ifb_list_del()
* API to delete member in ifb list.
*/
void nss_ifb_list_del(struct nss_ifb_info *ifb_info)
{
spin_lock_bh(&nss_ifb_list_lock);
list_del(&ifb_info->map_list);
spin_unlock_bh(&nss_ifb_list_lock);
}
/*
* nss_ifb_list_add()
* API to add member in ifb list.
*/
static void nss_ifb_list_add(struct nss_ifb_info *ifb_info)
{
spin_lock_bh(&nss_ifb_list_lock);
list_add(&(ifb_info->map_list), &nss_ifb_list);
spin_unlock_bh(&nss_ifb_list_lock);
}
/*
* nss_ifb_is_mapped()
* Returns the map status of the given ifb bind structure.
*/
bool nss_ifb_is_mapped(struct nss_ifb_info *ifb_info)
{
bool is_mapped;
spin_lock_bh(&nss_ifb_list_lock);
is_mapped = ifb_info->is_mapped;
spin_unlock_bh(&nss_ifb_list_lock);
return is_mapped;
}
/*
* nss_ifb_config_msg_init()
* Initialize IFB configure interface's message.
*/
static void nss_ifb_config_msg_init(struct nss_if_msg *ncm, uint16_t if_num,
uint32_t type, uint32_t len, void *cb, void *app_data)
{
nss_cmn_msg_init(&ncm->cm, if_num, type, len, cb, app_data);
}
/*
* nss_ifb_clear_config_cb()
* CLEAR configure handler for an IFB mapped interface.
*/
static void nss_ifb_clear_config_cb(void *app_data, struct nss_if_msg *nim)
{
struct nss_ifb_info *ifb_info = (struct nss_ifb_info *)app_data;
bool ret;
if (nim->cm.response != NSS_CMN_RESPONSE_ACK) {
nss_igs_error("Response error: %d\n", nim->cm.response);
return;
}
do {
ret = spin_trylock_bh(&nss_ifb_list_lock);
} while (!ret);
ifb_info->is_mapped = false;
spin_unlock_bh(&nss_ifb_list_lock);
}
/*
* nss_ifb_async_cb()
* IFB asynchronous handler for an IFB mapped interface.
*/
static void nss_ifb_async_cb(void *app_data, struct nss_if_msg *nim)
{
if (nim->cm.response != NSS_CMN_RESPONSE_ACK) {
nss_igs_error("Response error: %d\n", nim->cm.response);
}
}
/*
* nss_ifb_wake_up_cb()
* IFB wake up handler for an IFB mapped interface.
*/
static void nss_ifb_wake_up_cb(void *app_data, struct nss_if_msg *nim)
{
msg_response.response = nim->cm.response;
msg_response.cond = 0;
wake_up(&msg_response.wq);
}
/*
* nss_ifb_config_msg_fill()
* Fill the IFB configure message.
*/
static bool nss_ifb_config_msg_fill(struct nss_if_msg *nim_ptr, struct net_device *dev,
int32_t ifb_num, enum nss_ifb_if_config config, void *cb)
{
uint32_t if_src_num;
if (netif_is_ifb_dev(dev)) {
if_src_num = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS);
if (if_src_num < 0) {
nss_igs_error("invalid IFB device %s\n", dev->name);
return -1;
}
} else {
if_src_num = nss_cmn_get_interface_number_by_dev(dev);
if (if_src_num < 0) {
nss_igs_error("invalid device %s\n", dev->name);
return -1;
}
}
switch (config) {
case NSS_IFB_SET_IGS_NODE:
nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_SET_IGS_NODE,
sizeof(struct nss_if_igs_config), nss_ifb_wake_up_cb, cb);
nim_ptr->msg.config_igs.igs_num = ifb_num;
break;
case NSS_IFB_CLEAR_IGS_NODE:
nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_CLEAR_IGS_NODE,
sizeof(struct nss_if_igs_config), nss_ifb_clear_config_cb, cb);
nim_ptr->msg.config_igs.igs_num = ifb_num;
break;
case NSS_IFB_SET_NEXTHOP:
nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_SET_NEXTHOP,
sizeof(struct nss_if_set_nexthop), nss_ifb_wake_up_cb, cb);
nim_ptr->msg.set_nexthop.nexthop = ifb_num;
break;
case NSS_IFB_RESET_NEXTHOP:
nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_RESET_NEXTHOP,
sizeof(struct nss_if_set_nexthop), nss_ifb_async_cb, cb);
nim_ptr->msg.set_nexthop.nexthop = ifb_num;
break;
case NSS_IFB_OPEN:
nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_OPEN,
sizeof(struct nss_if_open), nss_ifb_async_cb, cb);
/*
* Reset the elements of interface's open configuration.
*/
memset (&nim_ptr->msg.open, 0, sizeof(struct nss_if_open));
break;
case NSS_IFB_CLOSE:
nss_ifb_config_msg_init(nim_ptr, if_src_num, NSS_IF_CLOSE,
sizeof(struct nss_if_close), nss_ifb_async_cb, cb);
/*
* Reset the elements of interface's close configuration.
*/
memset (&nim_ptr->msg.close, 0, sizeof(struct nss_if_close));
break;
}
return 0;
}
/*
* nss_ifb_config_msg_tx()
* Send IFB configure message to an IFB mapped interface.
*/
int32_t nss_ifb_config_msg_tx(struct net_device *dev, int32_t ifb_num,
enum nss_ifb_if_config config, void *cb)
{
struct nss_if_msg nim;
int32_t ret;
if ((ret = nss_ifb_config_msg_fill(&nim, dev, ifb_num, config, cb))) {
nss_igs_error("Error in setting up IFB %d config message\n", config);
return -1;
}
ret = nss_if_tx_msg(nss_igs_get_context(), &nim);
if (ret != NSS_TX_SUCCESS) {
nss_igs_error("failed to send config message\n");
return -1;
}
return 0;
}
/*
* nss_ifb_config_msg_tx_sync()
* Send IFB configure message to an IFB mapped interface and wait for the response.
*/
int32_t nss_ifb_config_msg_tx_sync(struct net_device *dev, int32_t ifb_num,
enum nss_ifb_if_config config, void *cb)
{
struct nss_if_msg nim;
int32_t ret;
if ((ret = nss_ifb_config_msg_fill(&nim, dev, ifb_num, config, cb))) {
nss_igs_error("Error in setting up IFB %d config message\n", config);
return -1;
}
down(&msg_response.sem);
ret = nss_if_tx_msg(nss_igs_get_context(), &nim);
if (ret != NSS_TX_SUCCESS) {
up(&msg_response.sem);
nss_igs_error("failed to send config message\n");
return -1;
}
msg_response.cond = 1;
if (!wait_event_timeout(msg_response.wq, msg_response.cond == 0, NSS_IFB_MSG_TIMEOUT)) {
nss_igs_error("config for attach interface msg timeout\n");
up(&msg_response.sem);
return -1;
} else if (msg_response.response != NSS_CMN_RESPONSE_ACK) {
up(&msg_response.sem);
nss_igs_error("config for attach interface msg return with response: %d\n", msg_response.response);
return -1;
}
up(&msg_response.sem);
return 0;
}
/*
* nss_ifb_reset_nexthop()
* Send RESET NEXTHOP configure message to an IFB mapped interface.
*/
bool nss_ifb_reset_nexthop(struct nss_ifb_info *ifb_info)
{
int32_t if_num;
spin_lock_bh(&nss_ifb_list_lock);
if (!(ifb_info->is_mapped)) {
nss_igs_info("%s IFB device mapped flag is not set\n", ifb_info->ifb_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return true;
}
/*
* Send RESET NEXTHOP config message to the mapped interface.
*/
if_num = nss_cmn_get_interface_number_by_dev_and_type(ifb_info->ifb_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS);
if (if_num < 0) {
nss_igs_error("No %s IFB device found in NSS firmware\n", ifb_info->ifb_dev->name);
}
if (nss_ifb_config_msg_tx(ifb_info->map_dev, if_num, NSS_IFB_RESET_NEXTHOP, NULL) < 0) {
nss_igs_error("Sending RESET NEXTHOP config to %s dev failed\n", ifb_info->map_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return false;
}
spin_unlock_bh(&nss_ifb_list_lock);
return true;
}
/*
* nss_ifb_down()
* Send interface's DOWN configure message to an IFB interface.
*/
bool nss_ifb_down(struct nss_ifb_info *ifb_info)
{
int32_t ifb_num;
spin_lock_bh(&nss_ifb_list_lock);
if (!(ifb_info->is_mapped)) {
nss_igs_info("%s IFB device mapped flag is not set\n", ifb_info->ifb_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return true;
}
/*
* Send interface's DOWN config message to an IFB interface.
*/
ifb_num = nss_cmn_get_interface_number_by_dev_and_type(ifb_info->ifb_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS);
if (ifb_num < 0) {
nss_igs_error("No %s IFB device found in NSS FW\n", ifb_info->ifb_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return false;
}
if (nss_ifb_config_msg_tx(ifb_info->ifb_dev, ifb_num, NSS_IFB_CLOSE, ifb_info) < 0) {
nss_igs_error("Sending unassign to %s dev failed\n", ifb_info->map_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return false;
}
spin_unlock_bh(&nss_ifb_list_lock);
return true;
}
/*
* nss_ifb_up()
* Send interface's UP configure message to an IFB interface.
*/
bool nss_ifb_up(struct nss_ifb_info *ifb_info)
{
int32_t ifb_num;
spin_lock_bh(&nss_ifb_list_lock);
if (!(ifb_info->is_mapped)) {
nss_igs_info("%s IFB device mapped flag is not set\n", ifb_info->ifb_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return true;
}
/*
* Send interface's UP config message to an IFB interface.
*/
ifb_num = nss_cmn_get_interface_number_by_dev_and_type(ifb_info->ifb_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS);
if (ifb_num < 0) {
nss_igs_error("No %s IFB device found in NSS FW\n", ifb_info->ifb_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return false;
}
if (nss_ifb_config_msg_tx(ifb_info->ifb_dev, ifb_num, NSS_IFB_OPEN, ifb_info) < 0) {
nss_igs_error("Sending unassign to %s dev failed\n", ifb_info->map_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return false;
}
spin_unlock_bh(&nss_ifb_list_lock);
return true;
}
/*
* nss_ifb_clear_igs_node()
* Send CLEAR configure message to an IFB mapped interface.
*/
bool nss_ifb_clear_igs_node(struct nss_ifb_info *ifb_info)
{
int32_t if_num;
spin_lock_bh(&nss_ifb_list_lock);
if (!(ifb_info->is_mapped)) {
nss_igs_info("%s IFB device mapped flag is not set\n", ifb_info->ifb_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return true;
}
/*
* Send CLEAR config message to the mapped interface.
*/
if_num = nss_cmn_get_interface_number_by_dev_and_type(ifb_info->ifb_dev, NSS_DYNAMIC_INTERFACE_TYPE_IGS);
if (if_num < 0) {
nss_igs_error("No %s IFB device found in NSS firmware\n", ifb_info->ifb_dev->name);
}
if (nss_ifb_config_msg_tx(ifb_info->map_dev, if_num, NSS_IFB_CLEAR_IGS_NODE, ifb_info) < 0) {
nss_igs_error("Sending unassign to %s dev failed\n", ifb_info->map_dev->name);
spin_unlock_bh(&nss_ifb_list_lock);
return false;
}
spin_unlock_bh(&nss_ifb_list_lock);
return true;
}
/*
* nss_ifb_init()
* Initialization API.
*/
void nss_ifb_init()
{
sema_init(&msg_response.sem, 1);
init_waitqueue_head(&msg_response.wq);
}
/*
* nss_ifb_find_map_dev()
* Find and return the IFB mapped netdev in the ifb list.
*/
struct nss_ifb_info *nss_ifb_find_map_dev(struct net_device *dev)
{
struct nss_ifb_info *ifb_info;
spin_lock_bh(&nss_ifb_list_lock);
list_for_each_entry(ifb_info, &nss_ifb_list, map_list) {
if (ifb_info->map_dev == dev) {
spin_unlock_bh(&nss_ifb_list_lock);
return ifb_info;
}
}
spin_unlock_bh(&nss_ifb_list_lock);
return NULL;
}
/*
* nss_ifb_find_dev()
* Find and return the IFB netdev in the ifb list.
*/
struct nss_ifb_info *nss_ifb_find_dev(struct net_device *dev)
{
struct nss_ifb_info *ifb_info;
spin_lock_bh(&nss_ifb_list_lock);
list_for_each_entry(ifb_info, &nss_ifb_list, map_list) {
if (ifb_info->ifb_dev == dev) {
spin_unlock_bh(&nss_ifb_list_lock);
return ifb_info;
}
}
spin_unlock_bh(&nss_ifb_list_lock);
return NULL;
}
/*
* nss_ifb_bind()
* API to bind an IFB device with its requested mapped interface.
*/
int32_t nss_ifb_bind(struct nss_ifb_info *ifb_info, struct net_device *from_dev,
struct net_device *to_dev)
{
if (!ifb_info) {
/*
* IFB not present in local LL.
* Add the entry in LL.
*/
ifb_info = kmalloc(sizeof(*ifb_info), GFP_KERNEL);
if (!ifb_info) {
nss_igs_error("kmalloc failed\n");
return -ENOMEM;
}
ifb_info->ifb_dev = to_dev;
ifb_info->map_dev = from_dev;
ifb_info->is_mapped = true;
nss_ifb_list_add(ifb_info);
} else {
/*
* IFB present in local LL and its is_mapped is not set.
* make the ifb_info's is_mapped to true again.
*/
spin_lock_bh(&nss_ifb_list_lock);
ifb_info->map_dev = from_dev;
ifb_info->is_mapped = true;
spin_unlock_bh(&nss_ifb_list_lock);
}
return 0;
}
/*
* nss_ifb_update_dev_stats()
* IFB stats function to copy the common stats to the netdevice.
*/
static void nss_ifb_update_dev_stats(struct net_device *dev, struct nss_igs_stats_sync_msg *sync_stats)
{
struct pcpu_sw_netstats stats;
struct nss_cmn_node_stats *node_stats = &(sync_stats->node_stats);
if (!dev) {
nss_igs_error("Device is NULL\n");
return;
}
u64_stats_init(&stats.syncp);
u64_stats_update_begin(&stats.syncp);
/*
* In NSS firmware, the IFB interface's stats are getting updated
* post shaping. Therefore IFB interface's stats should be updated
* with NSS firmware's IFB TX stats only.
*/
stats.rx_packets = stats.tx_packets = node_stats->tx_packets;
stats.rx_bytes = stats.tx_bytes = node_stats->tx_bytes;
dev->stats.rx_dropped = dev->stats.tx_dropped += sync_stats->igs_stats.tx_dropped;
u64_stats_update_end(&stats.syncp);
ifb_update_offload_stats(dev, &stats);
}
/*
* nss_ifb_event_cb()
* Event Callback for IFB interface to receive events from NSS firmware.
*/
static void nss_ifb_event_cb(void *if_ctx, struct nss_cmn_msg *ncm)
{
struct net_device *netdev = if_ctx;
struct nss_igs_msg *nim = (struct nss_igs_msg *)ncm;
switch (ncm->type) {
case NSS_IGS_MSG_SYNC_STATS:
nss_ifb_update_dev_stats(netdev, (struct nss_igs_stats_sync_msg *)&nim->msg.stats);
break;
default:
nss_igs_error("%px: Unknown Event from NSS\n", netdev);
break;
}
}
/*
* nss_ifb_delete_if()
* Delete an IFB interface in NSS Firmware.
*/
void nss_ifb_delete_if(int32_t if_num)
{
nss_igs_unregister_if(if_num);
if (nss_dynamic_interface_dealloc_node(if_num, NSS_DYNAMIC_INTERFACE_TYPE_IGS)
!= NSS_TX_SUCCESS) {
nss_igs_error("Failed to de-alloc IFB dynamic interface\n");
}
}
/*
* nss_ifb_create_if()
* Create an IFB interface in NSS Firmware.
*/
int32_t nss_ifb_create_if(struct net_device *dev)
{
int32_t if_num, ret;
uint32_t features = 0;
struct nss_ctx_instance *nss_ctx;
if_num = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_IGS);
if (if_num < 0) {
nss_igs_error("%d interface creation failed\n", if_num);
return -1;
}
nss_ctx = nss_igs_register_if(if_num,
NSS_DYNAMIC_INTERFACE_TYPE_IGS,
nss_ifb_event_cb,
dev,
features);
if (!nss_ctx) {
nss_igs_error("%d interface registration failed\n", if_num);
goto registration_fail;
}
return if_num;
registration_fail:
ret = nss_dynamic_interface_dealloc_node(if_num, NSS_DYNAMIC_INTERFACE_TYPE_IGS);
if (ret != NSS_TX_SUCCESS) {
nss_igs_error("%d interface dealloc failed\n", if_num);
}
return -1;
}