Skip to content

Commit 098f900

Browse files
aykevldeadprogram
authored andcommitted
esp8266: implement task based scheduler
I have chosed to call this implementation `esp8266` instead of `xtensa` as it has been written specifically for the ESP8266 and there are no other Xtensa chips with the CALL0 ABI (no windowing) that I know of. The only other related chip is the ESP32, which does implement register windowing and thus needs a very different implementation.
1 parent caf35cf commit 098f900

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
.section .text.tinygo_startTask,"ax",@progbits
2+
.global tinygo_startTask
3+
.type tinygo_startTask, %function
4+
tinygo_startTask:
5+
// Small assembly stub for starting a goroutine. This is already run on the
6+
// new stack, with the callee-saved registers already loaded.
7+
// Most importantly, r4 contains the pc of the to-be-started function and r5
8+
// contains the only argument it is given. Multiple arguments are packed
9+
// into one by storing them in a new allocation.
10+
11+
// Set the first argument of the goroutine start wrapper, which contains all
12+
// the arguments.
13+
mov.n a2, a13
14+
15+
// Branch to the "goroutine start" function.
16+
callx0 a12
17+
18+
// After return, exit this goroutine. This is a tail call.
19+
call0 tinygo_pause
20+
.size tinygo_startTask, .-tinygo_startTask
21+
22+
.global tinygo_swapTask
23+
.type tinygo_swapTask, %function
24+
tinygo_swapTask:
25+
// This function gets the following parameters:
26+
// a2 = newStack uintptr
27+
// a3 = oldStack *uintptr
28+
// Note:
29+
// a0 is the return address
30+
// a1 is the stack pointer (sp)
31+
32+
// Save all callee-saved registers:
33+
addi sp, sp, -20
34+
s32i.n a12, sp, 0
35+
s32i.n a13, sp, 4
36+
s32i.n a14, sp, 8
37+
s32i.n a15, sp, 12
38+
s32i.n a0, sp, 16
39+
40+
// Save the current stack pointer in oldStack.
41+
s32i.n sp, a3, 0
42+
43+
// Switch to the new stack pointer.
44+
mov.n sp, a2
45+
46+
// Load state from new task and branch to the previous position in the
47+
// program.
48+
l32i.n a12, sp, 0
49+
l32i.n a13, sp, 4
50+
l32i.n a14, sp, 8
51+
l32i.n a15, sp, 12
52+
l32i.n a0, sp, 16
53+
addi sp, sp, 20
54+
ret.n
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// +build scheduler.tasks,esp8266
2+
3+
package task
4+
5+
// Stack switch implementation for the ESP8266, which does not use the windowed
6+
// ABI of Xtensa. Registers are assigned as follows:
7+
// a0: return address (link register)
8+
// a1: stack pointer (must be 16-byte aligned)
9+
// a2-a7: incoming arguments
10+
// a8: static chain (unused)
11+
// a12-a15: callee-saved
12+
// a15: stack frame pointer (optional, unused)
13+
// Sources:
14+
// http://cholla.mmto.org/esp8266/xtensa.html
15+
// https://0x04.net/~mwk/doc/xtensa.pdf
16+
17+
import "unsafe"
18+
19+
var systemStack uintptr
20+
21+
// calleeSavedRegs is the list of registers that must be saved and restored when
22+
// switching between tasks. Also see task_stack_esp8266.S that relies on the
23+
// exact layout of this struct.
24+
type calleeSavedRegs struct {
25+
a12 uintptr
26+
a13 uintptr
27+
a14 uintptr
28+
a15 uintptr
29+
30+
pc uintptr // also link register or r0
31+
}
32+
33+
// archInit runs architecture-specific setup for the goroutine startup.
34+
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
35+
// Store the initial sp for the startTask function (implemented in assembly).
36+
s.sp = uintptr(unsafe.Pointer(r))
37+
38+
// Initialize the registers.
39+
// These will be popped off of the stack on the first resume of the goroutine.
40+
41+
// Start the function at tinygo_startTask (defined in
42+
// src/internal/task/task_stack_esp8266.S).
43+
// This assembly code calls a function (passed in a12) with a single argument
44+
// (passed in a13). After the function returns, it calls Pause().
45+
r.pc = uintptr(unsafe.Pointer(&startTask))
46+
47+
// Pass the function to call in a12.
48+
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
49+
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
50+
r.a12 = fn
51+
52+
// Pass the pointer to the arguments struct in a13.
53+
r.a13 = uintptr(args)
54+
}
55+
56+
func (s *state) resume() {
57+
swapTask(s.sp, &systemStack)
58+
}
59+
60+
func (s *state) pause() {
61+
newStack := systemStack
62+
systemStack = 0
63+
swapTask(newStack, &s.sp)
64+
}
65+
66+
// SystemStack returns the system stack pointer when called from a task stack.
67+
// When called from the system stack, it returns 0.
68+
func SystemStack() uintptr {
69+
return systemStack
70+
}

targets/esp8266.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
"inherits": ["xtensa"],
33
"cpu": "esp8266",
44
"build-tags": ["esp8266", "esp"],
5+
"scheduler": "tasks",
56
"linker": "xtensa-esp32-elf-ld",
7+
"default-stack-size": 2048,
68
"cflags": [
79
"-mcpu=esp8266"
810
],
911
"linkerscript": "targets/esp8266.ld",
1012
"extra-files": [
11-
"src/device/esp/esp8266.S"
13+
"src/device/esp/esp8266.S",
14+
"src/internal/task/task_stack_esp8266.S"
1215
],
1316
"binary-format": "esp8266",
1417
"flash-command": "esptool.py --chip=esp8266 --port {port} write_flash 0x00000 {bin} -fm qio"

0 commit comments

Comments
 (0)