/* RLPOS: RLPotter Operating System Version 0.8 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.8 April 29, 2003: - made mutexes dynamic, and much more powerfull and complete. I had been calling them semaphores, but they've evolved into mutexes. - 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 (*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 }; // mutex control block typedef struct mutex_block { unsigned char id; // ID of mutex instance unsigned char type; // COM1, ATD1, etc char name[5]; // Name of mutex enum mutex_state state; // State (busy, free...) signed char owner; // Current mutex owner signed char queue[3]; // Tasks waiting on mutex unsigned char queue_ptr; // Next free spot in queue }; struct task_block *task[MAXTASKS]; struct mutex_block *mutex[MAXMUTEXES]; 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 mutex ID", // error 6 "illegal mutex 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 }; void main(void) { // 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 mutex #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) } // end main() #pragma interrupt_handler RTI_handler() void RTI_handler(void) { //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 some kernel mods/compilations!! */ asm("JMP $116A"); } void kernel_init(void) { // LOCAL VARIABLES extern int _bss_end, _textmode; int i; // START/INITIALIZE the os // KDB_TRACE Section 1 // initialize globals _textmode = 1; // maps '\n' to "CR/LF" for Windows terminals // 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 // print the opening comments puts("rlpOS v0.8\n\n"); // create the shell if (create_task("shell", &shell, 0, PENDING, DEFAULT_SHELL_STACK_SIZE) < 0) puts("shell failure"); #ifdef KDB_TRACE_LEVEL_1 if (kdb_trace) puts("kDB1-2"); #endif if (create_task("idleTask", &idleTask, 255, PENDING, 32) < 0) puts("idleTask failure"); #ifdef KDB_TRACE_LEVEL_1 if (kdb_trace) puts("kDB1-2"); #endif // create the COM1 mutex if (create_mutex(COM1, "COM1") < 0) puts("COM1 failure"); #ifdef KDB_TRACE_LEVEL_1 if (kdb_trace) puts("kDB1-1"); #endif system_tick = 0; current = 0; cop_cycle = 0; COP_PET(0x55); COP_PET(0xAA); /*// create the sysInit task if (create_task("sysInit", &sysInit, 0, PENDING, 0) < 0) puts("sysInit failure"); #ifdef KDB_TRACE_LEVEL_1 if (kdb_trace) puts("kDB1-1"); #endif*/ sysInit(); } // KERNEL FUNCTIONS int if_task_exists(unsigned char id) { if (task[id] == NULL) return -1; else return task[id]->id; } 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; } char *get_mutex_name(unsigned char id) { if (mutex[id] == NULL) return NULL; else return mutex[id]->name; } int get_mutex_state(unsigned char id) { if (id < MAXMUTEXES) { return mutex[id]->state; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[6]); #endif return -1; } } int get_mutex_owner(unsigned char id) { if (id < MAXMUTEXES) { return mutex[id]->owner; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[6]); #endif return -1; } } int get_mutex_queuelen(unsigned char id) { if (id < MAXMUTEXES) { return mutex[id]->queue_ptr; } else { #ifdef KERNEL_ERROR_MSG puts(error_src[1]); puts(error_msg[6]); #endif return -1; } } int create_mutex(unsigned char type, char *name) { // LOCAL VARIABLES unsigned char id; INTR_OFF(); // critical section // determine if the type already exists for (id=0; idtype == type) { INTR_ON(); return 0; } } // determine lowest free id available to assign to this task for (id=0; idid = id; mutex[id]->type = type; strcpy(mutex[id]->name, name); mutex[id]->state = NOTBUSY; mutex[id]->owner = -1; mutex[id]->queue[0] = -1; mutex[id]->queue[1] = -1; mutex[id]->queue[2] = -1; mutex[id]->queue_ptr = 0; } INTR_ON(); return id; } int get_mutex(unsigned char type) { /* Gives a mutex to a requesting task. returns the mutex id number (0,1,2,...) if free. otherwise returns -1. At this point, mutexs are a procedural control that the tasks have to follow to avoid resource contention. There is no kernel control over resources yet. */ // LOCAL VARIABLES int id; // critical section INTR_OFF(); // find id for mutex type for (id=0; idtype == type) break; } // GET mutex // if mutex isn't taken, give it to the requesting task if (mutex[id]->state == NOTBUSY) { mutex[id]->state = BUSY; mutex[id]->owner = current; INTR_ON(); return id; } // mutex is taken/busy so make task wait else { // put waiting task into the mutexs queue if there's space if (mutex[id]->queue_ptr < 3) { task[current]->message |= STATE_FLAG; task[current]->message_data[STATE_BOX] = WAITING; mutex[id]->queue[mutex[id]->queue_ptr] = current; mutex[id]->queue_ptr++; INTR_ON(); asm("JMP $116A"); return 0; } else { // can't give mutex INTR_ON(); return -1; } } } int give_mutex(char id) { /* Takes a mutex back from a task. will (eventually) pass a message to a waiting task. At this point, mutexs 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(); // if any tasks are waiting on the mutex, give it to them if (mutex[id]->queue_ptr > 0) { //mutex[id]->state = BUSY; mutex[id]->owner = mutex[id]->queue[0]; // take the receiving task out of the waiting state task[current]->message |= STATE_FLAG; task[current]->message_data[STATE_BOX] = RUNNING; // shift the queue mutex[id]->queue[0] = mutex[id]->queue[1]; mutex[id]->queue[1] = mutex[id]->queue[2]; mutex[id]->queue_ptr--; INTR_ON(); return 0; } else { // return the mutex mutex[id]->state = NOTBUSY; mutex[id]->owner = -1; 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; unsigned long int slop; time_tick = ((hours*3600)+(minutes*60)+(seconds)) * TIME_TICKS_PER_SECOND; slop = time_tick / 40; time_tick += slop; } int get_free_memory(void) { // LOCAL VARIABLES char *memory, *last; int i; INTR_OFF(); // check for largest free memory block for (i=0; i