/*	RLPOS: RLPotter Operating System

		   Version 0.2 for the 68HC12D60A microcontroller
		   by Ryan Potter
		   ryan@rlpotter.com
	
	
	
	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;
		   - 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.
		   
	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)
		3) kernel takes up too large a fraction of the processing time
*/
	
	
	
#include <912d60.h>
#include <stdio.h>
#include <stdlib.h>



/* these must be set correctly for the code 
to operate properly, or at all.  */
#define numtasks 3	 	   		   // number of tasks compiled w/ the kernel
#define initial_heap_size 200	   // best guess for heap initialization



// 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;	   		   // bottom of main() frame
unsigned char *main_frame_x_ptr;		   // top of main() frame (x-reg ptr)
unsigned char *temp_task_frame_ptr;		   // temp CCR pointer for RTI

unsigned int next_task = 0;
unsigned int current = 0;
unsigned int last_task = 0;

enum task_state {idle=0, pending=1, running=2, waiting=3, finished=4};
typedef struct task_block {
	 	unsigned char id; 			  		// ID of task
		enum task_state state;				// State
		unsigned char priority;				// Priority
	 	unsigned char *heap_ptr;			// heap addr while not current task
		unsigned int heap_size;				// heap size
		unsigned char *frame_ptr;			// CCR pointer
		};
struct task_block task[numtasks];

unsigned long int system_tick = 0;




main() {

	 // LOCAL VARIABLES
	 int result;
	 unsigned int i, temp_heap_size;
	 extern int _bss_end;
	 unsigned char *temp_heap_ptr, temp;
	  
	 
	 
	 // INITIALIZE GLOBAL VARIABLES
	 
	 // initialize the task structures
	 for (i=0; i<numtasks; i++) {
	 	 task[i].id = i;
 	 	 task[i].state = pending;
		 task[i].priority = 0;
		 task[i].heap_ptr = NULL;
		 task[i].heap_size = 0;
		 task[i].frame_ptr = NULL;
	 }
	 
 	 // 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
	 
	 // set up the interrupts
	 INTR_OFF();			  	 	// disable all maskable interrupts
	 RTICTL = 0x87;			  	 	// enable RTI at 263.44 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*numtasks));
	 for (i=0; i<numtasks; i++)
	 	 task[i].heap_ptr = malloc(50);
	 
	 // set up the serial port
	 setbaud(BAUD9600);	 	  // actually running at 4800 baud due to xtal speed
	 
	 // print the opening comments
	 puts("rlpOS v0.1\n\n");

	 

	 
	 // MULTITASKING KERNEL: round-robin
	 while(1) {	 
	 	
		/* No interrupts allowed inside of main().  Only allowed
		   inside of non-critical sections of tasks */ 
	 	INTR_OFF();	
		
		
		current = next_task;
		
		// DISPATCH, or otherwise deal with the current task
		switch (task[current].state) {
			   case idle:			   // skip task
					break;
			   case pending:		   // ready and waiting to run
			   		task[current].state = running;
					putchar('S');
					putchar(current + 48);
			   		(*task_ptr[current])();	 	// start the task
					putchar('F');
					putchar(current + 48);
					task[current].state = idle;				// task finished
					putchar('%');
			   		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<task[current].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");
					putchar('&');
			   		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);
		}
		
		
		
		// REENTRY POINT after either 1) task finishes, or 2) RTI
		
		// deal with status setting/resetting here
		if (task[current].state == idle)
		   task[current].state = pending;
		
		
		
		// update task counters
		last_task=current;
		next_task++;
		if (next_task >= numtasks)
	 	   next_task = 0;
		
		   
		
		// diagnostic
		putchar(':');
		
	 }	// end 'while(1)'
	 
	 return 0;
}



#pragma interrupt_handler RTI_handler()

void RTI_handler(void) {

	 size_t frame_size;
	 unsigned int i;
	 
	 
	 //ACKNOWLEDGE THE INTERRUPT
	 INTR_OFF();  	   	// redundant. automatically done by the processor
	 RTIFLG = 0x0080;  	// acknowledge/clear the interrupt
	 putchar('I');
	 putchar(current + 48);
	 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);	

	 if (task[current].heap_ptr == 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++) {
		*(task[current].heap_ptr + i) = *(temp_task_frame_ptr + i);
		//putchar('.');
	 }
	 

	 
	 // update system time base
	 system_tick++;
	 putchar(')');
	 
	 
	 
	 // 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
	 asm("JMP 4647");
	 

	 // 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.  */
	 	 
}

