diff options
Diffstat (limited to 'src-avr/ao_task.c')
-rw-r--r-- | src-avr/ao_task.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/src-avr/ao_task.c b/src-avr/ao_task.c new file mode 100644 index 00000000..3c6b34a4 --- /dev/null +++ b/src-avr/ao_task.c @@ -0,0 +1,347 @@ +/* + * Copyright © 2009 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" + +#define AO_NO_TASK_INDEX 0xff + +__xdata struct ao_task * __xdata ao_tasks[AO_NUM_TASKS]; +__data uint8_t ao_num_tasks; +__data uint8_t ao_cur_task_index; +__xdata struct ao_task *__data ao_cur_task; + +#ifdef AVR + +#define PUSH8(stack, val) (*((stack)--) = (val)) + +static void +ao_init_stack(__xdata struct ao_task *task, void (*start)(void)) +{ + uint8_t *stack = task->stack + AO_STACK_SIZE - 1; + uint16_t a = (uint16_t) start; + int i; + + /* Return address */ + PUSH8(stack, a); + PUSH8(stack, (a >> 8)); + + /* Clear register values */ + i = 32; + while (i--) + PUSH8(stack, 0); + + /* SREG with interrupts enabled */ + PUSH8(stack, 0x80); + task->stack_count = stack - task->stack; +} +#else +static void +ao_init_stack(__xdata struct ao_task *task, void (*start)(void)) +{ + uint8_t __xdata *stack = task->stack; + /* + * Construct a stack frame so that it will 'return' + * to the start of the task + */ + + *stack++ = ((uint16_t) start); + *stack++ = ((uint16_t) start) >> 8; + + /* and the stuff saved by ao_switch */ + *stack++ = 0; /* acc */ + *stack++ = 0x80; /* IE */ + *stack++ = 0; /* DPL */ + *stack++ = 0; /* DPH */ + *stack++ = 0; /* B */ + *stack++ = 0; /* R2 */ + *stack++ = 0; /* R3 */ + *stack++ = 0; /* R4 */ + *stack++ = 0; /* R5 */ + *stack++ = 0; /* R6 */ + *stack++ = 0; /* R7 */ + *stack++ = 0; /* R0 */ + *stack++ = 0; /* R1 */ + *stack++ = 0; /* PSW */ + *stack++ = 0; /* BP */ + task->stack_count = stack - task->stack; +} +#endif + +void +ao_add_task(__xdata struct ao_task * task, void (*start)(void), __code char *name) __reentrant +{ + uint8_t task_id; + uint8_t t; + if (ao_num_tasks == AO_NUM_TASKS) + ao_panic(AO_PANIC_NO_TASK); + for (task_id = 1; task_id != 0; task_id++) { + for (t = 0; t < ao_num_tasks; t++) + if (ao_tasks[t]->task_id == task_id) + break; + if (t == ao_num_tasks) + break; + } + ao_tasks[ao_num_tasks++] = task; + task->task_id = task_id; + task->name = name; + task->wchan = NULL; + ao_init_stack(task, start); +} + +/* Task switching function. This must not use any stack variables */ +void +ao_yield(void) __naked +{ +#ifdef AVR + asm("push r31" "\n\t" "push r30"); + asm("push r29" "\n\t" "push r28" "\n\t" "push r27" "\n\t" "push r26" "\n\t" "push r25"); + asm("push r24" "\n\t" "push r23" "\n\t" "push r22" "\n\t" "push r21" "\n\t" "push r20"); + asm("push r19" "\n\t" "push r18" "\n\t" "push r17" "\n\t" "push r16" "\n\t" "push r15"); + asm("push r14" "\n\t" "push r13" "\n\t" "push r12" "\n\t" "push r11" "\n\t" "push r10"); + asm("push r9" "\n\t" "push r8" "\n\t" "push r7" "\n\t" "push r6" "\n\t" "push r5"); + asm("push r4" "\n\t" "push r3" "\n\t" "push r2" "\n\t" "push r1" "\n\t" "push r0"); + asm("in r0, __SREG__" "\n\t" "push r0"); + sei(); +#else + /* Save current context */ + _asm + /* Push ACC first, as when restoring the context it must be restored + * last (it is used to set the IE register). */ + push ACC + /* Store the IE register then enable interrupts. */ + push _IEN0 + setb _EA + push DPL + push DPH + push b + push ar2 + push ar3 + push ar4 + push ar5 + push ar6 + push ar7 + push ar0 + push ar1 + push PSW + _endasm; + PSW = 0; + _asm + push _bp + _endasm; +#endif + + if (ao_cur_task_index == AO_NO_TASK_INDEX) + ao_cur_task_index = ao_num_tasks-1; + else + { +#ifdef AVR + uint8_t sp_l, sp_h; + uint8_t *sp; + asm("in %0,__SP_L__" : "=&r" (sp_l) ); + asm("in %0,__SP_H__" : "=&r" (sp_h) ); + sp = (uint8_t *) ((uint16_t) sp_l | ((uint16_t) sp_h << 8)); + ao_cur_task->stack_count = sp - ao_cur_task->stack; +#else + uint8_t stack_len; + __data uint8_t *stack_ptr; + __xdata uint8_t *save_ptr; + /* Save the current stack */ + stack_len = SP - (AO_STACK_START - 1); + ao_cur_task->stack_count = stack_len; + stack_ptr = (uint8_t __data *) AO_STACK_START; + save_ptr = (uint8_t __xdata *) ao_cur_task->stack; + do + *save_ptr++ = *stack_ptr++; + while (--stack_len); +#endif + } + +#ifndef AVR + /* Empty the stack; might as well let interrupts have the whole thing */ + SP = AO_STACK_START - 1; +#endif + + /* Find a task to run. If there isn't any runnable task, + * this loop will run forever, which is just fine + */ + { + __pdata uint8_t ao_next_task_index = ao_cur_task_index; + for (;;) { + ++ao_next_task_index; + if (ao_next_task_index == ao_num_tasks) + ao_next_task_index = 0; + + ao_cur_task = ao_tasks[ao_next_task_index]; + if (ao_cur_task->wchan == NULL) { + ao_cur_task_index = ao_next_task_index; + break; + } + + /* Check if the alarm is set for a time which has passed */ + if (ao_cur_task->alarm && + (int16_t) (ao_time() - ao_cur_task->alarm) >= 0) { + ao_cur_task_index = ao_next_task_index; + break; + } + +#ifdef AVR +#else + /* Enter lower power mode when there isn't anything to do */ + if (ao_next_task_index == ao_cur_task_index) + PCON = PCON_IDLE; +#endif + } + } + +#ifdef AVR + { + uint8_t *sp = (ao_cur_task->stack + ao_cur_task->stack_count); + uint8_t sp_l, sp_h; + sp_l = (uint16_t) sp; + sp_h = ((uint16_t) sp) >> 8; + asm("out __SP_H__,%0" : : "r" (sp_h) ); + asm("out __SP_L__,%0" : : "r" (sp_l) ); + asm("pop r0" "\n\t" + "out __SREG__, r0"); + asm("pop r0" "\n\t" "pop r1" "\n\t" "pop r2" "\n\t" "pop r3" "\n\t" "pop r4"); + asm("pop r5" "\n\t" "pop r6" "\n\t" "pop r7" "\n\t" "pop r8" "\n\t" "pop r9"); + asm("pop r10" "\n\t" "pop r11" "\n\t" "pop r12" "\n\t" "pop r13" "\n\t" "pop r14"); + asm("pop r15" "\n\t" "pop r16" "\n\t" "pop r17" "\n\t" "pop r18" "\n\t" "pop r19"); + asm("pop r20" "\n\t" "pop r21" "\n\t" "pop r22" "\n\t" "pop r23" "\n\t" "pop r24"); + asm("pop r25" "\n\t" "pop r26" "\n\t" "pop r27" "\n\t" "pop r28" "\n\t" "pop r29"); + asm("pop r30" "\n\t" "pop r31"); + asm("ret"); + } +#else + { + uint8_t stack_len; + __data uint8_t *stack_ptr; + __xdata uint8_t *save_ptr; + + /* Restore the old stack */ + stack_len = ao_cur_task->stack_count; + SP = AO_STACK_START - 1 + stack_len; + + stack_ptr = (uint8_t __data *) AO_STACK_START; + save_ptr = (uint8_t __xdata *) ao_cur_task->stack; + do + *stack_ptr++ = *save_ptr++; + while (--stack_len); + } + + _asm + pop _bp + pop PSW + pop ar1 + pop ar0 + pop ar7 + pop ar6 + pop ar5 + pop ar4 + pop ar3 + pop ar2 + pop b + pop DPH + pop DPL + /* The next byte of the stack is the IE register. Only the global + enable bit forms part of the task context. Pop off the IE then set + the global enable bit to match that of the stored IE register. */ + pop ACC + JB ACC.7,0098$ + CLR _EA + LJMP 0099$ + 0098$: + SETB _EA + 0099$: + /* Finally pop off the ACC, which was the first register saved. */ + pop ACC + ret + _endasm; +#endif +} + +uint8_t +ao_sleep(__xdata void *wchan) +{ + __critical { + ao_cur_task->wchan = wchan; + } + ao_yield(); + ao_cur_task->alarm = 0; + if (ao_cur_task->wchan) { + ao_cur_task->wchan = NULL; + return 1; + } + return 0; +} + +void +ao_wakeup(__xdata void *wchan) +{ + uint8_t i; + + for (i = 0; i < ao_num_tasks; i++) + if (ao_tasks[i]->wchan == wchan) + ao_tasks[i]->wchan = NULL; +} + +void +ao_alarm(uint16_t delay) +{ + /* Make sure we sleep *at least* delay ticks, which means adding + * one to account for the fact that we may be close to the next tick + */ + if (!(ao_cur_task->alarm = ao_time() + delay + 1)) + ao_cur_task->alarm = 1; +} + +void +ao_exit(void) __critical +{ + uint8_t i; + ao_num_tasks--; + for (i = ao_cur_task_index; i < ao_num_tasks; i++) + ao_tasks[i] = ao_tasks[i+1]; + ao_cur_task_index = AO_NO_TASK_INDEX; + ao_yield(); + /* we'll never get back here */ +} + +void +ao_task_info(void) +{ + uint8_t i; + uint8_t pc_loc; + __xdata struct ao_task *task; + + for (i = 0; i < ao_num_tasks; i++) { + task = ao_tasks[i]; + pc_loc = task->stack_count - 17; + printf("%12s: wchan %04x pc %04x\n", + task->name, + (int16_t) task->wchan, + (task->stack[pc_loc]) | (task->stack[pc_loc+1] << 8)); + } +} + +void +ao_start_scheduler(void) +{ + ao_cur_task_index = AO_NO_TASK_INDEX; + ao_cur_task = NULL; + ao_yield(); +} |