/* RLPOS: RLPotter Operating System Version 0.7.1 for the 68HC12D60A microcontroller by Ryan Potter ryan@rlpotter.com Compiled with the ImageCraft ICC12 compiler v6.15A - 912D60 Single Chip Mode - printf option: long v0.7.2: + make resources dynamic + add shell cmd: remove <-t|-r> + make an active_tasks[numtasks] array for searching task list v0.7.1: - implemented the TOF_handler interrupt for use with sysTime. brings time resolution from 64 ms to 16 ms. - added kernel funct: get_sysTime(), set_sysTime(); shell cmd: time - now tasks are int f(), and must return a value to the kernel when exiting. kernel doesn't handle it yet, though. - in shell, now names of tasks are shell commands (dynamic). - put all kernel initialization code into kernel_init() - added kernel funct: remove_task() v0.7 April 25, 2003: 11174 bytes: - combined kernel.c & semlib.c with rlpos.c to make one big kernel.c - changed context switch mechanism to give each task a dedicated stack, making the kernel context switches >400% faster. - added a basic timer task that will eventually keep system time/date. v0.6 April 24, 2003: 11042 bytes - made the task block and the task control block dynamic, so that now tasks can create tasks, and can also start/stop them - implemented the sysInit() task for kicking off a user system - implemented a kernel debug command for tracing code execution - implemented small msg box system in kernel for tasks - 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 (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. 6) chars are one byte ints are two bytes longs are 4 bytes 7) only 1536 (1.5k) of the 2k ram are available 8) register base is 0x0000 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 #include #include #include "kernel.h" // FUNCTION PROTOTYPES void RTI_handler(void); int sysInit(void); int shell(void); int idleTask(void); int sysTime(void); int (*task_ptr[MAXTASKS])(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 unsigned int current; // current task id number unsigned long int system_tick; unsigned int cop_cycle; unsigned long int time_tick; // one tick every x microseconds // task control block typedef struct task_block { int (*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 int stack_size; // heap allocated for task stack unsigned char *top_of_stack; // top of task stack in the heap unsigned char *frame_ptr; // CCR pointer in idle stack }; // resource control block typedef 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 }; struct task_block *task[MAXTASKS]; struct resource_block resource[NUMRESOURCES]; int kdb_trace = 1; int kdb_trace_cycle = 0; struct time { char hours; char minutes; char seconds; int milliseconds; }; int exit_status; // global interrupt flags //unsigned int interrupt_flags_ADC; //unsigned int interrupt_flags_TC; // error massages const char error_msg[][25] = {"unimplimented function", // error 0 "syntax error", // error 1 "illegal task ID", // error 2 "illegal task state", // error 3 "illegal task priority", // error 4 "out of memory", // error 5 "illegal resource ID", // error 6 "illegal resource state", // error 7 "cannot create task" // error 8 }; const char error_src[][18] = {"kernel error:", // source 0 "kernel.c error:", // source 1 "kernel RTI error:", // source 2 "shell error:", // source 3 "semlib.c error:" // source 4 }; main() { // LOCAL VARIABLES int result, id, i; unsigned char priority_check; unsigned int deadline; // INITIALIZE the kernel kernel_init(); // 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"); // MULTITASKING KERNEL: Priority Preemptive, Real Time, multitasking // KDB_TRACE Section 2 while(1) { //------------------------------- /* REENTRY POINT after either 1) task finishes, or 2) RTI */ //------------------------------- if (kdb_trace_cycle >= KDB_CYCLES) kdb_trace = 0; #ifdef KDB_TRACE_LEVEL_1 if (kdb_trace) puts("kDB2.1"); #endif /* No RTI 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; //putchar(':'); } #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; imessage && 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; istate == 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; istate == 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 #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[current]->state = RUNNING; task[current]->period_tick = system_tick; temp_task_frame_ptr = task[current]->top_of_stack; asm("LDS _temp_task_frame_ptr"); // set the SP exit_status = (*task_ptr[current])(); // start the task asm("LDS _main_frame_ptr"); // reset the SP 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('F'); putchar(current+48); putchar('\n'); } #endif break; case RUNNING: // interrupted. continue running. // 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 // exit_status HANDLER //... } // 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 RTIFLG = 0x0080; // acknowledge/clear the interrupt #ifdef KDB_TRACE_LEVEL_1 if (kdb_trace) putchar('.'); #endif //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; // update system time base and cop reset counter system_tick++; cop_cycle++; if (time_tick > TIME_TICKS_PER_DAY) { time_tick = 0; } // 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 $117C"); } void kernel_init(void) { // LOCAL VARIABLES extern int _bss_end, _textmode; int i; // START/INITIALIZE the os // KDB_TRACE Section 1 // initialize the rcb pointer block for (i=0; iid; } int get_task_address(unsigned char id) { return (int)task[id]->address; } char get_task_id() { return task[current]->id; } char *get_task_name(unsigned char id) { if (task[id] == NULL) return NULL; else return task[id]->name; } int get_task_state(unsigned char id) { if (task[id] == NULL) return -1; else if (id < MAXTASKS) return task[id]->state; else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[3]); #endif return -1; } } int get_task_messages(unsigned char id) { if (task[id] == NULL) return -1; else if (id < MAXTASKS) return task[id]->message; else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[3]); #endif return -1; } } int get_task_priority(unsigned char id) { INTR_OFF(); if (id < MAXTASKS) { INTR_ON(); return task[id]->priority; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[4]); #endif INTR_ON(); return -1; } } int set_task_state(unsigned char id, unsigned char newstate) { INTR_OFF(); // check id validity if (id < MAXTASKS) { if (task[id] == NULL) { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[2]); #endif INTR_ON(); return -1; } // check newstate validity if ((newstate == RUNNING) || (newstate == WAITING) || (newstate > STOPPED)) { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[3]); #endif INTR_ON(); return -1; } // referenced to the task's current state: switch (task[id]->state) { case IDLE: if (newstate == IDLE) { // no change INTR_ON(); return 0; } else if (newstate == PENDING) { // pass msg to task[id]->msgbox; task[id]->message |= STATE_FLAG; task[id]->message_data[STATE_BOX] = PENDING; INTR_ON(); return 0; } else if (newstate == STOPPED) { // pass msg to task[id]->msgbox; task[id]->message |= STATE_FLAG; task[id]->message_data[STATE_BOX] = STOPPED; INTR_ON(); return 0; } else { // nothing else is legal; #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[3]); #endif INTR_ON(); return -1; } break; case PENDING: if (newstate == PENDING) { // no change INTR_ON(); return 0; } else if (newstate == IDLE) { // pass msg to task[id]->msgbox; #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[0]); #endif INTR_ON(); return -1; } else if (newstate == STOPPED) { // pass msg to task[id]->msgbox; task[id]->message |= STATE_FLAG; task[id]->message_data[STATE_BOX] = STOPPED; INTR_ON(); return 0; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[3]); #endif INTR_ON(); return -1; } break; case RUNNING: if (newstate == STOPPED) { // pass msg to task[id]->msgbox; task[id]->message |= STATE_FLAG; task[id]->message_data[STATE_BOX] = STOPPED; INTR_ON(); return 0; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[3]); #endif INTR_ON(); return -1; } break; case STOPPED: if (newstate == STOPPED) { // no change INTR_ON(); return 0; } else if (newstate == PENDING) { // pass msg to task[id]->msgbox; task[id]->message |= STATE_FLAG; task[id]->message_data[STATE_BOX] = PENDING; INTR_ON(); return 0; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[3]); #endif INTR_ON(); return -1; } break; default: #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[3]); #endif INTR_ON(); return -1; break; } // end switch (task[id]->state) } // if (id < MAXTASKS) else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[2]); #endif INTR_ON(); return -1; } INTR_ON(); return 0; } int set_task_priority(unsigned char id, unsigned char priority) { if (priority == 0) // priority 0 is reserved for the shell priority = 1; if (id >= (MAXTASKS)) { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[4]); #endif return -1; } else if ((priority > 255) || (priority < 0)) { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[4]); #endif return -1; } else { task[id]->priority = priority; return 0; } } int create_task(char *name, int (*addr)(), unsigned char priority, int state, int stack_size) { // LOCAL VARIABLES unsigned char id; INTR_OFF(); // critical section //if (get_free_memory() > 64) { // determine lowest free id available to assign to this task for (id=0; idaddress = addr; task[id]->id = id; strcpy(task[id]->name, name); task[id]->state = state; task[id]->priority = priority; task[id]->period_tick = 0; task[id]->interrupt_msg_box = NULL; task[id]->message = NULL; task[id]->message_data[0] = NULL; task[id]->message_data[1] = NULL; if (stack_size <= 0) stack_size = DEFAULT_STACK_SIZE; task[id]->stack_size = stack_size; task[id]->top_of_stack = malloc(task[id]->stack_size); task[id]->top_of_stack += task[id]->stack_size; task[id]->frame_ptr = NULL; } //} INTR_ON(); return id; } int remove_task(unsigned char id) { if (task[id] == NULL) return -1; free(task[id]->top_of_stack - task[id]->stack_size); free(task[id]); task[id] = NULL; task_ptr[id] = NULL; return 0; } int get_resource_state(unsigned char rid) { if (rid < NUMRESOURCES) { return resource[rid].state; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[6]); #endif return -1; } } int get_resource_owner(unsigned char rid) { if (rid < NUMRESOURCES) { return resource[rid].owner; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[6]); #endif return -1; } } int get_resource_queuelen(unsigned char rid) { if (rid < NUMRESOURCES) { return resource[rid].queue_ptr; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[6]); #endif return -1; } } int get_free_memory(void) { // LOCAL VARIABLES char *memory, *last; int i; INTR_OFF(); // check for largest free memory block for (i=0; imessage |= STATE_FLAG; task[current]->message_data[STATE_BOX] = WAITING; // put waiting task into the resources queue if there's space if (resource[rid].queue_ptr < 3) { resource[rid].queue[resource[rid].queue_ptr] = current; resource[rid].queue_ptr++; } INTR_ON(); return -1; } } int sem_give(char rid) { /* Takes a resource back from a task. will (eventually) pass a message to a waiting task. At this point, semaphores are a procedural control that the tasks have to follow to avoid resource contention. There is no kernel control over resources yet. */ // critical section INTR_OFF(); // return the resource resource[rid].state = NOTBUSY; //resource[rid].owner = NULL; INTR_ON(); return 0; } unsigned long int get_sysTime(void) { // LOCAL VARIABLES unsigned long int slop; // returns the number of milliseconds since midnight (time_tick=0); slop = time_tick / 40; return (time_tick * ms_PER_TIME_TICK + slop); } void set_sysTime(unsigned int hours, unsigned int minutes, unsigned int seconds) { // LOCAL VARIABLES extern unsigned long int time_tick; time_tick = ((hours*3600)+(minutes*60)+(seconds)) * TIME_TICKS_PER_SECOND; }