avr: add support for tasks scheduler

This adds support for the `-scheduler=tasks` flag for AVR. On most AVR
chips you wouldn't want to run a real scheduler but it may be useful in
some cases, especially on devices with more RAM. It is disabled by
default.
Этот коммит содержится в:
Ayke van Laethem 2020-03-08 23:10:21 +01:00 коммит произвёл Ayke
родитель c5cb2cec9b
коммит add014f21b
4 изменённых файлов: 282 добавлений и 0 удалений

Просмотреть файл

@ -292,6 +292,8 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1

88
src/internal/task/task_stack_avr.go Обычный файл
Просмотреть файл

@ -0,0 +1,88 @@
// +build scheduler.tasks,avr
package task
import "unsafe"
const stackSize = 256
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see scheduler_avr.S that relies on the
// exact layout of this struct.
//
// https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers
type calleeSavedRegs struct {
r2r3 uintptr
r4r5 uintptr
r6r7 uintptr
r8r9 uintptr
r10r11 uintptr
r12r13 uintptr
r14r15 uintptr
r16r17 uintptr
r28r29 uintptr // Y register
pc uintptr
}
// registers gets a pointer to the registers stored at the top of the stack.
func (s *state) registers() *calleeSavedRegs {
return (*calleeSavedRegs)(unsafe.Pointer(s.sp + 1))
}
// startTask is a small wrapper function that sets up the first (and only)
// argument to the new goroutine and makes sure it is exited when the goroutine
// finishes.
//go:extern tinygo_startTask
var startTask [0]uint8
// archInit runs architecture-specific setup for the goroutine startup.
// Note: adding //go:noinline to work around an AVR backend bug.
//go:noinline
func (s *state) archInit(stack []uintptr, fn uintptr, args unsafe.Pointer) {
// Set up the stack canary, a random number that should be checked when
// switching from the task back to the scheduler. The stack canary pointer
// points to the first word of the stack. If it has changed between now and
// the next stack switch, there was a stack overflow.
s.canaryPtr = &stack[0]
*s.canaryPtr = stackCanary
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(&stack[uintptr(len(stack))-(unsafe.Sizeof(calleeSavedRegs{})/unsafe.Sizeof(uintptr(0)))])) - 1
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
r := s.registers()
// Start the function at tinygo_startTask.
startTask := uintptr(unsafe.Pointer(&startTask))
r.pc = startTask/2>>8 | startTask/2<<8
// Pass the function to call in r2:r3.
// This function is a compiler-generated wrapper which loads arguments out
// of a struct pointer. See createGoroutineStartWrapper (defined in
// compiler/goroutine.go) for more information.
r.r2r3 = fn
// Pass the pointer to the arguments struct in r4:45.
r.r4r5 = uintptr(args)
}
func (s *state) resume() {
switchToTask(s.sp)
}
//export tinygo_switchToTask
func switchToTask(uintptr)
//export tinygo_switchToScheduler
func switchToScheduler(*uintptr)
func (s *state) pause() {
switchToScheduler(&s.sp)
}
//export tinygo_pause
func pause() {
Pause()
}

189
src/runtime/scheduler_avr.S Обычный файл
Просмотреть файл

@ -0,0 +1,189 @@
.section .bss.tinygo_systemStack
.global tinygo_systemStack
.type tinygo_systemStack, %object
tinygo_systemStack:
.short 0
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, r2r3 contain the pc of the to-be-started function and
// r4r5 contain the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
movw r24, r4
// Branch to the "goroutine start" function. Note that the Z register is
// call-clobbered, so does not need to be restored after use.
movw Z, r2
icall
// After return, exit this goroutine. This is a tail call.
#if __AVR_ARCH__ == 2 || __AVR_ARCH__ == 25
// Small memory devices (8kB flash) that do not have the long call
// instruction availble will need to use rcall instead.
// Note that they will probably not be able to run more than the main
// goroutine anyway, but this file is compiled for all AVRs so it needs to
// compile at least.
rcall tinygo_pause
#else
// Other devices can (and must) use the regular call instruction.
call tinygo_pause
#endif
// Get the system stack pointer, independent of whether we're currently on the
// system stack or a task stack.
.global tinygo_getSystemStackPointer
.type tinygo_getSystemStackPointer, %function
tinygo_getSystemStackPointer:
// Load system stack pointer.
lds r24, tinygo_systemStack
lds r25, tinygo_systemStack+1
// Compare against 0.
cp r24, r1
cpc r25, r1
// Branch (and then return) if tinygo_systemStack has a non-zero value.
brne 1f
// tinygo_systemStack is zero, so return the current stack pointer.
in r24, 0x3d; SPL
in r25, 0x3e; SPH
1:
ret
.global tinygo_switchToTask
.type tinygo_switchToTask, %function
tinygo_switchToTask:
// The sp parameter is the only parameter, so it will take up r24:r25.
// r24:r25 = sp uintptr
// Save all call-saved registers:
// https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers
push r29 // Y
push r28 // Y
push r17
push r16
push r15
push r14
push r13
push r12
push r11
push r10
push r9
push r8
push r7
push r6
push r5
push r4
push r3
push r2
// Save the system stack pointer in a global.
in r2, 0x3d; SPL
in r3, 0x3e; SPH
sts tinygo_systemStack+0, r2
sts tinygo_systemStack+1, r3
// Switch to the task stack pointer.
out 0x3d, r24; SPL
out 0x3e, r25; SPH
// Load saved register from the task stack.
pop r2
pop r3
pop r4
pop r5
pop r6
pop r7
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
pop r16
pop r17
pop r28 // Y
pop r29 // Y
// Return into the new task, as if tinygo_switchToScheduler was a regular
// call.
ret
.global tinygo_switchToScheduler
.type tinygo_switchToScheduler, %function
tinygo_switchToScheduler:
// The sp parameter is the only parameter, so it will take up r24:r25.
// r24:r25 = sp *uintptr
// Save all call-saved registers on the task stack:
// https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers
push r29 // Y
push r28 // Y
push r17
push r16
push r15
push r14
push r13
push r12
push r11
push r10
push r9
push r8
push r7
push r6
push r5
push r4
push r3
push r2
// Save the task stack.
in r2, 0x3d; SPL
in r3, 0x3e; SPH
movw Y, r24
std Y+0, r2
std Y+1, r3
// Switch to the system stack.
lds r2, tinygo_systemStack
lds r3, tinygo_systemStack+1
out 0x3d, r2; SPL
out 0x3e, r3; SPH
// Clear tinygo_systemStack to make sure tinygo_getSystemStackPointer knows
// which pointer to return.
sts tinygo_systemStack+0, r1
sts tinygo_systemStack+1, r1
// Load saved register from the system stack.
pop r2
pop r3
pop r4
pop r5
pop r6
pop r7
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
pop r16
pop r17
pop r28 // Y
pop r29 // Y
// Return into the scheduler, as if tinygo_switchToTask was a regular call.
ret

Просмотреть файл

@ -9,5 +9,8 @@
"ldflags": [ "ldflags": [
"-T", "targets/avr.ld", "-T", "targets/avr.ld",
"-Wl,--gc-sections" "-Wl,--gc-sections"
],
"extra-files": [
"src/runtime/scheduler_avr.S"
] ]
} }