/*	RLPOS: RLPotter Operating System

		   Version 0.4 for the 68HC12D60A microcontroller
		   by Ryan Potter
		   ryan@rlpotter.com
	
	
	v0.4 April 13, 2004: 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.
		   
	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>
#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
		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
		};


// resource control block
extern struct resource_block {
		unsigned char id;	   				// ID of resource
		enum resource_state state;			// State (busy, free...)
		unsigned char user;					// Current resource user/owner
		signed char queue[4];				// Tasks waiting on resource
		unsigned char queue_pos;			// Next free spot in queue
		};
		
		
extern struct task_block task[numtasks];
extern struct resource_block resource[numresources];






main() {

	 // LOCAL VARIABLES
	 int result;
	 unsigned int i, temp_heap_size;
	 unsigned int last_task = 0;
	 unsigned int next_task = 0;
	 unsigned char *temp_heap_ptr, temp;
	 
	 
	 // INITIALIZE GLOBAL VARIABLES
	 extern int _bss_end, _textmode;
	 extern unsigned int current;		   	 		
	 extern unsigned long int system_tick;
	  
	  
	 _textmode = 1;
	 current = 0;
	 system_tick = 0;
	 
	 
	 // initialize the task structures
	 for (i=0; i<numtasks; i++) {
	 	 task[i].id = i;
 	 	 task[i].state = finished;
		 task[i].priority = 255;
		 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 the os
	 
	 // 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(BAUD38K);	 	  // actually running at 19K baud due to xtal speed
	 
	 // initialize the shell
	 task[0].priority = 0;
	 task[0].state = pending;
	 
	 // print the opening comments
	 puts("rlpOS v0.4\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();	
		
		
		// set the current task id
		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
			   		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");
					//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;
	 unsigned char *local_thp;
	 
	 
	 //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);
	 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++;
	 //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!! 
	 // and must be adjusted after each compilation!!
	 asm("JMP 4640");
	 

	 // 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.  */
	 	 
}


