blob: 8f27ed473f947fc4ddd59f594a4b98dcb110bbf3 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2014, 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_capwap.h
* NSS CAPWAP driver interface APIs
*/
#include "nss_core.h"
#include <nss_hal.h>
#include <linux/module.h>
#include "nss_cmn.h"
#include "nss_capwap.h"
/*
* Spinlock for protecting tunnel operations colliding with a tunnel destroy
*/
DEFINE_SPINLOCK(nss_capwap_spinlock);
/*
* Array of pointer for NSS CAPWAP handles. Each handle has per-tunnel
* stats based on the if_num which is an index.
*
* Per CAPWAP tunnel/interface number instance.
*/
struct nss_capwap_handle {
atomic_t refcnt; /**< Reference count on the tunnel */
uint32_t interface_num; /**< Interface number */
uint32_t tunnel_status; /**< 0=disable, 1=enabled */
struct nss_ctx_instance *ctx; /**< Pointer to context */
nss_capwap_msg_callback_t msg_callback; /**< Msg callback */
void *app_data; /**< App data (argument) */
struct nss_capwap_tunnel_stats stats; /**< Stats per-interface number */
};
static struct nss_capwap_handle *nss_capwap_hdl[NSS_MAX_DYNAMIC_INTERFACES];
/*
* Global definitions.
*/
extern struct nss_top_instance nss_top_main;
/*
* nss_capwap_verify_if_num()
* Verify if_num passed to us.
*/
static bool nss_capwap_verify_if_num(uint32_t if_num)
{
if (nss_is_dynamic_interface(if_num) == false) {
return false;
}
if (nss_dynamic_interface_get_type(if_num) != NSS_DYNAMIC_INTERFACE_TYPE_CAPWAP) {
return false;
}
return true;
}
/*
* nss_capwap_refcnt_inc()
* Increments refcnt on the tunnel.
*/
static void nss_capwap_refcnt_inc(int32_t if_num)
{
if_num = if_num - NSS_DYNAMIC_IF_START;
atomic_inc(&nss_capwap_hdl[if_num]->refcnt);
nss_assert(atomic_read(&nss_capwap_hdl[if_num]->refcnt) > 0);
}
/*
* nss_capwap_refcnt_dec()
* Decrements refcnt on the tunnel.
*/
static void nss_capwap_refcnt_dec(int32_t if_num)
{
if_num = if_num - NSS_DYNAMIC_IF_START;
nss_assert(atomic_read(&nss_capwap_hdl[if_num]->refcnt) > 0);
atomic_dec(&nss_capwap_hdl[if_num]->refcnt);
}
/*
* nss_capwap_get_refcnt()
* Get refcnt on the tunnel.
*/
static uint32_t nss_capwap_get_refcnt(int32_t if_num)
{
if_num = if_num - NSS_DYNAMIC_IF_START;
return atomic_read(&nss_capwap_hdl[if_num]->refcnt);
}
/*
* nss_capwap_set_msg_callback()
* This sets the message callback handler and its associated context
*/
static void nss_capwap_set_msg_callback(int32_t if_num, nss_capwap_msg_callback_t cb, void *app_data)
{
struct nss_capwap_handle *h;
h = nss_capwap_hdl[if_num - NSS_DYNAMIC_IF_START];
if (!h) {
return;
}
h->app_data = app_data;
h->msg_callback = cb;
}
/*
* nss_capwap_get_msg_callback()
* This gets the message callback handler and its associated context
*/
static nss_capwap_msg_callback_t nss_capwap_get_msg_callback(int32_t if_num, void **app_data)
{
struct nss_capwap_handle *h;
h = nss_capwap_hdl[if_num - NSS_DYNAMIC_IF_START];
if (!h) {
*app_data = NULL;
return NULL;
}
*app_data = h->app_data;
return h->msg_callback;
}
/*
* nss_capwapmgr_update_stats()
* Update per-tunnel stats for each CAPWAP interface.
*/
static void nss_capwapmgr_update_stats(struct nss_capwap_handle *handle, struct nss_capwap_stats_msg *fstats)
{
struct nss_capwap_tunnel_stats *stats;
stats = &handle->stats;
stats->rx_segments += fstats->rx_segments;
stats->tx_segments += fstats->tx_segments;
stats->dtls_pkts += fstats->dtls_pkts;
stats->rx_dup_frag += fstats->rx_dup_frag;
stats->oversize_drops += fstats->oversize_drops;
stats->frag_timeout_drops += fstats->frag_timeout_drops;
stats->rx_queue_full_drops += fstats->rx_queue_full_drops;
stats->rx_n2h_queue_full_drops += fstats->rx_n2h_queue_full_drops;
stats->rx_mem_failure_drops += fstats->rx_mem_failure_drops;
stats->tx_queue_full_drops += fstats->tx_queue_full_drops;
stats->tx_mem_failure_drops += fstats->tx_mem_failure_drops;
stats->tx_dropped += fstats->tx_dropped;
/*
* add pnode stats now.
*/
stats->pnode_stats.rx_packets += fstats->pnode_stats.rx_packets;
stats->pnode_stats.rx_bytes += fstats->pnode_stats.rx_bytes;
stats->pnode_stats.rx_dropped += fstats->pnode_stats.rx_dropped;
stats->pnode_stats.tx_packets += fstats->pnode_stats.tx_packets;
stats->pnode_stats.tx_bytes += fstats->pnode_stats.tx_bytes;
}
/*
* nss_capwap_handler()
* Handle NSS -> HLOS messages for CAPWAP
*/
static void nss_capwap_msg_handler(struct nss_ctx_instance *nss_ctx, struct nss_cmn_msg *ncm, __attribute__((unused))void *app_data)
{
struct nss_capwap_msg *ntm = (struct nss_capwap_msg *)ncm;
nss_capwap_msg_callback_t cb;
/*
* Is this a valid request/response packet?
*/
if (ncm->type > NSS_CAPWAP_MSG_TYPE_MAX) {
nss_warning("%p: received invalid message %d for CAPWAP interface", nss_ctx, ncm->type);
return;
}
if (ncm->len > sizeof(struct nss_capwap_msg)) {
nss_warning("%p: Length of message is greater than required: %d", nss_ctx, ncm->interface);
return;
}
nss_core_log_msg_failures(nss_ctx, ncm);
switch (ntm->cm.type) {
case NSS_CAPWAP_MSG_TYPE_SYNC_STATS: {
uint32_t if_num;
if_num = ncm->interface - NSS_DYNAMIC_IF_START;
if (nss_capwap_hdl[if_num] != NULL) {
nss_capwapmgr_update_stats(nss_capwap_hdl[if_num], &ntm->msg.stats);
}
}
}
/*
* Update the callback and app_data for NOTIFY messages.
*/
if (ncm->response == NSS_CMM_RESPONSE_NOTIFY) {
void *app_data;
ncm->cb = (uint32_t)nss_capwap_get_msg_callback(ncm->interface, &app_data);
ncm->app_data = (uint32_t)app_data;
}
/*
* Do we have a callback
*/
if (!ncm->cb) {
nss_trace("%p: cb is null for interface %d", nss_ctx, ncm->interface);
return;
}
cb = (nss_capwap_msg_callback_t)ncm->cb;
cb((void *)ncm->app_data, ntm);
}
/*
* nss_capwap_instance_alloc()
* Allocate CAPWAP tunnel instance
*/
static bool nss_capwap_instance_alloc(struct nss_ctx_instance *nss_ctx, uint32_t if_num)
{
struct nss_capwap_handle *h;
/*
* Allocate a handle
*/
h = kmalloc(sizeof(struct nss_capwap_handle), GFP_ATOMIC);
if (h == NULL) {
nss_warning("%p: no memory for allocating CAPWAP instance for interface : %d", nss_ctx, if_num);
return false;
}
memset(h, 0, sizeof (struct nss_capwap_handle));
atomic_set(&h->refcnt, 0);
memset(&h->stats, 0, sizeof (struct nss_capwap_tunnel_stats));
h->interface_num = if_num;
spin_lock(&nss_capwap_spinlock);
if (nss_capwap_hdl[if_num - NSS_DYNAMIC_IF_START] != NULL) {
spin_unlock(&nss_capwap_spinlock);
kfree(h);
nss_warning("%p: Another thread is already allocated instance for :%d", nss_ctx, if_num);
return false;
}
nss_capwap_hdl[if_num - NSS_DYNAMIC_IF_START] = h;
spin_unlock(&nss_capwap_spinlock);
return true;
}
/*
* nss_capwap_tx_msg()
* Transmit a CAPWAP message to NSS FW. Don't call this from softirq/interrupts.
*/
nss_tx_status_t nss_capwap_tx_msg(struct nss_ctx_instance *nss_ctx, struct nss_capwap_msg *msg)
{
struct nss_capwap_msg *nm;
struct nss_cmn_msg *ncm = &msg->cm;
struct sk_buff *nbuf;
int32_t status;
int32_t if_num;
BUG_ON(in_interrupt());
BUG_ON(in_softirq());
BUG_ON(in_serving_softirq());
if (nss_capwap_verify_if_num(msg->cm.interface) == false) {
return NSS_TX_FAILURE_BAD_PARAM;
}
if (ncm->type >= NSS_CAPWAP_MSG_TYPE_MAX) {
return NSS_TX_FAILURE_BAD_PARAM;
}
if_num = msg->cm.interface - NSS_DYNAMIC_IF_START;
spin_lock(&nss_capwap_spinlock);
if (!nss_capwap_hdl[if_num]) {
spin_unlock(&nss_capwap_spinlock);
nss_warning("%p: capwap tunnel if_num is not there: %d", nss_ctx, msg->cm.interface);
return NSS_TX_FAILURE_BAD_PARAM;
}
nss_capwap_refcnt_inc(msg->cm.interface);
spin_unlock(&nss_capwap_spinlock);
if (unlikely(nss_ctx->state != NSS_CORE_STATE_INITIALIZED)) {
nss_warning("%p: capwap msg dropped as core not ready", nss_ctx);
status = NSS_TX_FAILURE_NOT_READY;
goto out;
}
if (ncm->len > sizeof(struct nss_capwap_msg)) {
nss_warning("%p: message length is invalid: %d", nss_ctx, ncm->len);
status = NSS_TX_FAILURE_BAD_PARAM;
goto out;
}
nbuf = dev_alloc_skb(NSS_NBUF_PAYLOAD_SIZE);
if (unlikely(!nbuf)) {
spin_lock_bh(&nss_ctx->nss_top->stats_lock);
nss_ctx->nss_top->stats_drv[NSS_STATS_DRV_NBUF_ALLOC_FAILS]++;
spin_unlock_bh(&nss_ctx->nss_top->stats_lock);
nss_warning("%p: msg dropped as command allocation failed", nss_ctx);
status = NSS_TX_FAILURE;
goto out;
}
/*
* Copy the message to our skb
*/
nm = (struct nss_capwap_msg *)skb_put(nbuf, sizeof(struct nss_capwap_msg));
memcpy(nm, msg, sizeof(struct nss_capwap_msg));
status = nss_core_send_buffer(nss_ctx, 0, nbuf, NSS_IF_CMD_QUEUE, H2N_BUFFER_CTRL, 0);
if (status != NSS_CORE_STATUS_SUCCESS) {
dev_kfree_skb_any(nbuf);
nss_warning("%p: Unable to enqueue 'capwap message' \n", nss_ctx);
goto out;
}
nss_hal_send_interrupt(nss_ctx->nmap, nss_ctx->h2n_desc_rings[NSS_IF_CMD_QUEUE].desc_ring.int_bit,
NSS_REGS_H2N_INTR_STATUS_DATA_COMMAND_QUEUE);
NSS_PKT_STATS_INCREMENT(nss_ctx, &nss_ctx->nss_top->stats_drv[NSS_STATS_DRV_TX_CMD_REQ]);
out:
nss_capwap_refcnt_dec(msg->cm.interface);
return status;
}
EXPORT_SYMBOL(nss_capwap_tx_msg);
/*
* nss_capwap_tx_data()
* Transmit data buffer (skb) to a NSS interface number
*/
nss_tx_status_t nss_capwap_tx_data(struct nss_ctx_instance *nss_ctx, struct sk_buff *os_buf, uint32_t if_num)
{
return nss_if_tx_buf(nss_ctx, os_buf, if_num);
}
EXPORT_SYMBOL(nss_capwap_tx_data);
/*
***********************************
* Register/Unregister/Miscellaneous APIs
***********************************
*/
/*
* nss_capwap_get_stats()
* API for getting stats from a CAPWAP tunnel interface stats
*/
bool nss_capwap_get_stats(uint32_t if_num, struct nss_capwap_tunnel_stats *stats)
{
if (nss_capwap_verify_if_num(if_num) == false) {
return false;
}
if_num = if_num - NSS_DYNAMIC_IF_START;
spin_lock(&nss_capwap_spinlock);
if (nss_capwap_hdl[if_num] == NULL) {
spin_unlock(&nss_capwap_spinlock);
return false;
}
memcpy(stats, &nss_capwap_hdl[if_num]->stats, sizeof(struct nss_capwap_tunnel_stats));
spin_unlock(&nss_capwap_spinlock);
return true;
}
EXPORT_SYMBOL(nss_capwap_get_stats);
/*
* nss_capwap_notify_register()
* Registers a message notifier with NSS FW. It should not be called from
* softirq or interrupts.
*/
struct nss_ctx_instance *nss_capwap_notify_register(uint32_t if_num, nss_capwap_msg_callback_t cb, void *app_data)
{
struct nss_ctx_instance *nss_ctx;
nss_ctx = &nss_top_main.nss[nss_top_main.capwap_handler_id];
if (nss_capwap_verify_if_num(if_num) == false) {
nss_warning("%p: notfiy register received for invalid interface %d", nss_ctx, if_num);
return NULL;
}
spin_lock(&nss_capwap_spinlock);
if (nss_capwap_hdl[if_num - NSS_DYNAMIC_IF_START] != NULL) {
spin_unlock(&nss_capwap_spinlock);
nss_warning("%p: notfiy register tunnel already exists for interface %d", nss_ctx, if_num);
return NULL;
}
spin_unlock(&nss_capwap_spinlock);
return nss_ctx;
}
EXPORT_SYMBOL(nss_capwap_notify_register);
/*
* nss_capwap_notify_unregister()
* unregister the CAPWAP notifier for the given interface number (if_num).
* It shouldn't be called from softirq or interrupts.
*/
nss_tx_status_t nss_capwap_notify_unregister(struct nss_ctx_instance *nss_ctx, uint32_t if_num)
{
struct nss_top_instance *nss_top;
int index;
if (nss_capwap_verify_if_num(if_num) == false) {
nss_warning("%p: notify unregister received for invalid interface %d", nss_ctx, if_num);
return NSS_TX_FAILURE_BAD_PARAM;
}
nss_top = nss_ctx->nss_top;
if (nss_top == NULL) {
nss_warning("%p: notify unregister received for invalid nss_top %d", nss_ctx, if_num);
return NSS_TX_FAILURE_BAD_PARAM;
}
index = if_num - NSS_DYNAMIC_IF_START;
spin_lock(&nss_capwap_spinlock);
if (nss_capwap_hdl[index] == NULL) {
spin_unlock(&nss_capwap_spinlock);
nss_warning("%p: notify unregister received for unallocated if_num: %d", nss_ctx, if_num);
return NSS_TX_FAILURE_BAD_PARAM;
}
/*
* It's the responsibility of caller to wait and call us again. We return failure saying
* that we can't remove msg handler now.
*/
if (nss_capwap_get_refcnt(if_num) != 0) {
spin_unlock(&nss_capwap_spinlock);
nss_warning("%p: notify unregister tunnel %d: has reference", nss_ctx, if_num);
return NSS_TX_FAILURE_QUEUE;
}
nss_capwap_set_msg_callback(if_num, NULL, NULL);
spin_unlock(&nss_capwap_spinlock);
return NSS_TX_SUCCESS;
}
EXPORT_SYMBOL(nss_capwap_notify_unregister);
/*
* nss_capwap_data_register()
* Registers a data packet notifier with NSS FW.
*/
struct nss_ctx_instance *nss_capwap_data_register(uint32_t if_num, nss_capwap_buf_callback_t cb, struct net_device *netdev, uint32_t features)
{
struct nss_ctx_instance *nss_ctx;
int core_status;
nss_ctx = nss_capwap_get_ctx();
if (nss_capwap_verify_if_num(if_num) == false) {
nss_warning("%p: data register received for invalid interface %d", nss_ctx, if_num);
return NULL;
}
spin_lock(&nss_capwap_spinlock);
if (nss_ctx->nss_top->subsys_dp_register[if_num].ndev != NULL) {
spin_unlock(&nss_capwap_spinlock);
return NULL;
}
spin_unlock(&nss_capwap_spinlock);
core_status = nss_core_register_handler(if_num, nss_capwap_msg_handler, NULL);
if (core_status != NSS_CORE_STATUS_SUCCESS) {
nss_warning("%p: nss core register handler failed for if_num:%d with error :%d", nss_ctx, if_num, core_status);
return NULL;
}
if (nss_capwap_instance_alloc(nss_ctx, if_num) == false) {
nss_warning("%p: couldn't allocate tunnel instance for if_num:%d", nss_ctx, if_num);
return NULL;
}
nss_ctx->nss_top->subsys_dp_register[if_num].cb = cb;
nss_ctx->nss_top->subsys_dp_register[if_num].app_data = NULL;
nss_ctx->nss_top->subsys_dp_register[if_num].ndev = netdev;
nss_ctx->nss_top->subsys_dp_register[if_num].features = features;
return nss_ctx;
}
EXPORT_SYMBOL(nss_capwap_data_register);
/*
* nss_capwap_data_unregister()
* Unregister a data packet notifier with NSS FW
*/
bool nss_capwap_data_unregister(uint32_t if_num)
{
struct nss_ctx_instance *nss_ctx;
struct nss_capwap_handle *h;
nss_ctx = nss_capwap_get_ctx();
if (nss_capwap_verify_if_num(if_num) == false) {
nss_warning("%p: data unregister received for invalid interface %d", nss_ctx, if_num);
return false;
}
spin_lock(&nss_capwap_spinlock);
/*
* It's the responsibility of caller to wait and call us again.
*/
if (nss_capwap_get_refcnt(if_num) != 0) {
spin_unlock(&nss_capwap_spinlock);
nss_warning("%p: notify unregister tunnel %d: has reference", nss_ctx, if_num);
return false;
}
h = nss_capwap_hdl[if_num - NSS_DYNAMIC_IF_START];
nss_capwap_hdl[if_num - NSS_DYNAMIC_IF_START] = NULL;
spin_unlock(&nss_capwap_spinlock);
(void) nss_core_unregister_handler(if_num);
nss_ctx->nss_top->subsys_dp_register[if_num].cb = NULL;
nss_ctx->nss_top->subsys_dp_register[if_num].app_data = NULL;
nss_ctx->nss_top->subsys_dp_register[if_num].ndev = NULL;
nss_ctx->nss_top->subsys_dp_register[if_num].features = 0;
kfree(h);
return true;
}
EXPORT_SYMBOL(nss_capwap_data_unregister);
/*
* nss_capwap_get_ctx()
* Return a CAPWAP NSS context.
*/
struct nss_ctx_instance *nss_capwap_get_ctx()
{
struct nss_ctx_instance *nss_ctx;
nss_ctx = &nss_top_main.nss[nss_top_main.capwap_handler_id];
return nss_ctx;
}
EXPORT_SYMBOL(nss_capwap_get_ctx);
/*
* nss_capwap_get_max_buf_size()
* Return a CAPWAP NSS max_buf_size.
*/
uint32_t nss_capwap_get_max_buf_size(struct nss_ctx_instance *nss_ctx)
{
return nss_core_get_max_buf_size(nss_ctx);
}
EXPORT_SYMBOL(nss_capwap_get_max_buf_size);
/*
* nss_capwap_init()
* Initializes CAPWAP. Gets called from nss_init.c
*/
void nss_capwap_init()
{
memset(&nss_capwap_hdl, 0, sizeof(nss_capwap_hdl));
}
/*
* nss_capwap_msg_init()
* Initialize capwap message.
*/
void nss_capwap_msg_init(struct nss_capwap_msg *ncm, uint16_t if_num, uint32_t type, uint32_t len,
nss_capwap_msg_callback_t *cb, void *app_data)
{
nss_cmn_msg_init(&ncm->cm, if_num, type, len, (void*)cb, app_data);
}
EXPORT_SYMBOL(nss_capwap_msg_init);