/*
 * Copyright 2019, Cypress Semiconductor Corporation or a subsidiary of
 * Cypress Semiconductor Corporation. All Rights Reserved.
 * 
 * This software, associated documentation and materials ("Software")
 * is owned by Cypress Semiconductor Corporation,
 * or one of its subsidiaries ("Cypress") and is protected by and subject to
 * worldwide patent protection (United States and foreign),
 * United States copyright laws and international treaty provisions.
 * Therefore, you may use this Software only as provided in the license
 * agreement accompanying the software package from which you
 * obtained this Software ("EULA").
 * If no EULA applies, Cypress hereby grants you a personal, non-exclusive,
 * non-transferable license to copy, modify, and compile the Software
 * source code solely for use in connection with Cypress's
 * integrated circuit products. Any reproduction, modification, translation,
 * compilation, or representation of this Software except as specified
 * above is prohibited without the express written permission of Cypress.
 *
 * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress
 * reserves the right to make changes to the Software without notice. Cypress
 * does not assume any liability arising out of the application or use of the
 * Software or any product or circuit described in the Software. Cypress does
 * not authorize its products for use in any products where a malfunction or
 * failure of the Cypress product may reasonably be expected to result in
 * significant property damage, injury or death ("High Risk Product"). By
 * including Cypress's product in a High Risk Product, the manufacturer
 * of such system or application assumes all risk of such use and in doing
 * so agrees to indemnify Cypress against all liability.
 */

/** @file
 *  Provides an implementation of the Broadcom SDPCM protocol.
 *  The Broadcom SDPCM protocol provides multiplexing of Wireless Data frames,
 *  I/O Control functions (IOCTL), and Asynchronous Event signalling.
 *  It is required when communicating with Broadcom 802.11 devices.
 *
 */

#include <stdint.h>
#include <string.h> /* For strlen, memcpy */
#include "wwd_rtos.h"
#include "wwd_buffer.h"
#include "wwd_constants.h"
#include "wwd_wifi.h"
#include "wwd_assert.h"
#include "wwd_logging.h"
#include "wwd_events.h"
#include "RTOS/wwd_rtos_interface.h"
#include "network/wwd_buffer_interface.h"
#include "network/wwd_network_interface.h"
#include "network/wwd_network_constants.h"
#include "internal/wwd_sdpcm.h"
#include "internal/wwd_ap.h"
#include "internal/wwd_thread.h"
#include "internal/wwd_bcmendian.h"
#include "internal/bus_protocols/wwd_bus_protocol_interface.h"
#include "internal/wwd_internal.h"
#include "wwd_management.h"
#include "wiced_utilities.h"

/******************************************************
 * @cond       Constants
 ******************************************************/

#ifndef WWD_EVENT_HANDLER_LIST_SIZE
#define WWD_EVENT_HANDLER_LIST_SIZE    (5)      /** Maximum number of simultaneously registered event handlers */
#endif

#ifndef WICED_IOCTL_PACKET_TIMEOUT
#define WICED_IOCTL_PACKET_TIMEOUT      WICED_DEFAULT_IOCTL_PACKET_TIMEOUT
#endif

#define WWD_IOCTL_TIMEOUT_MS         ( 5000 )   /** Need to give enough time for coming out of Deep sleep (was 400) */
#define WWD_IOCTL_MAX_TX_PKT_LEN     ( 1500 )


#define BDC_PROTO_VER                  (2)      /** Version number of BDC header */
#define BDC_FLAG_VER_SHIFT             (4)      /** Number of bits to shift BDC version number in the flags field */
#define ETHER_TYPE_BRCM           (0x886C)      /** Broadcom Ethertype for identifying event packets - Copied from DHD include/proto/ethernet.h */
#define BRCM_OUI            "\x00\x10\x18"      /** Broadcom OUI (Organizationally Unique Identifier): Used in the proprietary(221) IE (Information Element) in all Broadcom devices */
#define BCM_MSG_IFNAME_MAX            (16)      /** Maximum length of an interface name in a wl_event_msg_t structure*/

/* QoS related definitions (type of service) */
#define IPV4_DSCP_OFFSET              (15)      /** Offset for finding the DSCP field in an IPv4 header */

/* CDC flag definitions taken from bcmcdc.h */
#define CDCF_IOC_ERROR              (0x01)      /** 0=success, 1=ioctl cmd failed */
#define CDCF_IOC_IF_MASK          (0xF000)      /** I/F index */
#define CDCF_IOC_IF_SHIFT             (12)      /** # of bits of shift for I/F Mask */
#define CDCF_IOC_ID_MASK      (0xFFFF0000)      /** used to uniquely id an ioctl req/resp pairing */
#define CDCF_IOC_ID_SHIFT             (16)      /** # of bits of shift for ID Mask */

#define BDC_FLAG2_IF_MASK           (0x0f)

#define SDPCM_HEADER_LEN              (12)
#define BDC_HEADER_LEN                 (4)

/* Event flags */
#define WLC_EVENT_MSG_LINK      (0x01)    /** link is up */
#define WLC_EVENT_MSG_FLUSHTXQ  (0x02)    /** flush tx queue on MIC error */
#define WLC_EVENT_MSG_GROUP     (0x04)    /** group MIC error */
#define WLC_EVENT_MSG_UNKBSS    (0x08)    /** unknown source bsscfg */
#define WLC_EVENT_MSG_UNKIF     (0x10)    /** unknown source OS i/f */


/******************************************************
 *             Macros
 ******************************************************/

/* bit map related macros */
#ifndef setbit
#ifndef NBBY      /* the BSD family defines NBBY */
#define  NBBY  8  /* 8 bits per byte */
#endif /* #ifndef NBBY */
#define  setbit(a, i)   (((uint8_t*)       a)[(int)(i)/(int)(NBBY)] |= (uint8_t)(1<<((i)%NBBY)))
#define  clrbit(a, i)   (((uint8_t*)       a)[(int)(i)/(int)(NBBY)] &= (uint8_t)~(1<<((i)%NBBY)))
#define  isset(a, i)    (((const uint8_t*) a)[(int)(i)/(int)(NBBY)] & (1<<((i)%NBBY)))
#define  isclr(a, i)    ((((const uint8_t*)a)[(int)(i)/(int)(NBBY)] & (1<<((i)%NBBY))) == 0)
#endif /* setbit */

#define DATA_AFTER_HEADER( x )   ((void*)(&x[1]))

/******************************************************
 *             Local Structures
 ******************************************************/

typedef enum
{
    DATA_HEADER       = 2,
    ASYNCEVENT_HEADER = 1,
    CONTROL_HEADER    = 0
} sdpcm_header_type_t;

#pragma pack(1)

/*TODO: Keep this typedef? (in preference to the defines above */
#if 0
typedef struct
{
    uint16_t control_id;
    uint8_t  interface_index :4;
    uint16_t reserved        :10;
    uint8_t  set             :1;
    uint8_t  error           :1;
}sdpcm_cdc_flags_t;
#endif /* if 0 */
typedef struct
{
    uint8_t sequence;
    uint8_t channel_and_flags;
    uint8_t next_length;
    uint8_t header_length;
    uint8_t wireless_flow_control;
    uint8_t bus_data_credit;
    uint8_t _reserved[2];
} sdpcm_sw_header_t;

typedef struct
{
    uint32_t cmd;    /* ioctl command value */
    uint32_t len;    /* lower 16: output buflen; upper 16: input buflen (excludes header) */
    uint32_t flags;  /* flag defns given in bcmcdc.h */
    uint32_t status; /* status code returned from the device */
} sdpcm_cdc_header_t;

typedef struct
{
    uint8_t flags;      /* Flags */
    uint8_t priority;   /* 802.1d Priority (low 3 bits) */
    uint8_t flags2;
    uint8_t data_offset; /* Offset from end of BDC header to packet data, in 4-uint8_t words.  Leaves room for optional headers.*/
} sdpcm_bdc_header_t;

typedef struct
{
    wiced_mac_t   destination_address;
    wiced_mac_t   source_address;
    uint16_t    ethertype;
} sdpcm_ethernet_header_t;


/*
 * SDPCM header definitions
 */
typedef struct
{
    uint16_t           frametag[2];
    sdpcm_sw_header_t  sw_header;
} sdpcm_header_t;

/*
 * SDPCM Packet structure definitions
 */
typedef struct
{
    wwd_buffer_header_t    buffer_header;
    sdpcm_header_t         sdpcm_header;
} sdpcm_common_header_t;

typedef struct
{
    sdpcm_common_header_t  common;
    sdpcm_cdc_header_t     cdc_header;
} sdpcm_control_header_t;

typedef struct
{
    sdpcm_common_header_t  common;
    uint8_t                _padding[2];
    sdpcm_bdc_header_t     bdc_header;
} sdpcm_data_header_t;

typedef struct bcmeth_hdr
{
    uint16_t subtype;      /** Vendor specific..32769 */
    uint16_t length;
    uint8_t  version;      /** Version is 0 */
    uint8_t  oui[3];       /** Broadcom OUI */
    uint16_t usr_subtype;  /** user specific Data */
} sdpcm_bcmeth_header_t;

/* these fields are stored in network order */
typedef struct
{

    uint16_t     version;                     /** Version 1 has fields up to ifname.  Version 2 has all fields including ifidx and bss_cfg_idx */
    uint16_t     flags;                       /** see flags */
    uint32_t     event_type;                  /** Message */
    uint32_t     status;                      /** Status code */
    uint32_t     reason;                      /** Reason code (if applicable) */
    uint32_t     auth_type;                   /** WLC_E_AUTH */
    uint32_t     datalen;                     /** data buf */
    wiced_mac_t  addr;                        /** Station address (if applicable) */
    char         ifname[BCM_MSG_IFNAME_MAX];  /** name of the packet incoming interface */
    uint8_t      ifidx;                       /** destination OS i/f index */
    uint8_t      bss_cfg_idx;                 /** source bsscfg index */
} sdpcm_raw_event_header_t;

/* used by driver msgs */
typedef struct bcm_event
{
    sdpcm_ethernet_header_t      ether;
    sdpcm_bcmeth_header_t        bcmeth;
    union
    {
        wwd_event_header_t       wwd;
        sdpcm_raw_event_header_t raw;
    } event;
} sdpcm_bcm_event_t;

#pragma pack()

/** Event list element structure
 *
 * events : A pointer to a wwd_event_num_t array that is terminated with a WLC_E_NONE event
 * handler: A pointer to the wwd_event_handler_t function that will receive the event
 * handler_user_data : User provided data that will be passed to the handler when a matching event occurs
 */
typedef struct
{
    const /*@null@*/ wwd_event_num_t* events;
    /*@null@*/ wwd_event_handler_t    handler;
    /*@null@*/ void*                  handler_user_data;
} sdpcm_event_list_elem_t;

/** @endcond */

/******************************************************
 *             Static Variables
 ******************************************************/

/* Event list variables */
static sdpcm_event_list_elem_t  wwd_sdpcm_event_list[WWD_EVENT_HANDLER_LIST_SIZE];
static host_semaphore_type_t    wwd_sdpcm_event_list_mutex;

/* IOCTL variables*/
static uint16_t                  wwd_sdpcm_requested_ioctl_id;
static host_semaphore_type_t     wwd_sdpcm_ioctl_mutex;
static /*@only@*/ wiced_buffer_t wwd_sdpcm_ioctl_response;
static host_semaphore_type_t     wwd_sdpcm_ioctl_sleep;

/* Bus data credit variables */
static uint8_t wwd_sdpcm_packet_transmit_sequence_number;
static uint8_t wwd_sdpcm_last_bus_data_credit    = 0;
static uint8_t wwd_sdpcm_credit_diff             = 0;
static uint8_t wwd_sdpcm_largest_credit_diff     = 0;

/* Packet send queue variables */
static host_semaphore_type_t                 wwd_sdpcm_send_queue_mutex;
static wiced_buffer_t /*@owned@*/ /*@null@*/ wwd_sdpcm_send_queue_head   = (wiced_buffer_t) NULL;
static wiced_buffer_t /*@owned@*/ /*@null@*/ wwd_sdpcm_send_queue_tail   = (wiced_buffer_t) NULL;

static wwd_wifi_raw_packet_processor_t       wwd_sdpcm_raw_packet_processor = NULL;

static uint32_t        wwd_host_interface_to_bss_index_array[3] = { WWD_STA_INTERFACE, WWD_AP_INTERFACE, WWD_P2P_INTERFACE }; /* Default mapping of host interface to BSS index */
static wwd_interface_t wwd_bss_index_to_host_interface_array[3] = { WWD_STA_INTERFACE, WWD_AP_INTERFACE, WWD_P2P_INTERFACE };

/* helper function for event messages ext API */
static uint8_t* wwd_management_alloc_event_msgs_buffer( wiced_buffer_t *buffer, wwd_interface_t interface );
/******************************************************
 *             SDPCM Logging
 *
 * Enable this section to allow logging of SDPCM packets
 * into a buffer for later perusal
 *
 * See sdpcm_log  and  next_sdpcm_log_pos
 *
 ******************************************************/
/** @cond */

#if 0

#define SDPCM_LOG_SIZE 30
#define SDPCM_LOG_HEADER_SIZE (0x60)

typedef enum { UNUSED, LOG_TX, LOG_RX } sdpcm_log_direction_t;
typedef enum { IOCTL, DATA, EVENT } sdpcm_log_type_t;

typedef struct SDPCM_log_entry_struct
{
    sdpcm_log_direction_t direction;
    sdpcm_log_type_t      type;
    unsigned long         time;
    unsigned long         length;
    unsigned char         header[SDPCM_LOG_HEADER_SIZE];
}sdpcm_log_entry_t;

static int next_sdpcm_log_pos = 0;
static sdpcm_log_entry_t sdpcm_log[SDPCM_LOG_SIZE];

static void add_sdpcm_log_entry( sdpcm_log_direction_t dir, sdpcm_log_type_t type, unsigned long length, char* eth_data )
{

    sdpcm_log[next_sdpcm_log_pos].direction = dir;
    sdpcm_log[next_sdpcm_log_pos].type = type;
    sdpcm_log[next_sdpcm_log_pos].time = host_rtos_get_time();
    sdpcm_log[next_sdpcm_log_pos].length = length;
    memcpy( sdpcm_log[next_sdpcm_log_pos].header, eth_data, SDPCM_LOG_HEADER_SIZE );
    next_sdpcm_log_pos++;
    if (next_sdpcm_log_pos >= SDPCM_LOG_SIZE)
    {
        next_sdpcm_log_pos = 0;
    }
}
#else
#define add_sdpcm_log_entry( dir, type, length, eth_data )
#endif

/** @endcond */

/******************************************************
 *             Static Function Prototypes
 ******************************************************/

static wiced_buffer_t  wwd_sdpcm_get_next_buffer_in_queue ( wiced_buffer_t buffer );
static void            wwd_sdpcm_set_next_buffer_in_queue ( wiced_buffer_t buffer, wiced_buffer_t prev_buffer );
static void            wwd_sdpcm_send_common              ( /*@only@*/ wiced_buffer_t buffer, sdpcm_header_type_t header_type );
static uint8_t         wwd_map_dscp_to_priority           ( uint8_t dscp_val );
static wwd_interface_t wwd_wifi_get_source_interface      ( uint8_t flags2 );

/******************************************************
 *             Function definitions
 ******************************************************/

/** Initialises the SDPCM protocol handler
 *
 *  Initialises mutex and semaphore flags needed by the SDPCM handler.
 *  Also initialises the list of event handlers. This function is called
 *  from the @ref wwd_thread_init function.
 *
 * @return    WWD result code
 */

wwd_result_t wwd_sdpcm_init( void )
{
    /* Create the mutex protecting the packet send queue */
    if ( host_rtos_init_semaphore( &wwd_sdpcm_ioctl_mutex ) != WWD_SUCCESS )
    {
        return WWD_SEMAPHORE_ERROR;
    }
    if ( host_rtos_set_semaphore( &wwd_sdpcm_ioctl_mutex, WICED_FALSE ) != WWD_SUCCESS )
    {
        return WWD_SEMAPHORE_ERROR;
    }

    /* Create the event flag which signals the wwd thread needs to wake up */
    if ( host_rtos_init_semaphore( &wwd_sdpcm_ioctl_sleep ) != WWD_SUCCESS )
    {
        host_rtos_deinit_semaphore( &wwd_sdpcm_ioctl_mutex );
        return WWD_SEMAPHORE_ERROR;
    }

    /* Create the sdpcm packet queue semaphore */
    if ( host_rtos_init_semaphore( &wwd_sdpcm_send_queue_mutex ) != WWD_SUCCESS )
    {
        host_rtos_deinit_semaphore( &wwd_sdpcm_ioctl_sleep );
        host_rtos_deinit_semaphore( &wwd_sdpcm_ioctl_mutex );
        return WWD_SEMAPHORE_ERROR;
    }
    if ( host_rtos_set_semaphore( &wwd_sdpcm_send_queue_mutex, WICED_FALSE ) != WWD_SUCCESS )
    {
        return WWD_SEMAPHORE_ERROR;
    }

    /* Create semaphore to protect event list management */
    if ( host_rtos_init_semaphore( &wwd_sdpcm_event_list_mutex ) != WWD_SUCCESS )
    {
        host_rtos_deinit_semaphore( &wwd_sdpcm_ioctl_sleep );
        host_rtos_deinit_semaphore( &wwd_sdpcm_ioctl_mutex );
        host_rtos_deinit_semaphore( &wwd_sdpcm_send_queue_mutex );
        return WWD_SEMAPHORE_ERROR;
    }
    if ( host_rtos_set_semaphore( &wwd_sdpcm_event_list_mutex, WICED_FALSE ) != WWD_SUCCESS )
    {
        return WWD_SEMAPHORE_ERROR;
    }

    wwd_sdpcm_send_queue_head = NULL;
    wwd_sdpcm_send_queue_tail = NULL;

    /* Initialise the list of event handler functions */
    memset( wwd_sdpcm_event_list, 0, sizeof(wwd_sdpcm_event_list) );

    wwd_sdpcm_bus_vars_init( );

    return WWD_SUCCESS;
}


/* Re-initialize the bus variables after deep sleep */
void wwd_sdpcm_bus_vars_init( void )
{
    /* Bus data credit variables */
    wwd_sdpcm_credit_diff                     = 0;
    wwd_sdpcm_largest_credit_diff             = 0;

    wwd_sdpcm_packet_transmit_sequence_number = 0;
    wwd_sdpcm_last_bus_data_credit            = (uint8_t) 1;
}

/** Initialises the SDPCM protocol handler
 *
 *  De-initialises mutex and semaphore flags needed by the SDPCM handler.
 *  This function is called from the @ref wwd_thread_func function when it is exiting.
 */

void wwd_sdpcm_quit( void ) /*@globals killed wwd_sdpcm_ioctl_sleep, killed wwd_sdpcm_ioctl_mutex, killed wwd_sdpcm_send_queue_mutex@*/ /*@modifies wwd_sdpcm_send_queue_head@*/
{
    /* Delete the sleep mutex */
    (void) host_rtos_deinit_semaphore( &wwd_sdpcm_ioctl_sleep ); /* Ignore return - not much can be done about failure */

    /* Delete the queue mutex.  */
    (void) host_rtos_deinit_semaphore( &wwd_sdpcm_ioctl_mutex ); /* Ignore return - not much can be done about failure */

    /* Delete the SDPCM queue mutex */
    (void) host_rtos_deinit_semaphore( &wwd_sdpcm_send_queue_mutex ); /* Ignore return - not much can be done about failure */

    /* Delete the event list management mutex */
    (void) host_rtos_deinit_semaphore( &wwd_sdpcm_event_list_mutex ); /* Ignore return - not much can be done about failure */

    /* Free any left over packets in the queue */
    while (wwd_sdpcm_send_queue_head != NULL)
    {
        wiced_buffer_t buf = wwd_sdpcm_get_next_buffer_in_queue( wwd_sdpcm_send_queue_head );
        host_buffer_release(wwd_sdpcm_send_queue_head, WWD_NETWORK_TX);
        wwd_sdpcm_send_queue_head = buf;
    }
}


/** Sets/Gets an I/O Variable (IOVar)
 *
 *  This function either sets or retrieves the value of an I/O variable from the Broadcom 802.11 device.
 *  The data which is set or retrieved must be in a format structure which is appropriate for the particular
 *  I/O variable being accessed. These structures can only be found in the DHD source code such as wl/exe/wlu.c.
 *
 *  @Note: The function blocks until the I/O variable read/write has completed
 *
 * @param type       : SDPCM_SET or SDPCM_GET - indicating whether to set or get the I/O variable value
 * @param send_buffer_hnd : A handle for a packet buffer containing the data value to be sent.
 * @param response_buffer_hnd : A pointer which will receive the handle for the packet buffer containing the response data value received..
 * @param interface : Which interface to send the iovar to (AP or STA)
 *
 * @return    WWD result code
 */
wwd_result_t wwd_sdpcm_send_iovar( sdpcm_command_type_t type, /*@only@*/ wiced_buffer_t send_buffer_hnd, /*@special@*/ /*@out@*/ /*@null@*/ wiced_buffer_t* response_buffer_hnd, wwd_interface_t interface )  /*@allocates *response_buffer_hnd@*/  /*@defines **response_buffer_hnd@*/
{
    if ( type == SDPCM_SET )
    {
        return wwd_sdpcm_send_ioctl( SDPCM_SET, (uint32_t) WLC_SET_VAR, send_buffer_hnd, response_buffer_hnd, interface );
    }
    else
    {
        return wwd_sdpcm_send_ioctl( SDPCM_GET, (uint32_t) WLC_GET_VAR, send_buffer_hnd, response_buffer_hnd, interface );
    }
}

/** Sends a data packet.
 *
 *  This function should be called by the bottom of the network stack in order for it
 *  to send an ethernet frame.
 *  The function prepends a BDC header, before sending to @ref wwd_sdpcm_send_common where
 *  the SDPCM header will be added
 *
 * @param buffer  : The ethernet packet buffer to be sent
 * @param interface : the interface over which to send the packet (AP or STA)
 *
 */
void wwd_network_send_ethernet_data( /*@only@*/ wiced_buffer_t buffer, wwd_interface_t interface ) /* Returns immediately - Wiced_buffer_tx_completed will be called once the transmission has finished */
{
    sdpcm_data_header_t* packet;
    wwd_result_t         result;
    uint8_t* dscp = NULL;
    uint8_t priority = 0;
    sdpcm_ethernet_header_t* ethernet_header = (sdpcm_ethernet_header_t*)host_buffer_get_current_piece_data_pointer( buffer );
    uint16_t ether_type;

    ether_type = NTOH16( ethernet_header->ethertype );
    if (( ether_type == WICED_ETHERTYPE_IPv4 ) || (ether_type == WICED_ETHERTYPE_DOT1AS))
    {
        dscp = (uint8_t*)host_buffer_get_current_piece_data_pointer( buffer ) + IPV4_DSCP_OFFSET;
    }

    add_sdpcm_log_entry( LOG_TX, DATA, host_buffer_get_current_piece_size( buffer ), (char*) host_buffer_get_current_piece_data_pointer( buffer ) );

    WWD_LOG( ( "Wcd:> DATA pkt 0x%08lX len %d\n", (unsigned long)buffer, (int)host_buffer_get_current_piece_size( buffer ) ) );


    /* Add link space at front of packet */
    result = host_buffer_add_remove_at_front( &buffer, - (int) (sizeof(sdpcm_data_header_t)) );
    if ( result != WWD_SUCCESS )
    {
        WPRINT_WWD_DEBUG(("Unable to adjust header space\n"));
        host_buffer_release( buffer, WWD_NETWORK_TX );
        return;
    }

    packet = (sdpcm_data_header_t*) host_buffer_get_current_piece_data_pointer( buffer );

    if ( interface > WWD_P2P_INTERFACE )
    {
        WPRINT_WWD_DEBUG(("No interface for packet send\n"));
        host_buffer_release( buffer, WWD_NETWORK_TX );
        return;
    }

    /* Prepare the BDC header */
    packet->bdc_header.flags    = 0;
    packet->bdc_header.flags    = (uint8_t) ( BDC_PROTO_VER << BDC_FLAG_VER_SHIFT );
    /* If it's an IPv4 packet set the BDC header priority based on the DSCP field */
    if ( ((ether_type == WICED_ETHERTYPE_IPv4) || (ether_type == WICED_ETHERTYPE_DOT1AS)) && (dscp != NULL) )
    {
        if (*dscp != 0) /* If it's equal 0 then it's best effort traffic and nothing needs to be done */
        {
            priority = wwd_map_dscp_to_priority( *dscp );
        }
    }

    /* If STA interface, re-map prio to the prio allowed by the AP, regardless of whether it's an IPv4 packet */
    if ( interface == WWD_STA_INTERFACE )
    {
        packet->bdc_header.priority = wwd_tos_map[priority];
    }
    else
    {
        packet->bdc_header.priority = priority;
    }

    packet->bdc_header.flags2   = (uint8_t)wwd_get_bss_index( interface );
    packet->bdc_header.data_offset = 0;

    /* Add the length of the BDC header and pass "down" */
    wwd_sdpcm_send_common( buffer, DATA_HEADER );
}

void wwd_sdpcm_update_credit(uint8_t* data)
{
    sdpcm_sw_header_t* header = (sdpcm_sw_header_t*)(data + 4);

    if ( ( header->channel_and_flags & 0x0f ) < (uint8_t) 3 )
    {
        wwd_sdpcm_credit_diff = (uint8_t)(header->bus_data_credit - wwd_sdpcm_last_bus_data_credit);
        WWD_LOG(("credit update =%d\n ",header->bus_data_credit) );
        if ( wwd_sdpcm_credit_diff <= (uint8_t) CHIP_MAX_BUS_DATA_CREDIT_DIFF )
        {
            wwd_sdpcm_last_bus_data_credit = header->bus_data_credit;
        }
        else
        {
            if (wwd_sdpcm_credit_diff > wwd_sdpcm_largest_credit_diff)
            {
                wwd_sdpcm_largest_credit_diff = wwd_sdpcm_credit_diff;
            }
        }
    }

    wwd_bus_set_flow_control(header->wireless_flow_control);
}

/** Processes and directs incoming SDPCM packets
 *
 *  This function receives SDPCM packets from the Broadcom 802.11 device and decodes the SDPCM header
 *  to determine where the packet should be directed.
 *
 *  - Control packets (IOCTL/IOVAR) cause the IOCTL flag to be set to allow the resumption of the thread
 *    which sent the IOCTL
 *  - Data Packets are sent to the bottom layer of the network stack via the @ref host_network_process_ethernet_data function
 *  - Event Packets are decoded to determine which event occurred, and the event handler list is consulted
 *    and the appropriate event handler is called.
 *
 * @param buffer  : The SDPCM packet buffer received from the Broadcom 802.11 device
 *
 */
void wwd_sdpcm_process_rx_packet( /*@only@*/ wiced_buffer_t buffer )
{
    sdpcm_common_header_t* packet;
    uint16_t i;
    uint16_t j;
    uint16_t size;
    uint16_t size_inv;
    uint32_t flags;
    uint16_t id;
    sdpcm_common_header_t*  common_header;
    sdpcm_cdc_header_t*     cdc_header;

    packet = (sdpcm_common_header_t*) host_buffer_get_current_piece_data_pointer( buffer );

    /* Extract the total SDPCM packet size from the first two frametag bytes */
    size = packet->sdpcm_header.frametag[0];

    /* Check that the second two frametag bytes are the binary inverse of the size */
    size_inv = (uint16_t) ~size;  /* Separate variable due to GCC Bug 38341 */
    if ( packet->sdpcm_header.frametag[1] != size_inv )
    {
        WPRINT_WWD_DEBUG(("Received a packet with a frametag which is wrong\n"));
        host_buffer_release( buffer, WWD_NETWORK_RX );
        return;
    }

    /* Check whether the packet is big enough to contain the SDPCM header (or) it's too big to handle */
    if ( ( size < (uint16_t) SDPCM_HEADER_LEN ) || ( size > (uint16_t) WICED_LINK_MTU ) )
    {
        wiced_minor_assert( "Packet size invalid", 0 == 1 );
        WPRINT_WWD_DEBUG(("Received a packet that is too small to contain anything useful (or) too big. Packet Size = [%d]\n", size));
        host_buffer_release( buffer, WWD_NETWORK_RX );
        return;
    }

    /* Get address of packet->sdpcm_header.frametag indirectly to avoid IAR's unaligned address warning */
    wwd_sdpcm_update_credit((uint8_t*)&packet->sdpcm_header.sw_header - sizeof(packet->sdpcm_header.frametag));

    if ( size == (uint16_t) SDPCM_HEADER_LEN )
    {
        /* This is a flow control update packet with no data - release it. */
        host_buffer_release( buffer, WWD_NETWORK_RX );
        return;
    }

    /* Check the SDPCM channel to decide what to do with packet. */
    switch ( packet->sdpcm_header.sw_header.channel_and_flags & 0x0f )
    {
        case CONTROL_HEADER:  /* IOCTL/IOVAR reply packet */
            add_sdpcm_log_entry( LOG_RX, IOCTL, host_buffer_get_current_piece_size( buffer ), (char*) host_buffer_get_current_piece_data_pointer( buffer ) );

            /* Check that packet size is big enough to contain the CDC header as well as the SDPCM header */
            if ( packet->sdpcm_header.frametag[0] < ( sizeof( packet->sdpcm_header.frametag ) + sizeof(sdpcm_sw_header_t) + sizeof(sdpcm_cdc_header_t) ) )
            {
                /* Received a too-short SDPCM packet! */
                WPRINT_WWD_DEBUG(("Received a too-short SDPCM packet!\n"));
                host_buffer_release( buffer, WWD_NETWORK_RX );
                break;
            }

            /* Check that the IOCTL identifier matches the identifier that was sent */
            common_header = (sdpcm_common_header_t*) host_buffer_get_current_piece_data_pointer( buffer );
            cdc_header    = (sdpcm_cdc_header_t*) &( (char*) &common_header->sdpcm_header )[ common_header->sdpcm_header.sw_header.header_length ];
            flags         = ltoh32( cdc_header->flags );
            id            = (uint16_t) ( ( flags & CDCF_IOC_ID_MASK ) >> CDCF_IOC_ID_SHIFT );

            if ( id == wwd_sdpcm_requested_ioctl_id )
            {
                /* Save the response packet in a global variable */
                wwd_sdpcm_ioctl_response = buffer;

                WWD_LOG( ( "Wcd:< Procd pkt 0x%08lX: IOCTL Response (%d bytes)\n", (unsigned long)buffer, size ) );

                /* Wake the thread which sent the IOCTL/IOVAR so that it will resume */
                host_rtos_set_semaphore( &wwd_sdpcm_ioctl_sleep, WICED_FALSE );
            }
            else
            {
                host_buffer_release( buffer, WWD_NETWORK_RX );
                WPRINT_WWD_DEBUG(("Received a response for a different IOCTL - retry\n"));
            }
            break;

        case DATA_HEADER:
            {
                sdpcm_bdc_header_t* bdc_header;
                int32_t headers_len_below_payload;

                /* Check that the packet is big enough to contain SDPCM & BDC headers */
                if ( packet->sdpcm_header.frametag[0] <= (uint16_t)( SDPCM_HEADER_LEN + BDC_HEADER_LEN ) )
                {
                    WPRINT_WWD_DEBUG(("Packet too small to contain SDPCM + BDC headers\n"));
                    host_buffer_release( buffer, WWD_NETWORK_RX );
                    break;
                }

                /* Calculate where the BDC header is - this is dependent on the data offset field of the SDPCM SW header */
                bdc_header = (sdpcm_bdc_header_t*) &((char*)&packet->sdpcm_header)[ packet->sdpcm_header.sw_header.header_length ];

                /* Calculate where the payload is - this is dependent on the data offset fields of the SDPCM SW header and the BDC header */
                headers_len_below_payload = (int32_t) ( (int32_t)sizeof(wwd_buffer_header_t) + (int32_t) packet->sdpcm_header.sw_header.header_length + (int32_t)BDC_HEADER_LEN + (int32_t)(bdc_header->data_offset<<2) );

                /* Move buffer pointer past gSPI, SDPCM, BCD headers and padding, so that the network stack or 802.11 monitor sees only the payload */
                if ( WWD_SUCCESS != host_buffer_add_remove_at_front( &buffer, headers_len_below_payload ) )
                {
                    WPRINT_WWD_DEBUG(("No space for headers without chaining. this should never happen\n"));
                    host_buffer_release( buffer, WWD_NETWORK_RX );
                    break;
                }

                add_sdpcm_log_entry( LOG_RX, DATA, host_buffer_get_current_piece_size( buffer ), (char*) host_buffer_get_current_piece_data_pointer( buffer ) );
                WWD_LOG( ( "Wcd:< Procd pkt 0x%08lX: Data (%d bytes)\n", (unsigned long)buffer, size ) );


                /* Check if we are in monitor mode */
                if ( wwd_wifi_monitor_mode_is_enabled() == WICED_TRUE )
                {
                    if ( wwd_sdpcm_raw_packet_processor != NULL )
                    {
                        wwd_sdpcm_raw_packet_processor( buffer, WWD_STA_INTERFACE );
                    }
                    else
                    {
                        host_buffer_release( buffer, WWD_NETWORK_RX );
                    }
                }
                else
                {
                    /* Send packet to bottom of network stack */
                    host_network_process_ethernet_data( buffer, wwd_wifi_get_source_interface( bdc_header->flags2 ) );
                }
            }
            break;

        case ASYNCEVENT_HEADER:
            {
                sdpcm_bdc_header_t* bdc_header;
                uint16_t ether_type;
                wwd_event_header_t* wwd_event;
                sdpcm_bcm_event_t* event;

                bdc_header = (sdpcm_bdc_header_t*) &((char*)&packet->sdpcm_header)[ packet->sdpcm_header.sw_header.header_length ];

                event = (sdpcm_bcm_event_t*) &bdc_header[ bdc_header->data_offset + 1 ];

                ether_type = NTOH16( event->ether.ethertype );

                /* If frame is truly an event, it should have EtherType equal to the Broadcom type. */
                if ( ether_type != (uint16_t)ETHER_TYPE_BRCM )
                {
                    WPRINT_WWD_DEBUG(("Error - received a channel 1 packet which was not BRCM ethertype\n"));
                    host_buffer_release( buffer, WWD_NETWORK_RX );
                    break;
                }

                /* If ethertype is correct, the contents of the ethernet packet
                 * are a structure of type bcm_event_t
                 */

                /* Check that the OUI matches the Broadcom OUI */
                if ( 0 != memcmp( BRCM_OUI, &event->bcmeth.oui[0], (size_t)DOT11_OUI_LEN ) )
                {
                    WPRINT_WWD_DEBUG(("Event OUI mismatch\n"));
                    host_buffer_release( buffer, WWD_NETWORK_RX );
                    break;
                }

                wwd_event = &event->event.wwd;

                /* Search for the event type in the list of event handler functions
                 * event data is stored in network endianness
                 */
                wwd_event->flags      =                        NTOH16( wwd_event->flags      );
                wwd_event->event_type = (wwd_event_num_t)      NTOH32( wwd_event->event_type );
                wwd_event->status     = (wwd_event_status_t)   NTOH32( wwd_event->status     );
                wwd_event->reason     = (wwd_event_reason_t)   NTOH32( wwd_event->reason     );
                wwd_event->auth_type  =                        NTOH32( wwd_event->auth_type  );
                wwd_event->datalen    =                        NTOH32( wwd_event->datalen    );
                /* Ensure data length is correct */
                if ( wwd_event->datalen > (uint32_t)( size - ((char *)DATA_AFTER_HEADER( event ) - (char *)&packet->sdpcm_header ) ) )
                {
                    WPRINT_WWD_DEBUG(("Error - (data length received [%d] > expected data length [%d]). SDPCM packet size = [%d]. Ignoring the packet\n",
                            (int)wwd_event->datalen, size - ((char *)DATA_AFTER_HEADER( event ) - (char *)&packet->sdpcm_header ), size));
                    host_buffer_release( buffer, WWD_NETWORK_RX );
                    break;
                }
                //wwd_event->interface  = ( event->event.raw.ifidx & 3);
                wwd_event->interface  =  (uint8_t)wwd_bss_index_to_host_interface_array[event->event.raw.ifidx];

                /* This is necessary because people who defined event statuses and reasons overlapped values. */
                if ( wwd_event->event_type == WLC_E_PSK_SUP )
                {
                    wwd_event->status = (wwd_event_status_t) ( (int)wwd_event->status + WLC_SUP_STATUS_OFFSET   );
                    wwd_event->reason = (wwd_event_reason_t) ( (int)wwd_event->reason + WLC_E_SUP_REASON_OFFSET );
                }
                else if ( wwd_event->event_type == WLC_E_PRUNE )
                {
                    wwd_event->reason = (wwd_event_reason_t) ( (int)wwd_event->reason + WLC_E_PRUNE_REASON_OFFSET );
                }
                else if ( ( wwd_event->event_type == WLC_E_DISASSOC ) || ( wwd_event->event_type == WLC_E_DEAUTH ) )
                {
                    wwd_event->status = (wwd_event_status_t) ( (int)wwd_event->status + WLC_DOT11_SC_STATUS_OFFSET   );
                    wwd_event->reason = (wwd_event_reason_t) ( (int)wwd_event->reason + WLC_E_DOT11_RC_REASON_OFFSET );
                }

                /* do any needed debug logging of event */
                wwd_log_event( wwd_event, (uint8_t*) DATA_AFTER_HEADER( event ) );

                if ( host_rtos_get_semaphore( &wwd_sdpcm_event_list_mutex, NEVER_TIMEOUT, WICED_FALSE ) != WWD_SUCCESS )
                {
                    WPRINT_WWD_DEBUG(("Failed to obtain mutex for event list access!\n"));
                    host_buffer_release( buffer, WWD_NETWORK_RX );
                    break;
                }

                for ( i = 0; i < (uint16_t) WWD_EVENT_HANDLER_LIST_SIZE; i++ )
                {
                    if ( wwd_sdpcm_event_list[i].events != NULL )
                    {
                        for ( j = 0; wwd_sdpcm_event_list[i].events[j] != WLC_E_NONE; ++j )
                        {
                            if ( wwd_sdpcm_event_list[i].events[j] == wwd_event->event_type )
                            {
                                /* Correct event type has been found - call the handler function and exit loop */
                                wwd_sdpcm_event_list[i].handler_user_data = wwd_sdpcm_event_list[i].handler( wwd_event, (uint8_t*) DATA_AFTER_HEADER( event ), wwd_sdpcm_event_list[i].handler_user_data );
                                /*@innerbreak@*/
                                break;
                            }
                        }
                    }
                }

                host_rtos_set_semaphore( &wwd_sdpcm_event_list_mutex, WICED_FALSE );

                add_sdpcm_log_entry( LOG_RX, EVENT, host_buffer_get_current_piece_size( buffer ), (char*) host_buffer_get_current_piece_data_pointer( buffer ) );
                WWD_LOG( ( "Wcd:< Procd pkt 0x%08lX: Evnt %d (%d bytes)\n", (unsigned long)buffer, (int)event->event.raw.event_type, size ) );

                /* Release the event packet buffer */
                host_buffer_release( buffer, WWD_NETWORK_RX );
            }
            break;

        default:
            wiced_minor_assert("SDPCM packet of unknown channel received - dropping packet", 0 != 0);
            host_buffer_release( buffer, WWD_NETWORK_RX );
            break;
    }
}

static void (*finished_client_func)( sdpcm_command_type_t type, uint32_t command, const char* name, wwd_interface_t interface ) = NULL;
static void (*started_client_func)( void ) = NULL;


static void wwd_sdpcm_notify_client_ioctl_finished( sdpcm_command_type_t type, uint32_t command, const char* name, wwd_interface_t interface )
{
    if ( NULL != finished_client_func )
    {
        finished_client_func( type, command, name, interface );
    }
}

static void wwd_sdpcm_notify_client_ioctl_start( void )
{
    if ( NULL != started_client_func )
    {
        started_client_func( );
    }
}

static wiced_bool_t wwd_sdpcm_notify_client_ioctl_end_registered( void )
{
    return ( NULL != finished_client_func ) ? WICED_TRUE : WICED_FALSE;
}

wwd_result_t wwd_sdpcm_register_fw_cmd_exit_hook( void (*func)( sdpcm_command_type_t type, uint32_t command, const char* name, wwd_interface_t interface ) )
{
    finished_client_func = func;
    return WWD_SUCCESS;
}

/** Sends an IOCTL command
 *
 *  Sends a I/O Control command to the Broadcom 802.11 device.
 *  The data which is set or retrieved must be in a format structure which is appropriate for the particular
 *  I/O control being sent. These structures can only be found in the DHD source code such as wl/exe/wlu.c.
 *  The I/O control will always respond with a packet buffer which may contain data in a format specific to
 *  the I/O control being used.
 *
 *  @Note: The caller is responsible for releasing the response buffer.
 *  @Note: The function blocks until the IOCTL has completed
 *  @Note: Only one IOCTL may happen simultaneously.
 *
 *  @param type       : SDPCM_SET or SDPCM_GET - indicating whether to set or get the I/O control
 *  @param send_buffer_hnd : A handle for a packet buffer containing the data value to be sent.
 *  @param response_buffer_hnd : A pointer which will receive the handle for the packet buffer containing the response data value received..
 *  @param interface : Which interface to send the iovar to (WWD_STA_INTERFACE or WWD_AP_INTERFACE)
 *
 *  @return    WWD result code
 */
wwd_result_t wwd_sdpcm_send_ioctl( sdpcm_command_type_t type, uint32_t command, /*@only@*/ wiced_buffer_t send_buffer_hnd, /*@special@*/ /*@out@*/ /*@null@*/  wiced_buffer_t* response_buffer_hnd, wwd_interface_t interface )  /*@allocates *response_buffer_hnd@*/  /*@defines **response_buffer_hnd@*/
{
    uint32_t data_length;
    uint32_t flags;
    wwd_result_t retval;
    sdpcm_control_header_t* send_packet;
    sdpcm_common_header_t*  common_header;
    sdpcm_cdc_header_t*     cdc_header;
    uint32_t                bss_index = wwd_host_interface_to_bss_index_array[interface & 3];
    const char*             iovar_name[IOVAR_NAME_STR_MAX_SIZE];

    /* call here to allow re-entrance for client, in case it needs to do ioctl/iovar */
    wwd_sdpcm_notify_client_ioctl_start( );

    /* Acquire mutex which prevents multiple simultaneous IOCTLs */
    retval = host_rtos_get_semaphore( &wwd_sdpcm_ioctl_mutex, NEVER_TIMEOUT, WICED_FALSE );
    if ( retval != WWD_SUCCESS )
    {
        host_buffer_release( send_buffer_hnd, WWD_NETWORK_TX );
        return retval;
    }

    /* Get the data length and cast packet to a CDC SDPCM header */
    data_length = (uint32_t)( host_buffer_get_current_piece_size( send_buffer_hnd ) - sizeof(sdpcm_common_header_t) - sizeof(sdpcm_cdc_header_t) );
    send_packet = (sdpcm_control_header_t*) host_buffer_get_current_piece_data_pointer( send_buffer_hnd );

    WWD_IOCTL_LOG_ADD(command, send_buffer_hnd);

    /* Check if IOCTL is actually IOVAR */
    if ( command == WLC_SET_VAR || command == WLC_GET_VAR )
    {
        uint8_t* data = (uint8_t*)DATA_AFTER_HEADER( send_packet );
        uint8_t* ptr = data;

        /* Calculate the offset added to compensate for IOVAR string creating unaligned data section */
        while ( *ptr == 0 )
        {
            ptr++;
        }
        if ( data != ptr )
        {
            data_length -= (uint32_t)( ptr - data );
            memmove( data, ptr, data_length );
            host_buffer_set_size( send_buffer_hnd, (uint16_t) ( data_length + sizeof(sdpcm_common_header_t) + sizeof(sdpcm_cdc_header_t) ) );
        }

        /* save name if needed */
        if ( WICED_TRUE == wwd_sdpcm_notify_client_ioctl_end_registered( ) )
        {
            memcpy(iovar_name, data, MIN( sizeof(iovar_name), data_length ) );
        }
    }

    /* Prepare the CDC header */
    send_packet->cdc_header.cmd    = command;
    send_packet->cdc_header.len    = data_length;
    send_packet->cdc_header.flags  = ( ( (uint32_t) ++wwd_sdpcm_requested_ioctl_id << CDCF_IOC_ID_SHIFT ) & CDCF_IOC_ID_MASK ) | type | bss_index << CDCF_IOC_IF_SHIFT;
    send_packet->cdc_header.status = 0;

    /* Manufacturing test can receive big buffers, but sending big buffers causes a wlan firmware error */
    /* Even though data portion needs to be truncated, cdc_header should have the actual length of the ioctl packet */
    if ( host_buffer_get_current_piece_size( send_buffer_hnd ) > WWD_IOCTL_MAX_TX_PKT_LEN )
    {
        host_buffer_set_size( send_buffer_hnd, WWD_IOCTL_MAX_TX_PKT_LEN );
    }

    /* Store the length of the data and the IO control header and pass "down" */
    wwd_sdpcm_send_common( send_buffer_hnd, CONTROL_HEADER );

    /* Wait till response has been received  */
    retval = host_rtos_get_semaphore( &wwd_sdpcm_ioctl_sleep, (uint32_t) WWD_IOCTL_TIMEOUT_MS, WICED_FALSE );
    if ( retval != WWD_SUCCESS )
    {
        /* Release the mutex since wwd_sdpcm_ioctl_response will no longer be referenced. */
        host_rtos_set_semaphore( &wwd_sdpcm_ioctl_mutex, WICED_FALSE );
        return retval;
    }

    common_header = (sdpcm_common_header_t*) host_buffer_get_current_piece_data_pointer( wwd_sdpcm_ioctl_response );
    cdc_header    = (sdpcm_cdc_header_t*) &( (char*) &common_header->sdpcm_header )[ common_header->sdpcm_header.sw_header.header_length ];
    flags         = ltoh32( cdc_header->flags );

    retval = (wwd_result_t) ( WLAN_ENUM_OFFSET -  ltoh32( cdc_header->status ) );

    /* Check if the caller wants the response */
    if ( response_buffer_hnd != NULL )
    {
        *response_buffer_hnd = wwd_sdpcm_ioctl_response;
        host_buffer_add_remove_at_front( response_buffer_hnd,
                (int32_t) ( sizeof(wwd_buffer_header_t) + sizeof(sdpcm_cdc_header_t) +
                common_header->sdpcm_header.sw_header.header_length ));
    }
    else
    {
        host_buffer_release( wwd_sdpcm_ioctl_response, WWD_NETWORK_RX );
    }

    wwd_sdpcm_ioctl_response = NULL;

    /* Release the mutex since wwd_sdpcm_ioctl_response will no longer be referenced. */
    host_rtos_set_semaphore( &wwd_sdpcm_ioctl_mutex, WICED_FALSE );

    /* notify client that the firmware ioctl was sent. NOTE: done outside semaphore so client can re-enter. */
    wwd_sdpcm_notify_client_ioctl_finished( type, command, (const char*)iovar_name, interface );

    /* Check whether the IOCTL response indicates it failed. */
    if ( ( flags & CDCF_IOC_ERROR ) != 0)
    {
        if ( response_buffer_hnd != NULL )
        {
            host_buffer_release( *response_buffer_hnd, WWD_NETWORK_RX );
            *response_buffer_hnd = NULL;
        }
        wiced_minor_assert("IOCTL failed\n", 0 != 0 );
        return retval;
    }

    return WWD_SUCCESS;
}

/**
 * Registers locally a handler to receive event callbacks.
 * Does not notify Wi-Fi about event subscription change.
 * Can be used to refresh local callbacks (e.g. after deep-sleep)
 * if Wi-Fi is already notified about them.
 *
 * This function registers a callback handler to be notified when
 * a particular event is received.
 *
 * Alternately the function clears callbacks for given event type.
 *
 * @note : Currently each event may only be registered to one handler
 *         and there is a limit to the number of simultaneously registered
 *         events
 *
 * @param  event_nums     An array of event types that is to trigger the handler. The array must be terminated with a WLC_E_NONE event
 *                        See @ref wwd_event_num_t for available events
 * @param handler_func   A function pointer to the new handler callback,
 *                        or NULL if callbacks are to be disabled for the given event type
 * @param handler_user_data  A pointer value which will be passed to the event handler function
 *                            at the time an event is triggered (NULL is allowed)
 * @param interface      The interface to set the handler for.
 *
 * @return WWD result code
 */
wwd_result_t wwd_management_set_event_handler_locally( /*@keep@*/ const wwd_event_num_t* event_nums, /*@null@*/ wwd_event_handler_t handler_func, /*@null@*/ /*@keep@*/ void* handler_user_data, wwd_interface_t interface )
{
    uint16_t entry = (uint16_t) 0xFF;
    uint16_t i;

    UNUSED_PARAMETER( interface );

    /* Find an existing matching entry OR the next empty entry */
    for ( i = 0; i < (uint16_t) WWD_EVENT_HANDLER_LIST_SIZE; i++ )
    {
        /* Find a matching event list OR the first empty event entry */
        if ( wwd_sdpcm_event_list[i].events == event_nums )
        {
            /* Check if all the data already matches */
            if ( wwd_sdpcm_event_list[i].handler           == handler_func &&
                 wwd_sdpcm_event_list[i].handler_user_data == handler_user_data )
            {
                return WWD_SUCCESS;
            }

            /* Delete the entry */
            wwd_sdpcm_event_list[i].events = NULL;
            wwd_sdpcm_event_list[i].handler = NULL;
            wwd_sdpcm_event_list[i].handler_user_data = NULL;

            entry = i;
            break;
        }
        else if ( ( entry == (uint16_t) 0xFF ) && ( wwd_sdpcm_event_list[i].events == NULL ) )
        {
            entry = i;
        }
    }

    /* Check if handler function was provided */
    if ( handler_func != NULL )
    {
        /* Check if an empty entry was not found */
        if ( entry == (uint16_t) 0xFF )
        {
            WPRINT_WWD_DEBUG(("Out of space in event handlers table - try increasing WWD_EVENT_HANDLER_LIST_SIZE\n"));
            return WWD_OUT_OF_EVENT_HANDLER_SPACE;
        }

        /* Add the new handler in at the free space */
        wwd_sdpcm_event_list[entry].handler           = handler_func;
        wwd_sdpcm_event_list[entry].handler_user_data = handler_user_data;
        wwd_sdpcm_event_list[entry].events            = event_nums;
    }

    return WWD_SUCCESS;
}

/* allocates memory for the needed iovar and returns a pointer to the event mask */
static uint8_t* wwd_management_alloc_event_msgs_buffer( wiced_buffer_t *buffer, wwd_interface_t interface )
{
    uint16_t i;
    uint16_t j;
    wiced_bool_t use_extended_evt       = WICED_FALSE;
    uint32_t max_event                  = 0;
    eventmsgs_ext_t *eventmsgs_ext_data = NULL;
    uint32_t *data                      = NULL;
    /* Check to see if event that's set requires more than 128 bit */
    for ( i = 0; i < (uint16_t) WWD_EVENT_HANDLER_LIST_SIZE; i++ )
    {
        if ( wwd_sdpcm_event_list[i].events != NULL )
        {
            for ( j = 0; wwd_sdpcm_event_list[i].events[j] != WLC_E_NONE; j++ )
            {
                uint32_t event_value = wwd_sdpcm_event_list[i].events[j];
                if ( event_value > 127 )
                {
                    use_extended_evt = WICED_TRUE;
                    if ( event_value > max_event )
                    {
                        max_event = event_value;
                    }
                    /* keep going to get highest value */
                }
            }
        }
    }

    if ( WICED_FALSE == use_extended_evt )
    {
        /* use old iovar for backwards compat */
        data = (uint32_t*) wwd_sdpcm_get_iovar_buffer( buffer, (uint16_t) WL_EVENTING_MASK_LEN + 4, "bsscfg:" IOVAR_STR_EVENT_MSGS );

        if ( NULL == data )
        {
            return NULL;
        }

        data[0] = wwd_get_bss_index( interface );

        return (uint8_t*)&data[1];
    }
    else
    {
        uint8_t mask_len   = (uint8_t)( ( max_event + 8 )/8 );
        data = (uint32_t*) wwd_sdpcm_get_iovar_buffer( buffer, (uint16_t)(sizeof(eventmsgs_ext_t) + mask_len + 4), "bsscfg:" IOVAR_STR_EVENT_MSGS_EXT );

        if ( NULL == data )
        {
            return NULL;
        }

        data[0] = wwd_get_bss_index( interface );

        eventmsgs_ext_data = (eventmsgs_ext_t*)&data[1];

        memset( eventmsgs_ext_data, 0, sizeof(*eventmsgs_ext_data) );
        eventmsgs_ext_data->ver     = EVENTMSGS_VER;
        eventmsgs_ext_data->command = EVENTMSGS_SET_MASK;
        eventmsgs_ext_data->len     = mask_len;
        return eventmsgs_ext_data->mask;
    }
}


/**
 * Registers a handler to receive event callbacks.
 * Subscribe locally and notify Wi-Fi about subscription.
 *
 * This function registers a callback handler to be notified when
 * a particular event is received.
 *
 * Alternately the function clears callbacks for given event type.
 *
 * @note : Currently each event may only be registered to one handler
 *         and there is a limit to the number of simultaneously registered
 *         events
 *
 * @param  event_nums     An array of event types that is to trigger the handler. The array must be terminated with a WLC_E_NONE event
 *                        See @ref wwd_event_num_t for available events
 * @param handler_func   A function pointer to the new handler callback,
 *                        or NULL if callbacks are to be disabled for the given event type
 * @param handler_user_data  A pointer value which will be passed to the event handler function
 *                            at the time an event is triggered (NULL is allowed)
 * @param interface      The interface to set the handler for.
 *
 * @return WWD result code
 */
wwd_result_t wwd_management_set_event_handler( /*@keep@*/ const wwd_event_num_t* event_nums, /*@null@*/ wwd_event_handler_t handler_func, /*@null@*/ /*@keep@*/ void* handler_user_data, wwd_interface_t interface )
{
    wiced_buffer_t buffer;
    uint8_t* event_mask;
    uint16_t i;
    uint16_t j;
    wwd_result_t res;

    /* Acquire mutex preventing multiple threads accessing the handler at the same time */
    res = host_rtos_get_semaphore( &wwd_sdpcm_event_list_mutex, NEVER_TIMEOUT, WICED_FALSE );
    if ( res != WWD_SUCCESS )
    {
        return res;
    }

    /* Set event handler locally  */
    res = wwd_management_set_event_handler_locally( event_nums, handler_func, handler_user_data, interface );
    if ( res != WWD_SUCCESS )
    {
        goto set_event_handler_exit;
    }

    /* Send the new event mask value to the wifi chip */
    event_mask = wwd_management_alloc_event_msgs_buffer( &buffer, interface );

    if ( NULL == event_mask )
    {
        res = WWD_BUFFER_UNAVAILABLE_PERMANENT;
        goto set_event_handler_exit;
    }

    /* Keep the wlan awake while we set the event_msgs */
    WWD_WLAN_KEEP_AWAKE( );

    /* Set the event bits for each event from every handler */
    memset( event_mask, 0, (size_t) WL_EVENTING_MASK_LEN );
    for ( i = 0; i < (uint16_t) WWD_EVENT_HANDLER_LIST_SIZE; i++ )
    {
        if ( wwd_sdpcm_event_list[i].events != NULL )
        {
            for ( j = 0; wwd_sdpcm_event_list[i].events[j] != WLC_E_NONE; j++ )
            {
                setbit(event_mask, wwd_sdpcm_event_list[i].events[j]);
            }
        }
    }

    /* set the wwd_sdpcm_event_list_mutex from calling thread before sending iovar\r
     * as the RX thread also waits on this Mutex when an ASYNC Event received\r
     * causing deadlock
     */
    host_rtos_set_semaphore( &wwd_sdpcm_event_list_mutex, WICED_FALSE );

    res = wwd_sdpcm_send_iovar( SDPCM_SET, buffer, 0, WWD_STA_INTERFACE );

    /* The wlan chip can sleep from now on */
    WWD_WLAN_LET_SLEEP( );
    return res;

set_event_handler_exit:
    host_rtos_set_semaphore( &wwd_sdpcm_event_list_mutex, WICED_FALSE );
    return res;
}

/** A helper function to easily acquire and initialise a buffer destined for use as an iovar
 *
 * @param  buffer      : A pointer to a wiced_buffer_t object where the created buffer will be stored
 * @param  data_length : The length of space reserved for user data
 * @param  name        : The name of the iovar
 *
 * @return A pointer to the start of user data with data_length space available
 */
/*@null@*/ /*@exposed@*/ void* wwd_sdpcm_get_iovar_buffer( /*@special@*/ /*@out@*/ wiced_buffer_t* buffer, uint16_t data_length, const char* name )  /*@allocates *buffer@*/ /*@defines **buffer@*/
{
    uint32_t name_length = (uint32_t) strlen( name ) + 1; /* + 1 for terminating null */
    uint32_t name_length_alignment_offset = (64 - name_length) % sizeof(uint32_t);

    if ( internal_host_buffer_get( buffer, WWD_NETWORK_TX, (unsigned short) ( IOCTL_OFFSET + data_length + name_length + name_length_alignment_offset ), (unsigned long) WICED_IOCTL_PACKET_TIMEOUT ) == WWD_SUCCESS )
    {
        uint8_t* data = ( host_buffer_get_current_piece_data_pointer( *buffer ) + IOCTL_OFFSET );
        memset( data, 0, name_length_alignment_offset );
        memcpy( data + name_length_alignment_offset, name, name_length );
        return ( data + name_length + name_length_alignment_offset );
    }
    else
    {
        WPRINT_WWD_DEBUG(("Error - failed to allocate a packet buffer for IOVAR\n"));
        return NULL;
    }
}

/** A helper function to easily acquire and initialise a buffer destined for use as an ioctl
 *
 * @param  buffer      : A pointer to a wiced_buffer_t object where the created buffer will be stored
 * @param  data_length : The length of space reserved for user data
 *
 * @return A pointer to the start of user data with data_length space available
 */
/*@null@*/ /*@exposed@*/ void* wwd_sdpcm_get_ioctl_buffer( /*@special@*/ /*@out@*/ wiced_buffer_t* buffer, uint16_t data_length ) /*@allocates *buffer@*/  /*@defines **buffer@*/
{
    if ( internal_host_buffer_get( buffer, WWD_NETWORK_TX, (unsigned short) ( IOCTL_OFFSET + data_length ), (unsigned long) WICED_IOCTL_PACKET_TIMEOUT ) == WWD_SUCCESS )
    {
        return ( host_buffer_get_current_piece_data_pointer( *buffer ) + IOCTL_OFFSET );
    }
    else
    {
        WPRINT_WWD_DEBUG(("Error - failed to allocate a packet buffer for IOCTL\n"));
        return NULL;
    }
}

wiced_bool_t wwd_sdpcm_has_tx_packet( void )
{
    if ( wwd_sdpcm_send_queue_head != NULL )
    {
        return WICED_TRUE;
    }

    return WICED_FALSE;
}

wwd_result_t wwd_sdpcm_get_packet_to_send( /*@special@*/ /*@out@*/  wiced_buffer_t* buffer) /*@allocates *buffer@*/  /*@defines **buffer@*/
{
    sdpcm_common_header_t* packet;
    if ( wwd_sdpcm_send_queue_head != NULL )
    {
        /* Check if we're being flow controlled */
        if ( wwd_bus_is_flow_controlled() == WICED_TRUE )
        {
            WWD_STATS_INCREMENT_VARIABLE( flow_control );
            return WWD_FLOW_CONTROLLED;
        }

        /* Check if we have enough bus data credits spare */
        if ( wwd_sdpcm_packet_transmit_sequence_number == wwd_sdpcm_last_bus_data_credit )
        {
            WWD_STATS_INCREMENT_VARIABLE( no_credit );
            return WWD_NO_CREDITS;
        }

        /* There is a packet waiting to be sent - send it then fix up queue and release packet */
        if ( host_rtos_get_semaphore( &wwd_sdpcm_send_queue_mutex, NEVER_TIMEOUT, WICED_FALSE ) != WWD_SUCCESS )
        {
            /* Could not obtain mutex, push back the flow control semaphore */
            return WWD_SEMAPHORE_ERROR;
        }

        /* Pop the head off and set the new send_queue head */
        *buffer = wwd_sdpcm_send_queue_head;
        wwd_sdpcm_send_queue_head = wwd_sdpcm_get_next_buffer_in_queue( *buffer );
        if ( wwd_sdpcm_send_queue_head == NULL )
        {
            wwd_sdpcm_send_queue_tail = NULL;
        }
        host_rtos_set_semaphore( &wwd_sdpcm_send_queue_mutex, WICED_FALSE );

        /* Set the sequence number */
        packet = (sdpcm_common_header_t*) host_buffer_get_current_piece_data_pointer( *buffer );
        packet->sdpcm_header.sw_header.sequence = wwd_sdpcm_packet_transmit_sequence_number;
        wwd_sdpcm_packet_transmit_sequence_number++;

        return WWD_SUCCESS;
    }
    else
    {
        return WWD_NO_PACKET_TO_SEND;
    }
}


/** Returns the number of bus credits available
 *
 * @return The number of bus credits available
 */
uint8_t wwd_sdpcm_get_available_credits( void )
{
    return (uint8_t)( wwd_sdpcm_last_bus_data_credit - wwd_sdpcm_packet_transmit_sequence_number );
}


/** Sets a handler functions for monitor mode
 *
 * Packets received in monitor mode have raw 802.11 headers
 * and cannot be processed by the normal stack.
 * Use this function to set a handler function which will
 * process and free the raw packets received in monitor mode.
 *
 * @param func : function pointer to handler. Set to NULL to clear handler.
 *
 * @return result code
 */
wwd_result_t wwd_wifi_set_raw_packet_processor( wwd_wifi_raw_packet_processor_t function )
{
    wwd_sdpcm_raw_packet_processor = function;
    return WWD_SUCCESS;
}


uint32_t wwd_get_bss_index( wwd_interface_t interface )
{
    if ( interface <= WWD_P2P_INTERFACE )
    {
        return wwd_host_interface_to_bss_index_array[interface];
    }
    return 0;
}


/******************************************************
 *             Static Functions
 ******************************************************/

/** Writes SDPCM headers and sends packet to WWD Thread
 *
 *  Prepends the given packet with a new SDPCM header,
 *  then passes the packet to the WWD thread via a queue
 *
 *  This function is called by @ref wwd_network_send_ethernet_data and @ref wwd_sdpcm_send_ioctl
 *
 *  @param buffer     : The handle of the packet buffer to send
 *  @param header_type  : DATA_HEADER, ASYNCEVENT_HEADER or CONTROL_HEADER - indicating what type of SDPCM packet this is.
 */

static void wwd_sdpcm_send_common( /*@only@*/ wiced_buffer_t buffer, sdpcm_header_type_t header_type )
{
    uint16_t size;
    sdpcm_common_header_t* packet = (sdpcm_common_header_t *) host_buffer_get_current_piece_data_pointer( buffer );

    size = host_buffer_get_current_piece_size( buffer );

#ifdef SUPPORT_BUFFER_CHAINING
    wiced_buffer_t tmp_buff;
    while ( NULL != ( tmp_buff = host_buffer_get_next_piece( tmp_buff ) ) )
    {
        size += host_buffer_get_current_piece_size( tmp_buff );
    }
#endif /* ifdef SUPPORT_BUFFER_CHAINING */

    size = (uint16_t) ( size - (uint16_t) sizeof(wwd_buffer_header_t) );

    /* Prepare the SDPCM header */
    memset( (uint8_t*) &packet->sdpcm_header, 0, sizeof(sdpcm_header_t) );
    packet->sdpcm_header.sw_header.channel_and_flags = (uint8_t) header_type;
    packet->sdpcm_header.sw_header.header_length = ( header_type == DATA_HEADER ) ? sizeof(sdpcm_header_t) + 2 : sizeof(sdpcm_header_t);
    packet->sdpcm_header.sw_header.sequence = 0; /* Note: The real sequence will be written later */
    packet->sdpcm_header.frametag[0] = size;
    packet->sdpcm_header.frametag[1] = (uint16_t) ~size;

    add_sdpcm_log_entry( LOG_TX, ( header_type == DATA_HEADER )? DATA : ( header_type == CONTROL_HEADER)? IOCTL : EVENT, host_buffer_get_current_piece_size( buffer ), (char*) host_buffer_get_current_piece_data_pointer( buffer ) );

    /* Add the length of the SDPCM header and pass "down" */
    if ( host_rtos_get_semaphore( &wwd_sdpcm_send_queue_mutex, NEVER_TIMEOUT, WICED_FALSE ) != WWD_SUCCESS )
    {
        /* Could not obtain mutex */
        /* Fatal error */
        host_buffer_release(buffer, WWD_NETWORK_TX);
        return;
    }

    wwd_sdpcm_set_next_buffer_in_queue( NULL, buffer );
    if ( wwd_sdpcm_send_queue_tail != NULL )
    {
        wwd_sdpcm_set_next_buffer_in_queue( buffer, wwd_sdpcm_send_queue_tail );
    }
    wwd_sdpcm_send_queue_tail = buffer;
    if ( wwd_sdpcm_send_queue_head == NULL )
    {
        wwd_sdpcm_send_queue_head = buffer;
    }
    host_rtos_set_semaphore( &wwd_sdpcm_send_queue_mutex, WICED_FALSE );

    wwd_thread_notify();
}


static wiced_buffer_t wwd_sdpcm_get_next_buffer_in_queue( wiced_buffer_t buffer )
{
    wwd_buffer_header_t* packet = (wwd_buffer_header_t*) host_buffer_get_current_piece_data_pointer( buffer );
    return packet->queue_next;
}


/** Sets the next buffer in the send queue
 *
 *  The send queue is a linked list of packet buffers where the 'next' pointer
 *  is stored in the first 4 bytes of the buffer content.
 *  This function sets that pointer.
 *
 * @param buffer       : handle of packet in the send queue
 *        prev_buffer  : handle of new packet whose 'next' pointer will point to 'buffer'
 */
static void wwd_sdpcm_set_next_buffer_in_queue( wiced_buffer_t buffer, wiced_buffer_t prev_buffer )
{
    wwd_buffer_header_t* packet = (wwd_buffer_header_t*) host_buffer_get_current_piece_data_pointer( prev_buffer );
    packet->queue_next = buffer;
}


/** Map a DSCP value from an IP header to a WMM QoS priority
 *
 * @param dscp_val : DSCP value from IP header
 *
 * @return wmm_qos : WMM priority
 *
 */
static const uint8_t dscp_to_wmm_qos[] =
                                    { 0, 0, 0, 0, 0, 0, 0, 0,   /* 0  - 7 */
                                      1, 1, 1, 1, 1, 1, 1,      /* 8  - 14 */
                                      1, 1, 1, 1, 1, 1, 1,      /* 15 - 21 */
                                      1, 1, 0, 0, 0, 0, 0,      /* 22 - 28 */
                                      0, 0, 0, 5, 5, 5, 5,      /* 29 - 35 */
                                      5, 5, 5, 5, 5, 5, 5,      /* 36 - 42 */
                                      5, 5, 5, 5, 5, 7, 7,      /* 43 - 49 */
                                      7, 7, 7, 7, 7, 7, 7,      /* 50 - 56 */
                                      7, 7, 7, 7, 7, 7, 7,      /* 57 - 63 */
                                    };

static uint8_t wwd_map_dscp_to_priority( uint8_t val )
{
    uint8_t dscp_val = (uint8_t)(val >> 2); /* DSCP field is the high 6 bits of the second byte of an IPv4 header */

    return dscp_to_wmm_qos[dscp_val];
}

static wwd_interface_t wwd_wifi_get_source_interface( uint8_t flags2 )
{
    uint32_t bssid_index = (uint32_t)(flags2 & BDC_FLAG2_IF_MASK);

    if ( bssid_index <= WWD_P2P_INTERFACE )
    {
        return wwd_bss_index_to_host_interface_array[bssid_index];
    }
    return WWD_STA_INTERFACE;
}

void wwd_update_host_interface_to_bss_index_mapping( wwd_interface_t interface, uint32_t bss_index )
{
    wwd_host_interface_to_bss_index_array[interface] = bss_index;
    wwd_bss_index_to_host_interface_array[bss_index] = interface;
}