/*	RLPOS: RLPotter Operating System

		   Version 0.5.1 for the 68HC12D60A microcontroller
		   by Ryan Potter
		   ryan@rlpotter.com
	
	
	v0.5.2:
		   - make task_block and resource_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 shell(void);
void task1(void);
void task2(void);
void (*task_ptr[])(void) = {&shell, &task1, &task2};




// 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 {
	 	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[8][25];
extern char error_src[5][18];






main() {

	 // LOCAL VARIABLES
	 int result;
	 unsigned int i, temp_heap_size;
	 unsigned int last_task = 0;
	 unsigned char *temp_heap_ptr, temp, priority_check, cop_cycle;
	 unsigned long int deadline;
	 
	 extern int _bss_end, _textmode;
	 extern unsigned int current;		   	 		
	 extern unsigned long int system_tick;
	  
	 _textmode = 1;	 	  // maps '\n' to "CR/LF" for Windows terminals
	 current = 0;  	 	  // start the shell first
	 cop_cycle = 0;		  // fresh watchdog

	 
	 
	 INTR_OFF();		  // disable all maskable interrupts
	 
	 
	 
	 // initialize the task structures
	 for (i=0; i<NUMTASKS; i++) {
	 	 task[i].id = i;
 	 	 task[i].state = idle;
		 task[i].priority = 255;  	  	  // low priority
		 task[i].period_tick = 0;
		 task[i].heap_ptr = NULL;
		 task[i].heap_size = 0;
		 task[i].frame_ptr = 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;
		 }

		 
	 // debug code:
	 task[0].priority = 100;
	 strcpy(task[0].name, "shell");
	 task[1].priority = 5;
	 strcpy(task[1].name, "task 1");
	 task[2].priority = 3;
	 strcpy(task[2].name, "task 2");
	 
	 
 	 // 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 os
	 
	 // set up the interrupts
	 RTICTL = 0x86;			  	 	// enable RTI at 131.72 msec.
	 RTIFLG = 0x80;					// clear real time interrupt flag
	 
	 // create and initialize the heap (for dynamic (runtime) var allocation)
	 _NewHeap(&_bss_end, (char *)(&_bss_end) + (INITIAL_HEAP_SIZE));
	 for (i=0; i<NUMTASKS; i++)
	 	 task[i].heap_ptr = malloc(50);
	 
	 // set up the serial port
	 setbaud(BAUD38K);	 	  // actually running at 19K baud due to xtal speed
	 
	 // initialize the shell
	 task[0].priority = 100;  // low until serial port is interrupt driven
	 task[0].state = pending;
	 
	 // print the opening comments
	 puts("rlpOS v0.5.1\n\n");

	 

	 
	 // MULTITASKING KERNEL: Priority Premptive
 	 system_tick = 0;
	 COP_ON();
	 
	 while(1) {	 
	 	
		/* REENTRY POINT after either 
		   1) task finishes, or 
		   2) RTI  */
		   
		   		
		/* No interrupts allowed inside of main().  Only allowed
		   inside of non-critical sections of tasks */ 
	 	INTR_OFF();	
		
		
		// PET THE DOG: cop watchdog reset timer
		switch (cop_cycle) {
			   case 0:
			   		cop_cycle++;
					break;
			   case 1:
			   		COP_PET(0x55);
					cop_cycle++;
					//puts("0x55");
					break;
			   case 2:
			   		cop_cycle++;
					break;
			   case 3:
			   		COP_PET(0xAA);
					cop_cycle = 0;
					//puts("0xAA");
					break;
				}
		
		
		   
		last_task=current;
		
		
		
		/* this block is the old Round-Robin way of things:
		if (task[current].state == idle)
		   task[current].state = pending;
		next_task++;     
		if (next_task >= NUMTASKS)
	 	   next_task = 0;
		current = next_task;*/
		
		
		
		// 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);
			   }
			}
			
		
		// 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
				  //puts("promote");
			   	  } 	
				}
			 // if (task[i].message) {} 		
			 }			
		
		// set current = to highest priority pending/running task.
		priority_check = 255;   	  	  // lowest possible
		for (i=0; i<NUMTASKS; i++) {
			if ((task[i].state == pending) || (task[i].state == running)) {
			   //puts("tp1");
			   if (task[i].priority <= priority_check) {
				 current = i;
				 priority_check = task[i].priority;
				 //puts("tp2"); 
			   	 }
			   }
			 }
		
		
		
		// DISPATCH, or otherwise deal with the current task
		
		//printf("task[%d].state = %d\n", current, task[current].state);
		
		switch (task[current].state) {
			   case idle:			   // skip task
					break;
			   case pending:		   // ready and waiting to run
			   		task[current].state = running;
					task[current].period_tick = system_tick;
					/*putchar('S');
					  putchar(current + 48); */
			   		(*task_ptr[current])();	 	  			// start the task
					task[current].state = idle;				// task finished
					/*putchar('F');
					  putchar(current + 48);  */				
			   		break;
			   case running:			// interrupted. continue running.
					// restore the task heap to the stack
					/*putchar('R');
					  putchar(current + 48); */
					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
			   		break;
			   case finished:		   // done running until later
			   		break;
			   default:	  			   // shouldn't happen, but, error if so.
			   		puts("KERNEL: task status error\n");
			   		exit(1);
				}	// end switch
		
	 		}		// end while(1)
	 
	 exit(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
	 
	 
	 //putchar('.');
	 
	 
	 // 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) {
	 	puts("out of heap space!");
		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);
		//putchar('.');
	 	}
	 

	 
	 // 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
	    address must be in decimal!! 
	    and must be adjusted after each compilation!!  */
	 asm("JMP 4570");
	 

	 // 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.  */
	 	 
}


