commit 55194a74acb500ca74f1508d269568efa3696130 Author: Stephan Brunner Date: Tue Jul 2 22:06:34 2019 +0200 Initial commit diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..36b5279 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2019 Stephan Brunner + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..20068ef --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Simple operating system for the ATMega328P +This operating system was creating during a class assignment. The code is experimental and highly untested, but it should work. + +## How it works +The OS uses a very simple round robin scheduler and does a context switch every 262144 clock cycles. This behaviour is controllable by setting OCR0A and the prescalers of Timer0. You cannot use Timer0 in your application, it is exclusively used by the scheduler. + +## How to use +Simply put the files starting with `os` in your project. Edit `os_config.h` to fit your needs. From your main, call `os_init()` and create your tasks with `os_task_add(task_func, task_data)`. To start the scheduler, call `os_run()`. Note that this call never returns, so subsequent code will not be executed. + +Please note that tasks currently may never terminate and **must** be terminated using `os_current_task_kill()`. To access the void-pointer given to `os_task_add`, call `os_current_task_get_data()`. + +Please do not try to use recursion or deep call stacks, as you only have a very limited stack. +**You may not use Timer0, because it is used for the scheduler! Do not try to reenable interrupts when using custom interrupt service routines!** + +## Configuration +The operating system can be configured at compile time using the following define-macros in `os_config.h`: + - `OS_STACK_SIZE` controls the stack size for _one_ task. + - `OS_TASK_COUNT` controls how many tasks _may_ be created simultaneously. + +## License +Copyright 2019 Stephan Brunner + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/main.c b/main.c new file mode 100644 index 0000000..2dbd894 --- /dev/null +++ b/main.c @@ -0,0 +1,22 @@ +#include "os.h" + +static void single_shot_task(void* data) { + /* Do stuff */ + + os_current_task_kill(); +} + +static void looping_task(void* data) { + while (1) { + /* Do stuff */ + } +} + +int main(void) { + os_init(); + + os_task_add(single_shot_task, 0); + os_task_add(looping_task, 0); + + os_run(); +} \ No newline at end of file diff --git a/os.c b/os.c new file mode 100755 index 0000000..809d301 --- /dev/null +++ b/os.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include "os.h" +#include "os_config.h" + +#ifndef ARRAYSIZE +#define ARRAYSIZE(x) (sizeof(x)/sizeof(*(x))) +#endif + +// Assemble tasks +void os_asm_switch_to_task(void); + +// Temporary buffers for context switch +volatile uint16_t ins_tmp; +volatile uint8_t sreg_tmp; +volatile uint16_t stack_tmp; +volatile uint8_t reg_tmp[32]; + +struct os_task { + uint8_t valid; + os_task_func code_ptr; + void* data_ptr; + uint8_t* stack_ptr; + uint8_t status; + uint8_t registers[32]; + uint8_t stack[OS_STACK_SIZE]; +} tasks[OS_TASK_COUNT]; + +typedef size_t os_task_id_t; +#define TASK_IDX_INVALID -1 +static os_task_id_t task_current_idx = TASK_IDX_INVALID; + +void os_init(void) { + for (size_t i = 0; i < ARRAYSIZE(tasks); i++) { + tasks[i].valid = 0; + } +} + +void os_run(void) { + TCCR0A = 0; + OCR0A = 0xFF; + TCCR0B = _BV(WGM12) | _BV(CS10); + TIMSK0 |= _BV(OCIE1A); + + // Wait fo fist task switch + sei(); + TCNT0 = 0xFF; + while (1); +} + +static void os_do_switch_to_task(os_task_id_t task_idx) { + task_current_idx = task_idx; + struct os_task* task = tasks + task_idx; + + for (size_t i = 0; i < ARRAYSIZE(task->registers); i++) { + reg_tmp[i] = task->registers[i]; + } + ins_tmp = (uint16_t) task->code_ptr; + sreg_tmp = task->status; + stack_tmp = (uint16_t) task->stack_ptr; + + // Reset Timer and do context switch + TCNT0 = 0; + TIFR0 &= ~_BV(OCF0A); + os_asm_switch_to_task(); +} + +void os_interrupt_saved(void) { + os_task_id_t next_task_idx = TASK_IDX_INVALID; + + if (task_current_idx != TASK_IDX_INVALID) { + struct os_task* task_current = tasks + task_current_idx; + + for (size_t i = 0; i < ARRAYSIZE(task_current->registers); i++) { + task_current->registers[i] = reg_tmp[i]; + } + + task_current->code_ptr = (void*) ins_tmp; + task_current->status = sreg_tmp; + task_current->stack_ptr = (uint8_t*) stack_tmp; + + next_task_idx = task_current_idx + 1; + next_task_idx %= ARRAYSIZE(tasks); + } else { + if (tasks[0].valid) { + next_task_idx = 0; + } + } + + if (next_task_idx == TASK_IDX_INVALID) { + while (1); + } + + os_do_switch_to_task(next_task_idx); +} + +void os_task_add(os_task_func func, void* data) { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + for (size_t i = 0; i < ARRAYSIZE(tasks); i++) { + struct os_task* t = tasks + i; + if (t->valid) { + continue; + } + + t->valid = 1; + t->code_ptr = func; + t->stack_ptr = t->stack + sizeof (t->stack) - 1; + t->status = 0; + t->data_ptr = data; + break; + } + } +} + +void* os_current_task_get_data(void) { + return tasks[task_current_idx].data_ptr; +} + +void os_current_task_kill(void) { + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + tasks[task_current_idx].valid = 0; + } + + // Wait fo context switch. + while (1); +} + +// Linker magic to add interrupt vector +void TIMER0_COMPA_vect(void) __attribute__((signal, naked, __INTR_ATTRS)); diff --git a/os.h b/os.h new file mode 100755 index 0000000..5e08799 --- /dev/null +++ b/os.h @@ -0,0 +1,12 @@ +#ifndef _OS_H_ +#define _OS_H_ + +typedef void (*os_task_func)(void*); + +void os_init(void); +void os_run(void); +void os_task_add(os_task_func func, void* data); +void os_current_task_kill(void); +void* os_current_task_get_data(void); + +#endif /* _OS_H_ */ \ No newline at end of file diff --git a/os_asm.s b/os_asm.s new file mode 100755 index 0000000..3141aff --- /dev/null +++ b/os_asm.s @@ -0,0 +1,189 @@ +#include + +.section .text + +// Task switch interrupt +.global TIMER0_COMPA_vect +TIMER0_COMPA_vect: + push r26 + push r27 + push r28 + push r29 + push r30 + push r31 + + // Save SREG + in r26, 0x3F + ldi r31, hi8(sreg_tmp) + ldi r30, lo8(sreg_tmp) + st Z, r26 + + // Save Instruction + Stack + in r29, 0x3E // SPH + in r28, 0x3D // SPL + adiw YL, 6+2 + + // Save Stack + ldi r31, hi8(stack_tmp) + ldi r30, lo8(stack_tmp) + st Z+, r28 + st Z+, r29 + sbiw YL, 1 + + // Save instruction + ld r26, Y+ + ld r27, Y+ + ldi r31, hi8(ins_tmp) + ldi r30, lo8(ins_tmp) + st Z+, r27 + st Z+, r26 + + // r26 = i + // r27 = tmp + // Z = register file + // Y = reg_tmp + + // Z = 0 + ldi r31, 0 + ldi r30, 0 + ldi r29, hi8(reg_tmp) + ldi r28, lo8(reg_tmp) + ldi r26, 0 + +_os_interrupt_cpy_next: + ld r27, Z+ + st Y+, r27 + + inc r26 + + // We can only save the first 26 registers, the rest is used here + cpi r26, 26 + breq _os_interrupt_regsaved + rjmp _os_interrupt_cpy_next + +_os_interrupt_regsaved: + pop r31 + pop r30 + pop r29 + pop r28 + pop r27 + pop r26 + + mov r16, r26 + mov r17, r27 + mov r18, r28 + mov r19, r29 + mov r20, r30 + mov r21, r31 + + // Z = register file + // Y = reg_tmp + ldi ZH, 0 + ldi ZL, 16 + ldi YH, hi8(reg_tmp + 26) + ldi YL, lo8(reg_tmp + 26) + + mov r26, 0 + + // r26 = i + // r27 = tmp +_os_interrupt_cpy_next_2: + ld r27, Z+ + st Y+, r27 + + inc r26 + + cpi r26, 6 + breq _os_interrupt_regsaved_2 + rjmp _os_interrupt_cpy_next_2 + + // Pop last instruction addr + pop r31 + +_os_interrupt_regsaved_2: + jmp os_interrupt_saved + + + + + +// Restore task context and jump to task +.global os_asm_switch_to_task +os_asm_switch_to_task: + + // Stack + ldi YL, lo8(stack_tmp) + ldi YH, hi8(stack_tmp) + ld ZL, Y+ + ld ZH, Y + out 0x3E, ZH // SPH + out 0x3D, ZL // SPL + + // Old Instruction + ldi YH, hi8(ins_tmp) + ldi YL, lo8(ins_tmp) + ld ZL, Y+ + ld ZH, Y+ + push ZL + push ZH + + // R30-31 + ldi YH, hi8(reg_tmp+30) + ldi YL, lo8(reg_tmp+30) + ld ZH, Y+ + push ZH + ld ZH, Y+ + push ZH + + // SREG + ldi YL, lo8(sreg_tmp) + ldi YH, hi8(sreg_tmp) + ld ZL, Y + push ZL + + // R26-R29 + ldi YL, lo8(reg_tmp+26) + ldi YH, hi8(reg_tmp+26) + ldi ZL, 0 + +_reg_copy1_next: + cpi ZL, 4 + breq _reg_copy1_finished + + ld ZH, Y+ + push ZH + + inc ZL + + jmp _reg_copy1_next +_reg_copy1_finished: + + // R0-R25 + ldi YL, lo8(reg_tmp) + ldi YH, hi8(reg_tmp) + ldi ZL, 0 + ldi ZH, 0 + +_reg_copy2_next: + cpi ZL, 26 + brge _reg_copy2_finished + + ld r26, Y+ + st Z+, r26 + + rjmp _reg_copy2_next + +_reg_copy2_finished: + + pop r29 + pop r28 + pop r27 + pop r26 + + pop r31 + out 0x3F, r31 // SREG + + pop r31 + pop r30 + + reti diff --git a/os_config.h b/os_config.h new file mode 100644 index 0000000..63220c5 --- /dev/null +++ b/os_config.h @@ -0,0 +1,7 @@ +#ifndef _OS_CONFIG_H +#define _OS_CONFIG_H + +#define OS_STACK_SIZE 128 +#define OS_TASK_COUNT 8 + +#endif /* _OS_CONFIG_H */ \ No newline at end of file