/*
 * Copyright (c) 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: setNxtBlk.c  
 * Author: Ken Wong
 * Email: kenw@arl.wustl.edu
 * Organization: Applied Research Laboratory
 * 
 * Derived from: NONE
 *
 * History:
 *   2/11/2009	Creation (Ken Wong)
 * 
 * Description: set dlNextBlock demo program
 *
 * Note:  Started with mycount.c; added control message for setting dlNextBlock;
 * 		modified handle_pkt_user()
 *
 */

#include "plugin_api.h"
#include <memory.h>			// needed by plugin_helpers.h

#define	PKT_COUNT	0		// plugin counters
#define ERR_COUNT	3

//-----------------------------------------------------------
// 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)

// my global declarations
//-----------------------
__declspec(shared gp_reg) int sharedNextBlock;
__declspec(shared gp_reg) unsigned int npkts;
__declspec(shared gp_reg) unsigned int debug_on;

#include "./plugin_helpers.h"

//-----------------------------------------------------------
// Helper functions
//-----------------------------------------------------------

// convert dlNextBlock from internal value to external label
//
void
dlNextBlock2str( char *str ) {
    switch( dlNextBlock ) {
    case PACKET_IN_RING_0:	strcpy( str, "PLUGIN0" ); break;
    case PACKET_IN_RING_1:	strcpy( str, "PLUGIN1" ); break;
    case PACKET_IN_RING_2:	strcpy( str, "PLUGIN2" ); break;
    case PACKET_IN_RING_3:	strcpy( str, "PLUGIN3" ); break;
    case PACKET_IN_RING_4:	strcpy( str, "PLUGIN4" ); break;
    case MUX:			strcpy( str, "MUX" );	break;
    case QM:			strcpy( str, "QM" );	break;
    case DROP:			strcpy( str, "DROP" );	break;
    default:			strcpy( str, "HUH?" );	break;
    }
}

// convert external label for dlNextBlock to internal value
//
__declspec(gp_reg) int
str2dlNextBlock( char *str ) {
    if( strncmp(str, "PLUGIN0", 7) == 0 )		return PACKET_IN_RING_0;
    else if( strncmp(str, "PLUGIN1", 7) == 0 )	return PACKET_IN_RING_1;
    else if( strncmp(str, "PLUGIN2", 7) == 0 )	return PACKET_IN_RING_2;
    else if( strncmp(str, "PLUGIN3", 7) == 0 )	return PACKET_IN_RING_3;
    else if( strncmp(str, "PLUGIN4", 7) == 0 )	return PACKET_IN_RING_4;
    else if( strncmp(str, "MUX", 3) == 0 )		return MUX;
    else if( strncmp(str, "DROP", 4) == 0 )		return DROP;
    else					return QM;
}

//=========
//
// check meta-packet fields (used for debugging)
// 	datagram qid = 17 (n1p0 to n1p1)
// 	ring_out.plugin_qm_data_out.qid = (1+out_port)*8192 + 17
// 					qid = 16401 for out_port = 1
// 	ring_out.plugin_qm_data_out.buf_handle_lo24:
// 		rightmost 3 bits = 0
// 		rightmost 24 bits != 0
// 	out_port in {0:4}
//
int
helper_check_meta( void ) {
    if( (   ring_out.plugin_qm_data_out.out_port != 1 )
	|| (ring_out.plugin_qm_data_out.qid != 16401)		// QID = 2*8192 + 17 = 16401
	|| ((ring_out.plugin_qm_data_out.buf_handle_lo24 & 0x7) != 0)
	|| (ring_out.plugin_qm_data_out.buf_handle_lo24 == 0) )

		return -1;

    else	return 0;
}
//=========


//-----------------------------------------------------------
// Function prototypes
//-----------------------------------------------------------
/* functions */
void handle_packet_count();
void callback_user();
void plugin_init_user();
void default_format_out_data(unsigned int);


void handle_pkt_user() {
    char	dbgmsg[10] = "got pkt ";
    char	dbgmsg2[16] = "dlNextBlock= ";
    char	dbgmsg3[16] = "BAD set_meta= ";
    int		rc;

    ++npkts;
    if( debug_on )	helper_sram_dbgmsg_str_1ul( dbgmsg, npkts );

    onl_api_plugin_cntr_inc(pluginId, PKT_COUNT);

    dlNextBlock = sharedNextBlock;

    if( debug_on )	helper_sram_dbgmsg_str_1ul( dbgmsg2, dlNextBlock );

    rc = helper_set_meta_default( dlNextBlock );
    if( rc != 0 ) {		// bad dlNextBlock
	helper_sram_dbgmsg_str_1ul( dbgmsg3, dlNextBlock );
	onl_api_plugin_cntr_inc(pluginId, ERR_COUNT);
    }
    if( dlNextBlock == MUX )	helper_inc_meta_mux_tag( );
}


// handle control messages
//									<<<<<
// op codes:
//   set:
//   	next=	send meta-pkt to plugin k (0..4), MUX (5), QM (default)
//   get:
//   	=vers	display version number
//	=npkts	display npkts
//	=next	display dlNextBlock in external string form (e.g., QM instead
//			of 0)
//	=raw	display dlNextBlock in internal decimal form (e.g., 0 instead
//			of QM)
//   miscellaneous:
//	reset	reset counters (npkts, plugin counter 0)
//	debug	toggle debug_on
//
void handle_msg()
{
    // assume messages are at most 8 words for now
    __declspec(gp_reg) unsigned int i;
    __declspec(gp_reg) unsigned int message[8];
    __declspec(gp_reg) onl_api_ctrl_msg_hdr hdr;
    __declspec(local_mem) char inmsgstr[28];			// inbound
    __declspec(local_mem) char outmsgstr[28];			// outbound
    __declspec(sram) char sram_inmsgstr[28];
    __declspec(sram) char sram_outmsgstr[28];
    __declspec(sram) char vers[4] = "1.7";

    char SET_next[8]	= "next=";
    char GET_next[8]	= "=next";
    char GET_raw[8]	= "=raw";
    char GET_vers[8]	= "=vers";
    char GET_npkts[8]	= "=npkts";
    char RESET[8]	= "reset";
    char DEBUG_op[8]	= "debug";

    char OK_msg[3]	= "OK";			// return msgs
    char BAD_OP_msg[8]	= "BAD OP";
    char NEED_ARG_msg[12]= "NEED ARG";

    // don't use for-loop:
    //   to get rid of the compiler error:  "Incorrect use of register variable
    //   message - check for & operator or non-constant buffer index"
    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], inmsgstr );

    outmsgstr[0] = '\0';
    memcpy_sram_lmem( sram_inmsgstr, inmsgstr, 28 );

    if( strncmp_sram(sram_inmsgstr, GET_vers, 8) == 0 ) {
	memcpy_lmem_sram( outmsgstr, (void *)vers, 4 );
    } else if( strncmp_sram(sram_inmsgstr, GET_npkts, 8) == 0 ) {
	helper_sram_outmsg_1ul( npkts, outmsgstr );
    } else if( strncmp_sram(sram_inmsgstr, RESET, 5) == 0 ) {
    	npkts = 0;
	helper_plugin_cntr_zero( PKT_COUNT );
	helper_plugin_cntr_zero( ERR_COUNT );
	memcpy_lmem_sram( outmsgstr, OK_msg, 3 );
    } else if( strncmp_sram(sram_inmsgstr, DEBUG_op, 5) == 0 ) {
    	debug_on = (debug_on+1) & 0x1;
	helper_sram_outmsg_1ul( debug_on, outmsgstr );
    } else if( strncmp_sram(sram_inmsgstr, GET_raw, 4) == 0 ) {
	helper_sram_outmsg_1ul( dlNextBlock, outmsgstr );
    } else if( strncmp_sram(sram_inmsgstr, GET_next, 5) == 0 ) {
	dlNextBlock2str( sram_outmsgstr );
	memcpy_lmem_sram( outmsgstr, sram_outmsgstr, 28 );
    } else if( strncmp_sram(sram_inmsgstr, SET_next, 5) == 0 ) {
	__declspec(sram) char *valptr;
	valptr = helper_nxt_token( sram_inmsgstr, 28 ); 
	if( valptr == 0 ) {
	    memcpy_lmem_sram( outmsgstr, NEED_ARG_msg, 12 );
	} else {
	    dlNextBlock = str2dlNextBlock( valptr );
	    sharedNextBlock = dlNextBlock;
	    memcpy_lmem_sram( outmsgstr, valptr, 21 );
	}
    } else {
	memcpy_lmem_sram( outmsgstr, BAD_OP_msg, 8 );
    }

    if( onl_api_str2intarr(outmsgstr, &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);
}

void callback_user() {}

void plugin_init_user()
{
    if(ctx() == 0)	// only thread 0 should do the initialization
    {
	npkts = 0;
	debug_on = 0;
	sharedNextBlock = QM;

	sleep(timeout);		// not sure why I need this to zero counter
	helper_plugin_cntr_zero( PKT_COUNT );
	helper_plugin_cntr_zero( ERR_COUNT );
    }
}



/**
	----------------------------------------------------------------
 @User: YOU SHOULD NOT NEED TO MAKE ANY CHANGES TO THE REST OF THIS FILE
	----------------------------------------------------------------
*/




// >>> NOTE:	I don't use this function in this plugin since I use
// 		helper_set_meta_default() in handle_pkt_user()
//
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);
  
  handle_pkt_user();

  if( debug_on ) {
    if( dlNextBlock == QM ) {
    	if( helper_check_meta( ) != 0 ) {
	    helper_sram_dbgmsg_3ul(
			ring_out.plugin_qm_data_out.out_port,
			ring_out.plugin_qm_data_out.qid,
			ring_out.plugin_qm_data_out.buf_handle_lo24 );
	    onl_api_plugin_cntr_inc( pluginId, ERR_COUNT );
	}
    }
  }

  dl_sink_packet(dlNextBlock);
}


/* 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
}

