/*
 * Copyright (c) 2008 John DeHart and Washington University in St. Louis.
 * All rights reserved
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *    3. The name of the author or Washington University may not be used 
 *       to endorse or promote products derived from this source code 
 *       without specific prior written permission.
 *    4. Conditions of any other entities that contributed to this are also
 *       met. If a copyright notice is present from another entity, it must
 *       be maintained in redistributions of the source code.
 *
 * THIS INTELLECTUAL PROPERTY (WHICH MAY INCLUDE BUT IS NOT LIMITED TO SOFTWARE,
 * FIRMWARE, VHDL, etc) IS PROVIDED BY THE AUTHOR AND WASHINGTON UNIVERSITY 
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR WASHINGTON UNIVERSITY 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS INTELLECTUAL PROPERTY, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * */

/*
 * File:   plugin_ipHdr.c
 * Author: John DeHart
 * Email:  jdd@arl.wustl.edu
 * Organization: Applied Research Laboratory
 * 
 * Derived from: NONE
 *
 * Date Created:  3/03/2008
 * 
 * Description: plugin to process an IP Hdr 
 *
 * Modification History: 
 *
 *   03/07/08: JDD: First version added
 */ 

#ifndef _PLUGIN_IPHDR_C
#define _PLUGIN_IPHDR_C

/* there are a number of pre-processor values that need to be set for this code:
 * 
 *   MICRO_C:              must be set (since this is microC, not assembly)
 *   DL_ORDERED:           should be set for ordered thread execution (optional)
 *   FIRST_PACKET_THREAD:  thread context number of first thread that will be assigned
 *                         to handling incoming data packets.  values from 0 to 7.
 *   LAST_PACKET_THREAD:   thread context number of last thread that will be assigned
 *                         to handling incoming data packets.  values from 0 to 7.
 *   MESSAGE_THREAD:       thread context number of the thread that will be assigned
 *                         to handling control messages.  values from 0 to 7. (optional)
 *   CALLBACK_THREAD:      thread context number of the thread that will be assigned
 *                         to handling periodic callbacks.  values from 0 to 7. (optional)
 *
 * an example might have these set (done in the build settings in the simulator for
 * now):  MICRO_C,DL_ORDERED,FIRST_PACKET_THREAD=0,LAST_PACKET_THREAD=5,
 * MESSAGE_THREAD=6,CALLBACK_THREAD=7
 */

#include "plugin_api.h"

//-----------------------------------------------------------
// Global variables/Registers
//-----------------------------------------------------------
// thread-specific globals 
__declspec(gp_reg) int dlNextBlock;  // where to send packets to next
__declspec(gp_reg) int dlFromBlock;  // where to get packets from
__declspec(gp_reg) int msgNextBlock; // where to send control messages to next
__declspec(gp_reg) int msgFromBlock; // where to get control messages from


// see ring_formats.h for struct definitions
volatile __declspec(gp_reg) plc_plugin_data ring_in;  // ring data from PLC
volatile __declspec(gp_reg) plugin_out_data ring_out; // ring data to next block

__declspec(local_mem) unsigned int timeout; // time (cycles) between callbacks

__declspec(gp_reg) unsigned int pluginId;  // plugin id (0...7)

//-----------------------------------------------------------
// Function prototypes
//-----------------------------------------------------------
void handle_pkt_user();
void handle_msg_user();
void callback_user();
void plugin_init_user();

__declspec(gp_reg) unsigned int hdrLengthCheckPass; 
__declspec(gp_reg) unsigned int hdrLengthCheckFail; 

/*
typedef struct _sixteenWordsOfData
{
    unsigned int i[16];
} sixteenWordsOfData;
*/

//-------------------------------------------------------------------
// handle_pkt_user
//
//    Description:
//       User-defined packet processing
//
//    Parameters:
//      Outputs: n/a
//      In/Outs: n/a
//      Inputs : n/a 
//      Constants: n/a
//      Labels: n/a
//
//    Side effects: n/a
//    See also: handle_pkt()
//
void handle_pkt_user() 
{
    //__declspec(gp_reg) ipv4_header ipv4_hdr; // defined in ixp_lib.h
    __declspec(gp_reg) onl_api_ip_hdr ipv4_hdr; // defined in ixp_lib.h
    __declspec(gp_reg) buf_handle_t buf_handle;  // see dl_buf.h, ixp_lib.h
    __declspec(gp_reg) onl_api_buf_desc bufDescriptor;  // see dl_buf.h, ixp_lib.h
    //    __declspec(gp_reg) onl_api_buf_desc bufDescriptor2;  // see dl_buf.h, ixp_lib.h
    __declspec(gp_reg) onl_api_udp_hdr udp_hdr;
    __declspec(gp_reg) onl_api_tcp_hdr tcp_hdr;
//    onl_api_icmp_hdr icmp_hdr;
    unsigned int bufDescPtr;
    unsigned int ipv4HdrPtr;
    unsigned int udpHdrPtr;
    unsigned int dataPtr;
    unsigned int tcpHdrPtr;
    unsigned int dramBufferPtr;
    unsigned int cksum;
    unsigned int out_port;
    unsigned int qid;
    unsigned int udpLength;
    unsigned int udpCksum;
    unsigned int tcpCksum;
    __declspec(gp_reg) sixteenWordsOfData payloadData;

    __declspec(sram_read_reg) buf_handle_t buf_handleAlloc;  // see dl_buf.h, ixp_lib.h

    onl_api_allocate_buffer(&buf_handleAlloc);
    // Calculate the buffer descriptor location in SRAM
    bufDescPtr = onl_api_getBufferDescriptorPtr(buf_handleAlloc);

    // Read the buffer Descriptor from SRAM
    onl_api_readBufferDescriptor(bufDescPtr, &bufDescriptor);

    dramBufferPtr = onl_api_getBufferPtr(buf_handleAlloc);



    // Read the buf handle from the input Ring
    onl_api_get_buf_handle(&buf_handle); 

    // Calculate the buffer descriptor location in SRAM
    bufDescPtr = onl_api_getBufferDescriptorPtr(buf_handle);

    // Read the buffer Descriptor from SRAM
    onl_api_readBufferDescriptor(bufDescPtr, &bufDescriptor);

    // Random, harmless fields to force something to change and then re-write Descriptor
    bufDescriptor.reserved1 = bufDescriptor.freelistId;
    bufDescriptor.reserved2 = bufDescriptor.bufferSize;
    bufDescriptor.reserved3 = bufDescriptor.bufferNext;

    onl_api_writeBufferDescriptor(bufDescPtr, &bufDescriptor);

    dramBufferPtr = onl_api_getBufferPtr(buf_handle);

    // calculate ip header DRAM ptr 
    ipv4HdrPtr = onl_api_getIpv4HdrPtr(dramBufferPtr, bufDescriptor.offset);

    // Read ip header from DRAM pkt buffer
    onl_api_readIpv4Hdr(ipv4HdrPtr, &ipv4_hdr);    


    if (ipv4_hdr.ip_v == 4) {
	cksum = onl_api_ipv4Hdr_cksum16(&ipv4_hdr);
	if (cksum != ipv4_hdr.ip_sum) return;

        switch(ipv4_hdr.ip_proto) {
            case PROTO_ICMP:
                // Read ICMP header from DRAM pkt buffer
	        /*
                onl_api_readIcmpHdr();       
    	        */
                break;
            case PROTO_TCP:
                // Read TCP header from DRAM pkt buffer
                tcpHdrPtr = onl_api_getTcpHdrPtr(ipv4HdrPtr, ipv4_hdr.ip_hl);       
                onl_api_readTcpHdr(tcpHdrPtr, &tcp_hdr);       
                break;
            case PROTO_UDP:
                // Read UDP header from DRAM pkt buffer
                udpHdrPtr = onl_api_getUdpHdrPtr(ipv4HdrPtr, ipv4_hdr.ip_hl);       
                onl_api_readUdpHdr(udpHdrPtr, &udp_hdr);       
                if (ipv4_hdr.ip_len == ((ipv4_hdr.ip_hl << 2) + udp_hdr.uh_ulen)) {
                    hdrLengthCheckPass++;
                }
                else {
                    hdrLengthCheckFail++;
                }
                break;
            default:
                break;
        }
    }

    onl_api_set_out_to_QM();
    out_port = ((ring_in.uc_mc_bits >> 3) & 0x7);
    qid = ((out_port + 1) << 13) | ring_in.qid;
    onl_api_update_ring_out_to_qm(buf_handle.value, out_port, qid, ring_in.l3_pkt_len);

    if (ipv4_hdr.ip_proto == PROTO_UDP) {
	udpCksum = onl_api_udp_cksum(&ipv4_hdr, &udp_hdr, udpHdrPtr);
	if (udpCksum != udp_hdr.uh_sum)
	    onl_api_set_out_to_PLUGIN(1);
	else 
	    onl_api_set_out_to_PLUGIN(2);

	dataPtr = onl_api_getUdpPacketPayloadPtr(udpHdrPtr);
	onl_api_ua_read_4W_dram(dataPtr,  (void *) &payloadData);
	// Rotate the words around and write them back, just as an exercise
	payloadData.i[4] = payloadData.i[0];
	payloadData.i[0] = payloadData.i[1];
	payloadData.i[1] = payloadData.i[2];
	payloadData.i[2] = payloadData.i[3];
	payloadData.i[3] = payloadData.i[4];
	onl_api_ua_write_4W_dram(dataPtr,  (void *) &payloadData);
    }
    else if (ipv4_hdr.ip_proto == PROTO_TCP) {
	tcpCksum = onl_api_tcp_cksum(&ipv4_hdr, &tcp_hdr, tcpHdrPtr);
	if (tcpCksum != tcp_hdr.th_sum)
	    onl_api_set_out_to_PLUGIN(1);
	else 
	    onl_api_set_out_to_PLUGIN(2);
    }

}



//-------------------------------------------------------------------
// handle_msg_user
//
//    Description:
//       User-defined (xscale) message handling
//
//    Parameters:
//      Outputs: n/a
//      In/Outs: n/a
//      Inputs : n/a 
//      Constants: n/a
//      Labels: n/a
//
//    Side effects: n/a
//    See also: handle_msg()
//
void handle_msg_user()
{
    //TODO: add your code here
}

//-------------------------------------------------------------------
// callback_user
//
//    Description:
//       User-defined peridoic processing 
//
//    Parameters:
//      Outputs: n/a
//      In/Outs: n/a
//      Inputs : n/a 
//      Constants: n/a
//      Labels: n/a
//
//    Side effects: n/a
//    See also: callback()
//
void callback_user()
{
    //TODO: add code here
}


//-------------------------------------------------------------------
// plugin_init_user
//
//    Description:
//       User-defined plugin initialization
//
//    Parameters:
//      Outputs: n/a
//      In/Outs: n/a
//      Inputs : n/a 
//      Constants: n/a
//      Labels: n/a
//
//    Side effects: n/a
//    See also: plugin_init()
//

void plugin_init_user()
{
    //TODO: add your code here
}



/**
	----------------------------------------------------------------
 @User: YOU SHOULD NOT NEED TO MAKE ANY CHANGES TO THE REST OF THIS FILE
	----------------------------------------------------------------
*/





void default_format_out_data(unsigned int nextblock)
{
  __declspec(gp_reg) int out_port;
  if(nextblock == QM)
  {
    __declspec(gp_reg) dl_buf_handle_t buf_handle;
    __declspec(sram) unsigned int *buf_desc_ptr;
    __declspec(sram_write_reg) unsigned int sram_wr_regs[3];
    SIGNAL sram_sig;

    // assume unicast
    out_port = (ring_in.uc_mc_bits >> 3) & 0x7;
    ring_out.plugin_qm_data_out.out_port = out_port;

    ring_out.plugin_qm_data_out.qid = ((out_port+1) << 13) | ring_in.qid;

    ring_out.plugin_qm_data_out.l3_pkt_len = ring_in.l3_pkt_len;
    ring_out.plugin_qm_data_out.buf_handle_lo24 = ring_in.buf_handle_lo24;

/*
    // also need to write the stats index, nh mac info and ethertype info into the  buffer
    // descriptor
    onl_api_get_buf_handle(&buf_handle);
    buf_desc_ptr = (__declspec(sram) unsigned int*) (Dl_BufGetDesc(buf_handle) + 12);

    sram_wr_regs[0] = (ring_in.stats_index << 16) | (ring_in.nh_eth_daddr_hi32 >> 16);
    sram_wr_regs[1] = (ring_in.nh_eth_daddr_hi32 << 16) | (ring_in.nh_eth_daddr_lo16);
    sram_wr_regs[2] = ring_in.eth_type << 16;

    // write data to payload buf desc
    sram_write(sram_wr_regs, buf_desc_ptr, 3, ctx_swap, &sram_sig);
*/
  }
  else // assume MUX and ignore xscale data packets for now
  {
    // assume unicast
    out_port = (ring_in.uc_mc_bits >> 3) & 0x7;
    ring_out.plugin_mux_data_out.out_port = out_port;
    
    ring_out.plugin_mux_data_out.qid = ((out_port+1) << 13) | ring_in.qid;

    // assume pass-through
    ring_out.plugin_mux_data_out.flags = 1;

    ring_out.plugin_mux_data_out.plugin_tag = ring_in.plugin_tag;
    ring_out.plugin_mux_data_out.in_port = ring_in.in_port;
    ring_out.plugin_mux_data_out.stats_index = ring_in.stats_index;
    ring_out.plugin_mux_data_out.l3_pkt_len = ring_in.l3_pkt_len;
    ring_out.plugin_mux_data_out.buf_handle_lo24 = ring_in.buf_handle_lo24;
  }
}

/* handle packets */
void handle_pkt()
{
  dl_source_packet(dlFromBlock);
  
  // format ring_out data based only on ring_in data
  default_format_out_data(dlNextBlock);

  handle_pkt_user();

  dl_sink_packet(dlNextBlock);
}


/* handle control messages */
void handle_msg()
{
  // assume messages are at most 8 words for now
  //__declspec(local_mem) unsigned int message[8];
  __declspec(gp_reg) unsigned int message[8];

  dl_source_message(msgFromBlock, message);

  // for now just send the message back out
  dl_sink_message(msgNextBlock, message);
}

/* handle periodic functionality */
void callback()
{
  // for now, the timeout value is in cycles (MEs operate at 1.4 GHz)
  // also, need to think about very large values for timeout
  sleep(timeout);
}


/* take care of any setup that needs to be done before processing begins */
void plugin_init()
{
  /* set a default timeout value for callback of 10000 cycles */
  timeout = 10000;

  /* set the default next block to be the Queue Manager */
  dlNextBlock = QM;

  /* by default, get packets and get and put control messages from input rings
   * based on which microengine we are currently running on; this assumes a
   * default one to one mapping */
  switch(__ME())
  {
    case 0x7:
      pluginId = 0;
      dlFromBlock  = PACKET_IN_RING_0;
      msgFromBlock = MESSAGE_IN_RING_0;
      msgNextBlock = MESSAGE_OUT_RING_0;
      break;
    case 0x10:
      pluginId = 1;
      dlFromBlock  = PACKET_IN_RING_1;
      msgFromBlock = MESSAGE_IN_RING_1;
      msgNextBlock = MESSAGE_OUT_RING_1;

      break;
    case 0x11:
      pluginId = 2;
      dlFromBlock  = PACKET_IN_RING_2;
      msgFromBlock = MESSAGE_IN_RING_2;
      msgNextBlock = MESSAGE_OUT_RING_2;  
      break;
    case 0x12:
      pluginId = 3;
      dlFromBlock  = PACKET_IN_RING_3;
      msgFromBlock = MESSAGE_IN_RING_3;
      msgNextBlock = MESSAGE_OUT_RING_3;    
      break;
    case 0x13:
      pluginId = 4;
      dlFromBlock  = PACKET_IN_RING_4;
      msgFromBlock = MESSAGE_IN_RING_4;
      msgNextBlock = MESSAGE_OUT_RING_4;
      break;
    default:  // keep the compiler happy
      pluginId = 0;
      dlFromBlock  = PACKET_IN_RING_0;
      msgFromBlock = MESSAGE_IN_RING_0;
      msgNextBlock = MESSAGE_OUT_RING_0;
      break;
  }

  plugin_init_user(); // user hook
}


/* entry point */
void main()
{
  int c;

  /* do initialization */
  plugin_init();
  dl_sink_init();
  dl_source_init();

  /* get the current thread's context number (0-7) */
  c = ctx();

  if(c >= FIRST_PACKET_THREAD && c <= LAST_PACKET_THREAD)
  {
    while(1)
    {
      handle_pkt();
    }
  }
#ifdef MESSAGE_THREAD
  else if(c == MESSAGE_THREAD)
  {
    while(1)
    {
      handle_msg();
    }
  }
#endif
#ifdef CALLBACK_THREAD
  else if(c == CALLBACK_THREAD)
  {
    while(1)
    {
      callback();
    }
  }
#endif
}


#endif	// _PLUGIN_IPHDR_C
