/*
 *  Copyright (c) 1996-2008 Digi International Inc., All Rights Reserved
 *
 *  This software contains proprietary and confidential information of Digi
 *  International Inc.  By accepting transfer of this copy, Recipient agrees
 *  to retain this software in confidence, to prevent disclosure to others,
 *  and to make no use of this software other than that for which it was
 *  delivered.  This is an unpublished copyrighted work of Digi International
 *  Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 *  prohibited.
 *
 *  Restricted Rights Legend
 *
 *  Use, duplication, or disclosure by the Government is subject to
 *  restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 *  Technical Data and Computer Software clause at DFARS 252.227-7031 or
 *  subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 *  Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 *  Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 *  Power management function, which duty cycles the power based on the user
 *  settings. This uses 802.11 powersave feature to control the data flow.     
 */

#include "mac.h"
#include "mac_api.h"
#include "tx_api.h"
#include "bsp_api.h"
#include "bsptimer.h"
#include <errno.h>

#ifdef MAC_PS_ENABLED

#define WLN_PS_STACK        8192
#define WLN_PS_ACK_WAIT     50   // max ack wait time in msec

TX_EVENT_FLAGS_GROUP wln_ps_event_flags;
wln_psmode_state_t wln_ps_mode = WLN_PSMODE_DISABLED;
MacBuffer *wlnNullFramePtr = NULL;
BOOL wlnClearTxFrame = FALSE;

static wln_duty_cycle wlnDutyCycle;    
static int wln_ps_intf_idx = 0;
static MacBuffer nullFrame; // Pre-initialized NULL frame for duty cycle

//
// Prepare a null frame. For now send only from the root ep
//
MacBuffer * wln_make_null_frame (BOOL psave)
{
    epStatus *pSta;
    MacBuffer *buf = NULL;

    pSta = rootEndPoint[wln_ps_intf_idx];
    if (pSta == NULL)
        return buf;
    
    buf = &nullFrame;
    buf->macHdr.fc.retry = 0;        
    SET_ADDR (buf->macHdr.addr2, pSta->staAddr);
    SET_ADDR (buf->macHdr.addr1, pSta->macStatus.bss_addr);
    SET_ADDR (buf->macHdr.addr3, pSta->macStatus.bss_addr);    
    buf->macHdr.fc.pwrMgt = psave;  // set powersave bit
    buf->macHdr.fc.moreFrag = 0;
    SetPLCP (buf, rateTx);
    buf->macHdr.duration = HTON16(GetAckDuration(rateTx));                  

    return buf;
}

/*
 * thread to handle powersave mode. always waiting on an one of the following
 * events to occur
 * 1. Timer expires
 * 2. Beacon received
 * 3. ACK of null packet, which carried powersave bit on, recived
 */
static void wln_ps_task (ULONG unused)
{
    ULONG next_event_time = 0, flags=0, req_flags = 0;
	epStatus *pSta = rootEndPoint[0];
    int i;
    
	NA_UNUSED_PARAM(unused);

    memset (&nullFrame, 0, sizeof nullFrame);
    nullFrame.length = DATA_SIZE;
    nullFrame.macHdr.fc.type   = TYPE_NULL_DATA;
    nullFrame.macHdr.fc.toDS   = TRUE;    
    wlnNullFramePtr = &nullFrame;

    // wait if first interface is not configured
    while ((pSta == NULL) || (pSta->staState != SS_ASSOC))
    {
        tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(30));
        pSta = rootEndPoint[0];
    }
     
    /* not for adhoc mode */
    if (pSta->macStatus.bss_caps & CAP_IBSS)
        return;

    do
    {
        for (i = 0; i < WLN_MAX_ADDRS; i++)
        {
            pSta = rootEndPoint[i];

            if (pSta == NULL)
                continue; 

            /* don't do duty cycle until all initial setups are complete */ 
            while ((pSta->staState != SS_ASSOC) || 
                   ((pSta->macStatus.state != WLN_ST_AUTH_ESS) && 
                    (pSta->macStatus.state != WLN_ST_ASSOC_ESS)))
            {
                tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(10));
                next_event_time = 0;
            }
        }
        
        tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(20));
    } while (next_event_time++ <  100);

    wln_ps_mode = WLN_PSMODE_AWAKE;            
    next_event_time = wlnDutyCycle.ontime;

    while (1)
    {
        // wait until above leyer enables this
        if (wln_ps_mode == WLN_PSMODE_DISABLED)
        {
            tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(50));
            continue;
        }

        /* don't enter sleep mode, if any of the interface is waiting to 
           associate */
        if (wln_ps_mode == WLN_PSMODE_AWAKE)
        {        
            for (i = 0; i < WLN_MAX_ADDRS; i++)
            {
                pSta = rootEndPoint[i];

                if (pSta == NULL)
                    continue; 

                /* don't do duty cycle until all initial setups are complete */ 
                while ((pSta->staState != SS_ASSOC) || 
                       ((pSta->macStatus.state != WLN_ST_AUTH_ESS) && 
                        (pSta->macStatus.state != WLN_ST_ASSOC_ESS)))
                {
                    tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(10));
                }
            }
        }

        if (wln_ps_intf_idx >= WLN_MAX_ADDRS)
            wln_ps_intf_idx = 0; 
                
        pSta = rootEndPoint[wln_ps_intf_idx];
        
        // Wait for an event
        tx_event_flags_get(&wln_ps_event_flags, req_flags, TX_OR_CLEAR, 
                           &flags, NS_MILLISECONDS_TO_TICKS(next_event_time));
        switch (wln_ps_mode)
        {
            case WLN_PSMODE_DISABLED:
                next_event_time = wlnDutyCycle.ontime;
                break;
                
            case WLN_PSMODE_AWAKE: 
                if ((pSta != NULL) && (pSta->staState == SS_ASSOC))
                {
                    wln_ps_mode     = WLN_PSMODE_NF_SET;                        
                    next_event_time = WLN_PS_ACK_WAIT;
                    req_flags      |= WLN_PSEVENT_ACK;
                    wlnClearTxFrame = TRUE;                    
                    SendQueue();
                }
                else
                {
                    wln_ps_mode     = WLN_PSMODE_NF_SET_ACK;                        
                    next_event_time = 0;
                }
                break;
                
            case WLN_PSMODE_NF_SET:
            case WLN_PSMODE_NF_SET_ACK:
                req_flags      &= ~WLN_PSEVENT_ACK;
                
                // try to send NF from all interfaces 
                while (++wln_ps_intf_idx < WLN_MAX_ADDRS)
                {
                    if (rootEndPoint[wln_ps_intf_idx] != NULL)
                    {
                        wln_ps_mode     = WLN_PSMODE_AWAKE;
                        next_event_time = 0;
                        break;
                    }
                }

                if (wln_ps_intf_idx < WLN_MAX_ADDRS)
                    break; 
                else
                    wln_ps_intf_idx = 0;    
                
                wln_ps_mode     = WLN_PSMODE_SLEEP;                        
                next_event_time = wlnDutyCycle.offtime;

                // turn off the wifi
                MacEnterSleepMode();
                break;

             case WLN_PSMODE_SLEEP:
                if (wln_ps_intf_idx == 0)
                {
                    // enable the wifi
                    MacEnterActiveMode();
                }

                if ((pSta != NULL) && (pSta->staState == SS_ASSOC))
                {
                    wln_ps_mode     = WLN_PSMODE_NF_CLR;                        
                    next_event_time = WLN_PS_ACK_WAIT;
                    req_flags      |= WLN_PSEVENT_ACK;
                    wlnClearTxFrame = TRUE;                    
                    SendQueue();
                }
                else
                {
                    wln_ps_mode     = WLN_PSMODE_NF_CLR_ACK;
                    next_event_time = 0;
                }
                break;
                
            case WLN_PSMODE_NF_CLR:
            case WLN_PSMODE_NF_CLR_ACK:
                req_flags      &= ~WLN_PSEVENT_ACK;

                // try to send NF from all interfaces 
                while (++wln_ps_intf_idx < WLN_MAX_ADDRS)
                {
                    if (rootEndPoint[wln_ps_intf_idx] != NULL)
                    {
                        wln_ps_mode     = WLN_PSMODE_SLEEP;
                        next_event_time = 0;
                        break;
                    }
                }

                if (wln_ps_intf_idx < WLN_MAX_ADDRS)
                    break; 
                else
                    wln_ps_intf_idx = 0;    
                                
                wln_ps_mode     = WLN_PSMODE_AWAKE;                        
                next_event_time = wlnDutyCycle.ontime;
                break;
                
            default:
                tx_thread_sleep(2);  // shouldn't come here at any time
                break;
        }
    }
}


static BOOL wln_ps_start (void)
{
    BOOL ret = FALSE;
    TX_THREAD *wln_ps_thread;
    char *wln_ps_stack;

    if ((wln_ps_thread = (TX_THREAD *) malloc(sizeof(TX_THREAD))) == NULL)
    {
        wln_ps_stack = NULL;
        goto done_ps;
    }
    memset(wln_ps_thread, 0, sizeof(TX_THREAD));
    
    if ((wln_ps_stack = (char *) malloc(WLN_PS_STACK)) == NULL)
        goto done_ps;

    // create event flag 
    tx_event_flags_create (&wln_ps_event_flags, "Wireless PS event flag"); 
    tx_event_flags_set(&wln_ps_event_flags, 0, TX_AND);
    
    // now start the thread
	if (tx_thread_create(wln_ps_thread, "Wireless PS thread", wln_ps_task, 0,
                         wln_ps_stack, WLN_PS_STACK, 12, 12, 1, TX_AUTO_START) == TX_SUCCESS)
    {        
        ret = TRUE;        
    }
                
    done_ps:
    if (ret == FALSE) // something is wrong, couldn't start the PS
    {
        if (wln_ps_thread != NULL)
            free(wln_ps_thread);
            
        if (wln_ps_stack != NULL)
            free(wln_ps_stack);
    }
    
    return ret;
}

/*
 * Allows user to get the current duty cycle values set in the driver.
 */
int wln_get_duty_cycle(wln_duty_cycle * dutycycle)
{
    if (dutycycle)
    {
        memcpy(dutycycle, &wlnDutyCycle, sizeof wlnDutyCycle);
        return 0;
    }

    return -1;
}

/*
 * Allows user to set the current duty cycle values in the driver.
 */
int wln_set_duty_cycle(wln_duty_cycle * dutycycle)
{
    static BOOL first_time = TRUE;

    if (dutycycle)
    {   
        if (dutycycle->offtime == 0)
        {
            if (wln_ps_mode == WLN_PSMODE_SLEEP)
            {
                // enable the wifi
                MacEnterActiveMode();
            }
                
            wln_ps_mode = WLN_PSMODE_DISABLED;
            memcpy(&wlnDutyCycle, dutycycle, sizeof wlnDutyCycle);
            return 0;
        }
        else if ((dutycycle->ontime >= WLN_PS_MIN_ON_TIME) && 
            (dutycycle->offtime <= WLN_PS_MAX_OFF_TIME))
        {
            memcpy(&wlnDutyCycle, dutycycle, sizeof wlnDutyCycle);
            if (first_time)
            {
                if (wln_ps_start())
                {
                    first_time = FALSE;
                    return 0;
                }
            }
            else
            {
                wln_ps_mode = WLN_PSMODE_AWAKE;                
                return 0;
            }
        }
    }

    return -1;
}


#endif  // MAC_PS_ENABLED

