/////////////////////////////////////////////////////////////////////////////////////
//                                                                      
//                  I N T E L   P R O P R I E T A R Y                   
//                                                                      
//     COPYRIGHT (c)  2001 BY  INTEL  CORPORATION.  ALL RIGHTS          
//     RESERVED.   NO  PART  OF THIS PROGRAM  OR  PUBLICATION  MAY      
//     BE  REPRODUCED,   TRANSMITTED,   TRANSCRIBED,   STORED  IN  A    
//     RETRIEVAL SYSTEM, OR TRANSLATED INTO ANY LANGUAGE OR COMPUTER    
//     LANGUAGE IN ANY FORM OR BY ANY MEANS, ELECTRONIC, MECHANICAL,    
//     MAGNETIC,  OPTICAL,  CHEMICAL, MANUAL, OR OTHERWISE,  WITHOUT    
//     THE PRIOR WRITTEN PERMISSION OF :                                
//                                                                      
//                        INTEL  CORPORATION                            
//                                                                     
//                     2200 MISSION COLLEGE BLVD                        
//                                                                      
//               SANTA  CLARA,  CALIFORNIA  95052-8119                  
//                                                                      
/////////////////////////////////////////////////////////////////////////////////////
//
// 		File Name: scheduler_packet.c
// 
// 		Purpose:	  DRR scheduler for Egress Sausalito
//
/////////////////////////////////////////////////////////////////////////////////////
//
// 		History:
//
// 		Date			Comment										By
//		---------------------------------------------------------------------------
//
//		03/17/02		Created										Uday Naik
//
/////////////////////////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////////////////////////////////////////
// 
// This scheduler runs on the egress Sausalito. It is a frame or packet based 
// scheduler. It implements WRR scheduling on the ports and DRR on the queues
// within a port. Currently 16 ports and 6 classes per port are supported
//
// 
// The following are the issues with implementing DRR under the IXP2400 environment
// 
//      1)  The packet size is not available for a queue when it is being scheduled.
//          Once the dequeue is issued, the packet size is received N beats later 
//          where each beat is 88 cycles.
//
//      2)  The bit vector with information on which queues have data may not be
//          current. This could mean that a dequeue is issued on a queue that has
//          no data
//
//      3)  The scheduler schedules a packet every beat (88 cycles). This means that
//          for large packets, the scheduler is running faster than the transmit
//
//      4)  The exact packet size is not available. The packet size is in multiples 
//			of CHUNK_SIZE which is MTU/128. The queue credit increments are also 
//			given in CHUNK_SIZE units.
//
//      5)  During a DRR round, a queue may go empty or come alive. While a queue is
//          empty it should not receive credit. But queues that frequently go empty
//          should not affect the bandwidth allocation.
//
// The above issues are worked around with modifications to DRR as follows
//    
//      1) We use a scheme of negative credits. The criteria for a queue to be 
//		   eligible to send is that it has data, flow control is off on the port and 
//		   the credits for the queue are positive. A packet is transmitted from a 
//		   queue if it meets the above criteria. Once the packet length is received, 
//		   (N beats later), the packet length is decremented from the current credit 
//		   of the queue. When the current credit of the queue goes negative, it can 
//		   no longer transmit. When all the queues on a port go negative, one DRR 
//		   round is over. Each queue gets another round of credit at this point. 
//		   To ensure that all the queues are schedulable with one round of credit, 
//		   we need to keep the minimum quantum for a queue as (N *  MTU)/CHUNK_SIZE.
//
//      2) If a dequeue is issued on a queue that has no data, then the QM returns 
//		   the packet size as 0. This is treated as a special case. The scheduler 
//		   will run slightly faster than the QM to allow it to make up for lost slots.
//
//      3) If the queue between TX and QM gets full due to large packets or because 
//		   the scheduler is running slightly faster, then the QM will not dequeue the
//		   packet and instead return a 0 for the packet size.
//
//      4) The algorithm will round robin among ports first and queues next. i.e. if 
//         queue i of port j is scheduled, the next queue scheduled will be queue k 
//		   of port j + 1. When the scheduler comes back to port j, the next queue 
//		   scheduled in port j will be port i+1. This increases the probability that 
//		   the packet length is back by the time the queue 
//  
//      5) While a queue is empty or flow control is on, it's credit remains 
//		   untouched. If a queue comes alive in the middle of a round, it is allowed 
//		   to participate right away with the available credit. 
//         
//  
/////////////////////////////////////////////////////////////////////////////////////

// include file system.h for system constants

#include "dl_system.h"

// header file with constants for algorithm

#include "scheduler_packet.h"

// include the queue manager message handling code 

#include "scheduler_qm.c"

/////////////////////////////////////////////////////////////////////////////////////
// 
// SchedulerGetCreditIncrement()
//
// Description:
// 	
//		Get the DRR credit quantum for a given queue. This should be read from SRAM  
//		control block. For now we will simply use a fixed number equal to the minimum 
//		credit increment
// 
//
/////////////////////////////////////////////////////////////////////////////////////
	
INLINE uint32_t SchedulerGetCreditIncrement (uint32_t queueId)
{

	// return the minium credit increment 

	return (MTU/8)/ (1 << BITS_FOR_PACKET_LENGTH);

}

/////////////////////////////////////////////////////////////////////////////////////
// 
// SchedulerGetPortWeight()
//
// Description:
// 	
//		 Get the WRR weight for a given port. This should be read from a SRAM control 
//		 block. For now use a fixed value
//
/////////////////////////////////////////////////////////////////////////////////////
	
INLINE uint32_t SchedulerGetPortWeight(uint32_t portNumber)
{

	//. For now we will set this up as 1 for every port

	return 1;

}

////////////////////////////////////////////////////////////////////////////////////////////
//
// SendSignal()
//
// Send a signal to a specific thread 
//
////////////////////////////////////////////////////////////////////////////////////////////

INLINE void SendSignal(int context, uint32_t signalNumber)
{

	uint32_t value;

	// set up the signal in bits 3..6 and the context in bits 0..2

	value = context | (signalNumber << 3);
	
	// write into local csr to send a signal 
		
	local_csr_write(local_csr_same_me_signal, value); 	

}

/////////////////////////////////////////////////////////////////////////////////////////

#ifdef UNIT_TEST

INLINE    void InitScratchRing()				
{																
																		
	SIGNAL	sig_cw1, sig_cw2, sig_cw3;												
																		
	__declspec(sram_write_reg) uint32_t base;						
	__declspec(sram_write_reg) uint32_t head;						
	__declspec(sram_write_reg) uint32_t tail;						
																		
	// set head and tail to 0											
																		
	head = tail = 0;													
																		
	// set base and size. size is in bits 30 and 31						
																		
	base = EGRESS_SCHEDULER_DEQUEUE_RING_BASE | (EGRESS_SCHEDULER_DEQUEUE_RING_SIZE << 30);								
																		
	cap_write(&base, EGRESS_SCHEDULER_DEQUEUE_RING_BASE_ID, 1, sig_initiator, sig_done, &sig_cw1);	
	cap_write(&head, EGRESS_SCHEDULER_DEQUEUE_RING_HEAD_ID, 1, sig_initiator, sig_done, &sig_cw2);	
	cap_write(&tail, EGRESS_SCHEDULER_DEQUEUE_RING_TAIL_ID, 1, sig_initiator, sig_done, &sig_cw3);	
																		
	// wait for all signals												
																		
	wait_for_all(&sig_cw1, &sig_cw2, &sig_cw3);										
																		
}													

#endif

/////////////////////////////////////////////////////////////////////////////////////////
//
// InitRings()
//
// Initialize the scratch ring used to send dequeue requests to the Queue Manager
// and the next neighbor ring used to receive QM messages from the Queue Manager
//
/////////////////////////////////////////////////////////////////////////////////////////

INLINE void SchedulerInitRings(void)
{


#ifdef UNIT_TEST

	InitScratchRing();
	
#endif
	
	//
	// Set up the CTX_ENABLES local csr for NN ring 
	// 
	// bit 20 NN_MODE = 0 : next neighbor register are written
	//                                      by previous ME 
	//
	// bits [19:18] NN_RING_EMPTY = 0 : NN_EMPTY asserts when
	//                                                  NN_PUT == NN_GET
	//													(default)
	// bits [15:8] CTX enables for contexts 0 to 7
	//
		
   local_csr_write(local_csr_ctx_enables, 0xff00); 	

}

/////////////////////////////////////////////////////////////////////////////////////
// 
// SchedulerInit()
//
// Description:
// 	
//		 Initialize the DRR scheduler
// 
//
/////////////////////////////////////////////////////////////////////////////////////

INLINE SchedulerInit(void)
{

	uint32_t 	i;

	// This portMask is used to control the round robin scheduling among ports
	// Initialize it to all 1's. Also initialize credit vector to all 1's.

	globalPortMask = globalPortCreditVector = ~0;

	// Initialize the port empty vector to 0. All ports are initially empty

	globalPortEmptyVector = 0;

	// Initialize the port data structures 

	for (i = 0; i < NUMBER_OF_PORTS; i++)
	{

		globalPorts[i].portScheduleVector 	= ~0;
		globalPorts[i].portCurrentQueueMask = ~0;
		globalPorts[i].portQueueEmptyVector	=  0;
		globalPorts[i].portPacketsScheduled =  0;
		globalPorts[i].portWeight 			= SchedulerGetPortWeight(i);
		globalPorts[i].portCurrentCredit 	= SchedulerGetPortWeight(i);

	}

	// Initialize the queue data structures

	for (i = 0; i < NUMBER_OF_PORTS * NUMBER_OF_QUEUES_PER_PORT; i++)
	{

		globalQueues[i].queueCurrentCredit   = SchedulerGetCreditIncrement(i);
		globalQueues[i].queueCreditIncrement = SchedulerGetCreditIncrement(i);

	}

	// Set up the registers for next neighbor and scratch rings

	SchedulerInitRings();

}

/////////////////////////////////////////////////////////////////////////////////////

INLINE uint32_t GetPacketsTransmitted(uint32_t	portNumber)
{

	uint32_t	packetsTransmitted;

	// Set the index register to xfer register holding packet transmit count 

	local_csr_write(local_csr_t_index, portNumber << 2);

	__asm alu [packetsTransmitted, --, B, *$$index];

	return packetsTransmitted;

}

/////////////////////////////////////////////////////////////////////////////////////
// 
// schedule[]
//
// Description:
// 	
//		Schedule a packet. When we exit this macro, we will always have scheduled a 
//		packet.  If no data is available on any port, we will loop inside this same 
//		macro. This will ensure that every time we execute this macro we will 
//		execute atleast 37 instructions. This is important for reuse of the xfer 
//		register
//
// 
//
/////////////////////////////////////////////////////////////////////////////////////


INLINE void Schedule 
(
	__declspec(sram_write_reg)  uint32_t 	*deqMessage,
	SIGNAL									*sig_scratch, 
	SIGNAL									*sig_wait,
	uint32_t								sigAction
)
{

	uint32_t	portNumber;
	uint32_t	queueNumber;
	uint32_t	tempVector;
	uint32_t	maskedTempVector;
	uint32_t	queueId;
	uint32_t	deqMessageTemp;
	__declspec(local_mem shared aligned(32))	port_t 			*pPort;

	do
	{


		// AND the port empty vector and the port credit vector

		tempVector = globalPortEmptyVector & globalPortCreditVector;

		// AND in the port mask

		maskedTempVector = tempVector & globalPortMask;

		// check if the result has any bit set 

		if (maskedTempVector)
		{
		
			// find the first bit set to get the port number

			portNumber = ffs(maskedTempVector);

		}
		else
		{
		
			// otherwise check if any port has data and credit 

			if (tempVector)
			{

				portNumber = ffs(tempVector);

			}
			else
			{
		
				// otherwise if any port has data then reset the credit vector

				if (globalPortEmptyVector)
					globalPortCreditVector = ~0;
				
				// Now swap out and continue

				ctx_swap();
				continue;
			}
		}


		// Get the pointer to the port structure 

		pPort = &globalPorts[portNumber];

		// Decrement the credit. If the port does not get selected because of flow
		// control reasons then it loses this turn and no port is scheduled for the
		// beat

		pPort->portCurrentCredit--; 

		// Check if the credit for the port has gone to 0

		if (pPort->portCurrentCredit == 0)
		{

			// Clear the bit in the port credit vector 

			globalPortCreditVector &= ~(1 << portNumber);
		
			// Give it another round of credit 

			pPort->portCurrentCredit += pPort->portWeight;
		
			// if there is no port schedulable reset the credit vector 

			if ((globalPortCreditVector & globalPortEmptyVector) == 0)
				globalPortCreditVector = ~0;

		}

		// move the port mask to the next port 

		globalPortMask = (~1) << portNumber;

		// Check the packets in flight. If the packets in flight for this port
		// exceeds the bounds, then the port loses its turn

		if ((pPort->portPacketsScheduled - GetPacketsTransmitted(portNumber)) 
			> MAX_IN_FLIGHT)
		{
			ctx_swap();
			continue;
		}

		
		// Now for this port AND the schedule and empty vector 

		tempVector =	pPort->portScheduleVector & pPort->portQueueEmptyVector;

		// Now and in the queue mask

		maskedTempVector = tempVector & pPort->portCurrentQueueMask;

		// If the result has any bit set then use it to get the queue number 

		if (maskedTempVector)
			queueNumber = ffs(maskedTempVector);
		else
		{
			
			// check if any queue has credit and data

			if (tempVector)		
				queueNumber = ffs(tempVector);
			else
			{
				// if any port has data then reset the schedule vector

				if (pPort->portQueueEmptyVector)
					pPort->portScheduleVector = ~0;

				// Now swap out and continue;

				ctx_swap();
				continue;
			}
		}

		// get the actual queue id for the dequeue request 

		queueId = queueNumber | (portNumber <<NUMBER_OF_BITS_FOR_PORT);
	
		// check if the dequeue request scratch ring is full

		while (inp_state_test(EGRESS_SCHEDULER_DEQUEUE_RING_FULL_VALUE)) 
			ctx_swap();

		// create the message which is basically just the queue id with MSB bit set

		deqMessageTemp = queueId | (1 << 31);

		// copy into write transfer register

		*deqMessage = deqMessageTemp;

		// write the data on the scratch ring. We dont wait for it to finish 

		scratch_put_ring( deqMessage, \
					  	  (volatile __declspec(scratch) void*) (
						  EGRESS_SCHEDULER_DEQUEUE_RING_ID << 2), \
					  	  1, sig_done, sig_scratch);
			
		// Compute the new queue mask for the port 

		pPort->portCurrentQueueMask = (~1) << queueNumber;
		
		// Increment packets scheduled for the port 

		pPort->portPacketsScheduled++;
		
		// Wait for the signal requested by caller function
		
		if (sigAction != SIG_NONE)
			__wait_for_all(sig_wait);
		else
			ctx_swap();
		
		// break out of loop 

		break;

	} while (1);


}



/////////////////////////////////////////////////////////////////////////////////////
//
// The xfer registers 0..15 will be used as a target for a reflector write by 
// the transmit microengine. They will contain the count of the packets transmitted 
// and will be accessed in absolute indexed mode. Since xfer registers 0..15 belong 
// to thread 0 in relative mode, this thread will initialize them to 0 by reading in 
// 0's into these registers initially. It will signal the scheduler thread once done
//
/////////////////////////////////////////////////////////////////////////////////////

INLINE void InitReflectorWrite()
{


	SIGNAL												sig_sramRead;

#if(TX_PHY_MODE == SPHY_1_32)

	// Initialize the transfer registers 

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

#else

#ifdef TX_IN_UCODE

	// Initialize the transfer registers 


	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count0, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count1, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count2, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count3, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count4, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count5, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count6, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count7, 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 1, 
			  ctx_swap, &sig_sramRead);

#else	
	// Initialize the transfer registers 

	sram_read_D((__declspec(dram_read_reg) void*) &transmit_count[0], 
			  (volatile __declspec(sram) void*) SRAM_ZERO_BLOCK, 16, 
			  ctx_swap, &sig_sramRead);

#endif
#endif
	
	// signal the scheduler thread 

	SendSignal(1, __signal_number(&sig_prevThread));

#ifdef TX_IN_UCODE

__implicit_read((void *) &transmit_count0);
__implicit_read((void *) &transmit_count1);
__implicit_read((void *) &transmit_count2);
__implicit_read((void *) &transmit_count3);
__implicit_read((void *) &transmit_count4);
__implicit_read((void *) &transmit_count5);
__implicit_read((void *) &transmit_count6);
__implicit_read((void *) &transmit_count7);

#endif

	// this thread dies
	ctx_wait(kill);

}

/////////////////////////////////////////////////////////////////////////////////////

INLINE void Scheduler(void)
{

	SIGNAL	sig_deq1, sig_deq2, sig_deq3, sig_deq4;

	__declspec(sram_write_reg)  uint32_t deq1, deq2, deq3, deq4;

	// wait for signal from previous thread
		
	__wait_for_all(&sig_prevThread);

	// Initialize the scheduler 

	SchedulerInit();

	// Call the schedule function 4 times to kickstart
	
	Schedule(&deq1, &sig_deq1, 0, SIG_NONE);
	Schedule(&deq2, &sig_deq2, 0, SIG_NONE);
	Schedule(&deq3, &sig_deq3, 0, SIG_NONE);
	Schedule(&deq4, &sig_deq4, 0, SIG_NONE);
	
	// In a loop call the schedule function 

	while (1)
	{

		Schedule(&deq1, &sig_deq1, &sig_deq2, SIG_DONE);
		Schedule(&deq2, &sig_deq2, &sig_deq3, SIG_DONE);
		Schedule(&deq3, &sig_deq3, &sig_deq4, SIG_DONE);
		Schedule(&deq4, &sig_deq4, &sig_deq1, SIG_DONE);

	}

}
	

/////////////////////////////////////////////////////////////////////////////////////
//
// main() : Code execution starts here
//
///////////////////////////////////////////////////////////////////////////////////// 

void main(void)
{

	uint32_t 	context;		// the thread number 

	// Get current context.

	context = (local_csr_read(local_csr_active_ctx_sts)) & 0x7;

	// Switch based on thread number. Each function is an infinite loop 

#ifdef TX_IN_UCODE

	__assign_relative_register((void *) &transmit_count0, 0);
	__assign_relative_register((void *) &transmit_count1, 1);
	__assign_relative_register((void *) &transmit_count2, 2);
	__assign_relative_register((void *) &transmit_count3, 3);
	__assign_relative_register((void *) &transmit_count4, 4);
	__assign_relative_register((void *) &transmit_count5, 5);
	__assign_relative_register((void *) &transmit_count6, 6);
	__assign_relative_register((void *) &transmit_count7, 7);

#endif

	switch (context)  
	{

		case 0:	
			
				InitReflectorWrite();
				break;

		case 1: 
				
				// Schedule a port and queue 

				Scheduler();
				break;

		case 2:
				
				// handle queue manager messages

				SchedulerQmMessageHandler();
				break;
		
		default:
				
				// All other threads should simply abort 

				ctx_wait(kill);
				break;

	}

}

/////////////////////////////////////////////////////////////////////////////////////	

