/*
 *  Copyright (c) 1996-2007 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
 *
 *     NET+OS/Treck interface to Embedded 802.11b wireless network interface
 *     Based on Digi International embedded ethernet driver, narmenet.c
 */

#include "mac.h"
#include "md5.h"

/* treck */
#include "trsocket.h"

/* threadx */
#include "tx_api.h"
#include "tx_port.h"

/* Digi International */
#include "bsp_api.h"
#include "bsptimer.h"
#include "Efe_def.h"
#include "reg_def.h"
#ifdef NET_OS_SPECIFIC
#include "naifconfig.h"
#endif

#define	WLN_TXQ_SIZE	256

BOOL wln_params_set = FALSE;
TX_MUTEX wln_mutex;
TX_MUTEX wln_mutex2;
BOOLEAN wln_up = FALSE;
static ttUserInterface wln_ifc;

static char *rx_data;
static int rx_length;
static ttUserBuffer rx_handle;

// Transmit/receive processing task
static TX_THREAD wln_thread;
static TX_EVENT_FLAGS_GROUP wln_event_flags;

#define WLN_STACK 8192
static char wln_stack[WLN_STACK];
static void wln_task (ULONG unused);

#if (USB_WIFI_ENABLED == 1)
static void start_polling_wiwave(void);
#endif

/*
 * Driver entry functions called by TCP/IP stack
 */

static int wlnOpen(ttUserInterface interfaceHandle)
{
	NA_UNUSED_PARAM(interfaceHandle);
	
    if (wln_up)
	{
		DPRINTF ("wlnOpen: driver aleady open\n");
		return TM_DEV_ERROR;
	}
	
	/* wireless params must be initialized */
	if (!wln_params_set)
	{
		DPRINTF ("wlnOpen: params not initialized\n");
		return TM_DEV_ERROR;
	}


	/* Confirm that radio device is present and responsive 
           and perform MAC/RF layer initialization */
	if (ERR_OK != wln_start())
	 {
	    DPRINTF ("wlnOpen: MAC/RF layer initialization failed\n");
	    return TM_DEV_ERROR;
	 }

        /* Spawn a higher level processing wireless task IF AND ONLY IF 
           the step above has succeeded. */

	/* start driver task */
	tx_thread_delete(&wln_thread);
	tx_thread_create(&wln_thread, "wireless task", wln_task,
		0, &wln_stack[0], WLN_STACK, 2, 2, TX_NO_TIME_SLICE, TX_AUTO_START);

	return TM_DEV_OKAY;
}

static int wlnClose(ttUserInterface interfaceHandle)
{
	NA_UNUSED_PARAM(interfaceHandle);

	/* set the event flag to wake up and exit the driver task. */
	wln_up = FALSE;
	tx_event_flags_set(&wln_event_flags, 1, TX_OR);

	return TM_DEV_OKAY;
}

static int wlnSend(ttUserInterface interfaceHandle,
	char TM_FAR *   dataPtr,
	int             dataLength,
	int             flag)
{
	// Message starts with an ethernet header
	ether_hdr *eth = (ether_hdr *) dataPtr;
	int length = dataLength - sizeof (ether_hdr);
	int ret;

	NA_UNUSED_PARAM(interfaceHandle);
	NA_UNUSED_PARAM(flag);

    if (!wln_up)
	{
		DPRINTF ("wlnSend: driver not open\n");
		return TM_DEV_ERROR;
	}

	// Send to MAC layer
	ret = MacSendData (eth->dst, eth->eptype, eth->data, length);

	// Out of buffers.
	// Return error so stack will queue the packet and send again later.
	if (ret == ERR_MEMORY)
		return TM_DEV_ERROR;
		
	if (ret != ERR_OK)
	{
		DPRINTF ("wlnSend: error %d length %d\n", ret, length);
	}

	// Success or unrecoverable error.
	// Tell stack to free the packet, not queue it.
	tfSendCompleteInterface(interfaceHandle, TM_DEV_SEND_COMPLETE_DRIVER);
	return TM_DEV_OKAY;
}

static int wlnReceive(ttUserInterface interfaceHandle,
	char TM_FAR * TM_FAR * dataPtr,
	int TM_FAR *           dataLength,
	ttUserBufferPtr        bufHandlePtr)
{
	NA_UNUSED_PARAM(interfaceHandle);

	// Return buffer allocated in MacRecvData()
	*dataPtr = rx_data;

	if (rx_data == NULL)
		return TM_DEV_ERROR;
		
	*dataLength = rx_length;
	*bufHandlePtr = rx_handle;
	
	rx_data = NULL;
	return TM_DEV_OKAY;
}

static int wlnIoctl(ttUserInterface interfaceHandle,
	int             flag,
	void TM_FAR *   optionPtr,
	int             optionLen)
{
	NA_UNUSED_PARAM(interfaceHandle);
	NA_UNUSED_PARAM(optionPtr);
	NA_UNUSED_PARAM(optionLen);

	// Nothing to do for multicast, stack will filter packets
    if ((flag & TM_DEV_SET_MCAST_LIST) || (flag & TM_DEV_SET_ALL_MCAST))
		return TM_DEV_OKAY;
		
    return TM_DEV_ERROR;
}

static int wlnGetPhyAddr(ttUserInterface interfaceHandle,
	char TM_FAR *   phyAddr)
{
	NA_UNUSED_PARAM(interfaceHandle);

	/* wireless params must be initialized */
	if (!wln_params_set)
		return TM_DEV_ERROR;

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


/*
 * Public API
 */

//
// Install wireless driver into OS network stack
// @param ifname network interface name (e.g. "wln0")
//
int wln_install (char *ifname)
{
    int errorCode;

#ifdef NET_OS_SPECIFIC
    unsigned multihome = 0;
    
    /* Install network interface */
    
    NaIfAddInterfaceParams_t if_config_params;
    int j;
    
    /* Set interface type info */
    strcpy(if_config_params.interface_type.if_name, ifname);
    if_config_params.interface_type.if_linklayer_type = NA_IF_LINK_LAYER_TYPE_ETH;
    if_config_params.interface_type.if_multihomed = TRUE;

    /* Set driver functions for network interface */
    if_config_params.driver_func.driver_open = wlnOpen;
    if_config_params.driver_func.driver_close = wlnClose;
    if_config_params.driver_func.driver_send = wlnSend;
    if_config_params.driver_func.driver_recv = (NaIfDriverRecvFn_t)wlnReceive;
    if_config_params.driver_func.driver_free_recv = NULL;
    if_config_params.driver_func.driver_ioctl = wlnIoctl;
    if_config_params.driver_func.driver_get_phy_addr = wlnGetPhyAddr;

    /* set interface configuration parameters                           */
    /* Note: if if_config_params.interface_type.if_multihomed is FALSE, */
    /* only index 0 of if_config_params.config_params.if_config_flags[] */
    /* and if_config_params.config_params.buffers_per_frame[] are used  */
    for (j = 0; j < NA_IF_MAX_MULTIHOMES_PER_IF; j++)
    {
        if_config_params.config_params[j].if_config_flags = TM_DEV_MCAST_ENB;
#if defined(VPN_ENABLED) || defined(FORWARDING_ENABLED)
        if_config_params.config_params[j].if_config_flags |= TM_DEV_IP_FORW_ENB; /* allow forwarding through this interface */
#endif
        if_config_params.config_params[j].buffers_per_frame = 1;
    }

    if (naIfConfigCreateInterface(&if_config_params) != NA_SUCCESS)
    {
	    DPRINTF ("wln_install: naIfConfigCreateInterface failed!\n");
        return -1;
    }
    
    if (naIfConfigGetInterfaceHandle(ifname, &wln_ifc, &multihome) != NA_SUCCESS)
    {
	    DPRINTF ("wln_install: naIfConfigGetInterfaceHandle failed!\n");
    	return -2;
    }
#endif // NET_OS_SPECIFIC

#ifdef NDS_SPECIFIC
    wln_ifc = tfAddInterface(
		ifname,
		tfUseEthernet(),
		wlnOpen,
		wlnClose,
		wlnSend,
		wlnReceive,
		(ttDevFreeRecvFuncPtr)0,
		wlnIoctl,
		wlnGetPhyAddr,
		&errorCode);
            
    if (wln_ifc == NULL)
    {
    	DPRINTF ("wln_install: tfAddInterface error %d\n", errorCode);
    	return -1;
    }
#endif // NDS_SPECIFIC
    	
    /* set up transmit queue */
	errorCode = tfUseInterfaceXmitQueue (wln_ifc, WLN_TXQ_SIZE);
    if (errorCode != 0)
    {
    	DPRINTF ("wln_install: tfUseInterfaceXmitQueue error %d\n", errorCode);
    	return -3;
    }

    /* create event flag */
    tx_event_flags_create (&wln_event_flags, "Wireless Event Flags");
    tx_event_flags_set(&wln_event_flags, 0, TX_AND);
    
    /* create mutex */
    tx_mutex_create(&wln_mutex, "Wireless mutex", TX_NO_INHERIT);
    tx_mutex_create(&wln_mutex2, "Wireless mutex2", TX_NO_INHERIT);

	wln_up = FALSE;
	rx_data = NULL;
	
   	return 0;
}

//
// Send a packet to wireless network
// @param data packet containing ethernet header and payload
// @param length total length in bytes
//
int wln_send (uint8 *data, int length)
{
	int ret;
	
	// Message starts with an ethernet header
	ether_hdr *eth = (ether_hdr *) data;
	length -= sizeof (ether_hdr);

    if (!wln_up)
	{
		DPRINTF ("wln_send: driver not open\n");
		return -1;
	}

	// Send to MAC layer
	ret = MacSendData (eth->dst, eth->eptype, eth->data, length);
	if (ret != ERR_OK)
	{
		DPRINTF ("wln_send: error %d length %d\n", ret, length);
		return -2;
	}
	
	return 0;
}


/*
 * OS interface functions called by the MAC layer
 */

//
// Receive a data MSDU.
// Called my MAC layer to provide data to an upper layer.
// This function must copy data by calling MacGetData() before returning.
// @param buf Start of buffer list
// @param dst Destination MAC address
// @param src Source MAC address
// @param epType Ethernet protocol type
// @param length Length of data in bytes
//
void MacRecvData (MacBuffer *buf, MacAddr dst, MacAddr src, int epType, int length)
{
	ether_hdr *eth;

	// Get an OS buffer
	rx_data = tfGetEthernetBuffer (&rx_handle);
	if (rx_data == NULL)
    {
		macStats.rxDropBuffer++;
		return;
    }

	// Copy header and data to OS buffer
	eth = (ether_hdr *) rx_data;
	SET_ADDR (eth->dst, dst);
	SET_ADDR (eth->src, src);
	eth->eptype = epType;
	MacGetData (buf, eth->data, length);

	length += sizeof(ether_hdr);
	rx_length = length;
	
	// Send to receive callback if any
	if (wln_receive ((uint8 *)rx_data, length))
	{
		tfFreeDriverBuffer (rx_handle);
		rx_data = NULL;
	}
		
	// No callback, pass up to the TCP/IP stack.
	// Stack will call wlnReceive() to get the data.
	else
	{
		tfRecvInterface (wln_ifc);
		ASSERT (rx_data == NULL);
	}
}

/*
 * MacEvent() is called when a MAC layer event occurs.
 * This may be called at interrupt level.
 */
void MacEvent (int event, void *param)
{
	NA_UNUSED_PARAM(param);

	switch (event)
	{
		case MAC_EVT_RX_INTR:
		case MAC_EVT_TX_INTR:
			tx_event_flags_set (&wln_event_flags, 1, TX_OR);
			break;
#if (USB_WIFI_ENABLED == 1)
        // These are events which cannot be handled in the WiWave ISR
        // because they may block over USB.
		case MAC_EVT_NO_BUFFERS:  // WiWave has no buffers
			tx_event_flags_set (&wln_event_flags, 2, TX_OR);
		case MAC_EVT_CLEAR_TX_END:  
			tx_event_flags_set (&wln_event_flags, 4, TX_OR);
		case MAC_EVT_RESET_RX_FIFO:  
			tx_event_flags_set (&wln_event_flags, 8, TX_OR);
			break;
#endif
	}
}

#if (USB_WIFI_ENABLED == 1)
#define WIFI_READ_LEN BUF_SIZE /* 512*3+24 */
extern int usb_wifi_read(unsigned char *buffer, int bytesToTransfer, 
          int *bytesTransferred, int *errorCode, MacBuffer *mac_buffer);
static void start_polling_wiwave(void)
{
    MacBuffer     *new_buffer;
    unsigned char *ptr;
    unsigned int   error=0, len, ret, bytesTransferred;
    void          *callback_ptr;

    /* Get a buffer for the next read */ 
    new_buffer = MacGetBuffer();
    if (new_buffer == (MacBuffer *)NULL)
    {
        tx_thread_sleep(10);
        MacEvent(MAC_EVT_NO_BUFFERS, (void *)NULL);
        return;
    }
    
    ptr = (unsigned char *)&new_buffer->usb_encoded_length;
    len = WIFI_READ_LEN;
    callback_ptr = (unsigned char *)new_buffer;
    
    /* Start polling the WiWave device */
    ret = usb_wifi_read(ptr,
                       len,
                       (int *)&bytesTransferred, (int *)&error, callback_ptr);
    if (error)
    {
        panic("wifi_handle_packet: usb_wifi_read failed.\n"); 
        trace(USBHOST_TRACE, TRACE_CRITICAL, 
          "wifi_handle_packet: usb_wifi_read errorCode %d\n", error);
    }
    return;
}
#endif

/*
 * Get system time value in milliseconds
 */
uint32 getTimeMS()
{
	return (tx_time_get() * (1000 / NABspTicksPerSecond));
}


/******************************************************************
 *   support functions
 ******************************************************************/
int wln_start(void)
{
    int ret;
    
    GET_MUTEX2();
    ret = MacInit();
	if (ret != ERR_OK)
	{
		DPRINTF ("wln_start: MacInit error %d\n", ret);
		return -1;
	}

	wln_up = TRUE;
	PUT_MUTEX2();
	return ERR_OK;
}
void wln_stop(void)
{
    ShutdownHW();  /*so the driver stops receiving*/
    GET_MUTEX2();
    wln_up = FALSE;
    MacShutdown ();
    tx_thread_sleep(100 * NABspTicksPerSecond/1000);
    PUT_MUTEX2();
    
}
/*
 *
 *  Function: void wln_task (ULONG unused)
 *
 *  Description:
 *
 *      This routine is the entry point to the driver thread.
 *      This thread is used to pass received packets up to the
 *      MAC layer and TCP/IP stack, transmit queued packets,
 *		and process periodic tasks for the MAC layer.
 *
 *      This thread waits for an event flag which the ISR
 *      uses to signal us when packets are available.
 *
 *  Parameters:
 *
 *      unused      a parameter passed to newly created thread which we
 *                  do not use.
 *
 *  Return Values:
 *
 *      none
 *
 */
static void wln_task (ULONG unused)
{
    ULONG flags;

#if (USB_WIFI_ENABLED == 1)    
    int level;
#endif    

	NA_UNUSED_PARAM(unused);

    while (1)
    {
        GET_MUTEX2();
        if (wln_up)
        {
    	    // Process receive queue and periodic tasks
    	    uint32 next = MacTimer();

		    // Process driver transmit queue
		    tfIoctlInterface (wln_ifc, TM_DEV_IOCTL_EMPTY_XMIT_FLAG, NULL, 0);
		
            // Wait for an event
            // Wake up periodically to process timer tasks
            tx_event_flags_get(&wln_event_flags, 0xf, TX_OR_CLEAR, &flags, 
        	    next*NABspTicksPerSecond/1000);
#if (USB_WIFI_ENABLED == 1)	    
            if (flags & 2)
            {
               trace(USBHOST_TRACE, TRACE_CRITICAL, 
                "start polling wiwave [%d]\n", flags);
                start_polling_wiwave(); /* The wiwave is out of buffers */
            }
            if (flags & 4)
            {
                extern void MacClearTxEnd(void);
                MacClearTxEnd();
            }
            if (flags & 8)
            {
                extern void MacResetRxFifo(void);
                MacResetRxFifo();
            }


            level = DISABLE();
            if (txState == ST_IDLE)
            {
                SendQueue();
            }
            ENABLE(level);
#endif	    
        }
        else
            tx_thread_sleep(10 * NABspTicksPerSecond/1000);
        
        PUT_MUTEX2();
    }
     
}

#ifdef NDS_SPECIFIC

/*
 * Get a cryptographic random number
 */
unsigned NA_Rand(void)
{
   MD5_CTX mdContext;
   unsigned char digest[16];
#if PROCESSOR == ns7520
   unsigned int t;
#endif

   MD5_Init(&mdContext);   /* initialize MD5 */

   /* get MAC random number, and MD5 it*/
   MD5_Update(&mdContext, &macRandom, sizeof macRandom);
   
#if PROCESSOR == ns7520
   /* get current timer 1 count and MD5 it */
   t = *NARM_T1SR_ADDR;
   MD5_Update(&mdContext, &t, sizeof t);
#endif

#if PROCESSOR == ns9750
    // TODO
#endif

   MD5_Final(digest, &mdContext);
   return *((unsigned *) digest);
}

// Called by nif_is_link_up()
int is_wireless_up(void)
{
    /* Check Link Status */
    switch (macStatus.state)
    {
		case WLN_ST_ASSOC_ESS:
		case WLN_ST_AUTH_ESS:
			return 1;

		default:
			return 0;
    }
}

#endif // NDS_SPECIFIC
