/*
 * Copyright (c) 2007 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:   drop.c
 * Author: Jessica Codr
 * Email:  jmc5@cec.wustl.edu; jmc5@arl.wustl.edu
 * Organization: Applied Research Laboratory
 * 
 * Derived from: null.c created by Charlie Wiseman 8/20/2007
 * 		 delay.c created by Ken Wong 6/09/2008
 *		 debug.c 
 *
 * Date Created:  7/16/2008
 * 
 * Description:  Plugin to drop up to ten specified packets and/or to drop
 * 		 every x packets the plugin sees.  Also counts packets seen
 * 		 and dropped and can be reset.
 *
 * Modification History: 
 *   From null.c
 *     08/29/07 - plugin_init() added control statErrCntr (Shakir James <scj1@arl>)
 *     09/30/07 - plugin_init() added stats plugin cntrs (Shakir James <scj1@arl>) 
 *     09/17/07 - added user hooks and blank functions: (scj)
 *                  + handle_packet_XXX, callback_user(), plugin_init_user();
 *     09/19/07 - rewrote plugin_init() to use PVAR #define (scj)
 *   From drop.c
 *     07/16/08 - Stride counting added to drop every x packets
 *     07/17/08 - drop_nums added to drop up to 10 specified packets
 *     07/21/08 - Removed direct use of onl_api_drop() function to prevent threads
 *                from dropping every packet after the first one they drop
 *     07/22/08 - Made all memory references specify memory type to use
 */ 

#ifndef _DROP2_C
#define _DROP2_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)

__declspec(shared gp_reg) unsigned int pkt_count;
__declspec(shared gp_reg) unsigned int pkt_drops;
__declspec(shared gp_reg) unsigned int stride = 3; //how often to drop pkts
__declspec(shared gp_reg) unsigned int drop_nums[10];
//-----------------------------------------------------------
// Function prototypes
//-----------------------------------------------------------
void handle_pkt_user();
void handle_msg_user();
void callback_user();
void plugin_init_user();
__declspec(local_mem) unsigned int to_drop(__declspec(local_mem) unsigned int pkt_num);


//-------------------------------------------------------------------
// 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() 
{
  
  ++pkt_count;
  
  if((pkt_count % stride) == 0){
	++pkt_drops;
	onl_api_drop();
  }
  else if(to_drop(pkt_count)){
	++pkt_drops;
	onl_api_drop();
  }
}

__declspec(local_mem) unsigned int 
to_drop(__declspec(local_mem) unsigned int pkt_num){
  return 
    (drop_nums[0] - pkt_num == 0) ||
    (drop_nums[1] - pkt_num == 0) ||
    (drop_nums[2] - pkt_num == 0) ||
    (drop_nums[2] - pkt_num == 0) ||
    (drop_nums[3] - pkt_num == 0) ||
    (drop_nums[4] - pkt_num == 0) ||
    (drop_nums[5] - pkt_num == 0) ||
    (drop_nums[6] - pkt_num == 0) ||
    (drop_nums[7] - pkt_num == 0) ||
    (drop_nums[8] - pkt_num == 0) ||
    (drop_nums[9] - pkt_num == 0);
}


void handle_msg_user()
{
}

void callback_user()
{
}


//-------------------------------------------------------------------
// plugin_init_user
//
//    Description:
//       User-defined plugin initialization
//
//    Side effects: n/a
//    See also: plugin_init()
//

void plugin_init_user()
{
  if(ctx() == 0)
  {
    pkt_count = 0;
    pkt_drops = 0;
    stride = 0; 
    drop_nums[0] = 15;
    drop_nums[1] = 24;
    drop_nums[2] = 39;
    drop_nums[3] = 46;
    drop_nums[4] = 51;
    drop_nums[5] = 60;
    drop_nums[6] = 75;
    drop_nums[7] = 82;
    drop_nums[8] = 98;
    drop_nums[9] = 110;
  }
}


//Following two functions taken from Dr. Wong, but changed
//to make not use sram and to use ints instead of longs

// convert ascii string to unsigned long
//
static __forceinline __declspec(local_mem) unsigned int
kenw_atoi(__declspec(local_mem) char *p ) {
    __declspec(gp_reg) unsigned int x = 0;
    __declspec(gp_reg) int	     y;

    //added && *p != ' ' to keep from adding digits after ' ' -jmc5
    while( *p != '\0' && *p != ' ') {
    	if( *p != ' ' )	{
    	    x *= 10;
    	    y = *p - '0';
	    if( (y<0) || (y>9) )	return 0;
	    x += y;
	}
	p++;
    }
    return x;
}

static __forceinline __declspec(local_mem) char *
nxt_token(__declspec(local_mem) char *p ) {

    while( *p != '\0' ) {  //find SP char
    	if( *p == ' ' )	break;
	p++;
    }
    if( *p == '\0' )	return 0;
    ++p;

    while(*p != '\0' ) {		// scan past SP characters
    	if( *p == ' ' )	++p;
	else		break;
    }

    if( *p == '\0' )	return 0;
    else		return p;
}

// fills valstr with the decimal representation of val
__forceinline void onl_api_int2dstr(__declspec(gp_reg) unsigned int val, __declspec(local_mem) char valcalc[9])
{
  //__declspec(gp_reg) unsigned int c;
  __declspec(gp_reg) unsigned int i = 0;
  __declspec(gp_reg) unsigned int j = 0;
  __declspec(gp_reg) unsigned int addVal = 1;
  __declspec(gp_reg) unsigned int addValCopy = 1;
  __declspec(gp_reg) unsigned int numDigits = 1;
  //__declspec(gp_reg) unsigned int found_non_zero = 0;
  __declspec(gp_reg) unsigned int nine = 57;
  __declspec(gp_reg) unsigned int zero = 48;
  //__declspec(sram) unsigned int tmpholder;

  valcalc[i] = 48;

  while(val > 0){
  
 	if(val & 0x1){
		addValCopy = addVal;
		//definitely is an easier way to do this, eg adding in 100s or 10s...
		while(addValCopy >= 196){ //to avoid wrap-arounds w/ chars
			i = numDigits - 1;
			valcalc[i] = valcalc[i] + 196;  //problems w/ this exceeding range of chars?
			while(valcalc[i] > nine){
				if(i==0){ // shift digits over for more space if needed. 
					//NOTE:  need to add accounting for too many digits
					j = numDigits;
					while(j>0){ //remember that j is unsigned!!!!
						valcalc[j] = valcalc[j-1];
						j = j - 1;
					}
					valcalc[0] = zero;
					i = 1;
					numDigits = numDigits + 1;
				}	
				valcalc[i] -= 10;
				valcalc[i-1] += 1;
				if(valcalc[i] <= nine && i > 0){
					i -= 1;
				}
			}
			addValCopy = addValCopy - 196;
		
		}
		i = numDigits - 1;
		valcalc[i] = valcalc[i] + addValCopy;  //problems w/ this exceeding range of chars?
		while(valcalc[i] > nine){
			if(i==0){ // shift digits over for more space if needed. 
				//NOTE:  need to add accounting for too many digits
				j = numDigits;
				while(j>0){ //remember that j is unsigned!!!!
					valcalc[j] = valcalc[j-1];
					j = j - 1;
				}
				valcalc[0] = zero;
				i = 1;
				numDigits = numDigits + 1;
			}	
			valcalc[i] -= 10;
			valcalc[i-1] += 1;
			if(valcalc[i] <= nine && i > 0){
				i -= 1;
			}
		}
  	}
	val = val >> 1;
  	addVal = addVal * 2;
  }
  /*j = 0;
  while(j < numDigits){
  	valdstr[j] = valcalc[j];
	j = j + 1;
  }*/
  valcalc[numDigits] = '\0';
}


/**
	----------------------------------------------------------------
 @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()
{

  //added
  __declspec(local_mem) unsigned int drop = 0;

  dl_source_packet(dlFromBlock);
  
  // format ring_out data based only on ring_in data
  default_format_out_data(dlNextBlock);

  //handle_pkt_user();
 
//all onl_api_drop() does is set dlNextBlock = DROP

  ++pkt_count;
 
  if((pkt_count % stride) == 0){
	++pkt_drops;
	drop = 1;
  }
  else if(to_drop(pkt_count)){
	++pkt_drops;
	drop = 1;
  }
  else{
	drop = 0; 
  }

  if(drop == 1) 
	dl_sink_packet(DROP);
  else 
	dl_sink_packet(dlNextBlock);


//doing the above instead of using the user method
//FIXES PROBLEM W/ DROPPING TOO MANY PACKETS!
//TODO:  mention that onl drop packet function causes problems!
// Maybe try to still use handle_pkt_user()?
}


/* handle control messages */
void handle_msg()
{
  // assume messages are at most 8 words or now
  __declspec(gp_reg) unsigned int message[8];
  __declspec(gp_reg) unsigned int i;
  __declspec(local_mem) char msgstr[28];
  __declspec(local_mem) char return_str[28];
  __declspec(local_mem) char getcnt_str[3] = "gc\0";
  __declspec(local_mem) char getdrops_str[3] = "gd\0";
  __declspec(local_mem) char getstride_str[3] = "gs\0";
  __declspec(local_mem) char setdrop_str[3] = "sd\0";
  __declspec(local_mem) char setstride_str[3] = "ss\0";
  __declspec(local_mem) char reset_str[4] = "rst\0";
  __declspec(local_mem) char bado_str[11] = "BAD OP\0"; 
  __declspec(local_mem) char bada_str[11] = "BAD ARG\0"; 
  __declspec(gp_reg) onl_api_ctrl_msg_hdr hdr;

  //for(i=0; i<8; ++i) { message[i] = 0; }
  //had to unroll loop like Dr. Wong did (after adding his methods)
  message[0] = 0;
  message[1] = 0;
  message[2] = 0;
  message[3] = 0;
  message[4] = 0;
  message[5] = 0;
  message[6] = 0;
  message[7] = 0;

  dl_source_message(msgFromBlock, message);

  hdr.value = message[0];
  if(hdr.type != CM_CONTROLMSG)
  {
    return;
  }
  if(hdr.response_requested != 1)
  {
    return;
  }

  onl_api_intarr2str(&message[1], msgstr);

  if(strncmp_lmem(msgstr, getcnt_str, 2) == 0)
  {
    onl_api_int2dstr(pkt_count, return_str);
    if(onl_api_str2intarr(return_str, &message[1]) < 0)
    {
      return;
    } 
  }
  else if(strncmp_lmem(msgstr, getdrops_str, 2) == 0)
  {
    onl_api_int2dstr(pkt_drops, return_str);
    if(onl_api_str2intarr(return_str, &message[1]) < 0)
    {
      return;
    }
  }
  else if(strncmp_lmem(msgstr, getstride_str, 2) == 0)
  {
    onl_api_int2dstr(stride, return_str);
    if(onl_api_str2intarr(return_str, &message[1]) < 0)
    {
      return;
    }
  }
  else if(strncmp_lmem(msgstr, setdrop_str, 2) == 0)
  {
    
    __declspec(local_mem) unsigned int index = kenw_atoi(nxt_token(msgstr));

    if(index == 0){
//perhaps should technically do nxt_token(nxt_token(msgstr)) instead of
//nxt_token(msgstr+4), but since we got into the if statement, we know
//our second token is only 1 digit/character, so msgstr+4 works
	drop_nums[0] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 1){
	drop_nums[1] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 2){
	drop_nums[2] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 3){
	drop_nums[3] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 4){
	drop_nums[4] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 5){
	drop_nums[5] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 6){
	drop_nums[6] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 7){
	drop_nums[7] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 8){
	drop_nums[8] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 9){
	drop_nums[9] = kenw_atoi(nxt_token(msgstr + 4));
    }
    else if(index == 10){
        __declspec(local_mem) unsigned int drop = kenw_atoi(nxt_token(msgstr+5));
	drop_nums[0] = drop;
	drop_nums[1] = drop;
	drop_nums[2] = drop;
	drop_nums[3] = drop;
	drop_nums[4] = drop;
	drop_nums[5] = drop;
	drop_nums[6] = drop;
	drop_nums[7] = drop;
	drop_nums[8] = drop;
	drop_nums[9] = drop;
    }
    else
    {
      if(onl_api_str2intarr(bada_str, &message[1]) < 0)
      {
        return;
      }
    }
  }
  else if(strncmp_lmem(msgstr, setstride_str, 2) == 0)
  {
    stride = kenw_atoi(nxt_token(msgstr));
  }
  else if(strncmp_lmem(msgstr, reset_str, 3) == 0)
  {
    pkt_count = 0;
    pkt_drops = 0;

  }
  else
  {
    if(onl_api_str2intarr(bado_str, &message[1]) < 0)
    {
      return;
    }
  }

  hdr.type = CM_CONTROLMSGRSP;
  hdr.response_requested = 0;
  hdr.num_words = 7;
  message[0] = hdr.value;

  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	// _DROP2_C
