//
// Embedded 802.11b wireless network interface
// Wireless API functions
// Copyright 2004 Digi International
//
#include <bele.h>
#include "mac.h"
#include "wpa_api.h"
#include "nastatus.h"
#include "bsp_api.h"

extern chan_counts_t recvCounts[];

#if     !BSP_WIFI_MULTIPLE_MAC
// Table of callback functions for each protocol type
ep_types types[MAX_TYPES];
#endif

int monitorMode = 0;

#if     !BSP_WIFI_MULTIPLE_MAC
static event_cb event_cbs[MAX_EVENTS];
#endif

// Validate a parameter
#define VALIDATE(code, expr) \
        if (!(expr)) { ret = (code); goto error; }

typedef struct chan_info_t
{
    int chan_num;
    int chan_freq;
}chan_info_t;


/*
 * This table holds a list of disabled channels.  A timeout is set when
 * radar is detected on the indicated channel.
 */
typedef struct
{
    ULONG overflowCorrection;           /* used to deal with cases where the timeout wraps the counter*/
    ULONG timeout;                      /* time at which we detect radar + the nonoccupancy period */
} naWifiIsChannelDisabledList_t;

static naWifiIsChannelDisabledList_t naWifiIsChannelDisabledList[NA_WIFI_MAX_CHANNEL_INDEX];

static chan_info_t channelFromChannelIndex[NA_WIFI_MAX_CHANNEL_INDEX] =
{    
        {0,  0},        // 0, search
    
    /* 802.11b/g channels */
    {1,  2412},     // 1, B-1
    {2,  2417},     // 2, B-2
    {3,  2422},     // 3, B-3
    {4,  2427},     // 4, B-4
    {5,  2432},     // 5, B-5
    {6,  2437},     // 6, B-6
    {7,  2442},     // 7, B-7
    {8,  2447},     // 8, B-8
    {9,  2452},     // 9, B-9
    {10, 2457},     // 10, B-10
    {11, 2462},     // 11, B-11
    {12, 2467},     // 12, B-12
    {13, 2472},     // 13, B-13
    {14, 2484},     // 14, B-14
        
    {0, 0},         // 15, reserved for future extension
    {0, 0},         // 16, reserved for future extension
        
    /* 802.11a channels */
    {184, 4920},    // 17, L-184 
    {188, 4940},    // 18, L-188
    {192, 4960},    // 19, L-192
    {196, 4980},        // 20, L-196
    
        {8,  5040},     // 21, A-8 
    {12, 5060},     // 22, A-12 
    {16, 5080},     // 23, A-16
    {34, 5170},     // 24, A-34
    {36, 5180},     // 25, A-36
    {38, 5190},     // 26, A-38
    {40, 5200},     // 27, A-40
    {42, 5210},     // 28, A-42
    {44, 5220},     // 29, A-44
    {46, 5230},     // 30, A-46
    {48, 5240},     // 31, A-48
    {52, 5260},     // 32, A-52
    {56, 5280},     // 33, A-56
    {60, 5300},     // 34, A-60
    {64, 5320},     // 35, A-61 
        
    {100, 5500},    // 36, A-100
    {104, 5520},    // 37, A-104
    {108, 5540},    // 38, A-108
    {112, 5560},    // 39, A-112
    {116, 5580},    // 40, A-116
    {120, 5600},    // 41, A-120 
    {124, 5620},    // 42, A-124
    {128, 5640},    // 43, A-128
    {132, 5660},    // 44, A-132
    {136, 5680},    // 45, A-136
    {140, 5700},    // 46, A-140        
        
    {149, 5745},    // 47, A-149
    {153, 5765},    // 48, A-153
    {157, 5785},    // 49, A-157
    {161, 5805},    // 50, A-161
    {165, 5825}     // 51, A-165 
};


// Set wireless parameters
// Must be called before starting wireless interface
// @param params parameters to set
// @return 0 if successful, negative if error
int wln_set_params (wln_params *p)
{
#define DFS_CHANNEL_MASK_LOW            (0x80000000)
#define DFS_CHANNEL_MASK_HIGH           (0x00003fff)

        wln_error ret;
        
        // Validate parameters
        VALIDATE (WLN_ERR_NULL,    p != NULL);
        VALIDATE (WLN_ERR_BSSTYPE, (int) p->bss_type >= WLN_BSS_ESS && p->bss_type <= WLN_BSS_WPS);
        VALIDATE (WLN_ERR_SSID,    p->ssid_len >= 0 && p->ssid_len <= WLN_SSID_SIZE);
        VALIDATE (WLN_ERR_CHANNEL, p->channel >= WLN_CHAN_SCAN && p->channel <= WLN_CHAN_NUM);
        VALIDATE (WLN_ERR_STAADDR, p->sta_addr[0] | p->sta_addr[1] | p->sta_addr[2] |
                                                           p->sta_addr[3] | p->sta_addr[4] | p->sta_addr[5]);
        if (   (p->options & WLN_OPT_SPECTRUM_MGMT)
            && ((p->bss_type == WLN_BSS_IBSS) || (p->bss_type == APP_WLN_BSS_IBSS_JOIN)))
        {
            /*
             * Don't allow IBSS if spectrum management is turned on.
             */
            ret = WLN_ERR_BSSTYPE;
            goto error;
        }

        // Shared key authentication, or WEP encryption without 802.1X
        if ((p->authen & WLN_AUTH_SHAREDKEY) ||
                ((p->encrypt & WLN_ENCR_WEP) && (p->authen & WLN_AUTH_OPEN)))
        {
                VALIDATE (WLN_ERR_WEPID,  p->wep_key_id >= 0 && p->wep_key_id < WLN_KEY_NUM);
                VALIDATE (WLN_ERR_WEPKEY, p->wep_key_len == 0 || 
                        p->wep_key_len == WLN_WEP40_SIZE || p->wep_key_len == WLN_WEP104_SIZE);
        }
        
#ifndef AES_ENABLED
        VALIDATE (WLN_ERR_ENCR,  p->encrypt != WLN_ENCR_CCMP);
#endif

        VALIDATE (WLN_ERR_POWER, p->power_mode >= WLN_PWR_MAX && p->power_mode <= WLN_PWR_MIN);
        VALIDATE (WLN_ERR_TXPOWER, p->tx_power >= WLN_TXPOWER_MIN && p->tx_power <= WLN_TXPOWER_MAX);
        
        // Set default values
        if (p->authen == 0)
                p->authen = WLN_AUTH_OPEN;
        if (p->encrypt == 0)
                p->encrypt = WLN_ENCR_ANY;
        if (p->eap_methods == 0)
                p->eap_methods = WLN_EAP_ANY;
        if (p->chan_mask[0] == 0)
    {
        if ((p->band == WLN_BAND_B) || (p->band == WLN_BAND_BG))
        {
            p->chan_mask[0] = WLN_CHANMASK_ALL & 0x3FFF;
            p->chan_mask[1] = 0;
        }
        else if (p->band == WLN_BAND_A)
        {
            p->chan_mask[0] = WLN_CHANMASK_ALL & 0xFFFFC000;
        }
        else
        {
            p->chan_mask[0] = WLN_CHANMASK_ALL;
            p->chan_mask[1] = WLN_CHANMASK_ALL_HIGH;
        }
    }
        if (p->tx_rate <= 0)
                p->tx_rate = 540;
        if (p->rts_thresh <= 0)
                p->rts_thresh = 2347;
                
        if (p->frag_thresh <= 0 || p->frag_thresh > DATA_SIZE+BUF_SIZE)
                p->frag_thresh = DATA_SIZE+BUF_SIZE;
#ifdef BSP_ARM9
	else if (p->frag_thresh < 256)
		p->frag_thresh = 256;
#else
    else if (p->frag_thresh < 400)
		p->frag_thresh = 400;
#endif
        p->frag_thresh &= ~1; // make even
        
#ifndef AES_ENABLED
        p->encrypt &= ~WLN_ENCR_CCMP;
#endif

    // Store params
    memcpy (&macParams, p, sizeof macParams);

    if (rootEndPoint[0] != NULL)
    {
        memcpy(rootEndPoint[0]->staAddr, macParams.sta_addr, sizeof(rootEndPoint[0]->staAddr));
    }
    else
    {
        return WLN_ERR_NULL;
    }
        

    memset(&naDot11, 0, sizeof(naDot11));
    /*
     * Set dot11SpectrumManagementRequired to value stored in NVRAM at first.  We may
     * update it if we receive beacons with this option set.
     */
    naDot11.spectrumManagementRequired = ((macParams.options & WLN_OPT_SPECTRUM_MGMT) != 0);

    if (naDot11.spectrumManagementRequired)
    {
        if (NABspTicksPerSecond < 1000)
        {
            /*
             * Sorry, but we need at least millisecond precision with the system timer if
             * we are going to try to support 802.11h.
             */
            panic("The system timer must be set to at least 1000 ticks per second to support 802.11h spectrum management.");
        }
#if WANT_TO_FORCE_DFS_ON
        macParams.chan_mask[0] |= DFS_CHANNEL_MASK_LOW;
        macParams.chan_mask[1] |= DFS_CHANNEL_MASK_HIGH;
#endif
        if (macParams.bss_type == APP_WLN_BSS_ANY)
        {
            /*
             * If spectrum management is turned on, then force ANY to be
             * ESS only because we don't support ad hoc mode when spectrum
             * management is turned on.
             */
            macParams.bss_type = APP_WLN_BSS_ESS;
        }

    }
	wln_params_set = TRUE;
	
	memset(naWifiIsChannelDisabledList, 0, sizeof(naWifiIsChannelDisabledList));
	
	return WLN_SUCCESS;
	
error:
        DPRINTF ("wln_set_params error: %d\n", ret);
        return ret;
}

unsigned char *wln_get_mac_addr(void * drv_info, void * usr_context)
{
        epStatus *pSta = (epStatus *)drv_info;

    NA_UNUSED_PARAM(usr_context);

//      if ((pSta = (epStatus *) wpa_get_usr_ptr(context)) == NULL)
//              return NULL;
    if (pSta == NULL) return NULL;
    
        /* this better not be on the stack */
        return pSta->staAddr;
}

// Get wireless parameters
// @param params area to store parameters
int wln_get_params (wln_params *params)
{
        memcpy (params, &macParams, sizeof macParams);
        return 0;
}

// Get status information
// @param params area to store data
static int internal_wln_get_status (epStatus *pSta, wln_status * status)
{
        wln_status *wifiStatus;

        wifiStatus = &pSta->macStatus;

        if (pSta->macStatus.state >= WLN_ST_SCANNING)
    {
            memcpy (status, &pSta->macStatus, sizeof pSta->macStatus);
            // Scale signal strength average by number of samples
            status->rx_signal =
                                (pSta->macStatus.rx_signal + RSSI_SCALE/2) / RSSI_SCALE;
        }
        else
        {
            memset (status, 0, sizeof pSta->macStatus);
            status->state = pSta->macStatus.state;
        }
        
        return 0;
}


int wln_get_status_multi_mac (void * drv_info, wln_status * status)
{
    epStatus *pSta = (epStatus *)drv_info;
    
    if (pSta == NULL)
                return -1;

    return internal_wln_get_status (pSta, status);
}

int wln_get_status (wln_status * status)
{
    epStatus *pSta = rootEndPoint[0];
    
    if (pSta == NULL)
                return -1;

    return internal_wln_get_status (pSta, status);
}

int wln_cli_get_status (unsigned epIndex, wln_status * status)
{
    if (epIndex >= WLN_MAX_ADDRS)
    {
        return -1;
    }

    return internal_wln_get_status (rootEndPoint[epIndex], status);
}



// Get statistics counters
// @param stats area to store data
// @param size maximum size of data in bytes
// @return size of data available
int wln_get_stats (wln_stats *stats, int size)
{
        if (size > (int) sizeof macStats)
                size = sizeof macStats;
                
        memcpy (stats, &macStats, size);
        return sizeof macStats;
}

// Set callback function for received packets
// One callback can be set for each protocol type
// Protocol types without a callback are sent to the OS network stack.
// @param func callback function
// @param ep_type ethernet protocol type
// @param context value passed to callback function

int wln_set_receive_cb (void * drv_info, int ep_type, wln_receive_cb func, void * usr_context)
{
        int i;
        epStatus *pSta = (epStatus *)drv_info;

//      if ((pSta = (epStatus *) wpa_get_usr_ptr(context)) == NULL)
    if (pSta == NULL)
                return -1;
        
        GET_MUTEX();
        for (i = 0; i < MAX_TYPES; i++)
        {
                // Entry unused or matches protocol
                if (pSta->types[i].ep_type == 0 || pSta->types[i].func == NULL ||
                        pSta->types[i].ep_type == ep_type)
                {
                        pSta->types[i].ep_type = ep_type;
                        pSta->types[i].func = func;
                        pSta->types[i].context = usr_context;
                        if (pSta->types[i].ep_type == WLN_MONITOR_TYPE)
                        {
                            if (func)
                            {
                                SetPromiscuousMode(TRUE);
                                monitorMode = 1;
                            }
                            else
                            {
                                SetPromiscuousMode(FALSE);
                                monitorMode = 0;
                            }
                        }
                        
                        PUT_MUTEX();
                        return 0;
                }
        }

    PUT_MUTEX();
        return -1;
}

// Internal function to handle received packet
// @return TRUE if packet sent to receive callback function.
#if     BSP_WIFI_MULTIPLE_MAC
BOOL wln_receive (epStatus *pSta, uint8 *data, int len)
{
#else
BOOL wln_receive (uint8 *data, int len)
{
    epStatus *pSta = rootEndPoint[0];
#endif
        // Data starts with ethernet header
        ether_hdr *eth = (ether_hdr *) data;
        int i;
        
        for (i = 0; i < MAX_TYPES; i++)
                if (pSta->types[i].ep_type == FROM_BE16(eth->eptype) && pSta->types[i].func != NULL)
                {
                        pSta->types[i].func (pSta->types[i].context, eth->src, eth->data, len-sizeof(ether_hdr));
                        return TRUE;
                }
                
        return FALSE;
}

// Set event callback function
// @param func callback function
// @param context value passed to callback function
int wln_set_event_cb (void * drv_info, wln_event_cb func, void * usr_context)
{
        int i;
        epStatus *pSta = (epStatus *)drv_info;

//      if ((pSta = (epStatus *) wpa_get_usr_ptr(context)) == NULL)
    if (pSta == NULL)
                return -1;

        for (i = 0; i < MAX_EVENTS; i++)
        {
                // Entry unused or matches function
                if (pSta->event_cbs[i].func == NULL || pSta->event_cbs[i].func == func)
                {
                        pSta->event_cbs[i].func = func;
                        pSta->event_cbs[i].context = usr_context;
                        return 0;
                }
        }

        return -1;
}

// Remove event callback function
// @param func callback function
int wln_remove_event_cb (void * drv_info, void * usr_context, wln_event_cb func)
{
        int i;
        epStatus *pSta = (epStatus *)drv_info;

    NA_UNUSED_PARAM(usr_context);

//      if ((pSta = (epStatus *) wpa_get_usr_ptr(context)) == NULL)
    if (pSta == NULL)
                return -1;

        for (i = 0; i < MAX_EVENTS; i++)
                if (pSta->event_cbs[i].func == func)
                        pSta->event_cbs[i].func = NULL;

        return 0;
}

static char *textWlnEvent[] = 
{
        "WLN_EVT_ASSOC",
        "WLN_EVT_DISASSOC",
        "WLN_EVT_MIC_FAIL",
        "WLN_EVT_JOIN",
        "WLN_EVT_SCAN_RESULTS"
};

// Internal function to handle events
// @return TRUE if event sent to event callback function.
BOOL wln_send_event (epStatus *pSta, wln_event event, void *param)
{
        BOOL sent = FALSE;
        
        int i;

    (void) textWlnEvent[0];     /* suppress compiler warning */
        XPRINTF(TRACE_INFO, "wln_send_event %d event %s param 0x%x\n\r",
                                        pSta->entryNdx, textWlnEvent[event], (unsigned) param);

        for (i = 0; i < MAX_EVENTS; i++)
                if (pSta->event_cbs[i].func)
                {
                        pSta->event_cbs[i].func (pSta->event_cbs[i].context, event, param);
                        sent = TRUE;;
                }
                
        return sent;
}


#ifdef BSP_ETH_WIFI_BRIDGE
#if BSP_ETH_WIFI_BRIDGE
/*
 * Called from bridge to register a (secondary) mac address.
 * We find a space in the root pointer array, allocate the status block,
 * register with wpa, and set the mac assist hardware configuration 
 * registers.
 */

int wln_register_mac_address(mac_addr pAddr)
{
        int ndx;
        epStatus *pSta;
                    
        /* find a spot in the root pointer array */
        for (ndx = 0; ndx < WLN_MAX_ADDRS; ndx++)
        {
                if (rootEndPoint[ndx] == NULL)
                        break;
        }
        if (ndx == WLN_MAX_ADDRS)
        {
            /*
             * If we come here, then we are trying to add more MAC addresses
             * than the wireless MAC H/W can support.  The multiple MAC address
             * feature is only used for Ethernet to wireless bridging.  If we get
             * this error, then the customer has enabled bridging, but placed us
             * on an Ethernet network with more than one Ethernet device on it.
             * We do not, and cannot support this.  Call panic so that this error
             * is seen during development, hopefully before end users see it.
             */
            panic("Can only bridge one Ethernet device at a time.");
                return -1;      /* no room at the inn */
    }
        /* use zalloc */
        if ((pSta = (epStatus *) malloc(sizeof (epStatus))) == NULL)
                return -1;

        memset(pSta, 0, sizeof (epStatus));
        rootEndPoint[ndx] = pSta;
        pSta->entryNdx = ndx;
        pSta->root = pSta;
        memcpy(pSta->staAddr, pAddr, WLN_ADDR_SIZE);
        MacSecondaryInit(pSta);
    naWlnmacInitializeCiphers(pSta);

        /* tell the wpa supplicant about this endpoint */
        wpa_supplicant_secondary_add("wln0", (void *) pSta);
    
        /* types and event_cbs must be set up via esm_set_event_cb and
         * esm_set_receive_cb */

        /* call into the wifi driver to set up the mac assist hardware */
        HWSetFilterAddress(pAddr);

        XPRINTF(TRACE_DEBUG, "register mac "WLN_MACSTR" pSta %x\n\r",
                                        WLN_MAC2STR(pAddr), (int) pSta);
        /* if the primary address is already established, we can't rely on
         * the LinkUp action, so we have to initiate association now. */

        MacCheckPrimaryLink(pSta);

        return 0;
}
#endif
#endif

#ifdef DEBUG
void assert_fail (char *expr, char *file, int line)
{
        DPRINTF ("ASSERT failed: %s at %s:%d\n", expr, file, line);
}
#endif

int wlnGetMACAddr(char *phyAddr)
{
        /* wireless params must be initialized */
        if (!wln_params_set)
                return -1;

        SET_ADDR (phyAddr, macParams.sta_addr);
    return NASTATUS_SUCCESS;
}

int wlnAddMacAddress(MacAddr staAddr)
{
        /* call the hardware specific function */
        return HWSetFilterAddress(staAddr);
}

int wln_get_recv_counts(int channel, int chanCount, char *buf)
{
        int ndx, jdx, *pd = (int *) buf;
        chan_counts_t *pChan;

        for (ndx = channel; ndx < (channel + chanCount); ndx++)
        {
                pChan = &recvCounts[ndx];

                for (jdx = 0; jdx < WLN_REC_COUNT; jdx++)
                        *pd++ = pChan->byType[jdx];
        }
        return 0;
}

/*
 * This function can be used to get the channel number and the frequency for a
 * given channel index. Function returns channel number for a given channel 
 * index. If the chan_freq is NULL then only channel number will be returned
 * otherwise frequncy used for that channel will be updated in chan_freq 
 * field.
 *
 */
int wln_get_chan_info(int chan_index, int *chan_freq)
{
    int channel = 0;
        
    if ((chan_index < 0) || (chan_index > WLN_CHAN_NUM))
    {
        if (chan_freq != NULL)
            *chan_freq = 0;
    }
    else
    {
        if (chan_freq != NULL)
            *chan_freq = channelFromChannelIndex[chan_index].chan_freq;
        channel = channelFromChannelIndex[chan_index].chan_num;
    }

    return channel;    
}



/*
 * This function is used to disable a channel when radar has been detected
 * on it.  Calling this function will cause wln_is_channel_supported() to
 * return -1 when this channel is specified.
 *
 * chan_indx    index of channel to disable
 */
void wln_disable_channel(int chan_indx)
{
/*
 * The minimum nonoccupancy period is 30 minutes.  I put 31 here because
 * our system timer is a little inaccurate and I wanted to provide a margin
 * for error.
 */
#define MAX_BG_BAND_CHANNEL_INDEX      (16)
    int chan_num = wln_get_chan_info(chan_indx, NULL);
#define NA_WIFI_MIN_NONOCCUPANCY_PERIOD     (31*60*NABspTicksPerSecond)
    int indx = wln_is_channel_supported(macParams.chan_mask, 
                                        chan_num, chan_indx > MAX_BG_BAND_CHANNEL_INDEX);
    
    if (indx != -1)
    {
        ULONG currentTime = tx_time_get();
        
        naWifiIsChannelDisabledList[indx].timeout = currentTime + NA_WIFI_MIN_NONOCCUPANCY_PERIOD;
        if (naWifiIsChannelDisabledList[indx].timeout < currentTime)
        {
            naWifiIsChannelDisabledList[indx].overflowCorrection = ~0 - currentTime;
        }
        else
        {
            naWifiIsChannelDisabledList[indx].overflowCorrection = 0;
        }
    }
}

/*
 * This routine returns TRUE if a channel whose index is passed to us was 
 * disabled due to radar, and the nonoccupancy period has not yet expired.
 */
int naWifiIsChannelDisabled(int indx)
{
    int result = FALSE;     /* assume channel is NOT disabled */
    
    if ((indx >= 0) && (naWifiIsChannelDisabledList[indx].timeout))
    {
        /*
         * If a timeout has been set, then the channel was disabled sometime in the past.
         */
        if (   (naWifiIsChannelDisabledList[indx].timeout + naWifiIsChannelDisabledList[indx].overflowCorrection)
             <= (tx_time_get() + naWifiIsChannelDisabledList[indx].overflowCorrection))
        {
            /*
             * If we are past the timeout, then clear the channel entry in the array.
             */
            naWifiIsChannelDisabledList[indx].timeout = 0;
            naWifiIsChannelDisabledList[indx].overflowCorrection = 0;
        }
        else
        {
            /*
             * The timeout has not expired, so the channel is still disabled.
             */
            result = TRUE;
        }
    }
    
    return result;
}



/*
 * API returns the channel index for a given channel number if that channel is
 * supported. Otherwise the it returns -1. The flag chan_lla is set when the
 * user wants the channel index for 802.11a channels which are also present in
 * 802.11 b/g (like channel number 8 and 12). Channel number 0 is used for
 * channel scan so the API returns 0 if 0 is passed as chan_num.
 */
int wln_is_channel_supported (uint32 * mask, int chan_num, BOOL chan_11a)
{
    int chan_indx = chan_11a ? 17:1;
    int result = -1;
    
    if (chan_num == 0)
        return 0;
        
    if (mask == NULL)
        return result;
            
    do
    {
        if (channelFromChannelIndex[chan_indx].chan_num == chan_num)
        {
            if (mask[(chan_indx-1)/32] & (1 << (chan_indx-1)%32))
            {
                result = chan_indx;
                break;
            }
            break;
        }
    } while (++chan_indx <= WLN_CHAN_NUM);        
    
    if (naWifiIsChannelDisabled(result))
    {
        result = -1;
    }
    
    return result;
}

