/*	RLPOS: RLPotter Operating System

		   Version 0.5.2 for the 68HC12D60A microcontroller
		   by Ryan Potter
		   ryan@rlpotter.com
	
	
	v0.5.2:
		   - make task_block dynamic
		   
	v0.5.1:
		   - implemented task msg box system for states in kernel
		   - implemented COP watchdog reset timer
		   - added system resources to semlib
		   - added skeleton ISR_handler() code for the other interrupts
		   - implemented _HC12Setup.c to initialize/harden the system
		   
	v0.5 April 16, 2003:
		   - added priority preemption
		   - finished kernel task state switcher 
		   - it is now officially a legitimate
		   	 	rate monotonic
				priority preemptive
				multitasking
				Real Time Operating System  :)
		   - made kernel.c and semlib.c consistent with the 
		   	 rest of the kernel
		   - included the early framework for a msg box system
			 
	v0.4 April 13, 2003: 8096 bytes
		   - gerneralized the shell command-line input parser: 
		   	 cmd <arg1, arg2, ... argn> (up to 32 chars)
		   - added kernel and shell functions
		   
	v0.3 April 12, 2003: 6155 bytes
		   - added a beginning shell user interface.
		   - added a resource control block and basic semaphore functions.
		   - added basic kernel functions
		   
	v0.2 April 9, 2003:
	  	   - able to round-robin with RTI interrupt.
		   - bonified/certified multitasking with 3 tasks. :)
		   
	v0.1 April 3, 2003:
	  	   - able to round-robin without interrupts.
		   - not multitasking, really.
		   
	v0.0 started April 1, 2003; 0 bytes
		   - no idea where to start.
		   - don't want to look at anyone else's work. ;0)



	
	Architecture/C assumptions:
		1) 'D' register is the accumulator
		2) 'X' register points to the top of the current stack
		3) 'Y' register is for general use in indexed operations
		4) the heap grows upward and mem segments allocated by malloc,
		   realloc, and calloc are linear and contiguous.
		5) the stack grows downwards, and there is no boundary checking.
		   
	Potential problem areas:
		1) 'running' section of the kernel get's compiled using extra
		   push and pop instructions
		2) run out of ram (global + stack + heap)
		
*/
	
	
	
#include <912d60.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"




// FUNCTION PROTOTYPES
void RTI_handler(void);
void sysInit(void);
void shell(void);
void sysTime(void);
void (*task_ptr[NUMTASKS])(void);




// GLOBAL VARIABLES
unsigned char *main_frame_ptr = NULL;	   	// bottom of main() frame
unsigned char *main_frame_x_ptr = NULL;		// top of main() frame (x-reg ptr)
unsigned char *temp_task_frame_ptr = NULL;	// temp CCR pointer for RTI


extern unsigned int current;		   	 		// current task id number
extern unsigned long int system_tick;


// task control block
extern struct task_block {
	 	void (*address)();			   		// Address of the task
		unsigned char id; 			  		// ID of task
		char name[9];	  					// Name
		enum task_state state;				// State
		unsigned char priority;				// Priority
		unsigned long int period_tick;		// for determining if deadline is up
		unsigned int interrupt_msg_box;		// flags for pending interrupts
		enum message_box message;			// misc flags
		unsigned char message_data[2];		// data for misc_msg_box flags
	 	unsigned char *heap_ptr;			// heap addr while not current task
		unsigned int heap_size;				// heap size
		unsigned char *frame_ptr;			// CCR pointer
		};


// resource control block
extern struct resource_block {
		unsigned char id;	   				// ID of resource
		char name[5];	  					// Name of resource
		enum resource_state state;			// State (busy, free...)
		unsigned char owner;				// Current resource owner
		signed char queue[3];				// Tasks waiting on resource
		unsigned char queue_ptr;			// Next free spot in queue
		};
		
		
extern struct task_block *task[NUMTASKS];
extern struct resource_block resource[NUMRESOURCES];


extern char error_msg[9][25];
extern char error_src[5][18];


extern char kdb_trace;





main() {

	 // LOCAL VARIABLES
	 int result, id;
	 unsigned int i, addr, temp_heap_size;
	 unsigned char *temp_heap_ptr, temp, priority_check, cop_cycle;
	 unsigned int deadline;
	 
	 extern int _bss_end, _textmode;		   	 		
	  
	 _textmode = 1;	 	  // maps '\n' to "CR/LF" for Windows terminals
	 current = 0;  	 	  // start the shell first
	 cop_cycle = 0;		  // fresh watchdog
	 
	 
	 // initialize the task block
	 for (i=0; i<NUMTASKS; i++)
	 	 task[i] = NULL;
		 
		 
	 // initialize the rcb pointer block
	 for (i=0; i<NUMRESOURCES; i++) {
	 	 resource[i].id = i;
		 resource[i].state = NOTBUSY;
		 resource[i].owner = 255;
		 resource[i].queue_ptr = 0;
		 }

	 
	 
 	 // save SP value for use in the RTI
	 asm("TFR s,d");
	 asm("STD _main_frame_ptr");
	 // save X reg value for use in the RTI
	 asm("TFR x,d");
	 asm("STD _main_frame_x_ptr");
	 
	 
	 
	 
	 // START/INITIALIZE the os3
		// KDB_TRACE Section 1
	 
	 // create and initialize the heap (for dynamic (runtime) var allocation)
	 _NewHeap(&_bss_end, (char *)(&_bss_end) + (INITIAL_HEAP_SIZE));
	 
	 
	 // set up the serial port
	 setbaud(BAUD38K);	 	  // actually running at 19K baud due to xtal speed
	 #ifdef KDB_TRACE_LEVEL_1
		if (kdb_trace) puts("kDB1-0");	 
	 #endif
	 

	 // create the sysInit task
	 if ((id = create_task("sysInit", &sysInit, 0)) >= 0) {
	 	task_ptr[id] = sysInit;
	 	task[id]->state = PENDING;
		}
	 else {
		if (KERNEL_ERROR_MSGS)
			printf("%s %s\n", error_src[0], error_msg[8]);
	      }
	#ifdef KDB_TRACE_LEVEL_1
		if (kdb_trace) puts("kDB1-1");	 
	 #endif 


		
	 // create the shell
	 if ((id = create_task("shell", &shell, 1)) >= 0)
	 	set_task_state(id, PENDING);
	 else {
		if (KERNEL_ERROR_MSGS)
			printf("%s %s\n", error_src[0], error_msg[8]);
	      }
	#ifdef KDB_TRACE_LEVEL_1
		if (kdb_trace) puts("kDB1-2");	 
	 #endif

		
		
	 // create sysTime
	 if ((id = create_task("sysTime", &sysTime, 5)) >= 0)
	 	set_task_state(id, STOPPED);
	 else {
		if (KERNEL_ERROR_MSGS)
			printf("%s %s\n", error_src[0], error_msg[8]);
	      }
	#ifdef KDB_TRACE_LEVEL_1
		if (kdb_trace) puts("kDB1-3");	 
	 #endif
	 

	 
	 // treat the COP and system tick
	 COP_PET(0x55);
	 COP_PET(0xAA);
	 system_tick = 0;
	 
	 
	 // print the opening comments
	 puts("rlpOS v0.5.2\n\n");

	 

	 
	 // MULTITASKING KERNEL: Priority Preemptive, Real Time, multitasking 
		// KDB_TRACE Section 2
	 while(1) {	 
	 	
		/* REENTRY POINT after either 
		   		   1) task finishes, or 
		   		   2) RTI
		*/
		#ifdef KDB_TRACE_LEVEL_1
			if (kdb_trace) puts("kDB2.1");	 
		#endif
		   
		   		
		/* No interrupts allowed inside of main().  Only allowed
		   inside of non-critical sections of tasks */ 
	 	INTR_OFF();	
		
		
		// PET THE DOG: cop watchdog reset timer (pet freq = 2x COP freq)
		if (cop_cycle == 4) {
		   COP_PET(0x55);
		   COP_PET(0xAA);
		   cop_cycle = 0;
		   }
		else
			cop_cycle++;
		#ifdef KDB_TRACE_LEVEL_1
			if (kdb_trace) puts("kDB2.2-1");	 
		#endif
		
		
		
		
		// CHANGE STATES ACCORDING TO MESSAGES
		// 'waiting' needs to have highest precedence here
		for (i=0; i<NUMTASKS; i++) {
			if (task[i]->message && STATE_FLAG) {  
			   // set task state to what the message says
		   	   task[i]->state = task[i]->message_data[STATE_BOX];
			   // clear the STATE_FLAG
			   task[i]->message &= ~(STATE_FLAG);
			   #ifdef KDB_TRACE_LEVEL_2
				if (kdb_trace) puts("kDB2.3-2a");	 
			   #endif
			   }
			#ifdef KDB_TRACE_LEVEL_2
				if (kdb_trace) puts("kDB2.3-2b");	 
			#endif
			}
		#ifdef KDB_TRACE_LEVEL_1
			if (kdb_trace) puts("kDB2.3-1");	 
		#endif
		
			

		// RT PRIORITY BLOCK:
		// set the current task id based on priority and deadline
		
		// determine if the deadline is up for idle tasks
		/* deadline, is equal to period plus an initial time (t0) reference
		   (t0 = system_tick).  Period = priority + 1.  
		   Changing the state from idle to PENDING occurs here.  */
		for (i=0; i<NUMTASKS; i++) {
			if (task[i]->state == IDLE) {
			   deadline = task[i]->period_tick + (task[i]->priority + 1);
			   if (system_tick >= deadline) {
			   	  task[i]->state = PENDING; 	  // change state at deadline
				  #ifdef KDB_TRACE_LEVEL_2
					if (kdb_trace) puts("kDB2.4-2");	 
				  #endif
			   	  } 	
				}
			 // if (task[i]->message) {} 		
			 }
		#ifdef KDB_TRACE_LEVEL_1
			if (kdb_trace) puts("kDB2.4-1");	 
		#endif			
		
		
		// set current = to highest priority pending/running task.
		priority_check = 255;   	  	  // lowest possible
		
		for (i=0; i<NUMTASKS; i++) {
			if (!(task[i] == NULL)) {
			   if ((task[i]->state == PENDING) || (task[i]->state == RUNNING)) {
			   	  if (task[i]->priority <= priority_check) {
				  	 current = i;
				 	 priority_check = task[i]->priority;
				 	 #ifdef KDB_TRACE_LEVEL_2
						if (kdb_trace) puts("kDB2.5-2");	 
				  	 #endif 
			   	 	 }
			   	   }
				}
			 }
		#ifdef KDB_TRACE_LEVEL_1
			if (kdb_trace) puts("kDB2.5-1");	 
		#endif
		
		
		
		// DISPATCH, or otherwise deal with the current task
			// KDB_TRACE Section 3

		#ifdef KDB_TRACE_LEVEL_2
		if (kdb_trace) {
			printf("task[%d]->state = %d\n", current, task[current]->state);
			printf("task[%d]->prior = %d\n", current, task[current]->priority);
			}
		#endif
		
		switch (task[current]->state) {
			   case IDLE:			   // skip task
					#ifdef KDB_TRACE_LEVEL_1
						if (kdb_trace) puts("kDB3.1-1");
					#endif
					break;
			   case PENDING:		   // ready and waiting to run
			   		task[current]->state = RUNNING;
					task[current]->period_tick = system_tick;
					#ifdef KDB_TRACE_LEVEL_1
						if (kdb_trace) puts("kDB3.2-1");
					#endif
					#ifdef KDB_TRACE_LEVEL_2
						if (kdb_trace) {
							//puts("kDB3.2-2");
							putchar('S');
							putchar(current+48);
							putchar('\n');
						    }
					#endif  
			   		(*task_ptr[current])();	 	  			// start the task
					task[current]->state = IDLE;			// task finished
					#ifdef KDB_TRACE_LEVEL_1
						if (kdb_trace) puts("kDB3.3-1");
					#endif
					#ifdef KDB_TRACE_LEVEL_2
						if (kdb_trace) {
							//puts("kDB3.3-2");
							putchar('S');
							putchar(current+48);
							putchar('\n');
						    }
					#endif  				
			   		break;
			   case RUNNING:			// interrupted. continue running.
					// restore the task heap to the stack
					#ifdef KDB_TRACE_LEVEL_1
						if (kdb_trace) puts("kDB3.4-1");
					#endif
					#ifdef KDB_TRACE_LEVEL_2
						if (kdb_trace) {
							//puts("kDB3.4-2");
							putchar('R');
							putchar(current+48);
							putchar('\n');
						    }
					#endif  
					temp_heap_size = task[current]->heap_size;
					temp_heap_ptr = task[current]->heap_ptr;
			   		for (i=0; i<temp_heap_size; i++) {
						temp = *(temp_heap_ptr + i);
						*(main_frame_ptr - temp_heap_size + i) = temp;
						//putchar('.');
						}
					
					// restore context and run
					temp_task_frame_ptr = task[current]->frame_ptr;
					asm("LDS _temp_task_frame_ptr");
					asm("RTI");
			   		break;
			   case WAITING:   		   // waiting on a resource
					#ifdef KDB_TRACE_LEVEL_1
						if (kdb_trace) puts("kDB3.5-1");
					#endif
			   		break;
			   case STOPPED:		   // done running until later
					#ifdef KDB_TRACE_LEVEL_1
						if (kdb_trace) puts("kDB3.6-1");
					#endif
			   		break;
			   default:	  			   // shouldn't happen, but, error if so.
			   		puts("KERNEL: task state error\n");
			   		exit(1);
				}	// end switch
		
	 		}		// end while(1)
	 
	 return 0;
	 
}			 		// end main()



#pragma interrupt_handler RTI_handler()

void RTI_handler(void) {

	 size_t frame_size;
	 unsigned int i;
	 unsigned char *local_thp;
	 
	 
	 //ACKNOWLEDGE THE INTERRUPT
	 INTR_OFF();  	   	// redundant. automatically done by the processor
	 RTIFLG = 0x0080;  	// acknowledge/clear the interrupt
	 
	 
	 #ifdef KBD_TRACE_LEVEL_1
	  	if (kbd_trace) putchar('.');
	 #endif
	 
	 
	 // SET THE FRAME POINTER for the interupted task.
	 // should point to the CCR entry on the stack.
	 asm("TFR x,d");  		  	  	  	 // start of RTI stack
	 asm("ADDD #2");				  	 // adjust to CCR stack entry
	 asm("STD _temp_task_frame_ptr");	 // put into task_frame_ptr
	 task[current]->frame_ptr = temp_task_frame_ptr;

	 
	 
	 //PLACE CURRENT TASK CONTEXT ONTO THE HEAP
	 // determine size of heap segment needed
	 frame_size = main_frame_ptr - temp_task_frame_ptr; 	// always positive
	 task[current]->heap_size = (int)frame_size;
	 
	 // reallocate heap memory
	 task[current]->heap_ptr = realloc(task[current]->heap_ptr, frame_size);
	 local_thp = task[current]->heap_ptr;	

	 if (local_thp == NULL) {
		#ifdef KERNEL_ERROR_MSGS
	 		puts("out of heap space!");
		#endif
		exit(0);
	 	}
	 
	 // transfer task frame to the heap one byte at a time.
	 // assumes the heap grows from low to high addr.
	 for(i=0; i<frame_size; i++) {
		*(local_thp + i) = *(temp_task_frame_ptr + i);
	 	}
	 

	 
	 // update system time base
	 system_tick++;
	 
	 
	 
	 // RETURN: simulate a RTS instruction
	 // set stack pointer for main()
	 asm("LDS _main_frame_ptr");
	 asm("LDX _main_frame_x_ptr");
	 /* return to main() at the reentry point
	    and must be adjusted after each kernel mod/compilation!!  */
	 asm("JMP $119C");
	 

	 // return (0)  -- NOT used 
	 /* this ISR should never use the 'return x' command.
	    It doesn't make sense since it always interrupts a Task, but
	    never returns to it... but to main() instead.  */
	 	 
}


