From adb79f2c259bf70c74a2a2be262af7c152825f58 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Sep 2021 01:39:56 +0200 Subject: [PATCH 01/15] cgo: add support for stdio in picolibc This is simple: just add the required header file. But this commit also adds a simple test to make sure it remains supported. --- compileopts/config.go | 7 ++++++- testdata/cgo/main.go | 4 ++++ testdata/cgo/out.txt | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/compileopts/config.go b/compileopts/config.go index 8f815ffb15..f1e34b7f0a 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -206,7 +206,12 @@ func (c *Config) CFlags() []string { } if c.Target.Libc == "picolibc" { root := goenv.Get("TINYGOROOT") - cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include")) + picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") + cflags = append(cflags, + "-nostdlibinc", + "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "include"), + "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "tinystdio"), + ) cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include")) } // Always emit debug information. It is optionally stripped at link time. diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 36ef2905c1..d3491c91cf 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -1,6 +1,7 @@ package main /* +#include int fortytwo(void); #include "main.h" int mul(int, int); @@ -129,6 +130,9 @@ func main() { buf2 := make([]byte, len(buf1)) C.strcpy((*C.char)(unsafe.Pointer(&buf2[0])), (*C.char)(unsafe.Pointer(&buf1[0]))) println("copied string:", string(buf2[:C.strlen((*C.char)(unsafe.Pointer(&buf2[0])))])) + + // libc: test basic stdio functionality + println("SEEK_CUR:", C.SEEK_CUR) } func printUnion(union C.joined_t) C.joined_t { diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 5f4b5f4119..6b2cc5e5b5 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -61,3 +61,4 @@ option 3A: 21 enum width matches: true CFLAGS value: 17 copied string: foobar +SEEK_CUR: 1 From 9d2c93d169a747a77fbac60fc6d4fa3d971d0694 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Sep 2021 03:05:10 +0200 Subject: [PATCH 02/15] cgo: implement rudimentary C array decaying This is just a first step. It's not complete, but it gets some real world C code to parse. This signature, from the ESP-IDF: esp_err_t esp_wifi_get_mac(wifi_interface_t ifx, uint8_t mac[6]); Was previously converted to something like this (pseudocode): C.esp_err_t esp_wifi_get_mac(ifx C.wifi_interface_t, mac [6]uint8) But this is not correct. C array parameters will decay. The array is passed by reference instead of by value. Instead, this would be the correct signature: C.esp_err_t esp_wifi_get_mac(ifx C.wifi_interface_t, mac *uint8) So that it can be called like this (using CGo): var mac [6]byte errCode := C.esp_wifi_get_mac(C.ESP_IF_WIFI_AP, &mac[0]) This stores the result in the 6-element array mac. --- cgo/libclang.go | 37 ++++++++++++++++++++++++++++++++++++- testdata/cgo/main.c | 4 ++++ testdata/cgo/main.go | 6 ++++++ testdata/cgo/main.h | 4 ++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/cgo/libclang.go b/cgo/libclang.go index 8e9af8a08c..e5370a89ca 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -196,7 +196,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient } fn.args = append(fn.args, paramInfo{ name: argName, - typeExpr: p.makeASTType(argType, pos), + typeExpr: p.makeDecayingASTType(argType, pos), }) } resultType := C.tinygo_clang_getCursorResultType(c) @@ -391,6 +391,41 @@ func (p *cgoPackage) addErrorAt(position token.Position, msg string) { }) } +// makeDecayingASTType does the same as makeASTType but takes care of decaying +// types (arrays in function parameters, etc). It is otherwise identical to +// makeASTType. +func (p *cgoPackage) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr { + // Strip typedefs, if any. + underlyingType := typ + if underlyingType.kind == C.CXType_Typedef { + c := C.tinygo_clang_getTypeDeclaration(typ) + underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c) + // TODO: support a chain of typedefs. At the moment, it seems to get + // stuck in an endless loop when trying to get to the most underlying + // type. + } + // Check for decaying type. An example would be an array type in a + // parameter. This declaration: + // void foo(char buf[6]); + // is the same as this one: + // void foo(char *buf); + // But this one: + // void bar(char buf[6][4]); + // equals this: + // void bar(char *buf[4]); + // so not all array dimensions should be stripped, just the first one. + // TODO: there are more kinds of decaying types. + if underlyingType.kind == C.CXType_ConstantArray { + // Apply type decaying. + pointeeType := C.clang_getElementType(underlyingType) + return &ast.StarExpr{ + Star: pos, + X: p.makeASTType(pointeeType, pos), + } + } + return p.makeASTType(typ, pos) +} + // makeASTType return the ast.Expr for the given libclang type. In other words, // it converts a libclang type to a type in the Go AST. func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index 7954b91f61..d408332469 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -61,3 +61,7 @@ void unionSetData(short f0, short f1, short f2) { globalUnion.data[1] = 8; globalUnion.data[2] = 1; } + +void arraydecay(int buf1[5], int buf2[3][8], int buf3[4][7][2]) { + // Do nothing. +} diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index d3491c91cf..2e286baaa3 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -125,6 +125,12 @@ func main() { // Check whether CFLAGS are correctly passed on to compiled C files. println("CFLAGS value:", C.cflagsConstant) + // Check array-to-pointer decaying. This signature: + // void arraydecay(int buf1[5], int buf2[3][8], int buf3[4][7][2]); + // decays to: + // void arraydecay(int *buf1, int *buf2[8], int *buf3[7][2]); + C.arraydecay((*C.int)(nil), (*[8]C.int)(nil), (*[7][2]C.int)(nil)) + // libc: test whether C functions work at all. buf1 := []byte("foobar\x00") buf2 := make([]byte, len(buf1)) diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index ddd07efa9c..09e1b4f095 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -144,3 +144,7 @@ extern int cflagsConstant; // test duplicate definitions int add(int a, int b); extern int global; + +// Test array decaying into a pointer. +typedef int arraydecay_buf3[4][7][2]; +void arraydecay(int buf1[5], int buf2[3][8], arraydecay_buf3 buf3); From e4bed68eda03d3cc635c06b405abd1d63ec4fcb4 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Sep 2021 18:26:51 +0200 Subject: [PATCH 03/15] cgo: add support for the -T flag This adds support for specifying linker scripts in LDFLAGS. I looked a bit into whether it allows arbitrary code execution but I couldn't find any sign of that. --- cgo/cgo.go | 4 ++-- cgo/security.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cgo/cgo.go b/cgo/cgo.go index 8581807fa8..8520fecd99 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -447,11 +447,11 @@ func makePathsAbsolute(args []string, packagePath string) { args[i] = filepath.Join(packagePath, arg) } } - if arg == "-I" || arg == "-L" { + if arg == "-I" || arg == "-L" || arg == "-T" { nextIsPath = true continue } - if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") { + if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") || strings.HasPrefix(arg, "-T") { path := arg[2:] if !filepath.IsAbs(path) { args[i] = arg[:2] + filepath.Join(packagePath, path) diff --git a/cgo/security.go b/cgo/security.go index 2fea40c8a2..d5f30c88a8 100644 --- a/cgo/security.go +++ b/cgo/security.go @@ -140,6 +140,7 @@ var validLinkerFlags = []*regexp.Regexp{ re(`-F([^@\-].*)`), re(`-l([^@\-].*)`), re(`-L([^@\-].*)`), + re(`-T([^@\-].*)`), re(`-O`), re(`-O([^@\-].*)`), re(`-f(no-)?(pic|PIC|pie|PIE)`), @@ -209,6 +210,7 @@ var validLinkerFlagsWithNextArg = []string{ "-F", "-l", "-L", + "-T", "-framework", "-isysroot", "--sysroot", From bb461494d9a2b1a56454c7c719202618d26fee41 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Sep 2021 18:28:22 +0200 Subject: [PATCH 04/15] esp32c3: add some sections to the linker script This is necessary to support the WiFi binary blobs from Espressif. --- targets/esp32c3.ld | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/targets/esp32c3.ld b/targets/esp32c3.ld index 850a02b8e7..4f6058efc5 100644 --- a/targets/esp32c3.ld +++ b/targets/esp32c3.ld @@ -97,6 +97,7 @@ SECTIONS _sdata = ABSOLUTE(.); *(.sdata) *(.data .data.*) + *(.dram1 .dram1.*) . = ALIGN (4); _edata = ABSOLUTE(.); } >DRAM @@ -119,6 +120,10 @@ SECTIONS .init : ALIGN(4) { *(.init) + *(.iram1 .iram1.*) + *(.wifi0iram .wifi0iram.*) + *(.wifirxiram .wifirxiram.*) + *(.wifislprxiram .wifislprxiram.*) } >IRAM /* Dummy section to put the IROM segment exactly behind the IRAM segment. @@ -142,6 +147,7 @@ SECTIONS .text : ALIGN(4) { *(.text .text.*) + *(.wifislpiram .wifislpiram.*) } >IROM /DISCARD/ : From ae1c3c0056c32a72a74ce1ff55da6797d310a0f9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Sep 2021 18:29:32 +0200 Subject: [PATCH 05/15] baremetal: define the abort function This is normally provided by the libc, but in our case it makes more sense to define it in the runtime (just like malloc). --- src/runtime/baremetal.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 5abd13710b..4e81a9cc92 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -46,6 +46,11 @@ func libc_free(ptr unsafe.Pointer) { free(ptr) } +//export abort +func libc_abort() { + abort() +} + //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { abort() From 884ba21bdedcae07037be950e278d7a6843ea338 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Sep 2021 18:59:57 +0200 Subject: [PATCH 06/15] cgo: add stdio support to picolibc --- builder/ar.go | 6 +-- builder/picolibc.go | 111 ++++++++++++++++++++++++++++++++++++++- lib/picolibc | 2 +- lib/picolibc-stdio.c | 12 +++++ src/runtime/baremetal.go | 5 ++ testdata/cgo/main.go | 2 + testdata/cgo/out.txt | 1 + 7 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 lib/picolibc-stdio.c diff --git a/builder/ar.go b/builder/ar.go index 98d574fc4b..d70eaf9e09 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -5,6 +5,7 @@ import ( "debug/elf" "encoding/binary" "errors" + "fmt" "io" "os" "path/filepath" @@ -49,7 +50,7 @@ func makeArchive(archivePath string, objs []string) error { // Read the symbols and add them to the symbol table. dbg, err := elf.NewFile(objfile) if err != nil { - return err + return fmt.Errorf("failed to open file %s: %w", objpath, err) } symbols, err := dbg.Symbols() if err != nil { @@ -61,9 +62,8 @@ func makeArchive(archivePath string, objs []string) error { // Don't include local symbols (STB_LOCAL). continue } - if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC { + if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT { // Not a function. - // TODO: perhaps globals variables should also be included? continue } // Include in archive. diff --git a/builder/picolibc.go b/builder/picolibc.go index 436c34815a..e73528b4c1 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -12,7 +12,17 @@ var Picolibc = Library{ name: "picolibc", cflags: func() []string { picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc") - return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"} + return []string{ + "-Werror", + "-Wall", + "-std=gnu11", + "-D_COMPILING_NEWLIB", + "-DTINY_STDIO", + "-nostdlibinc", + "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", + "-I" + picolibcDir + "/tinystdio", + "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include", + } }, sourceDir: "lib/picolibc/newlib/libc", sources: func(target string) []string { @@ -21,6 +31,105 @@ var Picolibc = Library{ } var picolibcSources = []string{ + "../../../picolibc-stdio.c", + + "tinystdio/asprintf.c", + "tinystdio/atod_engine.c", + "tinystdio/atod_ryu.c", + "tinystdio/atof_engine.c", + "tinystdio/atof_ryu.c", + //"tinystdio/atold_engine.c", // have_long_double and not long_double_equals_double + "tinystdio/clearerr.c", + "tinystdio/compare_exchange.c", + "tinystdio/dtoa_data.c", + "tinystdio/dtoa_engine.c", + "tinystdio/dtoa_ryu.c", + "tinystdio/ecvtbuf.c", + "tinystdio/ecvt.c", + "tinystdio/ecvt_data.c", + "tinystdio/ecvtfbuf.c", + "tinystdio/ecvtf.c", + "tinystdio/ecvtf_data.c", + "tinystdio/exchange.c", + //"tinystdio/fclose.c", // posix-io + "tinystdio/fcvtbuf.c", + "tinystdio/fcvt.c", + "tinystdio/fcvtfbuf.c", + "tinystdio/fcvtf.c", + "tinystdio/fdevopen.c", + //"tinystdio/fdopen.c", // posix-io + "tinystdio/feof.c", + "tinystdio/ferror.c", + "tinystdio/fflush.c", + "tinystdio/fgetc.c", + "tinystdio/fgets.c", + "tinystdio/fileno.c", + "tinystdio/filestrget.c", + "tinystdio/filestrputalloc.c", + "tinystdio/filestrput.c", + //"tinystdio/fopen.c", // posix-io + "tinystdio/fprintf.c", + "tinystdio/fputc.c", + "tinystdio/fputs.c", + "tinystdio/fread.c", + "tinystdio/fscanf.c", + "tinystdio/fseek.c", + "tinystdio/ftell.c", + "tinystdio/ftoa_data.c", + "tinystdio/ftoa_engine.c", + "tinystdio/ftoa_ryu.c", + "tinystdio/fwrite.c", + "tinystdio/gcvtbuf.c", + "tinystdio/gcvt.c", + "tinystdio/gcvtfbuf.c", + "tinystdio/gcvtf.c", + "tinystdio/getchar.c", + "tinystdio/gets.c", + "tinystdio/matchcaseprefix.c", + "tinystdio/perror.c", + //"tinystdio/posixiob.c", // posix-io + //"tinystdio/posixio.c", // posix-io + "tinystdio/printf.c", + "tinystdio/putchar.c", + "tinystdio/puts.c", + "tinystdio/ryu_divpow2.c", + "tinystdio/ryu_log10.c", + "tinystdio/ryu_log2pow5.c", + "tinystdio/ryu_pow5bits.c", + "tinystdio/ryu_table.c", + "tinystdio/ryu_umul128.c", + "tinystdio/scanf.c", + "tinystdio/setbuf.c", + "tinystdio/setvbuf.c", + //"tinystdio/sflags.c", // posix-io + "tinystdio/snprintf.c", + "tinystdio/snprintfd.c", + "tinystdio/snprintff.c", + "tinystdio/sprintf.c", + "tinystdio/sprintfd.c", + "tinystdio/sprintff.c", + "tinystdio/sscanf.c", + "tinystdio/strfromd.c", + "tinystdio/strfromf.c", + "tinystdio/strtod.c", + "tinystdio/strtod_l.c", + "tinystdio/strtof.c", + //"tinystdio/strtold.c", // have_long_double and not long_double_equals_double + //"tinystdio/strtold_l.c", // have_long_double and not long_double_equals_double + "tinystdio/ungetc.c", + "tinystdio/vasprintf.c", + "tinystdio/vfiprintf.c", + "tinystdio/vfiscanf.c", + "tinystdio/vfprintf.c", + "tinystdio/vfprintff.c", + "tinystdio/vfscanf.c", + "tinystdio/vfscanff.c", + "tinystdio/vprintf.c", + "tinystdio/vscanf.c", + "tinystdio/vsnprintf.c", + "tinystdio/vsprintf.c", + "tinystdio/vsscanf.c", + "string/bcmp.c", "string/bcopy.c", "string/bzero.c", diff --git a/lib/picolibc b/lib/picolibc index 80528c684b..3bf0a10736 160000 --- a/lib/picolibc +++ b/lib/picolibc @@ -1 +1 @@ -Subproject commit 80528c684b10aaee977397e7eb40c4784e6dc433 +Subproject commit 3bf0a107362bd2b2048733e92f33b6cfeb6899e8 diff --git a/lib/picolibc-stdio.c b/lib/picolibc-stdio.c new file mode 100644 index 0000000000..23013bd818 --- /dev/null +++ b/lib/picolibc-stdio.c @@ -0,0 +1,12 @@ +#include + +// Defined in the runtime package. Writes to the default console (usually, the +// first UART or an USB-CDC device). +int runtime_putchar(char, FILE*); + +// Define stdin, stdout, and stderr as a single object. +// This object must not reside in ROM. +static FILE __stdio = FDEV_SETUP_STREAM(runtime_putchar, NULL, NULL, _FDEV_SETUP_WRITE); + +// Define the underlying structs for stdin, stdout, and stderr. +FILE *const __iob[3] = { &__stdio, &__stdio, &__stdio }; diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 4e81a9cc92..457aa61700 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -51,6 +51,11 @@ func libc_abort() { abort() } +//export runtime_putchar +func runtime_putchar(c byte) { + putchar(c) +} + //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { abort() diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 2e286baaa3..68d5bda1ed 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -139,6 +139,8 @@ func main() { // libc: test basic stdio functionality println("SEEK_CUR:", C.SEEK_CUR) + putsBuf := []byte("line written using C puts\x00") + C.puts((*C.char)(unsafe.Pointer(&putsBuf[0]))) } func printUnion(union C.joined_t) C.joined_t { diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 6b2cc5e5b5..943ca2d03f 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -62,3 +62,4 @@ enum width matches: true CFLAGS value: 17 copied string: foobar SEEK_CUR: 1 +line written using C puts From 21a2d5a5c73d2b30d6d6cd6da14018c6b6dabe78 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 26 Sep 2021 22:17:00 +0200 Subject: [PATCH 07/15] esp32c3: add support for GDB debugging You can now debug the ESP32-C3 from the TinyGo command line, like this: tinygo flash -target=esp32c3 examples/serial tinygo gdb -target=esp32c3 examples/serial It's important to flash before running `tinygo gdb`, because loading a new firmware from GDB has not yet been implemented. Probably the easiest way to connect to the ESP32-C3 is by using the built-in JTAG connection. See: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/jtag-debugging/configure-builtin-jtag.html You will need to make sure that the `openocd` command in your $PATH is the one from Espressif. Otherwise GDB will hang. You can debug this by supplying the -ocd-output flag: $ tinygo gdb -target=esp32c3 -ocd-output examples/serial Open On-Chip Debugger 0.10.0 openocd: Licensed under GNU GPL v2 openocd: For bug reports, read openocd: http://openocd.org/doc/doxygen/bugs.html openocd: embedded:startup.tcl:60: Error: Can't find interface/esp_usb_jtag.cfg openocd: in procedure 'script' openocd: at file "embedded:startup.tcl", line 60 Make sure to configure OpenOCD correctly, until you get the correct version (that includes the string "esp32"): $ openocd --version Open On-Chip Debugger v0.10.0-esp32-20210721 (2021-07-21-13:33) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html If you are on Linux, you may also get the following error: $ tinygo gdb -target=esp32c3 -ocd-output examples/serial Open On-Chip Debugger v0.10.0-esp32-20210721 (2021-07-21-13:33) openocd: Licensed under GNU GPL v2 openocd: For bug reports, read openocd: http://openocd.org/doc/doxygen/bugs.html openocd: Info : only one transport option; autoselect 'jtag' openocd: adapter speed: 40000 kHz openocd: openocd: Warn : Transport "jtag" was already selected openocd: Info : Listening on port 6666 for tcl connections openocd: Info : Listening on port 4444 for telnet connections openocd: Error: libusb_open() failed with LIBUSB_ERROR_ACCESS openocd: Error: esp_usb_jtag: could not find or open device! The error LIBUSB_ERROR_ACCESS means that there is a permission error. You can fix this by creating the following file: $ cat /etc/udev/rules.d/50-esp.rules # ESP32-C3 SUBSYSTEMS=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1001", MODE="0666" For more details, see: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/jtag-debugging/index.html --- targets/esp32c3.json | 6 +++++- targets/riscv32.json | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/targets/esp32c3.json b/targets/esp32c3.json index bb70003514..9c2fc64f5c 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -11,6 +11,10 @@ "src/device/esp/esp32c3.S" ], "binary-format": "esp32c3", - "flash-command": "esptool.py --chip=esp32c3 --port {port} write_flash 0x0 {bin}" + "flash-command": "esptool.py --chip=esp32c3 --port {port} write_flash 0x0 {bin}", + "serial-port": ["acm:303a:1001"], + "openocd-interface": "esp_usb_jtag", + "openocd-target": "esp32c3", + "openocd-commands": ["gdb_memory_map disable"] } diff --git a/targets/riscv32.json b/targets/riscv32.json index 24f5cee906..e0cb609dac 100644 --- a/targets/riscv32.json +++ b/targets/riscv32.json @@ -9,5 +9,6 @@ ], "ldflags": [ "-melf32lriscv" - ] + ], + "gdb": ["gdb-multiarch"] } From 213cdf6028c4c77c7b3a1cf5f99036897c8b577e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 27 Sep 2021 16:36:05 +0200 Subject: [PATCH 08/15] baremetal: implement calloc libc function This simply calls runtime.alloc, because the memory will be zero-initialized anyway. --- src/runtime/baremetal.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 457aa61700..c7d283d290 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -46,6 +46,14 @@ func libc_free(ptr unsafe.Pointer) { free(ptr) } +//export calloc +func libc_calloc(nmemb, size uintptr) unsafe.Pointer { + // Note: we could be even more correct here and check that nmemb * size + // doesn't overflow. However the current implementation should normally work + // fine. + return alloc(nmemb * size) +} + //export abort func libc_abort() { abort() From 95c2fbaf5a7f156e6dc0b8aefa4ed0aa6e6e5099 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 28 Sep 2021 00:04:25 +0200 Subject: [PATCH 09/15] riscv: use MSTATUS.MIE bit instead of MIE to disable interrupts This should behave the same but is compatible with the ESP32-C3 which lacks the MIE CSR (but does have the MSTATUS CSR). --- src/device/riscv/riscv.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/device/riscv/riscv.go b/src/device/riscv/riscv.go index e4f9254b7d..b621a17c49 100644 --- a/src/device/riscv/riscv.go +++ b/src/device/riscv/riscv.go @@ -25,13 +25,14 @@ func AsmFull(asm string, regs map[string]interface{}) uintptr func DisableInterrupts() uintptr { // Note: this can be optimized with a CSRRW instruction, which atomically // swaps the value and returns the old value. - mask := MIE.Get() - MIE.Set(0) + mask := MSTATUS.Get() + MSTATUS.ClearBits(1 << 3) // clear the MIE bit return mask } // EnableInterrupts enables all interrupts again. The value passed in must be // the mask returned by DisableInterrupts. func EnableInterrupts(mask uintptr) { - MIE.Set(mask) + mask &= 1 << 3 // clear all bits except for the MIE bit + MSTATUS.SetBits(mask) // set the MIE bit, if it was previously cleared } From 4ddb10751abaf2f2dbbd8632b46377ec564d5b36 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 28 Sep 2021 00:14:14 +0200 Subject: [PATCH 10/15] riscv: implement 32-bit atomic operations This is necessary to support the ESP32-C3, which lacks the A (atomic) extension and thus requires these 32-bit atomic operations. With this commit, flashing ./testdata/atomic.go to the ESP32-C3 works correctly and produces the expected output on the serial console. --- src/runtime/arch_tinygoriscv.go | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go index 3dcbcec255..d7b0fee290 100644 --- a/src/runtime/arch_tinygoriscv.go +++ b/src/runtime/arch_tinygoriscv.go @@ -18,6 +18,53 @@ func getCurrentStackPointer() uintptr { // supported RISC-V chips have a single hart, we can simply disable interrupts // to get the same behavior. +//export __atomic_load_4 +func __atomic_load_4(ptr *uint32, ordering int32) uint32 { + mask := riscv.DisableInterrupts() + value := *ptr + riscv.EnableInterrupts(mask) + return value +} + +//export __atomic_store_4 +func __atomic_store_4(ptr *uint32, value uint32, ordering int32) { + mask := riscv.DisableInterrupts() + *ptr = value + riscv.EnableInterrupts(mask) +} + +//export __atomic_exchange_4 +func __atomic_exchange_4(ptr *uint32, value uint32, ordering int32) uint32 { + mask := riscv.DisableInterrupts() + oldValue := *ptr + *ptr = value + riscv.EnableInterrupts(mask) + return oldValue +} + +//export __atomic_compare_exchange_4 +func __atomic_compare_exchange_4(ptr, expected *uint32, desired uint32, success_ordering, failure_ordering int32) bool { + mask := riscv.DisableInterrupts() + oldValue := *ptr + success := oldValue == *expected + if success { + *ptr = desired + } else { + *expected = oldValue + } + riscv.EnableInterrupts(mask) + return success +} + +//export __atomic_fetch_add_4 +func __atomic_fetch_add_4(ptr *uint32, value uint32, ordering int32) uint32 { + mask := riscv.DisableInterrupts() + oldValue := *ptr + *ptr = oldValue + value + riscv.EnableInterrupts(mask) + return oldValue +} + //export __atomic_load_8 func __atomic_load_8(ptr *uint64, ordering int32) uint64 { mask := riscv.DisableInterrupts() From 7b77437208a74e40026fec55d2fec595166aa814 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 17 Mar 2020 20:45:30 +0100 Subject: [PATCH 11/15] riscv: switch to tasks-based scheduler This is only supported for RV32 at the moment. RV64 can be added at a later time. --- src/internal/task/task_stack_arm.go | 2 +- src/internal/task/task_stack_tinygoriscv.S | 69 +++++++++++++++++++++ src/internal/task/task_stack_tinygoriscv.go | 66 ++++++++++++++++++++ targets/riscv.json | 1 + targets/riscv32.json | 2 + 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/internal/task/task_stack_tinygoriscv.S create mode 100644 src/internal/task/task_stack_tinygoriscv.go diff --git a/src/internal/task/task_stack_arm.go b/src/internal/task/task_stack_arm.go index acd63ece80..5934f02841 100644 --- a/src/internal/task/task_stack_arm.go +++ b/src/internal/task/task_stack_arm.go @@ -1,4 +1,4 @@ -// +build scheduler.tasks,arm,!cortexm,!avr,!xtensa +// +build scheduler.tasks,arm,!cortexm,!avr,!xtensa,!tinygo.riscv package task diff --git a/src/internal/task/task_stack_tinygoriscv.S b/src/internal/task/task_stack_tinygoriscv.S new file mode 100644 index 0000000000..0ac6f1231e --- /dev/null +++ b/src/internal/task/task_stack_tinygoriscv.S @@ -0,0 +1,69 @@ +.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, s0 contains the pc of the to-be-started function and s1 + // contains 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. + mv a0, s1 + + // Branch to the "goroutine start" function. Use jalr to write the return + // address to ra so we'll return here after the goroutine exits. + jalr s0 + + // After return, exit this goroutine. This is a tail call. + tail tinygo_pause + +.section .text.tinygo_swapTask +.global tinygo_swapTask +.type tinygo_swapTask, %function +tinygo_swapTask: + // This function gets the following parameters: + // a0 = newStack uintptr + // a1 = oldStack *uintptr + + // Push all callee-saved registers. + addi sp, sp, -52 + sw ra, 48(sp) + sw s11, 44(sp) + sw s10, 40(sp) + sw s9, 36(sp) + sw s8, 32(sp) + sw s7, 28(sp) + sw s6, 24(sp) + sw s5, 20(sp) + sw s4, 16(sp) + sw s3, 12(sp) + sw s2, 8(sp) + sw s1, 4(sp) + sw s0, (sp) + + // Save the current stack pointer in oldStack. + sw sp, 0(a1) + + // Switch to the new stack pointer. + mv sp, a0 + + // Pop all saved registers from this new stack. + lw ra, 48(sp) + lw s11, 44(sp) + lw s10, 40(sp) + lw s9, 36(sp) + lw s8, 32(sp) + lw s7, 28(sp) + lw s6, 24(sp) + lw s5, 20(sp) + lw s4, 16(sp) + lw s3, 12(sp) + lw s2, 8(sp) + lw s1, 4(sp) + lw s0, (sp) + addi sp, sp, 52 + + // Return into the task. + ret diff --git a/src/internal/task/task_stack_tinygoriscv.go b/src/internal/task/task_stack_tinygoriscv.go new file mode 100644 index 0000000000..6f20eebf14 --- /dev/null +++ b/src/internal/task/task_stack_tinygoriscv.go @@ -0,0 +1,66 @@ +// +build scheduler.tasks,tinygo.riscv + +package task + +import "unsafe" + +var systemStack uintptr + +// calleeSavedRegs is the list of registers that must be saved and restored when +// switching between tasks. Also see scheduler_riscv.S that relies on the +// exact layout of this struct. +type calleeSavedRegs struct { + s0 uintptr // x8 (fp) + s1 uintptr // x9 + s2 uintptr // x18 + s3 uintptr // x19 + s4 uintptr // x20 + s5 uintptr // x21 + s6 uintptr // x22 + s7 uintptr // x23 + s8 uintptr // x24 + s9 uintptr // x25 + s10 uintptr // x26 + s11 uintptr // x27 + + pc uintptr +} + +// archInit runs architecture-specific setup for the goroutine startup. +func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { + // Store the initial sp for the startTask function (implemented in assembly). + s.sp = uintptr(unsafe.Pointer(r)) + + // Initialize the registers. + // These will be popped off of the stack on the first resume of the goroutine. + + // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_riscv.S). + // This assembly code calls a function (passed in s0) with a single argument + // (passed in s1). After the function returns, it calls Pause(). + r.pc = uintptr(unsafe.Pointer(&startTask)) + + // Pass the function to call in s0. + // 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.s0 = fn + + // Pass the pointer to the arguments struct in s1. + r.s1 = uintptr(args) +} + +func (s *state) resume() { + swapTask(s.sp, &systemStack) +} + +func (s *state) pause() { + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) +} + +// SystemStack returns the system stack pointer when called from a task stack. +// When called from the system stack, it returns 0. +func SystemStack() uintptr { + return systemStack +} diff --git a/targets/riscv.json b/targets/riscv.json index 1ccf123a49..f4374776d7 100644 --- a/targets/riscv.json +++ b/targets/riscv.json @@ -17,6 +17,7 @@ ], "extra-files": [ "src/device/riscv/start.S", + "src/internal/task/task_stack_tinygoriscv.S", "src/runtime/gc_riscv.S", "src/device/riscv/handleinterrupt.S" ], diff --git a/targets/riscv32.json b/targets/riscv32.json index e0cb609dac..506ba68224 100644 --- a/targets/riscv32.json +++ b/targets/riscv32.json @@ -2,6 +2,8 @@ "inherits": ["riscv"], "llvm-target": "riscv32--none", "build-tags": ["tinygo.riscv32"], + "scheduler": "tasks", + "default-stack-size": 2048, "cflags": [ "--target=riscv32--none", "-march=rv32imac", From 6542220de325c65fac6e30c7b4876071f0bef60f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 28 Sep 2021 00:20:49 +0200 Subject: [PATCH 12/15] esp32c3: use tasks scheduler by default --- targets/esp32c3.json | 1 - 1 file changed, 1 deletion(-) diff --git a/targets/esp32c3.json b/targets/esp32c3.json index 9c2fc64f5c..7bca64c49d 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -2,7 +2,6 @@ "inherits": ["riscv32"], "features": ["+c", "+m"], "build-tags": ["esp32c3", "esp"], - "scheduler": "none", "serial": "uart", "rtlib": "compiler-rt", "libc": "picolibc", From dcfa42c6ff19e5c3d2add4e6021aa0aaa0189b9b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Sep 2021 02:11:10 +0200 Subject: [PATCH 13/15] cgo: add FreeRTOS compatibility headers This is especially useful if we ever want to support the ESP-IDF. Currently implemented: - xTaskGetCurrentTaskHandle - xSemaphoreCreateRecursiveMutex - xSemaphoreDelete - xSemaphoreTakeRecursive - xSemaphoreGiveRecursive --- compileopts/config.go | 3 + loader/goroot.go | 1 + src/compat/freertos/freertos.go | 69 +++++++++++++++++++ .../freertos/include/freertos/FreeRTOS.h | 9 +++ src/compat/freertos/include/freertos/queue.h | 3 + src/compat/freertos/include/freertos/semphr.h | 12 ++++ src/compat/freertos/include/freertos/task.h | 5 ++ 7 files changed, 102 insertions(+) create mode 100644 src/compat/freertos/freertos.go create mode 100644 src/compat/freertos/include/freertos/FreeRTOS.h create mode 100644 src/compat/freertos/include/freertos/queue.h create mode 100644 src/compat/freertos/include/freertos/semphr.h create mode 100644 src/compat/freertos/include/freertos/task.h diff --git a/compileopts/config.go b/compileopts/config.go index f1e34b7f0a..57aa085d8e 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -201,6 +201,9 @@ func (c *Config) RP2040BootPatch() bool { // preprocessing. func (c *Config) CFlags() []string { var cflags []string + // Compatibility CFlags. + cflags = append(cflags, "-I"+filepath.Join(goenv.Get("TINYGOROOT"), "src/compat/freertos/include")) + // CFlags for the target. for _, flag := range c.Target.CFlags { cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) } diff --git a/loader/goroot.go b/loader/goroot.go index 8f0acf1b88..b29d51dcde 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -220,6 +220,7 @@ func needsSyscallPackage(buildTags []string) bool { func pathsToOverride(needsSyscallPackage bool) map[string]bool { paths := map[string]bool{ "/": true, + "compat/": false, "crypto/": true, "crypto/rand/": false, "device/": false, diff --git a/src/compat/freertos/freertos.go b/src/compat/freertos/freertos.go new file mode 100644 index 0000000000..920d8fd1e8 --- /dev/null +++ b/src/compat/freertos/freertos.go @@ -0,0 +1,69 @@ +// Package freertos provides a compatibility layer on top of the TinyGo +// scheduler for C code that wants to call FreeRTOS functions. One example is +// the ESP-IDF, which expects there to be a FreeRTOS-like RTOS. +package freertos + +// #include +// #include +// #include +import "C" +import ( + "sync" + "unsafe" + + "internal/task" +) + +//export xTaskGetCurrentTaskHandle +func xTaskGetCurrentTaskHandle() C.TaskHandle_t { + return C.TaskHandle_t(task.Current()) +} + +type Semaphore struct { + lock sync.Mutex // the lock itself + task *task.Task // the task currently locking this semaphore + count uint32 // how many times this semaphore is locked +} + +//export xSemaphoreCreateRecursiveMutex +func xSemaphoreCreateRecursiveMutex() C.SemaphoreHandle_t { + var mutex Semaphore + return (C.SemaphoreHandle_t)(unsafe.Pointer(&mutex)) +} + +//export vSemaphoreDelete +func vSemaphoreDelete(xSemaphore C.SemaphoreHandle_t) { + mutex := (*Semaphore)(unsafe.Pointer(xSemaphore)) + if mutex.task != nil { + panic("vSemaphoreDelete: still locked") + } +} + +//export xSemaphoreTakeRecursive +func xSemaphoreTakeRecursive(xMutex C.SemaphoreHandle_t, xTicksToWait C.TickType_t) C.BaseType_t { + // TODO: implement xTickToWait, or at least when xTicksToWait equals 0. + mutex := (*Semaphore)(unsafe.Pointer(xMutex)) + if mutex.task == task.Current() { + // Already locked. + mutex.count++ + return 1 // pdTRUE + } + // Not yet locked. + mutex.lock.Lock() + mutex.task = task.Current() + return 1 // pdTRUE +} + +//export xSemaphoreGiveRecursive +func xSemaphoreGiveRecursive(xMutex C.SemaphoreHandle_t) C.BaseType_t { + mutex := (*Semaphore)(unsafe.Pointer(xMutex)) + if mutex.task == task.Current() { + // Already locked. + mutex.count-- + if mutex.count == 0 { + mutex.lock.Unlock() + } + return 1 // pdTRUE + } + panic("xSemaphoreGiveRecursive: not locked by this task") +} diff --git a/src/compat/freertos/include/freertos/FreeRTOS.h b/src/compat/freertos/include/freertos/FreeRTOS.h new file mode 100644 index 0000000000..3e46754e3d --- /dev/null +++ b/src/compat/freertos/include/freertos/FreeRTOS.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +typedef uint32_t TickType_t; +typedef int BaseType_t; +typedef unsigned int UBaseType_t; + +#define portMAX_DELAY (TickType_t)0xffffffffUL diff --git a/src/compat/freertos/include/freertos/queue.h b/src/compat/freertos/include/freertos/queue.h new file mode 100644 index 0000000000..53d7595461 --- /dev/null +++ b/src/compat/freertos/include/freertos/queue.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct QueueDefinition * QueueHandle_t; diff --git a/src/compat/freertos/include/freertos/semphr.h b/src/compat/freertos/include/freertos/semphr.h new file mode 100644 index 0000000000..e394ad771e --- /dev/null +++ b/src/compat/freertos/include/freertos/semphr.h @@ -0,0 +1,12 @@ +#pragma once + +// Note: in FreeRTOS, SemaphoreHandle_t is an alias for QueueHandle_t. +typedef struct SemaphoreDefinition * SemaphoreHandle_t; + +SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void); + +void vSemaphoreDelete(SemaphoreHandle_t xSemaphore); + +// Note: these two functions are macros in FreeRTOS. +BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait); +BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex); diff --git a/src/compat/freertos/include/freertos/task.h b/src/compat/freertos/include/freertos/task.h new file mode 100644 index 0000000000..76a31228f3 --- /dev/null +++ b/src/compat/freertos/include/freertos/task.h @@ -0,0 +1,5 @@ +#pragma once + +typedef void * TaskHandle_t; + +TaskHandle_t xTaskGetCurrentTaskHandle(void); From 426c1935402d0b7357d31161dafd22eff729fe51 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 29 Sep 2021 01:22:36 +0200 Subject: [PATCH 14/15] cgo: add more FreeRTOS compatibility This commit implements: * xTaskCreate * vTaskDelay * xSemaphoreCreateCounting (partially) * xSemaphoreTake * xSemaphoreGive * xQueueCreate * vQueueDelete * xQueueReceive * xQueueSend * uxQueueMessagesWaiting --- src/compat/freertos/freertos.c | 6 ++ src/compat/freertos/freertos.go | 95 ++++++++++++++++++- .../freertos/include/freertos/FreeRTOS.h | 4 +- src/compat/freertos/include/freertos/queue.h | 9 +- src/compat/freertos/include/freertos/semphr.h | 7 +- src/compat/freertos/include/freertos/task.h | 5 + src/runtime/chan.go | 14 +++ 7 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 src/compat/freertos/freertos.c diff --git a/src/compat/freertos/freertos.c b/src/compat/freertos/freertos.c new file mode 100644 index 0000000000..bab472eb84 --- /dev/null +++ b/src/compat/freertos/freertos.c @@ -0,0 +1,6 @@ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +void freertos_callFunction(void (*function)(void *), void *parameter) { + function(parameter); +} diff --git a/src/compat/freertos/freertos.go b/src/compat/freertos/freertos.go index 920d8fd1e8..392b32259d 100644 --- a/src/compat/freertos/freertos.go +++ b/src/compat/freertos/freertos.go @@ -4,11 +4,14 @@ package freertos // #include +// #include // #include // #include +// void freertos_callFunction(void (*function)(void *), void *parameter); import "C" import ( "sync" + "time" "unsafe" "internal/task" @@ -19,30 +22,66 @@ func xTaskGetCurrentTaskHandle() C.TaskHandle_t { return C.TaskHandle_t(task.Current()) } +//export xTaskCreate +func xTaskCreate(pvTaskCode C.TaskFunction_t, pcName *C.char, usStackDepth uintptr, pvParameters unsafe.Pointer, uxPriority C.UBaseType_t, pxCreatedTask *C.TaskHandle_t) C.BaseType_t { + go func() { + C.freertos_callFunction(pvTaskCode, pvParameters) + }() + if pxCreatedTask != nil { + // Code expectes there to be *something*. + var tcb int + *pxCreatedTask = unsafe.Pointer(&tcb) + } + return 1 // pdPASS +} + +//export vTaskDelay +func vTaskDelay(xTicksToDelay C.TickType_t) { + // The default tick rate appears to be 100Hz (10ms per tick). + time.Sleep(time.Duration(xTicksToDelay) * C.portTICK_PERIOD_MS * time.Millisecond) +} + type Semaphore struct { lock sync.Mutex // the lock itself task *task.Task // the task currently locking this semaphore count uint32 // how many times this semaphore is locked } +//export xSemaphoreCreateCounting +func xSemaphoreCreateCounting(uxMaxCount C.UBaseType_t, uxInitialCount C.UBaseType_t) C.SemaphoreHandle_t { + if uxMaxCount != 1 || uxInitialCount != 0 { + println("TODO: xSemaphoreCreateCounting that's not a mutex") + return nil + } + mutex := Semaphore{} + return C.SemaphoreHandle_t(&mutex) +} + //export xSemaphoreCreateRecursiveMutex func xSemaphoreCreateRecursiveMutex() C.SemaphoreHandle_t { var mutex Semaphore - return (C.SemaphoreHandle_t)(unsafe.Pointer(&mutex)) + return (C.SemaphoreHandle_t)(&mutex) } //export vSemaphoreDelete func vSemaphoreDelete(xSemaphore C.SemaphoreHandle_t) { - mutex := (*Semaphore)(unsafe.Pointer(xSemaphore)) + mutex := (*Semaphore)(xSemaphore) if mutex.task != nil { panic("vSemaphoreDelete: still locked") } } +//export xSemaphoreTake +func xSemaphoreTake(xSemaphore C.QueueHandle_t, xTicksToWait C.TickType_t) C.BaseType_t { + mutex := (*Semaphore)(xSemaphore) + mutex.lock.Lock() + return 1 // pdTRUE +} + //export xSemaphoreTakeRecursive func xSemaphoreTakeRecursive(xMutex C.SemaphoreHandle_t, xTicksToWait C.TickType_t) C.BaseType_t { // TODO: implement xTickToWait, or at least when xTicksToWait equals 0. - mutex := (*Semaphore)(unsafe.Pointer(xMutex)) + mutex := (*Semaphore)(xMutex) if mutex.task == task.Current() { // Already locked. mutex.count++ @@ -54,9 +93,16 @@ func xSemaphoreTakeRecursive(xMutex C.SemaphoreHandle_t, xTicksToWait C.TickType return 1 // pdTRUE } +//export xSemaphoreGive +func xSemaphoreGive(xSemaphore C.QueueHandle_t) C.BaseType_t { + mutex := (*Semaphore)(xSemaphore) + mutex.lock.Unlock() + return 1 // pdTRUE +} + //export xSemaphoreGiveRecursive func xSemaphoreGiveRecursive(xMutex C.SemaphoreHandle_t) C.BaseType_t { - mutex := (*Semaphore)(unsafe.Pointer(xMutex)) + mutex := (*Semaphore)(xMutex) if mutex.task == task.Current() { // Already locked. mutex.count-- @@ -67,3 +113,44 @@ func xSemaphoreGiveRecursive(xMutex C.SemaphoreHandle_t) C.BaseType_t { } panic("xSemaphoreGiveRecursive: not locked by this task") } + +//export xQueueCreate +func xQueueCreate(uxQueueLength C.UBaseType_t, uxItemSize C.UBaseType_t) C.QueueHandle_t { + return chanMakeUnsafePointer(uintptr(uxItemSize), uintptr(uxQueueLength)) +} + +//export vQueueDelete +func vQueueDelete(xQueue C.QueueHandle_t) { + // TODO: close the channel +} + +//export xQueueReceive +func xQueueReceive(xQueue C.QueueHandle_t, pvBuffer unsafe.Pointer, xTicksToWait C.TickType_t) C.BaseType_t { + // Note: xTicksToWait is ignored. + chanRecvUnsafePointer(xQueue, pvBuffer) + return 1 // pdTRUE +} + +//export xQueueSend +func xQueueSend(xQueue C.QueueHandle_t, pvBuffer unsafe.Pointer, xTicksToWait C.TickType_t) C.BaseType_t { + // Note: xTicksToWait is ignored. + chanSendUnsafePointer(xQueue, pvBuffer) + return 1 // pdTRUE +} + +//export uxQueueMessagesWaiting +func uxQueueMessagesWaiting(xQueue C.QueueHandle_t) C.UBaseType_t { + return C.UBaseType_t(chanLenUnsafePointer(xQueue)) +} + +//go:linkname chanMakeUnsafePointer runtime.chanMakeUnsafePointer +func chanMakeUnsafePointer(elementSize uintptr, bufSize uintptr) unsafe.Pointer + +//go:linkname chanLenUnsafePointer runtime.chanLenUnsafePointer +func chanLenUnsafePointer(ch unsafe.Pointer) int + +//go:linkname chanSendUnsafePointer runtime.chanSendUnsafePointer +func chanSendUnsafePointer(ch, value unsafe.Pointer) + +//go:linkname chanRecvUnsafePointer runtime.chanRecvUnsafePointer +func chanRecvUnsafePointer(ch, value unsafe.Pointer) diff --git a/src/compat/freertos/include/freertos/FreeRTOS.h b/src/compat/freertos/include/freertos/FreeRTOS.h index 3e46754e3d..4758e2fd97 100644 --- a/src/compat/freertos/include/freertos/FreeRTOS.h +++ b/src/compat/freertos/include/freertos/FreeRTOS.h @@ -6,4 +6,6 @@ typedef uint32_t TickType_t; typedef int BaseType_t; typedef unsigned int UBaseType_t; -#define portMAX_DELAY (TickType_t)0xffffffffUL +#define portMAX_DELAY (TickType_t)0xffffffffUL +#define portTICK_PERIOD_MS (10) +#define configMAX_PRIORITIES (25) diff --git a/src/compat/freertos/include/freertos/queue.h b/src/compat/freertos/include/freertos/queue.h index 53d7595461..d50ddc37c6 100644 --- a/src/compat/freertos/include/freertos/queue.h +++ b/src/compat/freertos/include/freertos/queue.h @@ -1,3 +1,10 @@ #pragma once -typedef struct QueueDefinition * QueueHandle_t; +typedef void * QueueHandle_t; + +QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize); +void vQueueDelete(QueueHandle_t xQueue); + +BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait); +BaseType_t xQueueSend(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait); +UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue); diff --git a/src/compat/freertos/include/freertos/semphr.h b/src/compat/freertos/include/freertos/semphr.h index e394ad771e..672c51b33f 100644 --- a/src/compat/freertos/include/freertos/semphr.h +++ b/src/compat/freertos/include/freertos/semphr.h @@ -1,8 +1,9 @@ #pragma once // Note: in FreeRTOS, SemaphoreHandle_t is an alias for QueueHandle_t. -typedef struct SemaphoreDefinition * SemaphoreHandle_t; +typedef void * SemaphoreHandle_t; +SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount); SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void); void vSemaphoreDelete(SemaphoreHandle_t xSemaphore); @@ -10,3 +11,7 @@ void vSemaphoreDelete(SemaphoreHandle_t xSemaphore); // Note: these two functions are macros in FreeRTOS. BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait); BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex); + +// Note: these functions are macros in FreeRTOS. +BaseType_t xSemaphoreTake(QueueHandle_t xSemaphore, TickType_t xTicksToWait); +BaseType_t xSemaphoreGive(QueueHandle_t xSemaphore); diff --git a/src/compat/freertos/include/freertos/task.h b/src/compat/freertos/include/freertos/task.h index 76a31228f3..040ee316e2 100644 --- a/src/compat/freertos/include/freertos/task.h +++ b/src/compat/freertos/include/freertos/task.h @@ -1,5 +1,10 @@ #pragma once typedef void * TaskHandle_t; +typedef void (*TaskFunction_t)(void *); TaskHandle_t xTaskGetCurrentTaskHandle(void); + +BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, uintptr_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask); + +void vTaskDelay(const TickType_t xTicksToDelay); diff --git a/src/runtime/chan.go b/src/runtime/chan.go index c2da4f05cf..2209d42c6e 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -127,6 +127,10 @@ type channel struct { buf unsafe.Pointer // pointer to first element of buffer } +func chanMakeUnsafePointer(elementSize, bufSize uintptr) unsafe.Pointer { + return unsafe.Pointer(chanMake(elementSize, bufSize)) +} + // chanMake creates a new channel with the given element size and buffer length in number of elements. // This is a compiler intrinsic. func chanMake(elementSize uintptr, bufSize uintptr) *channel { @@ -495,6 +499,11 @@ func chanSend(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList sender.Ptr = nil } +func chanSendUnsafePointer(ch, value unsafe.Pointer) { + var blockedlist channelBlockedList + chanSend((*channel)(ch), value, &blockedlist) +} + // chanRecv receives a single value over a channel. // It blocks if there is no available value to recieve. // The recieved value is copied into the value pointer. @@ -532,6 +541,11 @@ func chanRecv(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList return ok } +func chanRecvUnsafePointer(ch, value unsafe.Pointer) { + var blockedlist channelBlockedList + chanRecv((*channel)(ch), value, &blockedlist) +} + // chanClose closes the given channel. If this channel has a receiver or is // empty, it closes the channel. Else, it panics. func chanClose(ch *channel) { From 909b5ebae479461ef8dca1028c6e5c61d645554d Mon Sep 17 00:00:00 2001 From: Dmitriy Date: Fri, 22 Oct 2021 21:13:24 -0400 Subject: [PATCH 15/15] add support for CPU interrupts for ESP32-C3 --- src/device/esp/esp32c3.S | 18 ++++ src/runtime/interrupt/interrupt_esp32c3.go | 114 +++++++++++++++++++++ src/runtime/runtime_esp32c3.go | 30 ++++++ targets/esp32c3.json | 3 +- targets/esp32c3.ld | 3 + 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 src/runtime/interrupt/interrupt_esp32c3.go diff --git a/src/device/esp/esp32c3.S b/src/device/esp/esp32c3.S index 7073398248..0395d73bcf 100644 --- a/src/device/esp/esp32c3.S +++ b/src/device/esp/esp32c3.S @@ -47,3 +47,21 @@ call_start_cpu0: // (It appears that the linker relaxes this jump and instead inserts the // _start function right after here). j _start + +.section .text.exception_vectors +.global _vector_table +.type _vector_table,@function + +_vector_table: + + .option push + .option norvc + + .rept 32 + j handleInterruptASM /* interrupt handler */ + .endr + + .option pop + +.size _vector_table, .-_vector_table + diff --git a/src/runtime/interrupt/interrupt_esp32c3.go b/src/runtime/interrupt/interrupt_esp32c3.go new file mode 100644 index 0000000000..20b055f8dc --- /dev/null +++ b/src/runtime/interrupt/interrupt_esp32c3.go @@ -0,0 +1,114 @@ +// +build esp32c3 + +package interrupt + +import ( + "device/esp" + "device/riscv" + "errors" + "runtime/volatile" + "unsafe" +) + +// Enable register CPU interrupt with interrupt.Interrupt. +// The ESP32-C3 has 31 CPU independent interrupts. +// The Interrupt.New(x, f) (x = [1..31]) attaches CPU interrupt to function f. +// Caller must map the selected interrupt using following sequence (for example using id 5): +// +// // map interrupt 5 to my XXXX module +// esp.INTERRUPT_CORE0.XXXX_INTERRUPT_PRO_MAP.Set( 5 ) +// _ = Interrupt.New(5, func(interrupt.Interrupt) { +// ... +// }).Enable() +func (i Interrupt) Enable() error { + if i.num < 1 && i.num > 31 { + return errors.New("interrupt for ESP32-C3 must be in range of 1 through 31") + } + mask := riscv.DisableInterrupts() + defer riscv.EnableInterrupts(mask) + + // enable CPU interrupt number i.num + esp.INTERRUPT_CORE0.CPU_INT_ENABLE.SetBits(1 << i.num) + + // Set pulse interrupt type (rising edge detection) + esp.INTERRUPT_CORE0.CPU_INT_TYPE.SetBits(1 << i.num) + + // Set default threshold to 5 + reg := (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.INTERRUPT_CORE0.CPU_INT_PRI_0)) + uintptr(i.num)*4))) + reg.Set(5) + + // Reset interrupt before reenabling + esp.INTERRUPT_CORE0.CPU_INT_CLEAR.SetBits(1 << i.num) + esp.INTERRUPT_CORE0.CPU_INT_CLEAR.ClearBits(1 << i.num) + + // we must wait for any pending write operations to complete + riscv.Asm("fence") + return nil +} + +//export handleInterrupt +func handleInterrupt() { + mcause := riscv.MCAUSE.Get() + exception := mcause&(1<<31) == 0 + interruptNumber := uint32(mcause & 0x1f) + + if !exception && interruptNumber > 0 { + // save mepc, which could be overwritten by another CPU interrupt + mepc := riscv.MEPC.Get() + + // disable interrupt + interruptBit := uint32(1 << interruptNumber) + esp.INTERRUPT_CORE0.CPU_INT_ENABLE.ClearBits(interruptBit) + + // reset pending status interrupt + if esp.INTERRUPT_CORE0.CPU_INT_TYPE.Get()&interruptBit != 0 { + // this is edge type interrupt + esp.INTERRUPT_CORE0.CPU_INT_CLEAR.SetBits(interruptBit) + esp.INTERRUPT_CORE0.CPU_INT_CLEAR.ClearBits(interruptBit) + } else { + // this is level type interrupt + esp.INTERRUPT_CORE0.CPU_INT_CLEAR.ClearBits(interruptBit) + } + + // enable CPU interrupts + riscv.MSTATUS.SetBits(0x8) + + // Call registered interrupt handler(s) + callInterruptHandler(int(interruptNumber)) + + // disable CPU interrupts + riscv.MSTATUS.ClearBits(0x8) + + // mpie must be set to 1 to resume interrupts after 'MRET' + riscv.MSTATUS.SetBits(0x80) + + // restore MEPC + riscv.MEPC.Set(mepc) + + // enable this interrupt + esp.INTERRUPT_CORE0.CPU_INT_ENABLE.SetBits(interruptBit) + + // do not enable CPU interrupts now + // the 'MRET' in src/device/riscv/handleinterrupt.S will copies the state of MPIE back into MIE, and subsequently clears MPIE. + // riscv.MSTATUS.SetBits(0x8) + } else { + // Topmost bit is clear, so it is an exception of some sort. + // We could implement support for unsupported instructions here (such as + // misaligned loads). However, for now we'll just print a fatal error. + handleException(mcause) + } +} + +func handleException(mcause uintptr) { + println("*** Exception: pc:", riscv.MEPC.Get()) + println("*** Exception: code:", uint32(mcause&0x1f)) + println("*** Exception: mcause:", mcause) + for { + riscv.Asm("wfi") + } +} + +// callInterruptHandler is a compiler-generated function that calls the +// appropriate interrupt handler for the given interrupt ID. +//go:linkname callInterruptHandler runtime.callInterruptHandler +func callInterruptHandler(id int) diff --git a/src/runtime/runtime_esp32c3.go b/src/runtime/runtime_esp32c3.go index 0f769c0fcc..2b73222ad6 100644 --- a/src/runtime/runtime_esp32c3.go +++ b/src/runtime/runtime_esp32c3.go @@ -5,6 +5,8 @@ package runtime import ( "device/esp" "device/riscv" + "runtime/volatile" + "unsafe" ) // This is the function called on startup after the flash (IROM/DROM) is @@ -47,6 +49,9 @@ func main() { clearbss() + // Configure interrupt handler + interruptInit() + // Initialize main system timer used for time.Now. initTimer() @@ -63,3 +68,28 @@ func abort() { riscv.Asm("wfi") } } + +// interruptInit initialize the interrupt controller and called from runtime once. +func interruptInit() { + mie := riscv.DisableInterrupts() + + // Reset all interrupt source priorities to zero. + priReg := &esp.INTERRUPT_CORE0.CPU_INT_PRI_1 + for i := 0; i < 31; i++ { + priReg.Set(0) + priReg = (*volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(priReg)) + uintptr(4))) + } + + // default threshold for interrupts is 5 + esp.INTERRUPT_CORE0.CPU_INT_THRESH.Set(5) + + // Set the interrupt address. + // Set MODE field to 1 - a vector base address (only supported by ESP32C3) + // Note that this address must be aligned to 256 bytes. + riscv.MTVEC.Set((uintptr(unsafe.Pointer(&_vector_table))) | 1) + + riscv.EnableInterrupts(mie) +} + +//go:extern _vector_table +var _vector_table [0]uintptr diff --git a/targets/esp32c3.json b/targets/esp32c3.json index 7bca64c49d..1ef709225f 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -14,6 +14,7 @@ "serial-port": ["acm:303a:1001"], "openocd-interface": "esp_usb_jtag", "openocd-target": "esp32c3", - "openocd-commands": ["gdb_memory_map disable"] + "openocd-commands": ["gdb_memory_map disable"], + "gdb": ["riscv32-esp-elf-gdb"] } diff --git a/targets/esp32c3.ld b/targets/esp32c3.ld index 4f6058efc5..3cef55ba4c 100644 --- a/targets/esp32c3.ld +++ b/targets/esp32c3.ld @@ -146,6 +146,9 @@ SECTIONS */ .text : ALIGN(4) { + . = ALIGN (256); + *(.text.exception_vectors) + . = ALIGN (4); *(.text .text.*) *(.wifislpiram .wifislpiram.*) } >IROM