Compare commits

...

26 Commits

Author SHA1 Message Date
deadprogram 6645f412ae compiler: disambiguate generic instances with function-local type aliases
x/tools go/ssa names instantiations using the type argument's String().
Function-local aliases like `type F = float64` therefore collide when two
callers use distinct aliases named F (Go 1.27's internal/strconv.ftoa32
and ftoa64 do exactly this). Extend localTypeArgsSuffix to detect local
aliases and include their declaration position, so the float32 and
float64 instantiations get distinct LLVM symbols.

Fixes wrong float formatting in strconv/math on Go 1.27, and adds a
regression test to testdata/localtypes.go.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-12 12:28:48 +02:00
Evan Wies 35a61ac8c5 compiler: support file-level //go:linkname directives
Modern golang.org/x/sys/unix (v0.36+) declares its linknames
detached from function declarations, e.g.:

    func syscall_syscall(...)
    //go:linkname syscall_syscall syscall.syscall

TinyGo's pragma parser only inspected function doc comments and
therefore missed these, producing link errors like:

    undefined symbol: _golang.org/x/sys/unix.syscall_syscall

Extend parsePragmas to also walk the enclosing *ast.File's
free-standing comments for //go:linkname directives matching the
function's name. Function-attached directives still take
precedence. The existing 'unsafe' import gate is preserved.

Fixes #4395, #5365
2026-07-12 11:54:38 +02:00
Konstantin Sharlaimov c1a4ed1489 machine/stm32: add OTG FS USB driver for F4/F7
STM32F4 and F7 share the same OTG FS IP but have no USB driver in
TinyGo. This adds a full device-mode driver covering CDC, HID, and
MSC with working examples.

- OTG FS has 4 physical EPs (0–3); virtual indices 4–7 fold onto
  them via physEP() so the existing machine/usb API is unchanged
- F4 bypasses VBUS sensing via GCCFG.NOVBUSSENS; F7 uses the USB
  voltage regulator + GOTGCTL B-valid override instead
- STM32F7 PLL_Q changed 2→9 to produce the 48 MHz clock required
  by USB/RNG/SDMMC; CK48MSEL cleared to select main PLL as source
- HID and MSC descriptors remapped at init() to physical endpoint
  addresses (EP2/EP1 for HID, EP2/EP3 for MSC)
- usb-storage example replaced machine.Flash with a FAT12 RAM disk
  so the host mounts without reformatting
- MSC sendCSW sets queuedBytes before state transition to fix a
  missed byte-count on the status phase
2026-07-12 10:45:49 +02:00
Jake Bailey 140c82e012 compiler: disambiguate generic instance link names with local type args 2026-07-11 16:20:08 +02:00
Jake Bailey dc23ce73d6 testdata: add regression test for generic instances with local type args 2026-07-11 16:20:08 +02:00
Jake Bailey e039ce131c runtime: encode pending Goexit in panic state
Treat panicState as a bitmask so a recovered panic during Goexit
can clear the panic bit while preserving the pending Goexit bit.
This removes the separate deferFrame Goexit field and restores the
previous defer frame size.
2026-07-11 11:53:33 +02:00
Jake Bailey d59c706e60 GNUmakefile: increase encoding/xml test stack
Top-level tests now run in goroutines, which puts stdlib tests on
TinyGo's default host goroutine stack. encoding/xml's depth-limit
test needs a larger stack on Linux and Darwin, so run just that
package with a larger stack while leaving the rest of the host stdlib
tests on the default stack.
2026-07-11 11:53:33 +02:00
Jake Bailey 26d3645317 GNUMakefile: change skip reason note 2026-07-11 11:53:33 +02:00
Jake Bailey 432ebe13ed runtime: make Goexit exit host threads
The threads scheduler handled runtime.Goexit using the generic deadlock
path, which parked the current pthread forever. Add a scheduler-specific
goexit hook so it can remove the current task and exit the pthread while
ordinary blocking deadlocks still block.

Track main goroutine Goexit so the last remaining goroutine reports the
expected deadlock instead of letting the process exit successfully.
Expand the crash coverage with the Goexit cases covered by Big Go's
runtime tests.
2026-07-11 11:53:33 +02:00
Jake Bailey 16eefc59eb runtime, testing: support Goexit, SkipNow, FailNow
Keep a pending Goexit separate from the active panic state so recovered
deferred panics do not cancel the original Goexit unwind. Goexit should
also ignore -panic=trap, since it is not a panic.

Wire testing.FailNow and SkipNow through runtime.Goexit, and run tests
and benchmarks in goroutines. This lets Goexit terminate the user
function without skipping test bookkeeping. Add Goexit/recover coverage
and enable crypto/ecdh in the Linux stdlib test list.
2026-07-11 11:53:33 +02:00
deadprogram fb1bf2e6bf build: statically link MinGW runtime in LLVM tools on Windows
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-10 23:54:46 +02:00
Matthew Hiles 0922e3e956 add device/uefi and device/amd64 2026-07-08 14:19:59 +02:00
Victor Costa bd61913973 wasm: patch wasm_exec.js and wasm_exec_node.js from Go 1.18+ (#5483)
* wasm: patch wasm_exec.js and wasm_exec_node.js from Go 1.18+

Port this commit https://github.com/golang/go/commit/680caf15355057ca84857a2a291b6f5c44e73329 removing polyfills from `wasm_exec.js`, now the environment is expected to provide all the required polyfills.

`wasm_exec.js` now only provides stub fallbacks for globalThis.fs and globalThis.process.

All NodeJS specific code is now in a separate file `wasm_exec_node.js` with its required polyfills.

* feat: bump minimum node version to 22

Drops official support for NodeJS 18 for WASM by removing the provided polyfills in the `wasm_exec.js`.

Now the environment is expected to provide the polyfill if it is running on older NodeJS versions

The new minimum required version for WASM is NodeJS 22+.

* chore: removed wrong comment

`wasm_exec_node.js` no longer contains the polyfill for NodeJS 18
2026-07-08 12:32:00 +02:00
Matthew Hiles 2dbfdbae10 UEFI Target Groundwork: Add LinkerFlavor compile option (#5465)
* Add LinkerFlavor so the odd pairing of COFF+Linux is possible (necessary for UEFI)

* But back bits of builder.go that were mistakenly removed

* Update compileopts/config.go

Co-authored-by: Ayke <aykevanlaethem@gmail.com>

* update LinkerFlavor test

---------

Co-authored-by: Ayke <aykevanlaethem@gmail.com>
2026-07-08 10:33:59 +02:00
Ayke van Laethem 05029c4fb5 machine: add support for the STM32U031 chip 2026-07-08 08:54:06 +02:00
Bryan Souza a7b30639d8 Adding support for the 'Plus' board variant of SeeedStudio XIAO nrf52840, works with Sense variants as well (#5479)
* adding support for SeeedStudio XIAO BLE Plus and Sense Plus boards;

* adjusting definitions for UARTs on XIAO BLE Plus;

* fixed buid tag on XIAO BLE Plus;

* tested by building some examples; fixed UART nomenclature;

* added xiao-ble-plus to GNUmakefile smoketests;
2026-07-07 13:36:18 +02:00
Pat Whittingslow 4adc661197 upgrade net (#5490)
* upgrade net

* add some missing crypto and os API (#5488)

* add missing crypto API

* gofmt crypto/tls/common.go

* pull new net with unixsock formatted

* update net to latest main
2026-07-07 10:40:36 +02:00
Pat Whittingslow 311c071ae6 add runtime.fastrandn (#5502) 2026-07-06 22:35:41 -03:00
Jake Bailey f1fdfe2313 reflect: add missing iterator methods 2026-07-06 23:47:28 +02:00
Jake Bailey 7c3dc05117 all: modernize (#5498)
* modernize string cut usage

* modernize string cut prefix usage

* modernize slices helper usage

* modernize min and max usage

* modernize loop variable copies

* modernize integer range loops

* modernize map copy loops

* modernize go types iterator usage

* modernize empty interface usage

* modernize atomic type usage

* modernize string builders

* modernize string split iteration

* modernize remaining loop variable copies

* modernize usb cdc min usage

* modernize src integer range loops

* modernize example empty interface usage

* modernize src min and max usage

* modernize src integer range loops

* modernize src empty interface usage

* modernize src atomic type usage

* modernize reflect type lookups

* modernize review nits
2026-07-06 21:58:48 +02:00
Ron Evans e569dcfe38 runtime: fix ticker not stopping when Stop races with its callback (#5487)
* runtime: fix ticker not stopping when Stop races with its callback

On the threads and cores schedulers, timer callbacks run concurrently
with user goroutines.

If Stop (or Reset) was called in the window after the node was popped
but before its callback re-added it, removeTimer would not find the
timer in the queue, so it would be re-added to timer anyway.

Track timers whose callback is currently running in a firing list, and
have removeTimer mark a firing timer as stopped so its callback does not
re-add it.

Also make the timers test drain robust: allow a possible in-flight tick
delivered concurrently with Stop to settle before draining the channel.

Signed-off-by: deadprogram <ron@hybridgroup.com>

* runtime: fix Stop/Reset semantics when racing a firing timer callback

Address review feedback on the ticker Stop-race fix. On the threads and
cores schedulers a timer callback runs concurrently with user goroutines,
which left several problems:

- removeTimer reported a firing timer as successfully removed, so
  Stop/Reset could return true even though the callback had already
  started (wrong semantics, notably for AfterFunc). firingTimerStop now
  returns a bool and removeTimer no longer hands the still-firing node
  back to resetTimer.

- The periodic advance (when += period) ran in timerCallback outside the
  scheduler timer lock. Move it into each scheduler's reAddTimer, under
  the lock and after the stopped check, so a concurrent Reset can't have
  its freshly-queued deadline corrupted.

- resetTimer now sets when/period after removeTimer for the same reason.

Add testdata/timer_stop_reset_race.go and TestTimerStopResetRace, which
reproduce the stop-while-firing and reset-while-firing races via the
runtime timer linkname hooks.

Signed-off-by: deadprogram <ron@hybridgroup.com>

* runtime: fix timer Stop/Reset race with firing periodic timers

Signed-off-by: deadprogram <ron@hybridgroup.com>

---------

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-06 20:14:53 +02:00
wikto 20038817ae [feature] moved ReadTemeprature to machine_esp32s3.go 2026-07-05 14:27:31 +02:00
Wikwoj0512 9ce73cfafa [feature] added temperature reading to esp32s3 2026-07-05 14:27:31 +02:00
deadprogram 0033c23848 machine/esp32c6: add ADC driver
Adds machine_esp32c6_adc.go implementing the machine.ADC interface for
ESP32-C6 (ADC1 only, GPIO0–GPIO6, channels 0–6; there is no ADC2).

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-05 14:22:58 +02:00
Jake Bailey 5c15a68d18 tests: support simavr 1.8+ 2026-07-05 13:57:05 +02:00
Hector Chu 02384c9414 add CGO_CFLAGS support (#5453)
* add CGO_CFLAGS support

* the environment variable must be split

* use shlex to handle double quotes

* wrap error
2026-07-05 10:17:26 +02:00
150 changed files with 4792 additions and 542 deletions
+1 -1
View File
@@ -209,7 +209,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: '18'
node-version: '22'
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-20-windows-v4
key: llvm-build-20-windows-v5
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
+16 -3
View File
@@ -142,6 +142,9 @@ ifeq ($(OS),Windows_NT)
# PIC needs to be disabled for libclang to work.
LLVM_OPTION += -DLLVM_ENABLE_PIC=OFF
# Statically link the C++ and GCC runtime into LLVM tools so they don't
# depend on MinGW DLLs that may not be on PATH when executed during the build.
LLVM_OPTION += '-DCMAKE_EXE_LINKER_FLAGS=-static-libgcc -static-libstdc++'
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
@@ -304,7 +307,7 @@ wasi-cm:
rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*_json.go' --exclude '*.md' --exclude LICENSE $(shell go list -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm
# Check for Node.js used during WASM tests.
MIN_NODEJS_VERSION=18
MIN_NODEJS_VERSION=22
.PHONY: check-nodejs-version
check-nodejs-version:
@@ -410,6 +413,7 @@ TEST_PACKAGES_LINUX := \
context \
crypto/aes \
crypto/des \
crypto/ecdh \
crypto/hmac \
debug/dwarf \
debug/plan9obj \
@@ -489,10 +493,12 @@ report-stdlib-tests-pass:
ifeq ($(uname),Darwin)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN)
TEST_IOFS := true
TEST_ENCODING_XML := true
endif
ifeq ($(uname),Linux)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX)
TEST_IOFS := true
TEST_ENCODING_XML := true
endif
ifeq ($(OS),Windows_NT)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
@@ -507,8 +513,11 @@ TEST_ADDITIONAL_FLAGS ?=
.PHONY: tinygo-test
tinygo-test:
@# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented.
@# TestParseAndBytesRoundTrip/P256/Generic: relies on t.Skip() which is not implemented
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
@# TestParseAndBytesRoundTrip/P256/Generic: needs Goexit to run defers on wasm.
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(filter-out encoding/xml,$(TEST_PACKAGES_HOST)) $(TEST_PACKAGES_SLOW)
ifeq ($(TEST_ENCODING_XML),true)
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) -stack-size=16MB encoding/xml
endif
@# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also
@# requires a large stack-size. Hence, io/fs is only run conditionally.
@# For more details, see the comments on issue #3143.
@@ -753,6 +762,8 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao-ble-plus examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=rak4631 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
@@ -900,6 +911,8 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32l0x1 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32u031 examples/empty
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/serial
+10 -10
View File
@@ -14,6 +14,7 @@ import (
"fmt"
"go/types"
"hash/crc32"
"maps"
"math/bits"
"os"
"os/exec"
@@ -137,9 +138,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if _, ok := globalValues[pkgPath]; !ok {
globalValues[pkgPath] = map[string]string{}
}
for k, v := range vals {
globalValues[pkgPath][k] = v
}
maps.Copy(globalValues[pkgPath], vals)
}
// Check for a libc dependency.
@@ -278,7 +277,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var embedFileObjects []*compileJob
for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string
for name := range globalValues[pkg.Pkg.Path()] {
@@ -775,7 +773,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// TODO: do this as part of building the package to be able to link the
// bitcode files together.
for _, pkg := range lprogram.Sorted() {
pkg := pkg
for _, filename := range pkg.CFiles {
abspath := filepath.Join(pkg.OriginalDir(), filename)
job := &compileJob{
@@ -842,22 +839,25 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker.
switch config.LinkerFlavor() {
case "coff":
// Options for driving ld.lld in PE/COFF mode.
ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
} else if config.GOOS() == "darwin" {
case "darwin":
// Options for the ld64-compatible lld linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else {
case "gnu":
// Options for the ELF linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
)
default:
return fmt.Errorf("unknown linker flavor: %s", config.LinkerFlavor())
}
if config.CodeModel() != "default" {
ldflags = append(ldflags,
@@ -914,7 +914,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
// Run wasm-opt for wasm binaries
if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" {
if arch, _, _ := strings.Cut(config.Triple(), "-"); arch == "wasm32" {
optLevel, _, _ := config.OptLevel()
opt := "-" + optLevel
-1
View File
@@ -47,7 +47,6 @@ func TestClangAttributes(t *testing.T) {
targetNames = append(targetNames, "esp32", "esp8266")
}
for _, targetName := range targetNames {
targetName := targetName
t.Run(targetName, func(t *testing.T) {
testClangAttributes(t, &compileopts.Options{Target: targetName})
})
+1 -1
View File
@@ -281,7 +281,7 @@ func parseDepFile(s string) ([]string, error) {
s = strings.ReplaceAll(s, "\\\n", " ")
// Only use the first line, which is expected to begin with "deps:".
line := strings.SplitN(s, "\n", 2)[0]
line, _, _ := strings.Cut(s, "\n")
if !strings.HasPrefix(line, "deps:") {
return nil, errors.New("readDepFile: expected 'deps:' prefix")
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
var commands = map[string][]string{}
func init() {
llvmMajor := strings.Split(llvm.Version, ".")[0]
llvmMajor, _, _ := strings.Cut(llvm.Version, ".")
commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
+1 -1
View File
@@ -15,7 +15,7 @@ func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJ
return &compileJob{
description: "compile Darwin libSystem.dylib",
run: func(job *compileJob) (err error) {
arch := strings.Split(config.Triple(), "-")[0]
arch, _, _ := strings.Cut(config.Triple(), "-")
job.result = filepath.Join(tmpdir, "libSystem.dylib")
objpath := filepath.Join(tmpdir, "libSystem.o")
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
+2 -2
View File
@@ -195,11 +195,11 @@ type intHeap struct {
sort.IntSlice
}
func (h *intHeap) Push(x interface{}) {
func (h *intHeap) Push(x any) {
h.IntSlice = append(h.IntSlice, x.(int))
}
func (h *intHeap) Pop() interface{} {
func (h *intHeap) Pop() any {
x := h.IntSlice[len(h.IntSlice)-1]
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
return x
-1
View File
@@ -232,7 +232,6 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
}
for _, path := range paths {
// Strip leading "../" parts off the path.
path := path
cleanpath := path
for strings.HasPrefix(cleanpath, "../") {
cleanpath = cleanpath[3:]
+2 -2
View File
@@ -28,8 +28,8 @@ func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
if err != nil {
return err
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
lines := strings.SplitSeq(string(data), "\n")
for line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1]
+3 -5
View File
@@ -42,16 +42,15 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 3699, 297, 0, 2252},
{"microbit", "examples/serial", 2736, 356, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
{"hifive1b", "examples/echo", 3771, 309, 0, 2260},
{"microbit", "examples/serial", 2832, 368, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8065, 1663, 132, 7488},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
// output varies by binaryen version.
}
for _, tc := range tests {
tc := tc
t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
t.Parallel()
@@ -85,7 +84,6 @@ func TestSizeFull(t *testing.T) {
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
for _, target := range tests {
target := target
t.Run(target, func(t *testing.T) {
t.Parallel()
+1 -1
View File
@@ -116,7 +116,7 @@ func parseLLDErrors(text string) error {
// This can happen in some cases like with CGo and //go:linkname tricker.
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
symbolName := matches[2]
for _, line := range strings.Split(message, "\n") {
for line := range strings.SplitSeq(message, "\n") {
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
if matches != nil {
parsedError = true
+1 -1
View File
@@ -40,7 +40,7 @@ func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byt
}
bl.SetNumBlocks(len(blocks))
for i := 0; i < len(blocks); i++ {
for i := range blocks {
bl.SetBlockNo(i)
bl.SetData(blocks[i])
+10 -8
View File
@@ -40,7 +40,7 @@ type cgoPackage struct {
tokenFiles map[string]*token.File
definedGlobally map[string]ast.Node
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
anonDecls map[interface{}]string
anonDecls map[any]string
cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte
@@ -259,7 +259,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
tokenFiles: map[string]*token.File{},
definedGlobally: map[string]ast.Node{},
noescapingFuncs: map[string]*noescapingFunc{},
anonDecls: map[interface{}]string{},
anonDecls: map[any]string{},
visitedFiles: map[string][]byte{},
}
@@ -302,7 +302,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Find `import "C"` C fragments in the file.
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
for i, f := range files {
var cgoHeader string
var cgoHeader strings.Builder
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl)
@@ -337,7 +337,8 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Iterate through all parts of the CGo header. Note that every //
// line is a new comment.
position := fset.Position(genDecl.Doc.Pos())
fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
var fragment strings.Builder
fragment.WriteString(fmt.Sprintf("# %d %#v\n", position.Line, position.Filename))
for _, comment := range genDecl.Doc.List {
// Find all #cgo lines, extract and use their contents, and
// replace the lines with spaces (to preserve locations).
@@ -354,12 +355,13 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
} else { // comment
c = " " + c[2:len(c)-2]
}
fragment += c + "\n"
fragment.WriteString(c)
fragment.WriteByte('\n')
}
cgoHeader += fragment
cgoHeader.WriteString(fragment.String())
}
p.cgoHeaders[i] = cgoHeader
p.cgoHeaders[i] = cgoHeader.String()
}
// Define CFlags that will be used while parsing the package.
@@ -1217,7 +1219,7 @@ func getPos(node ast.Node) token.Pos {
// getUnnamedDeclName creates a name (with the given prefix) for the given C
// declaration. This is used for structs, unions, and enums that are often
// defined without a name and used in a typedef.
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf any) string {
if name, ok := p.anonDecls[itf]; ok {
return name
}
-1
View File
@@ -45,7 +45,6 @@ func TestCGo(t *testing.T) {
"flags",
"const",
} {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) {
// Read the AST in memory.
path := filepath.Join("testdata", name+".go")
+3 -3
View File
@@ -160,7 +160,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
pos := f.getClangLocationPosition(location, unit)
f.addError(pos, severity+": "+spelling)
}
for i := 0; i < numDiagnostics; i++ {
for i := range numDiagnostics {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
addDiagnostic(diagnostic)
@@ -278,7 +278,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Text: strings.Join(doc, "\n"),
})
}
for i := 0; i < numArgs; i++ {
for i := range numArgs {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
argType := C.clang_getArgType(cursorType, C.uint(i))
@@ -534,7 +534,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
// Get the precise location in the source code. Used for uniquely identifying
// source locations.
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} {
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) any {
clangLocation := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
+4 -4
View File
@@ -12,17 +12,17 @@ import "C"
// C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer.
type refMap struct {
refs map[unsafe.Pointer]interface{}
refs map[unsafe.Pointer]any
lock sync.Mutex
}
// Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks.
func (m *refMap) Put(v interface{}) unsafe.Pointer {
func (m *refMap) Put(v any) unsafe.Pointer {
m.lock.Lock()
defer m.lock.Unlock()
if m.refs == nil {
m.refs = make(map[unsafe.Pointer]interface{}, 1)
m.refs = make(map[unsafe.Pointer]any, 1)
}
ref := C.malloc(1)
m.refs[ref] = v
@@ -31,7 +31,7 @@ func (m *refMap) Put(v interface{}) unsafe.Pointer {
// Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put.
func (m *refMap) Get(ref unsafe.Pointer) interface{} {
func (m *refMap) Get(ref unsafe.Pointer) any {
m.lock.Lock()
defer m.lock.Unlock()
return m.refs[ref]
+19 -8
View File
@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
@@ -140,13 +141,7 @@ func (c *Config) GC() string {
func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "custom", "precise", "boehm":
for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" {
return true
}
}
return false
return slices.Contains(c.BuildTags(), "tinygo.wasm")
default:
return false
}
@@ -245,7 +240,7 @@ func (c *Config) RP2040BootPatch() bool {
// Return a canonicalized architecture name, so we don't have to deal with arm*
// vs thumb* vs arm64.
func CanonicalArchName(triple string) string {
arch := strings.Split(triple, "-")[0]
arch, _, _ := strings.Cut(triple, "-")
if arch == "arm64" {
return "aarch64"
}
@@ -466,6 +461,22 @@ func (c *Config) LDFlags() []string {
return ldflags
}
// LinkerFlavor returns how the configured linker should be driven.
// Usually this is derived from GOOS, but targets may override it explicitly.
func (c *Config) LinkerFlavor() string {
if c.Target.LinkerFlavor != "" {
return c.Target.LinkerFlavor
}
switch c.GOOS() {
case "windows":
return "coff"
case "darwin":
return "darwin"
default:
return "gnu"
}
}
// ExtraFiles returns the list of extra files to be built and linked with the
// executable. This can include extra C and assembly files.
func (c *Config) ExtraFiles() []string {
+8 -16
View File
@@ -3,6 +3,7 @@ package compileopts
import (
"fmt"
"regexp"
"slices"
"strings"
"time"
)
@@ -67,7 +68,7 @@ type Options struct {
// Verify performs a validation on the given options, raising an error if options are not valid.
func (o *Options) Verify() error {
if o.BuildMode != "" {
valid := isInArray(validBuildModeOptions, o.BuildMode)
valid := slices.Contains(validBuildModeOptions, o.BuildMode)
if !valid {
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
o.BuildMode,
@@ -75,7 +76,7 @@ func (o *Options) Verify() error {
}
}
if o.GC != "" {
valid := isInArray(validGCOptions, o.GC)
valid := slices.Contains(validGCOptions, o.GC)
if !valid {
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
o.GC,
@@ -84,7 +85,7 @@ func (o *Options) Verify() error {
}
if o.Scheduler != "" {
valid := isInArray(validSchedulerOptions, o.Scheduler)
valid := slices.Contains(validSchedulerOptions, o.Scheduler)
if !valid {
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
o.Scheduler,
@@ -93,7 +94,7 @@ func (o *Options) Verify() error {
}
if o.Serial != "" {
valid := isInArray(validSerialOptions, o.Serial)
valid := slices.Contains(validSerialOptions, o.Serial)
if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial,
@@ -102,7 +103,7 @@ func (o *Options) Verify() error {
}
if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
valid := slices.Contains(validPrintSizeOptions, o.PrintSizes)
if !valid {
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
o.PrintSizes,
@@ -111,7 +112,7 @@ func (o *Options) Verify() error {
}
if o.PanicStrategy != "" {
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
valid := slices.Contains(validPanicStrategyOptions, o.PanicStrategy)
if !valid {
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
o.PanicStrategy,
@@ -120,19 +121,10 @@ func (o *Options) Verify() error {
}
if o.Opt != "" {
if !isInArray(validOptOptions, o.Opt) {
if !slices.Contains(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
}
}
return nil
}
func isInArray(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
}
+2 -1
View File
@@ -38,7 +38,8 @@ type TargetSpec struct {
Scheduler string `json:"scheduler,omitempty"`
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
Linker string `json:"linker,omitempty"`
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
LinkerFlavor string `json:"linker-flavor,omitempty"` // how to drive the configured linker (for example: gnu, coff, darwin)
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc,omitempty"`
AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time.
DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time.
+49
View File
@@ -112,3 +112,52 @@ func TestOverrideProperties(t *testing.T) {
}
}
func TestConfigLinkerFlavor(t *testing.T) {
tests := []struct {
name string
target *TargetSpec
goos string
want string
}{
{
name: "default gnu",
target: &TargetSpec{},
goos: "linux",
want: "gnu",
},
{
name: "default coff",
target: &TargetSpec{},
goos: "windows",
want: "coff",
},
{
name: "default darwin",
target: &TargetSpec{},
goos: "darwin",
want: "darwin",
},
{
name: "target override",
target: &TargetSpec{
LinkerFlavor: "coff",
},
goos: "linux",
want: "coff",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc.target.GOOS = tc.goos
config := &Config{
Options: &Options{},
Target: tc.target,
}
if got := config.LinkerFlavor(); got != tc.want {
t.Fatalf("LinkerFlavor() = %q, want %q", got, tc.want)
}
})
}
}
+8 -9
View File
@@ -90,7 +90,8 @@ type compilerContext struct {
astComments map[string]*ast.CommentGroup
embedGlobals map[string][]*loader.EmbedFile
pkg *types.Package
packageDir string // directory for this package
loaderPkg *loader.Package // current package being compiled (for AST access)
packageDir string // directory for this package
runtimePkg *types.Package
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
}
@@ -168,7 +169,7 @@ type builder struct {
dilocals map[*types.Var]llvm.Metadata
initInlinedAt llvm.Metadata // fake inlinedAt position
initPseudoFuncs map[string]llvm.Metadata // fake "inlined" functions for proper init debug locations
allDeferFuncs []interface{}
allDeferFuncs []any
deferFuncs map[*ssa.Function]int
deferInvokeFuncs map[string]int
deferClosureFuncs map[*ssa.Function]int
@@ -298,6 +299,7 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
c.packageDir = pkg.OriginalDir()
c.embedGlobals = pkg.EmbedGlobals
c.pkg = pkg.Pkg
c.loaderPkg = pkg
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
c.program = ssaPkg.Prog
@@ -2154,13 +2156,10 @@ func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
if elementSize == 0 {
elementSize = 1
}
maxSize := maxPointerValue / elementSize
// len(slice) is an int. Make sure the length remains small enough to fit in
// an int.
if maxSize > maxIntegerValue {
maxSize = maxIntegerValue
}
maxSize := min(
// len(slice) is an int. Make sure the length remains small enough to fit in
// an int.
maxPointerValue/elementSize, maxIntegerValue)
return maxSize
}
+3 -3
View File
@@ -188,9 +188,9 @@ func TestCompilerErrors(t *testing.T) {
t.Error(err)
}
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
for _, line := range strings.Split(errorsFileString, "\n") {
if strings.HasPrefix(line, "// ERROR: ") {
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
for line := range strings.SplitSeq(errorsFileString, "\n") {
if after, ok := strings.CutPrefix(line, "// ERROR: "); ok {
expectedErrors = append(expectedErrors, after)
}
}
+11 -7
View File
@@ -116,8 +116,8 @@ func (b *builder) createLandingPad() {
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
// Construct inline assembly equivalents of setjmp.
// The assembly works as follows:
// * All registers (both callee-saved and caller saved) are clobbered
// after the inline assembly returns.
// * Registers are either clobbered or, on 386, saved for longjmp to
// restore if the ABI requires them to survive calls.
// * The assembly stores the address just past the end of the assembly
// into the jump buffer.
// * The return value (eax, rax, r0, etc) is set to zero in the inline
@@ -130,8 +130,12 @@ func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
asmString = `
xorl %eax, %eax
movl $$1f, 4(%ebx)
movl %ebx, 8(%ebx)
movl %esi, 12(%ebx)
movl %edi, 16(%ebx)
movl %ebp, 20(%ebx)
1:`
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
constraints = "={eax},{ebx},~{ecx},~{edx},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
// This doesn't include the floating point stack because TinyGo uses
// newer floating point instructions.
case "x86_64":
@@ -669,8 +673,8 @@ func (b *builder) createRunDefers() {
fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
for v := range params.Variables() {
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
}
valueTypes = append(valueTypes, b.dataPtrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false)
@@ -695,8 +699,8 @@ func (b *builder) createRunDefers() {
//Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
for v := range params.Variables() {
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
+2 -2
View File
@@ -81,8 +81,8 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
paramTypes = append(paramTypes, info.llvmType)
}
}
for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type())
for v := range typ.Params().Variables() {
subType := c.getLLVMType(v.Type())
for _, info := range c.expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
+4 -8
View File
@@ -5,6 +5,7 @@ package compiler
import (
"go/token"
"slices"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
@@ -88,7 +89,7 @@ func (b *builder) trackValue(value llvm.Value) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
for i := range numElements {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
@@ -97,7 +98,7 @@ func (b *builder) trackValue(value llvm.Value) {
return
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
for i := range numElements {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
@@ -118,12 +119,7 @@ func typeHasPointers(t llvm.Type) bool {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
return slices.ContainsFunc(t.StructElementTypes(), typeHasPointers)
case llvm.ArrayTypeKind:
if t.ArrayLength() == 0 {
return false
+3 -3
View File
@@ -97,7 +97,7 @@ func (b *builder) createGo(instr *ssa.Go) {
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context, funcPtr)
hasContext = true
prefix = b.fn.RelString(nil)
prefix = b.getFunctionInfo(b.fn).linkName
}
paramBundle := b.emitPointerPack(params)
@@ -139,7 +139,7 @@ func (b *builder) createWasmExport() {
// Declare the exported function.
paramTypes := b.llvmFnType.ParamTypes()
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
exportedFn := llvm.AddFunction(b.mod, b.getFunctionInfo(b.fn).linkName+suffix, exportedFnType)
b.addStandardAttributes(exportedFn)
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
@@ -414,7 +414,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
// Extract parameters from the state object, and call the function
// that's being wrapped.
var callParams []llvm.Value
for i := 0; i < numParams; i++ {
for i := range numParams {
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
+12 -10
View File
@@ -146,13 +146,14 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={r0}"
var constraints strings.Builder
constraints.WriteString("={r0}")
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
constraints.WriteString(",0")
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
constraints.WriteString(",{r" + strconv.Itoa(i) + "}")
}
llvmValue := b.getValue(arg, pos)
llvmArgs = append(llvmArgs, llvmValue)
@@ -161,9 +162,9 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
// Implement the ARM calling convention by marking r1-r3 as
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
constraints.WriteString(",~{r1},~{r2},~{r3}")
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil
}
@@ -184,13 +185,14 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={x0}"
var constraints strings.Builder
constraints.WriteString("={x0}")
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
constraints.WriteString(",0")
} else {
constraints += ",{x" + strconv.Itoa(i) + "}"
constraints.WriteString(",{x" + strconv.Itoa(i) + "}")
}
llvmValue := b.getValue(arg, pos)
llvmArgs = append(llvmArgs, llvmValue)
@@ -199,9 +201,9 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
// Implement the ARM64 calling convention by marking x1-x7 as
// clobbered. x0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
constraints.WriteString(",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}")
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil
}
+42 -42
View File
@@ -145,8 +145,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// For a non-interface type, it returns the number of exported methods.
// For an interface type, it returns the number of exported and unexported methods.
var numMethods int
for i := 0; i < ms.Len(); i++ {
if isInterface || ms.At(i).Obj().Exported() {
for method := range ms.Methods() {
if isInterface || method.Obj().Exported() {
numMethods++
}
}
@@ -193,8 +193,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}
// Compute the method set value for types that support methods.
var methods []*types.Func
for i := 0; i < ms.Len(); i++ {
methods = append(methods, ms.At(i).Obj().(*types.Func))
for method := range ms.Methods() {
methods = append(methods, method.Obj().(*types.Func))
}
methodSetType := types.NewStruct([]*types.Var{
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
@@ -490,10 +490,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
c.getTypeMethodSet(typ),
}, typeFields...)
}
alignment := c.targetData.TypeAllocSize(c.dataPtrType)
if alignment < 4 {
alignment = 4
}
alignment := max(c.targetData.TypeAllocSize(c.dataPtrType), 4)
globalValue := c.ctx.ConstStruct(typeFields, false)
global.SetInitializer(globalValue)
if isLocal {
@@ -783,12 +780,12 @@ func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
walk(m, false)
case *ssa.Type:
mset := c.program.MethodSets.MethodSet(m.Type())
for i := 0; i < mset.Len(); i++ {
walk(c.program.MethodValue(mset.At(i)), false)
for method := range mset.Methods() {
walk(c.program.MethodValue(method), false)
}
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
for i := 0; i < pmset.Len(); i++ {
walk(c.program.MethodValue(pmset.At(i)), false)
for method := range pmset.Methods() {
walk(c.program.MethodValue(method), false)
}
}
}
@@ -846,8 +843,8 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
}
}
targs := t.TypeArgs()
for i := 0; i < targs.Len(); i++ {
visit(targs.At(i))
for t := range targs.Types() {
visit(t)
}
visit(t.Underlying())
case *types.Pointer:
@@ -862,23 +859,23 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
visit(t.Key())
visit(t.Elem())
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
visit(t.Field(i).Type())
for field := range t.Fields() {
visit(field.Type())
}
case *types.Signature:
if p := t.Params(); p != nil {
for i := 0; i < p.Len(); i++ {
visit(p.At(i).Type())
for v := range p.Variables() {
visit(v.Type())
}
}
if r := t.Results(); r != nil {
for i := 0; i < r.Len(); i++ {
visit(r.At(i).Type())
for v := range r.Variables() {
visit(v.Type())
}
}
case *types.Tuple:
for i := 0; i < t.Len(); i++ {
visit(t.At(i).Type())
for v := range t.Variables() {
visit(v.Type())
}
case *types.Interface:
// A synthetic local type can be reachable only through a
@@ -887,8 +884,8 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
// the interface's identifier, and the seen map breaks
// cycles formed by methods that mention the interface
// itself.
for i := 0; i < t.NumMethods(); i++ {
visit(t.Method(i).Type())
for method := range t.Methods() {
visit(method.Type())
}
}
}
@@ -939,8 +936,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// Create method set.
var signatures, wrappers []llvm.Value
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
for method := range ms.Methods() {
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
signatures = append(signatures, signatureGlobal)
fn := c.program.MethodValue(method)
@@ -1189,8 +1185,8 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
if llvmFn.IsNil() {
sig := instr.Method.Type().(*types.Signature)
var paramTuple []*types.Var
for i := 0; i < sig.Params().Len(); i++ {
paramTuple = append(paramTuple, sig.Params().At(i))
for v := range sig.Params().Variables() {
paramTuple = append(paramTuple, v)
}
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer]))
llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false))
@@ -1307,34 +1303,38 @@ func methodSignature(method *types.Func) string {
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
var s strings.Builder
if sig.Params().Len() == 0 {
s += "()"
s.WriteString("()")
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
s.WriteString("(")
i := 0
for v := range sig.Params().Variables() {
if i > 0 {
s += ", "
s.WriteString(", ")
}
s += typestring(sig.Params().At(i).Type())
s.WriteString(typestring(v.Type()))
i++
}
s += ")"
s.WriteString(")")
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + typestring(sig.Results().At(0).Type())
s.WriteString(" " + typestring(sig.Results().At(0).Type()))
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
s.WriteString(" (")
i := 0
for v := range sig.Results().Variables() {
if i > 0 {
s += ", "
s.WriteString(", ")
}
s += typestring(sig.Results().At(i).Type())
s.WriteString(typestring(v.Type()))
i++
}
s += ")"
s.WriteString(")")
}
return s
return s.String()
}
// typestring returns a stable (human-readable) type string for the given type
+3 -3
View File
@@ -206,7 +206,7 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
globalType := llvm.ArrayType(elementType, len(buf))
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
for i := 0; i < len(buf); i++ {
for i := range buf {
ch := uint64(buf[i])
value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "")
}
@@ -390,7 +390,7 @@ func (c *compilerContext) buildPointerBitmap(
return
}
elementSize /= ptrAlign
for i := 0; i < len; i++ {
for i := range len {
c.buildPointerBitmap(
dst,
ptrAlign,
@@ -417,7 +417,7 @@ func (c *compilerContext) archFamily() string {
// features string is not one for an ARM architecture.
func (c *compilerContext) isThumb() bool {
var isThumb, isNotThumb bool
for _, feature := range strings.Split(c.Features, ",") {
for feature := range strings.SplitSeq(c.Features, ",") {
if feature == "+thumb-mode" {
isThumb = true
}
+6 -4
View File
@@ -7,6 +7,7 @@ import (
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"strings"
"tinygo.org/x/go-llvm"
)
@@ -293,14 +294,15 @@ func hashmapCanonicalTypeName(t types.Type) string {
}
return t.String()
case *types.Struct:
s := "struct{"
var s strings.Builder
s.WriteString("struct{")
for i := 0; i < t.NumFields(); i++ {
if i > 0 {
s += "; "
s.WriteString("; ")
}
s += hashmapCanonicalTypeName(t.Field(i).Type())
s.WriteString(hashmapCanonicalTypeName(t.Field(i).Type()))
}
return s + "}"
return s.String() + "}"
case *types.Array:
return fmt.Sprintf("[%d]%s", t.Len(), hashmapCanonicalTypeName(t.Elem()))
}
+1 -2
View File
@@ -29,8 +29,7 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
// is the largest of the values unsafe.Alignof(x.f) for each
// field f of x, but at least 1."
max := int64(1)
for i := 0; i < t.NumFields(); i++ {
f := t.Field(i)
for f := range t.Fields() {
if a := s.Alignof(f.Type()); a > max {
max = a
}
+129 -25
View File
@@ -8,6 +8,8 @@ import (
"go/ast"
"go/token"
"go/types"
"path/filepath"
"slices"
"strconv"
"strings"
@@ -85,8 +87,8 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
for i := 0; i < fn.Signature.Results().Len(); i++ {
results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
for v := range fn.Signature.Results().Variables() {
results = append(results, c.getLLVMType(v.Type()))
}
retType = c.ctx.StructType(results, false)
}
@@ -322,6 +324,12 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
linkName: f.RelString(nil),
}
// RelString is not unique for local type arguments, so add a suffix
// when needed.
if suffix := c.localTypeArgsSuffix(f); suffix != "" {
info.linkName += suffix
}
// Check for a few runtime functions that are treated specially.
if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" {
info.linkName = "_initialize"
@@ -346,6 +354,42 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
return info
}
func (c *compilerContext) localTypeArgsSuffix(f *ssa.Function) string {
typeArgs := f.TypeArgs()
if len(typeArgs) == 0 {
return ""
}
var hasLocal bool
parts := make([]string, len(typeArgs))
for i, ta := range typeArgs {
name, isLocal := c.getTypeCodeName(ta)
if isLocal {
hasLocal = true
}
// A function-local type alias (e.g. `type F = float64` inside a
// function body) is invisible to getTypeCodeName because it calls
// types.Unalias first. Two callers that use distinct aliases with
// the same name (e.g. Go 1.27's internal/strconv.ftoa32 and ftoa64
// both declare a local `type F = ...`) then produce identical
// RelStrings for their shortFloat[F] instantiations and collide on
// mod.NamedFunction. Treat these aliases as local so the suffix
// disambiguates them.
if alias, ok := ta.(*types.Alias); ok {
if obj := alias.Obj(); obj.Pkg() != nil && obj.Parent() != obj.Pkg().Scope() {
hasLocal = true
pos := c.program.Fset.PositionFor(obj.Pos(), false)
parts[i] = fmt.Sprintf("%s$alias:%s:%d:%d", name, filepath.Base(pos.Filename), pos.Line, pos.Column)
continue
}
}
parts[i] = name
}
if !hasLocal {
return ""
}
return "$localtype:" + strings.Join(parts, ",")
}
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline.
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
@@ -372,6 +416,51 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
}
}
// Also scan file-level //go:linkname directives. These appear as
// free-standing comments in *ast.File.Comments (not attached to any
// declaration), and are used by modern golang.org/x/sys/unix and others.
// Function-attached directives (above) take precedence — we only add
// file-level ones if no doc-comment linkname was found for this function.
//
// TODO: the hasUnsafeImport gate enforced downstream (see the
// //go:linkname case below) is package-level. gc enforces it per
// file, on the file containing the directive. For file-level
// linknames this is more important than for function-attached ones,
// because the directive can live in a file separate from the
// function. A stricter implementation would check whether the file
// returned by fileForFunc imports "unsafe", not whether any file in
// the package does.
hasFunctionLinkname := false
for _, comment := range pragmas {
if strings.HasPrefix(comment.Text, "//go:linkname ") {
parts := strings.Fields(comment.Text)
if len(parts) == 3 && parts[1] == f.Name() {
hasFunctionLinkname = true
break
}
}
}
if !hasFunctionLinkname {
if file := c.fileForFunc(f); file != nil {
for _, group := range file.Comments {
// Skip the function's own doc comment — already handled above.
if decl, ok := syntax.(*ast.FuncDecl); ok && group == decl.Doc {
continue
}
for _, comment := range group.List {
if !strings.HasPrefix(comment.Text, "//go:linkname ") {
continue
}
parts := strings.Fields(comment.Text)
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
pragmas = append(pragmas, comment)
}
}
}
}
// Parse each pragma.
for _, comment := range pragmas {
parts := strings.Fields(comment.Text)
@@ -389,7 +478,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
info.wasmName = info.linkName
info.exported = true
case "//go:interrupt":
if hasUnsafeImport(f.Pkg.Pkg) {
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
info.interrupt = true
}
case "//go:wasm-module":
@@ -451,14 +540,14 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
info.linkName = parts[2]
}
case "//go:section":
// Only enable go:section when the package imports "unsafe".
// go:section also implies go:noinline since inlining could
// move the code to a different section than that requested.
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
if len(parts) == 2 && slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
info.section = parts[1]
info.inline = inlineNone
}
@@ -467,7 +556,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
info.nobounds = true
}
case "//go:noescape":
@@ -567,8 +656,8 @@ func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
hasHostLayout = false // package structs added in go1.23
}
}
for i := 0; i < typ.NumFields(); i++ {
ftyp := typ.Field(i).Type()
for field := range typ.Fields() {
ftyp := field.Type()
if types.Unalias(ftyp).String() == "structs.HostLayout" {
hasHostLayout = true
continue
@@ -600,8 +689,8 @@ func getParams(sig *types.Signature) []*types.Var {
if sig.Recv() != nil {
params = append(params, sig.Recv())
}
for i := 0; i < sig.Params().Len(); i++ {
params = append(params, sig.Params().At(i))
for v := range sig.Params().Variables() {
params = append(params, v)
}
return params
}
@@ -666,6 +755,34 @@ type globalInfo struct {
section string // go:section
}
// fileForFunc returns the *ast.File that contains the declaration of f, or
// nil if it cannot be determined. File-level pragmas are only consulted for
// functions in the package currently being compiled — functions imported from
// other packages have their file-level pragmas processed when those packages
// are compiled.
func (c *compilerContext) fileForFunc(f *ssa.Function) *ast.File {
if c.loaderPkg == nil || f.Pkg == nil || f.Pkg.Pkg != c.loaderPkg.Pkg {
return nil
}
syntax := f.Syntax()
if f.Origin() != nil {
syntax = f.Origin().Syntax()
}
if syntax == nil {
return nil
}
pos := syntax.Pos()
if !pos.IsValid() {
return nil
}
for _, file := range c.loaderPkg.Files {
if file.FileStart <= pos && pos < file.FileEnd {
return file
}
}
return nil
}
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
@@ -704,10 +821,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
// Set alignment from the //go:align comment.
alignment := c.targetData.ABITypeAlignment(llvmType)
if info.align > alignment {
alignment = info.align
}
alignment := max(info.align, c.targetData.ABITypeAlignment(llvmType))
if alignment <= 0 || alignment&(alignment-1) != 0 {
// Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360
@@ -781,7 +895,7 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, c *compilerContext,
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(g.Pkg.Pkg) {
if slices.Contains(g.Pkg.Pkg.Imports(), types.Unsafe) {
info.linkName = parts[2]
}
}
@@ -797,13 +911,3 @@ func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
}
return methods
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
+32 -27
View File
@@ -26,24 +26,25 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
constraints := "={rax},0"
var constraints strings.Builder
constraints.WriteString("={rax},0")
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
constraints.WriteString("," + [...]string{
"{rdi}",
"{rsi}",
"{rdx}",
"{r10}",
"{r8}",
"{r9}",
}[i]
}[i])
llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// rcx and r11 are clobbered by the syscall, so make sure they are not used
constraints += ",~{rcx},~{r11}"
constraints.WriteString(",~{rcx},~{r11}")
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
target := llvm.InlineAsm(fnType, "syscall", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "386" && b.GOOS == "linux":
@@ -55,22 +56,23 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
constraints := "={eax},0"
var constraints strings.Builder
constraints.WriteString("={eax},0")
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
constraints.WriteString("," + [...]string{
"{ebx}",
"{ecx}",
"{edx}",
"{esi}",
"{edi}",
"{ebp}",
}[i]
}[i])
llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux":
@@ -88,9 +90,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{}
// Constraints will look something like:
// ={r0},0,{r1},{r2},{r7},~{r3}
constraints := "={r0}"
var constraints strings.Builder
constraints.WriteString("={r0}")
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
constraints.WriteString("," + [...]string{
"0", // tie to output
"{r1}",
"{r2}",
@@ -98,20 +101,20 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{r4}",
"{r5}",
"{r6}",
}[i]
}[i])
llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
argTypes = append(argTypes, b.uintptrType)
constraints += ",{r7}" // syscall number
constraints.WriteString(",{r7}") // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
// r0-r3 get clobbered after the syscall returns
constraints += ",~{r" + strconv.Itoa(i) + "}"
constraints.WriteString(",~{r" + strconv.Itoa(i) + "}")
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm64" && b.GOOS == "linux":
@@ -120,31 +123,32 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{}
// Constraints will look something like:
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
constraints := "={x0}"
var constraints strings.Builder
constraints.WriteString("={x0}")
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
constraints.WriteString("," + [...]string{
"0", // tie to output
"{x1}",
"{x2}",
"{x3}",
"{x4}",
"{x5}",
}[i]
}[i])
llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
argTypes = append(argTypes, b.uintptrType)
constraints += ",{x8}" // syscall number
constraints.WriteString(",{x8}") // syscall number
for i := len(call.Args) - 1; i < 8; i++ {
// x0-x7 may get clobbered during the syscall following the aarch64
// calling convention.
constraints += ",~{x" + strconv.Itoa(i) + "}"
constraints.WriteString(",~{x" + strconv.Itoa(i) + "}")
}
constraints += ",~{x16},~{x17}" // scratch registers
constraints.WriteString(",~{x16},~{x17}") // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil
case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux":
@@ -163,7 +167,8 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// faster and smaller code.
args := []llvm.Value{num}
argTypes := []llvm.Type{b.uintptrType}
constraints := "={$2},={$7},0"
var constraints strings.Builder
constraints.WriteString("={$2},={$7},0")
syscallParams := call.Args[1:]
if len(syscallParams) > 7 {
// There is one syscall that uses 7 parameters: sync_file_range.
@@ -172,7 +177,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallParams = syscallParams[:7]
}
for i, arg := range syscallParams {
constraints += "," + [...]string{
constraints.WriteString("," + [...]string{
"{$4}", // arg1
"{$5}", // arg2
"{$6}", // arg3
@@ -180,7 +185,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"r", // arg5 on the stack
"r", // arg6 on the stack
"r", // arg7 on the stack
}[i]
}[i])
llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
@@ -221,10 +226,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"addu $$sp, $$sp, 32\n" +
".set at\n"
}
constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}"
constraints.WriteString(",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}")
returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)
fnType := llvm.FunctionType(returnType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, true, 0, false)
call := b.CreateCall(fnType, target, args, "")
resultCode := b.CreateExtractValue(call, 0, "") // r2
errorFlag := b.CreateExtractValue(call, 1, "") // r7
+28
View File
@@ -120,3 +120,31 @@ func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
func doesHeapAlloc() *int {
return new(int)
}
// Define a function in a different package using a file-level go:linkname.
// (Same as withLinkageName1, but with the //go:linkname directive detached
// from the function declaration — see https://github.com/tinygo-org/tinygo/issues/4395)
func withFileLevelLinkageName1() {
}
// Import a function from a different package using a file-level go:linkname.
// (Same as withLinkageName2, but with the //go:linkname directive detached
// from the function declaration.)
func withFileLevelLinkageName2()
//go:linkname withFileLevelLinkageName1 somepkg.someFileLevelFunction1
//go:linkname withFileLevelLinkageName2 somepkg.someFileLevelFunction2
// File-level linkname directives can also appear between two function
// declarations, in which case Go's AST attaches them as the doc comment
// of the following function — even when the directive's localname refers
// to a different function. Exercise that case: the directive below names
// withAdjacentLinkageName, but Go will attach it to
// sentinelAfterAdjacentLinkname's Doc. The file-level scan must find it
// by walking comment groups regardless of which decl they're attached to.
func withAdjacentLinkageName() {
}
//go:linkname withAdjacentLinkageName somepkg.someAdjacentFunction
func sentinelAfterAdjacentLinkname() {
}
+20
View File
@@ -102,6 +102,26 @@ entry:
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc_noheap(i32, ptr, ptr) #9
; Function Attrs: nounwind
define hidden void @somepkg.someFileLevelFunction1(ptr %context) unnamed_addr #1 {
entry:
ret void
}
declare void @somepkg.someFileLevelFunction2(ptr) #0
; Function Attrs: nounwind
define hidden void @somepkg.someAdjacentFunction(ptr %context) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.sentinelAfterAdjacentLinkname(ptr %context) unnamed_addr #1 {
entry:
ret void
}
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" }
-2
View File
@@ -59,7 +59,6 @@ func TestCorpus(t *testing.T) {
}
for _, repo := range repos {
repo := repo
name := repo.Repo
if repo.Tags != "" {
name += "(" + strings.ReplaceAll(repo.Tags, " ", "-") + ")"
@@ -132,7 +131,6 @@ func TestCorpus(t *testing.T) {
}
for _, dir := range repo.Subdirs {
dir := dir
t.Run(dir.Pkg, func(t *testing.T) {
t.Parallel()
+2 -5
View File
@@ -116,10 +116,7 @@ func Diff(oldName string, old []byte, newName string, new []byte) []byte {
// End chunk with common lines for context.
if len(ctext) > 0 {
n := end.x - start.x
if n > C {
n = C
}
n := min(end.x-start.x, C)
for _, s := range x[start.x : start.x+n] {
ctext = append(ctext, " "+s)
count.x++
@@ -234,7 +231,7 @@ func tgs(x, y []string) []pair {
for i := range T {
T[i] = n + 1
}
for i := 0; i < n; i++ {
for i := range n {
k := sort.Search(n, func(k int) bool {
return T[k] >= J[i]
})
+1 -1
View File
@@ -136,7 +136,7 @@ func readErrorMessages(t *testing.T, file string) string {
}
var errors []string
for _, line := range strings.Split(string(data), "\n") {
for line := range strings.SplitSeq(string(data), "\n") {
if strings.HasPrefix(line, "// ERROR: ") {
errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n"))
}
+2
View File
@@ -151,6 +151,8 @@ func Get(name string) string {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
case "CGO_CFLAGS":
return os.Getenv("CGO_CFLAGS")
case "CGO_ENABLED":
// Always enable CGo. It is required by a number of targets, including
// macOS and the rp2040.
+1 -1
View File
@@ -151,7 +151,7 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
case llvm.PHI:
inst.name = llvmInst.Name()
incomingCount := inst.llvmInst.IncomingCount()
for i := 0; i < incomingCount; i++ {
for i := range incomingCount {
incomingBB := inst.llvmInst.IncomingBlock(i)
incomingValue := inst.llvmInst.IncomingValue(i)
inst.operands = append(inst.operands,
-1
View File
@@ -22,7 +22,6 @@ func TestInterp(t *testing.T) {
"alloc",
"slicedata",
} {
name := name // make local to this closure
t.Run(name, func(t *testing.T) {
t.Parallel()
runTest(t, "testdata/"+name)
+5 -4
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"math"
"os"
"slices"
"strconv"
"strings"
"time"
@@ -470,7 +471,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// should be returned.
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
var method llvm.Value
for i := 0; i < numMethods; i++ {
for i := range numMethods {
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "")
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "")
if methodSignature == signature {
@@ -907,7 +908,7 @@ func (r *runner) interpretICmp(lhs, rhs value, predicate llvm.IntPredicate) bool
func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error {
numOperands := inst.llvmInst.OperandsCount()
operands := make([]llvm.Value, numOperands)
for i := 0; i < numOperands; i++ {
for i := range numOperands {
operand := inst.llvmInst.Operand(i)
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
var err error
@@ -985,9 +986,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
mem.instructions = append(mem.instructions, agg)
}
result = operands[1]
for i := len(indices) - 1; i >= 0; i-- {
for i, indice := range slices.Backward(indices) {
agg := aggregates[i]
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
result = r.builder.CreateInsertValue(agg, result, int(indice), inst.name+".insertvalue"+strconv.Itoa(i))
if i != 0 { // don't add last result to mem.instructions as it will be done at the end already
mem.instructions = append(mem.instructions, result)
}
+14 -14
View File
@@ -18,8 +18,10 @@ import (
"encoding/binary"
"errors"
"fmt"
"maps"
"math"
"math/big"
"slices"
"strconv"
"strings"
@@ -80,9 +82,7 @@ func (mv *memoryView) extend(sub memoryView) {
if mv.objects == nil && len(sub.objects) != 0 {
mv.objects = make(map[uint32]object)
}
for key, value := range sub.objects {
mv.objects[key] = value
}
maps.Copy(mv.objects, sub.objects)
mv.instructions = append(mv.instructions, sub.instructions...)
}
@@ -90,8 +90,8 @@ func (mv *memoryView) extend(sub memoryView) {
// created in this memoryView. Do not reuse this memoryView.
func (mv *memoryView) revert() {
// Erase instructions in reverse order.
for i := len(mv.instructions) - 1; i >= 0; i-- {
llvmInst := mv.instructions[i]
for _, llvmInst := range slices.Backward(mv.instructions) {
if llvmInst.IsAInstruction().IsNil() {
// The IR builder will try to create constant versions of
// instructions whenever possible. If it does this, it's not an
@@ -172,7 +172,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
continue
}
numOperands := inst.OperandsCount()
for i := 0; i < numOperands; i++ {
for i := range numOperands {
// Using mark '2' (which means read/write access)
// because this might be a store instruction.
err := mv.markExternal(inst.Operand(i), 2)
@@ -215,7 +215,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
// need any marking.
case llvm.StructTypeKind:
numElements := llvmType.StructElementTypesCount()
for i := 0; i < numElements; i++ {
for i := range numElements {
element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
err := mv.markExternal(element, mark)
if err != nil {
@@ -224,7 +224,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
}
case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength()
for i := 0; i < numElements; i++ {
for i := range numElements {
element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
err := mv.markExternal(element, mark)
if err != nil {
@@ -366,7 +366,7 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
buffer := obj.buffer.asRawValue(mv.r)
obj.buffer = buffer
v := v.asRawValue(mv.r)
for i := uint32(0); i < valueLen; i++ {
for i := range valueLen {
buffer.buf[p.offset()+i] = v.buf[i]
}
}
@@ -392,7 +392,7 @@ type value interface {
// literalValue contains simple integer values that don't need to be stored in a
// buffer.
type literalValue struct {
value interface{}
value any
}
// Make a literalValue given the number of bits.
@@ -1015,7 +1015,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
if err != nil {
panic(err)
}
for i := uint32(0); i < ptrSize; i++ {
for i := range ptrSize {
v.buf[i] = ptr.pointer
}
} else if !llvmValue.IsAConstantExpr().IsNil() {
@@ -1056,7 +1056,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
panic(err)
}
ptrValue.pointer += totalOffset
for i := uint32(0); i < ptrSize; i++ {
for i := range ptrSize {
v.buf[i] = ptrValue.pointer
}
case llvm.ICmp:
@@ -1113,7 +1113,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
}
case llvm.StructTypeKind:
numElements := llvmType.StructElementTypesCount()
for i := 0; i < numElements; i++ {
for i := range numElements {
offset := r.targetData.ElementOffset(llvmType, i)
field := rawValue{
buf: v.buf[offset:],
@@ -1124,7 +1124,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
numElements := llvmType.ArrayLength()
childType := llvmType.ElementType()
childTypeSize := r.targetData.TypeAllocSize(childType)
for i := 0; i < numElements; i++ {
for i := range numElements {
offset := i * int(childTypeSize)
field := rawValue{
buf: v.buf[offset:],
+6
View File
@@ -22,6 +22,7 @@ import (
"strings"
"unicode"
"github.com/google/shlex"
"github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
@@ -506,6 +507,11 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
var initialCFlags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...)
initialCFlags = append(initialCFlags, "-I"+p.Dir)
cgoCFlags, err := shlex.Split(goenv.Get("CGO_CFLAGS"))
if err != nil {
return nil, fmt.Errorf("failed to split CGO_CFLAGS: %w", err)
}
initialCFlags = append(initialCFlags, cgoCFlags...)
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS())
p.CFlags = append(initialCFlags, cflags...)
p.CGoHeaders = headerCode
+10 -11
View File
@@ -786,15 +786,15 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
}
defer func() {
daemon.Process.Signal(os.Interrupt)
var stopped uint32
var stopped atomic.Uint32
go func() {
time.Sleep(time.Millisecond * 100)
if atomic.LoadUint32(&stopped) == 0 {
if stopped.Load() == 0 {
daemon.Process.Kill()
}
}()
daemon.Wait()
atomic.StoreUint32(&stopped, 1)
stopped.Store(1)
}()
}
@@ -1046,7 +1046,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
func touchSerialPortAt1200bps(port string) (err error) {
retryCount := 3
for i := 0; i < retryCount; i++ {
for range retryCount {
// Open port
p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
if e != nil {
@@ -1244,7 +1244,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
if err != nil {
return nil, fmt.Errorf("could not list mount points: %w", err)
}
for _, line := range strings.Split(string(tab), "\n") {
for line := range strings.SplitSeq(string(tab), "\n") {
fields := strings.Fields(line)
if len(fields) <= 2 {
continue
@@ -1273,7 +1273,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
}
// Extract data to convert to a []mountPoint slice.
for _, line := range strings.Split(out.String(), "\n") {
for line := range strings.SplitSeq(out.String(), "\n") {
words := strings.Fields(line)
if len(words) < 3 {
continue
@@ -1685,18 +1685,18 @@ func (m globalValuesFlag) String() string {
}
func (m globalValuesFlag) Set(value string) error {
equalsIndex := strings.IndexByte(value, '=')
if equalsIndex < 0 {
before, after, ok := strings.Cut(value, "=")
if !ok {
return errors.New("expected format pkgpath.Var=value")
}
pathAndName := value[:equalsIndex]
pathAndName := before
pointIndex := strings.LastIndexByte(pathAndName, '.')
if pointIndex < 0 {
return errors.New("expected format pkgpath.Var=value")
}
path := pathAndName[:pointIndex]
name := pathAndName[pointIndex+1:]
stringValue := value[equalsIndex+1:]
stringValue := after
if m[path] == nil {
m[path] = make(map[string]string)
}
@@ -2054,7 +2054,6 @@ func main() {
// This uses an additional semaphore to reduce the memory usage.
testSema := make(chan struct{}, cap(options.Semaphore))
for i, pkgName := range explicitPkgNames {
pkgName := pkgName
buf := &bufs[i]
testSema <- struct{}{}
wg.Add(1)
+94 -12
View File
@@ -245,6 +245,21 @@ func TestBuild(t *testing.T) {
}
}
// TestTimerStopResetRace checks that stopping or resetting a timer while its
// callback is running behaves correctly. It reaches into the runtime timer
// hooks via //go:linkname and relies on the threads scheduler, which is only
// used on these hosts.
func TestTimerStopResetRace(t *testing.T) {
t.Parallel()
switch runtime.GOOS {
case "darwin", "linux":
default:
t.Skipf("host GOOS %s does not use the threads scheduler", runtime.GOOS)
}
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
}
func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
emuCheck(t, options)
@@ -383,7 +398,7 @@ func emuCheck(t *testing.T, options compileopts.Options) {
t.Fatal("failed to load target spec:", err)
}
if spec.Emulator != "" {
emulatorCommand := strings.SplitN(spec.Emulator, " ", 2)[0]
emulatorCommand, _, _ := strings.Cut(spec.Emulator, " ")
_, err := exec.LookPath(emulatorCommand)
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
@@ -458,6 +473,12 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
options.Directory = path
pkgName = "."
}
isWebAssembly := strings.HasPrefix(options.Target, "wasi") ||
strings.HasPrefix(options.Target, "wasm") ||
strings.HasPrefix(options.GOARCH, "wasm")
if name == "testing.go" && isWebAssembly {
expectedOutputPath = TESTDATA + "/testing-wasm.txt"
}
config, err := builder.NewConfig(&options)
if err != nil {
@@ -467,12 +488,18 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
// Build the test binary.
stdout := &bytes.Buffer{}
_, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, 2*time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
if config.EmulatorName() == "simavr" {
// simavr before v1.8 wrote firmware output to stderr and loader logs
// to stdout, but PR #490 swapped these streams:
// https://github.com/buserror/simavr/pull/490
cmd.Stdout = stdout
}
return cmd.Run()
})
if err != nil {
w := &bytes.Buffer{}
diagnostics.CreateDiagnostics(err).WriteTo(w, "")
for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
for line := range strings.SplitSeq(strings.TrimRight(w.String(), "\n"), "\n") {
t.Log(line)
}
if stdout.Len() != 0 {
@@ -484,11 +511,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
actual := stdout.Bytes()
if config.EmulatorName() == "simavr" {
// Strip simavr log formatting.
actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1)
actual = bytes.Replace(actual, []byte{0x1b, '[', '0', 'm'}, nil, -1)
actual = bytes.Replace(actual, []byte{'.', '.', '\n'}, []byte{'\n'}, -1)
actual = bytes.Replace(actual, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'}, -1)
actual = cleanSimAVRTestOutput(actual)
}
if name == "testing.go" {
// Strip actual time.
@@ -515,6 +538,28 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
}
}
func cleanSimAVRTestOutput(output []byte) []byte {
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{'.', '.', '\n'}, []byte{'\n'})
output = bytes.ReplaceAll(output, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'})
var cleaned []byte
for _, line := range bytes.SplitAfter(output, []byte{'\n'}) {
trimmedLine := bytes.TrimRight(line, "\r\n")
if simavrLoadTextLogPattern.Match(trimmedLine) || simavrLoadBytesLogPattern.Match(trimmedLine) {
continue
}
cleaned = append(cleaned, line...)
}
return cleaned
}
var (
simavrLoadTextLogPattern = regexp.MustCompile(`^Loaded [0-9]+ \.[A-Za-z0-9_]+( at address 0x[0-9a-fA-F]+)?$`)
simavrLoadBytesLogPattern = regexp.MustCompile(`^Loaded [0-9]+ bytes of [A-Za-z]+ data at (0x)?[0-9a-fA-F]+$`)
)
// Test WebAssembly files for certain properties.
func TestWebAssembly(t *testing.T) {
t.Parallel()
@@ -530,7 +575,6 @@ func TestWebAssembly(t *testing.T) {
{name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}},
{name: "panic-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
tmpdir := t.TempDir()
@@ -652,7 +696,6 @@ func TestWasmExport(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
@@ -822,7 +865,6 @@ func TestWasmExportJS(t *testing.T) {
{name: "c-shared", buildMode: "c-shared"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Build the wasm binary.
@@ -870,7 +912,6 @@ func TestWasmExit(t *testing.T) {
{name: "exit-1-sleep", output: "slept\nexit code: 1\n"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
options := optionsFromTarget("wasm", sema)
@@ -913,6 +954,48 @@ func checkOutputData(t *testing.T, expectedOutput, actual []byte) {
}
}
func TestGoexitCrash(t *testing.T) {
t.Parallel()
options := optionsFromTarget("", sema)
config, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
want string
}{
{"main", "all goroutines are asleep - deadlock!"},
{"deadlock", "all goroutines are asleep - deadlock!"},
{"exit", "all goroutines are asleep - deadlock!"},
{"main-other", "all goroutines are asleep - deadlock!"},
{"in-panic", "all goroutines are asleep - deadlock!"},
{"panic", "panic: panic after Goexit"},
{"recovered-panic", "all goroutines are asleep - deadlock!"},
{"recover-before-panic", "all goroutines are asleep - deadlock!"},
{"recover-before-panic-loop", "all goroutines are asleep - deadlock!"},
} {
t.Run(tc.name, func(t *testing.T) {
output := &bytes.Buffer{}
_, err = buildAndRun("testdata/goexit.go", config, output, []string{tc.name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
cmd.Stdout = nil
cmd.Stderr = nil
data, err := cmd.CombinedOutput()
output.Write(data)
return err
})
if err == nil {
t.Fatal("program unexpectedly exited successfully")
}
if !strings.Contains(output.String(), tc.want) {
t.Fatalf("output does not contain %q:\n%s", tc.want, output.String())
}
})
}
}
func TestTest(t *testing.T) {
t.Parallel()
@@ -945,7 +1028,6 @@ func TestTest(t *testing.T) {
)
}
for _, targ := range targs {
targ := targ
t.Run(targ.name, func(t *testing.T) {
t.Parallel()
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt)
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32u0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt)
// If you update the above build constraint, you'll probably also need to update
// src/runtime/rand_hwrng.go.
+107
View File
@@ -0,0 +1,107 @@
// TINYGO: cipher suite IDs and the exported CipherSuites/InsecureCipherSuites
// descriptors, copied verbatim from the Go official implementation. TinyGo has
// no software handshake, so only these declarations (no selection machinery)
// are provided to satisfy callers such as google.golang.org/grpc/credentials
// and github.com/pion/dtls that reference the cipher-suite API surface.
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tls
// A list of cipher suite IDs that are, or have been, implemented by this
// package.
//
// See https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
const (
// TLS 1.0 - 1.2 cipher suites.
TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000a
TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002f
TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003c
TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009c
TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009d
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xc007
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xc009
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xc00a
TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xc011
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc012
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xc013
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xc014
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc023
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc027
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc030
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc02c
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca8
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca9
// TLS 1.3 cipher suites.
TLS_AES_128_GCM_SHA256 uint16 = 0x1301
TLS_AES_256_GCM_SHA384 uint16 = 0x1302
TLS_CHACHA20_POLY1305_SHA256 uint16 = 0x1303
// TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator
// that the client is doing version fallback. See RFC 7507.
TLS_FALLBACK_SCSV uint16 = 0x5600
// Legacy names for the corresponding cipher suites with the correct _SHA256
// suffix, retained for backward compatibility.
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
)
var (
supportedUpToTLS12 = []uint16{VersionTLS10, VersionTLS11, VersionTLS12}
supportedOnlyTLS12 = []uint16{VersionTLS12}
supportedOnlyTLS13 = []uint16{VersionTLS13}
)
// CipherSuites returns a list of cipher suites currently implemented by this
// package, excluding those with security issues, which are returned by
// InsecureCipherSuites.
func CipherSuites() []*CipherSuite {
return []*CipherSuite{
{TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
{TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
{TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
}
}
// InsecureCipherSuites returns a list of cipher suites currently implemented by
// this package and which have security issues.
//
// Most applications should not use the cipher suites in this list, and should
// only use those returned by CipherSuites.
func InsecureCipherSuites() []*CipherSuite {
// This list includes legacy RSA kex, RC4, CBC_SHA256, and 3DES cipher
// suites. See cipherSuitesPreferenceOrder for details.
return []*CipherSuite{
{TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
{TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, true},
{TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, true},
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
}
}
+121 -2
View File
@@ -55,6 +55,16 @@ func VersionName(version uint16) string {
// only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7.
type CurveID uint16
const (
CurveP256 CurveID = 23
CurveP384 CurveID = 24
CurveP521 CurveID = 25
X25519 CurveID = 29
X25519MLKEM768 CurveID = 4588
SecP256r1MLKEM768 CurveID = 4587
SecP384r1MLKEM1024 CurveID = 4589
)
// CipherSuiteName returns the standard name for the passed cipher suite ID
//
// Not Implemented.
@@ -68,14 +78,42 @@ type ConnectionState struct {
//
// Minimum (empty) fields for fortio.org/log http logging and others
// to compile and run.
PeerCertificates []*x509.Certificate
CipherSuite uint16
Version uint16
ServerName string
PeerCertificates []*x509.Certificate
CipherSuite uint16
NegotiatedProtocol string
NegotiatedProtocolIsMutual bool // Deprecated: this value is always true.
}
// ClientAuthType declares the policy the server will follow for
// TLS Client Authentication.
type ClientAuthType int
const (
// NoClientCert indicates that no client certificate should be requested
// during the handshake, and if any certificates are sent they will not
// be verified.
NoClientCert ClientAuthType = iota
// RequestClientCert indicates that a client certificate should be requested
// during the handshake, but does not require that the client send any
// certificates.
RequestClientCert
// RequireAnyClientCert indicates that a client certificate should be requested
// during the handshake, and that at least one certificate is required to be
// sent by the client, but that certificate is not required to be valid.
RequireAnyClientCert
// VerifyClientCertIfGiven indicates that a client certificate should be requested
// during the handshake, but does not require that the client sends a
// certificate. If the client does send a certificate it is required to be
// valid.
VerifyClientCertIfGiven
// RequireAndVerifyClientCert indicates that a client certificate should be requested
// during the handshake, and that at least one valid certificate is required
// to be sent by the client.
RequireAndVerifyClientCert
)
// ClientSessionCache is a cache of ClientSessionState objects that can be used
// by a client to resume a TLS session with a given server. ClientSessionCache
// implementations should expect to be called concurrently from different
@@ -100,6 +138,45 @@ type ClientSessionCache interface {
// RFC 8446, Section 4.2.3.
type SignatureScheme uint16
const (
// RSASSA-PKCS1-v1_5 algorithms.
PKCS1WithSHA256 SignatureScheme = 0x0401
PKCS1WithSHA384 SignatureScheme = 0x0501
PKCS1WithSHA512 SignatureScheme = 0x0601
// RSASSA-PSS algorithms with public key OID rsaEncryption.
PSSWithSHA256 SignatureScheme = 0x0804
PSSWithSHA384 SignatureScheme = 0x0805
PSSWithSHA512 SignatureScheme = 0x0806
// ECDSA algorithms. Only constrained to a specific curve in TLS 1.3.
ECDSAWithP256AndSHA256 SignatureScheme = 0x0403
ECDSAWithP384AndSHA384 SignatureScheme = 0x0503
ECDSAWithP521AndSHA512 SignatureScheme = 0x0603
// EdDSA algorithms.
Ed25519 SignatureScheme = 0x0807
// Legacy signature and hash algorithms for TLS 1.2.
PKCS1WithSHA1 SignatureScheme = 0x0201
ECDSAWithSHA1 SignatureScheme = 0x0203
)
// CipherSuite is a TLS cipher suite. Note that most functions in this package
// accept and expose cipher suite IDs instead of this type.
type CipherSuite struct {
ID uint16
Name string
// Supported versions is the list of TLS protocol versions that can
// negotiate this cipher suite.
SupportedVersions []uint16
// Insecure is true if the cipher suite has known security issues
// due to its primitives, design, or implementation.
Insecure bool
}
// ClientHelloInfo contains information from a ClientHello message in order to
// guide application logic in the GetCertificate and GetConfigForClient callbacks.
type ClientHelloInfo struct {
@@ -459,6 +536,48 @@ type Config struct {
autoSessionTicketKeys []ticketKey
}
// Clone returns a shallow clone of c or nil if c is nil. It is safe to clone a
// Config that is being used concurrently by a TLS client or server.
func (c *Config) Clone() *Config {
if c == nil {
return nil
}
c.mutex.RLock()
defer c.mutex.RUnlock()
return &Config{
Rand: c.Rand,
Time: c.Time,
Certificates: c.Certificates,
NameToCertificate: c.NameToCertificate,
GetCertificate: c.GetCertificate,
GetClientCertificate: c.GetClientCertificate,
GetConfigForClient: c.GetConfigForClient,
VerifyPeerCertificate: c.VerifyPeerCertificate,
VerifyConnection: c.VerifyConnection,
RootCAs: c.RootCAs,
NextProtos: c.NextProtos,
ServerName: c.ServerName,
ClientAuth: c.ClientAuth,
ClientCAs: c.ClientCAs,
InsecureSkipVerify: c.InsecureSkipVerify,
CipherSuites: c.CipherSuites,
PreferServerCipherSuites: c.PreferServerCipherSuites,
SessionTicketsDisabled: c.SessionTicketsDisabled,
SessionTicketKey: c.SessionTicketKey,
ClientSessionCache: c.ClientSessionCache,
UnwrapSession: c.UnwrapSession,
WrapSession: c.WrapSession,
MinVersion: c.MinVersion,
MaxVersion: c.MaxVersion,
CurvePreferences: c.CurvePreferences,
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
KeyLogWriter: c.KeyLogWriter,
sessionTicketKeys: c.sessionTicketKeys,
autoSessionTicketKeys: c.autoSessionTicketKeys,
}
}
// ticketKey is the internal representation of a session ticket key.
type ticketKey struct {
aesKey [16]byte
+42 -3
View File
@@ -20,13 +20,52 @@ import (
"net"
)
// Conn represents a secured connection. TINYGO: the actual TLS handshake and
// record layer are offloaded to the network device (see net.TLSConn), so Conn
// is a thin wrapper over an underlying net.Conn that exists to satisfy callers
// (e.g. google.golang.org/grpc/credentials) which expect the *tls.Conn API
// shape — ConnectionState/Handshake — that this package does not implement in
// software.
type Conn struct {
net.Conn
}
// ConnectionState returns basic TLS details about the connection. TINYGO:
// empty; TLS is offloaded to the network device.
func (c *Conn) ConnectionState() ConnectionState {
return ConnectionState{}
}
// Handshake runs the client or server handshake protocol if it has not yet been
// run. TINYGO: no-op; the handshake is performed by the network device.
func (c *Conn) Handshake() error {
return c.HandshakeContext(context.Background())
}
// HandshakeContext is the context-aware variant of Handshake. TINYGO: no-op.
func (c *Conn) HandshakeContext(ctx context.Context) error {
return nil
}
// NetConn returns the underlying connection that is wrapped by c.
func (c *Conn) NetConn() net.Conn {
return c.Conn
}
// Client returns a new TLS client side connection
// using conn as the underlying transport.
// The config cannot be nil: users must set either ServerName or
// InsecureSkipVerify in the config.
func Client(conn net.Conn, config *Config) *net.TLSConn {
panic("tls.Client() not implemented")
return nil
func Client(conn net.Conn, config *Config) *Conn {
return &Conn{Conn: conn}
}
// Server returns a new TLS server side connection
// using conn as the underlying transport.
// The configuration config must be non-nil and must include
// at least one certificate or else set GetCertificate.
func Server(conn net.Conn, config *Config) *Conn {
return &Conn{Conn: conn}
}
// A listener implements a network listener (net.Listener) for TLS connections.
+74
View File
@@ -0,0 +1,74 @@
//go:build amd64
package amd64
const (
CPUIDTimeStampCounter = 0x15
CPUIDProcessorFrequency = 0x16
)
type CPUExtendedFamily uint16
const (
CPUFamilyIntelCore CPUExtendedFamily = 6
)
//export asmPause
func AsmPause()
//export asmReadRdtsc
func AsmReadRdtsc() uint64
//export asmCpuid
func AsmCpuid(index uint32, registerEax *uint32, registerEbx *uint32, registerEcx *uint32) int
var maxCpuidIndex uint32
var stdVendorName0 uint32
var stdCpuid1Eax uint32
func init() {
AsmCpuid(0, &maxCpuidIndex, &stdVendorName0, nil)
AsmCpuid(1, &stdCpuid1Eax, nil, nil)
}
func getExtendedCPUFamily() CPUExtendedFamily {
family := CPUExtendedFamily((stdCpuid1Eax >> 8) & 0x0f)
family += CPUExtendedFamily((stdCpuid1Eax >> 20) & 0xff)
return family
}
func isIntel() bool {
return stdVendorName0 == 0x756e6547
}
func isIntelFamilyCore() bool {
return isIntel() && getExtendedCPUFamily() == CPUFamilyIntelCore
}
func InternalGetPerformanceCounterFrequency() uint64 {
if maxCpuidIndex >= CPUIDTimeStampCounter {
return cpuidCoreClockCalculateTSCFrequency()
}
return 0
}
func cpuidCoreClockCalculateTSCFrequency() uint64 {
var eax uint32
var ebx uint32
var ecx uint32
AsmCpuid(CPUIDTimeStampCounter, &eax, &ebx, &ecx)
if eax == 0 || ebx == 0 {
return 0
}
coreCrystalFrequency := uint64(ecx)
if coreCrystalFrequency == 0 {
if !isIntelFamilyCore() {
return 0
}
coreCrystalFrequency = 24000000
}
return ((coreCrystalFrequency * uint64(ebx)) + (uint64(eax) / 2)) / uint64(eax)
}
+39
View File
@@ -0,0 +1,39 @@
.section .text
.global asmPause
asmPause:
pause
ret
.global asmReadRdtsc
asmReadRdtsc:
rdtsc
shlq $0x20, %rdx
orq %rdx, %rax
ret
.global asmCpuid
asmCpuid:
pushq %rbx
mov %ecx, %eax
pushq %rax
pushq %rdx
cpuid
test %r9, %r9
jz .SkipEcx
mov %ecx, (%r9)
.SkipEcx:
popq %rcx
jrcxz .SkipEax
mov %eax, (%rcx)
.SkipEax:
mov %r8, %rcx
jrcxz .SkipEbx
mov %ebx, (%rcx)
.SkipEbx:
popq %rax
popq %rbx
ret
+17
View File
@@ -0,0 +1,17 @@
//go:build i386 || amd64
package uefi
import "device/amd64"
func Ticks() uint64 {
return amd64.AsmReadRdtsc()
}
func CpuPause() {
amd64.AsmPause()
}
func getTSCFrequency() uint64 {
return amd64.InternalGetPerformanceCounterFrequency()
}
+201
View File
@@ -0,0 +1,201 @@
.section .text
.global uefiCall0
uefiCall0:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
callq *%rcx
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall1
uefiCall1:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall2
uefiCall2:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall3
uefiCall3:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall4
uefiCall4:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall5
uefiCall5:
pushq %rbp
movq %rsp, %rbp
subq $0x30, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall6
uefiCall6:
pushq %rbp
movq %rsp, %rbp
subq $0x30, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall7
uefiCall7:
pushq %rbp
movq %rsp, %rbp
subq $0x40, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall8
uefiCall8:
pushq %rbp
movq %rsp, %rbp
subq $0x40, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall9
uefiCall9:
pushq %rbp
movq %rsp, %rbp
subq $0x50, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
movq 0x58(%rbp), %r10
movq %r10, 0x40(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall10
uefiCall10:
pushq %rbp
movq %rsp, %rbp
subq $0x50, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
movq 0x58(%rbp), %r10
movq %r10, 0x40(%rsp)
movq 0x60(%rbp), %r10
movq %r10, 0x48(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global ___chkstk_ms
___chkstk_ms:
ret
+45
View File
@@ -0,0 +1,45 @@
package uefi
//go:nosplit
//export uefiCall0
func UefiCall0(fn uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall1
func UefiCall1(fn uintptr, a uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall2
func UefiCall2(fn uintptr, a uintptr, b uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall3
func UefiCall3(fn uintptr, a uintptr, b uintptr, c uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall4
func UefiCall4(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall5
func UefiCall5(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall6
func UefiCall6(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall7
func UefiCall7(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall8
func UefiCall8(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall9
func UefiCall9(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr, i uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall10
func UefiCall10(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr, i uintptr, j uintptr) EFI_STATUS
+77
View File
@@ -0,0 +1,77 @@
package uefi
import (
"unicode/utf16"
"unsafe"
)
// StringToCHAR16 converts a Go string to a UTF-16 code unit slice.
func StringToCHAR16(s string) []CHAR16 {
if s == "" {
return nil
}
encoded := utf16.Encode([]rune(s))
out := make([]CHAR16, len(encoded))
for i, r := range encoded {
out[i] = CHAR16(r)
}
return out
}
// StringToCHAR16Z converts a Go string to a NUL-terminated UTF-16 code unit slice.
func StringToCHAR16Z(s string) []CHAR16 {
out := StringToCHAR16(s)
return append(out, 0)
}
// BytesToCHAR16 converts UTF-8 text bytes to a UTF-16 code unit slice.
func BytesToCHAR16(b []byte) []CHAR16 {
return StringToCHAR16(string(b))
}
// BytesToCHAR16Z converts UTF-8 text bytes to a NUL-terminated UTF-16 code unit slice.
func BytesToCHAR16Z(b []byte) []CHAR16 {
return StringToCHAR16Z(string(b))
}
// CHAR16ToString converts a UTF-16 code unit slice to a Go string.
func CHAR16ToString(input []CHAR16) string {
if len(input) == 0 {
return ""
}
units := make([]uint16, len(input))
for i, c := range input {
units[i] = uint16(c)
}
return string(utf16.Decode(units))
}
// CHAR16ToBytes converts a UTF-16 code unit slice to UTF-8 text bytes.
func CHAR16ToBytes(input []CHAR16) []byte {
return []byte(CHAR16ToString(input))
}
// CHAR16PtrToString converts a NUL-terminated UTF-16 string pointer to a Go string.
func CHAR16PtrToString(input *CHAR16) string {
if input == nil {
return ""
}
ptr := uintptr(unsafe.Pointer(input))
length := 0
for *(*CHAR16)(unsafe.Pointer(ptr)) != 0 {
length++
ptr += 2
}
return CHAR16PtrLenToString(input, length)
}
// CHAR16PtrLenToString converts a UTF-16 string pointer with a known code unit count to a Go string.
func CHAR16PtrLenToString(input *CHAR16, length int) string {
if input == nil || length <= 0 {
return ""
}
return CHAR16ToString(unsafe.Slice(input, length))
}
+39
View File
@@ -0,0 +1,39 @@
//go:build uefi
package uefi
import "sync"
var calibrateMutex sync.Mutex
var calculatedFrequency uint64
func TicksFrequency() uint64 {
frequency := getTSCFrequency()
if frequency > 0 {
return frequency
}
calibrateMutex.Lock()
defer calibrateMutex.Unlock()
if calculatedFrequency > 0 {
return calculatedFrequency
}
var event EFI_EVENT
var index UINTN
if BS().CreateEvent(EVT_TIMER, TPL_CALLBACK, nil, nil, &event) != EFI_SUCCESS {
return 0
}
defer BS().CloseEvent(event)
start := Ticks()
if BS().SetTimer(event, TimerPeriodic, 250*10000) != EFI_SUCCESS {
return 0
}
if BS().WaitForEvent(1, &event, &index) != EFI_SUCCESS {
return 0
}
calculatedFrequency = (Ticks() - start) * 4
return calculatedFrequency
}
+66
View File
@@ -0,0 +1,66 @@
package uefi
type UINTN uintptr
type EFI_STATUS UINTN
type EFI_TPL UINTN
type EFI_HANDLE uintptr
type EFI_EVENT uintptr
type EFI_PHYSICAL_ADDRESS uint64
type CHAR16 uint16
type BOOLEAN bool
type VOID byte
type EFI_GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
type EFI_TABLE_HEADER struct {
Signature uint64
Revision uint32
HeaderSize uint32
CRC32 uint32
Reserved uint32
}
type EFI_ALLOCATE_TYPE int
const (
AllocateAnyPages EFI_ALLOCATE_TYPE = iota
AllocateMaxAddress
AllocateAddress
)
type EFI_MEMORY_TYPE int
const (
EfiReservedMemoryType EFI_MEMORY_TYPE = iota
EfiLoaderCode
EfiLoaderData
EfiBootServicesCode
EfiBootServicesData
EfiRuntimeServicesCode
EfiRuntimeServicesData
EfiConventionalMemory
)
type EVENT_TYPE uint32
const (
EVT_TIMER EVENT_TYPE = 0x80000000
)
const (
TPL_CALLBACK EFI_TPL = 8
)
type EFI_TIMER_DELAY int
const (
TimerCancel EFI_TIMER_DELAY = iota
TimerPeriodic
TimerRelative
)
+111
View File
@@ -0,0 +1,111 @@
package uefi
const (
uintnSize = 32 << (^uintptr(0) >> 63)
errorMask = 1 << uintptr(uintnSize-1)
)
const (
EFI_SUCCESS EFI_STATUS = 0
EFI_LOAD_ERROR EFI_STATUS = errorMask | 1
EFI_INVALID_PARAMETER EFI_STATUS = errorMask | 2
EFI_UNSUPPORTED EFI_STATUS = errorMask | 3
EFI_BAD_BUFFER_SIZE EFI_STATUS = errorMask | 4
EFI_BUFFER_TOO_SMALL EFI_STATUS = errorMask | 5
EFI_NOT_READY EFI_STATUS = errorMask | 6
EFI_DEVICE_ERROR EFI_STATUS = errorMask | 7
EFI_WRITE_PROTECTED EFI_STATUS = errorMask | 8
EFI_OUT_OF_RESOURCES EFI_STATUS = errorMask | 9
EFI_VOLUME_CORRUPTED EFI_STATUS = errorMask | 10
EFI_VOLUME_FULL EFI_STATUS = errorMask | 11
EFI_NO_MEDIA EFI_STATUS = errorMask | 12
EFI_MEDIA_CHANGED EFI_STATUS = errorMask | 13
EFI_NOT_FOUND EFI_STATUS = errorMask | 14
EFI_ACCESS_DENIED EFI_STATUS = errorMask | 15
EFI_NO_RESPONSE EFI_STATUS = errorMask | 16
EFI_NO_MAPPING EFI_STATUS = errorMask | 17
EFI_TIMEOUT EFI_STATUS = errorMask | 18
EFI_NOT_STARTED EFI_STATUS = errorMask | 19
EFI_ALREADY_STARTED EFI_STATUS = errorMask | 20
EFI_ABORTED EFI_STATUS = errorMask | 21
EFI_ICMP_ERROR EFI_STATUS = errorMask | 22
EFI_TFTP_ERROR EFI_STATUS = errorMask | 23
EFI_PROTOCOL_ERROR EFI_STATUS = errorMask | 24
EFI_INCOMPATIBLE_VERSION EFI_STATUS = errorMask | 25
EFI_SECURITY_VIOLATION EFI_STATUS = errorMask | 26
EFI_CRC_ERROR EFI_STATUS = errorMask | 27
EFI_END_OF_MEDIA EFI_STATUS = errorMask | 28
EFI_END_OF_FILE EFI_STATUS = errorMask | 31
EFI_INVALID_LANGUAGE EFI_STATUS = errorMask | 32
EFI_COMPROMISED_DATA EFI_STATUS = errorMask | 33
EFI_IP_ADDRESS_CONFLICT EFI_STATUS = errorMask | 34
EFI_HTTP_ERROR EFI_STATUS = errorMask | 35
)
var errMap = map[EFI_STATUS]*Error{}
var (
ErrLoadError = newError(EFI_LOAD_ERROR, "image failed to load")
ErrInvalidParameter = newError(EFI_INVALID_PARAMETER, "a parameter was incorrect")
ErrUnsupported = newError(EFI_UNSUPPORTED, "operation not supported")
ErrBadBufferSize = newError(EFI_BAD_BUFFER_SIZE, "buffer size incorrect for request")
ErrBufferTooSmall = newError(EFI_BUFFER_TOO_SMALL, "buffer too small; size returned in parameter")
ErrNotReady = newError(EFI_NOT_READY, "no data pending")
ErrDeviceError = newError(EFI_DEVICE_ERROR, "physical device reported an error")
ErrWriteProtected = newError(EFI_WRITE_PROTECTED, "device is write-protected")
ErrOutOfResources = newError(EFI_OUT_OF_RESOURCES, "out of resources")
ErrVolumeCorrupted = newError(EFI_VOLUME_CORRUPTED, "filesystem inconsistency detected")
ErrVolumeFull = newError(EFI_VOLUME_FULL, "no more space on filesystem")
ErrNoMedia = newError(EFI_NO_MEDIA, "device contains no medium")
ErrMediaChanged = newError(EFI_MEDIA_CHANGED, "medium changed since last access")
ErrNotFound = newError(EFI_NOT_FOUND, "item not found")
ErrAccessDenied = newError(EFI_ACCESS_DENIED, "access denied")
ErrNoResponse = newError(EFI_NO_RESPONSE, "server not found or no response")
ErrNoMapping = newError(EFI_NO_MAPPING, "no device mapping exists")
ErrTimeout = newError(EFI_TIMEOUT, "timeout expired")
ErrNotStarted = newError(EFI_NOT_STARTED, "protocol not started")
ErrAlreadyStarted = newError(EFI_ALREADY_STARTED, "protocol already started")
ErrAborted = newError(EFI_ABORTED, "operation aborted")
ErrICMPError = newError(EFI_ICMP_ERROR, "ICMP error during network operation")
ErrTFTPError = newError(EFI_TFTP_ERROR, "TFTP error during network operation")
ErrProtocolError = newError(EFI_PROTOCOL_ERROR, "protocol error during network operation")
ErrIncompatibleVersion = newError(EFI_INCOMPATIBLE_VERSION, "requested version incompatible")
ErrSecurityViolation = newError(EFI_SECURITY_VIOLATION, "security violation")
ErrCRCError = newError(EFI_CRC_ERROR, "CRC error detected")
ErrEndOfMedia = newError(EFI_END_OF_MEDIA, "beginning or end of media reached")
ErrEndOfFile = newError(EFI_END_OF_FILE, "end of file reached")
ErrInvalidLanguage = newError(EFI_INVALID_LANGUAGE, "invalid language specified")
ErrCompromisedData = newError(EFI_COMPROMISED_DATA, "data security status unknown or compromised")
ErrIPAddressConflict = newError(EFI_IP_ADDRESS_CONFLICT, "IP address conflict detected")
ErrHTTPError = newError(EFI_HTTP_ERROR, "HTTP error during network operation")
)
type Error struct {
code EFI_STATUS
msg string
}
func newError(code EFI_STATUS, msg string) *Error {
err := &Error{code: code, msg: msg}
errMap[code] = err
return err
}
func (e *Error) Error() string {
return e.msg
}
func (e *Error) Status() EFI_STATUS {
return e.code
}
func StatusError(status EFI_STATUS) *Error {
if status == EFI_SUCCESS {
return nil
}
err, ok := errMap[status]
if !ok {
return newError(status, "unknown EFI error")
}
return err
}
+74
View File
@@ -0,0 +1,74 @@
package uefi
import "unsafe"
func booleanArg(v BOOLEAN) uintptr {
if v {
return 1
}
return 0
}
type EFI_SIMPLE_TEXT_OUTPUT_MODE struct {
MaxMode int32
Mode int32
Attribute int32
CursorColumn int32
CursorRow int32
CursorVisible BOOLEAN
}
type EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL struct {
reset uintptr
outputString uintptr
testString uintptr
queryMode uintptr
setMode uintptr
setAttribute uintptr
clearScreen uintptr
setCursorPosition uintptr
enableCursor uintptr
Mode *EFI_SIMPLE_TEXT_OUTPUT_MODE
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) Reset(extendedVerification BOOLEAN) EFI_STATUS {
return UefiCall2(p.reset, uintptr(unsafe.Pointer(p)), booleanArg(extendedVerification))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) OutputString(s *CHAR16) EFI_STATUS {
return UefiCall2(p.outputString, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(s)))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) TestString(s *CHAR16) EFI_STATUS {
return UefiCall2(p.testString, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(s)))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) QueryMode(modeNumber UINTN, columns *UINTN, rows *UINTN) EFI_STATUS {
return UefiCall4(
p.queryMode,
uintptr(unsafe.Pointer(p)),
uintptr(modeNumber),
uintptr(unsafe.Pointer(columns)),
uintptr(unsafe.Pointer(rows)),
)
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetMode(modeNumber UINTN) EFI_STATUS {
return UefiCall2(p.setMode, uintptr(unsafe.Pointer(p)), uintptr(modeNumber))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetAttribute(attribute UINTN) EFI_STATUS {
return UefiCall2(p.setAttribute, uintptr(unsafe.Pointer(p)), uintptr(attribute))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) ClearScreen() EFI_STATUS {
return UefiCall1(p.clearScreen, uintptr(unsafe.Pointer(p)))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetCursorPosition(column UINTN, row UINTN) EFI_STATUS {
return UefiCall3(p.setCursorPosition, uintptr(unsafe.Pointer(p)), uintptr(column), uintptr(row))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) EnableCursor(visible BOOLEAN) EFI_STATUS {
return UefiCall2(p.enableCursor, uintptr(unsafe.Pointer(p)), booleanArg(visible))
}
+123
View File
@@ -0,0 +1,123 @@
package uefi
import "unsafe"
type EFI_RUNTIME_SERVICES struct {
Hdr EFI_TABLE_HEADER
getTime uintptr
setTime uintptr
getWakeupTime uintptr
setWakeupTime uintptr
setVirtualAddressMap uintptr
convertPointer uintptr
getVariable uintptr
getNextVariableName uintptr
setVariable uintptr
getNextHighMonoCount uintptr
resetSystem uintptr
updateCapsule uintptr
queryCapsuleCaps uintptr
queryVariableInfo uintptr
}
type EFI_BOOT_SERVICES struct {
Hdr EFI_TABLE_HEADER
raiseTPL uintptr
restoreTPL uintptr
allocatePages uintptr
freePages uintptr
getMemoryMap uintptr
allocatePool uintptr
freePool uintptr
createEvent uintptr
setTimer uintptr
waitForEvent uintptr
signalEvent uintptr
closeEvent uintptr
checkEvent uintptr
installProtocolInterface uintptr
reinstallProtocolIFace uintptr
uninstallProtocolIFace uintptr
handleProtocol uintptr
reserved *VOID
registerProtocolNotify uintptr
locateHandle uintptr
locateDevicePath uintptr
installConfigurationTable uintptr
loadImage uintptr
startImage uintptr
exit uintptr
unloadImage uintptr
exitBootServices uintptr
getNextMonotonicCount uintptr
stall uintptr
setWatchdogTimer uintptr
connectController uintptr
disconnectController uintptr
openProtocol uintptr
closeProtocol uintptr
openProtocolInformation uintptr
protocolsPerHandle uintptr
locateHandleBuffer uintptr
locateProtocol uintptr
}
func (p *EFI_BOOT_SERVICES) AllocatePages(typ EFI_ALLOCATE_TYPE, memoryType EFI_MEMORY_TYPE, pages UINTN, memory *EFI_PHYSICAL_ADDRESS) EFI_STATUS {
return UefiCall4(p.allocatePages, uintptr(typ), uintptr(memoryType), uintptr(pages), uintptr(unsafe.Pointer(memory)))
}
func (p *EFI_BOOT_SERVICES) FreePages(memory EFI_PHYSICAL_ADDRESS, pages UINTN) EFI_STATUS {
return UefiCall2(p.freePages, uintptr(memory), uintptr(pages))
}
func (p *EFI_BOOT_SERVICES) CreateEvent(typ EVENT_TYPE, notifyTPL EFI_TPL, notifyFunction unsafe.Pointer, notifyContext unsafe.Pointer, event *EFI_EVENT) EFI_STATUS {
return UefiCall5(p.createEvent, uintptr(typ), uintptr(notifyTPL), uintptr(notifyFunction), uintptr(notifyContext), uintptr(unsafe.Pointer(event)))
}
func (p *EFI_BOOT_SERVICES) SetTimer(event EFI_EVENT, typ EFI_TIMER_DELAY, triggerTime uint64) EFI_STATUS {
return UefiCall3(p.setTimer, uintptr(event), uintptr(typ), uintptr(triggerTime))
}
func (p *EFI_BOOT_SERVICES) WaitForEvent(numberOfEvents UINTN, event *EFI_EVENT, index *UINTN) EFI_STATUS {
return UefiCall3(p.waitForEvent, uintptr(numberOfEvents), uintptr(unsafe.Pointer(event)), uintptr(unsafe.Pointer(index)))
}
func (p *EFI_BOOT_SERVICES) CloseEvent(event EFI_EVENT) EFI_STATUS {
return UefiCall1(p.closeEvent, uintptr(event))
}
func (p *EFI_BOOT_SERVICES) CheckEvent(event EFI_EVENT) EFI_STATUS {
return UefiCall1(p.checkEvent, uintptr(event))
}
func (p *EFI_BOOT_SERVICES) HandleProtocol(handle EFI_HANDLE, protocol *EFI_GUID, iface unsafe.Pointer) EFI_STATUS {
return UefiCall3(p.handleProtocol, uintptr(handle), uintptr(unsafe.Pointer(protocol)), uintptr(iface))
}
func (p *EFI_BOOT_SERVICES) LocateProtocol(protocol *EFI_GUID, registration *VOID, iface unsafe.Pointer) EFI_STATUS {
return UefiCall3(p.locateProtocol, uintptr(unsafe.Pointer(protocol)), uintptr(unsafe.Pointer(registration)), uintptr(iface))
}
func (p *EFI_BOOT_SERVICES) Exit(imageHandle EFI_HANDLE, exitStatus EFI_STATUS, exitDataSize UINTN, exitData *CHAR16) EFI_STATUS {
return UefiCall4(p.exit, uintptr(imageHandle), uintptr(exitStatus), uintptr(exitDataSize), uintptr(unsafe.Pointer(exitData)))
}
func (p *EFI_BOOT_SERVICES) SetWatchdogTimer(timeout UINTN, watchdogCode uint64, dataSize UINTN, watchdogData *CHAR16) EFI_STATUS {
return UefiCall4(p.setWatchdogTimer, uintptr(timeout), uintptr(watchdogCode), uintptr(dataSize), uintptr(unsafe.Pointer(watchdogData)))
}
type EFI_SYSTEM_TABLE struct {
Hdr EFI_TABLE_HEADER
FirmwareVendor *CHAR16
FirmwareRevision uint32
ConsoleInHandle EFI_HANDLE
ConIn *VOID
ConsoleOutHandle EFI_HANDLE
ConOut *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
StandardErrorHandle EFI_HANDLE
StdErr *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
RuntimeServices *EFI_RUNTIME_SERVICES
BootServices *EFI_BOOT_SERVICES
NumberOfTableEntries UINTN
ConfigurationTable *VOID
}
+41
View File
@@ -0,0 +1,41 @@
package uefi
import "errors"
var errNilTextOutputProtocol = errors.New("uefi: nil simple text output protocol")
type TextOutput struct {
proto *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
}
func NewTextOutput(proto *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) *TextOutput {
return &TextOutput{proto: proto}
}
func ConsoleOut() *TextOutput {
return NewTextOutput(ST().ConOut)
}
func StandardError() *TextOutput {
return NewTextOutput(ST().StdErr)
}
func (w *TextOutput) Write(p []byte) (int, error) {
if w == nil || w.proto == nil {
return 0, errNilTextOutputProtocol
}
if len(p) == 0 {
return 0, nil
}
buf := StringToCHAR16Z(string(p))
status := w.proto.OutputString(&buf[0])
if status != EFI_SUCCESS {
return 0, StatusError(status)
}
return len(p), nil
}
func (w *TextOutput) WriteString(s string) (int, error) {
return w.Write([]byte(s))
}
+29
View File
@@ -0,0 +1,29 @@
//go:build uefi
package uefi
import "unsafe"
var systemTable *EFI_SYSTEM_TABLE
var imageHandle uintptr
//go:nobounds
func Init(argImageHandle uintptr, argSystemTable uintptr) {
systemTable = (*EFI_SYSTEM_TABLE)(unsafe.Pointer(argSystemTable))
imageHandle = argImageHandle
}
func ST() *EFI_SYSTEM_TABLE {
return systemTable
}
func BS() *EFI_BOOT_SERVICES {
if systemTable == nil {
return nil
}
return systemTable.BootServices
}
func GetImageHandle() EFI_HANDLE {
return EFI_HANDLE(imageHandle)
}
+1
View File
@@ -22,6 +22,7 @@ func main() {
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
kb := keyboard.Port()
machine.USBDev.Configure(machine.UARTConfig{}) // no-op if already init'd by serial.usb
for {
if !button.Get() {
+1 -1
View File
@@ -25,7 +25,7 @@ func main() {
func escapesToHeap() {
n := rand.Intn(100)
println("Doing ", n, " iterations")
for i := 0; i < n; i++ {
for i := range n {
s := make([]byte, i)
_ = append(s, 42)
}
+2 -2
View File
@@ -70,7 +70,7 @@ func main() {
printItf(Number(3))
s := Stringer(thing)
println("Stringer.String():", s.String())
var itf interface{} = s
var itf any = s
println("Stringer.(*Thing).String():", itf.(Stringer).String())
// unusual calls
@@ -124,7 +124,7 @@ func strlen(s string) int {
return len(s)
}
func printItf(val interface{}) {
func printItf(val any) {
switch val := val.(type) {
case Doubler:
println("is Doubler:", val.Double())
+78 -1
View File
@@ -6,8 +6,85 @@ import (
"time"
)
// Disk geometry.
const (
sectorSize = 512
diskSectors = 128 // 64 KB total
)
var diskData [diskSectors * sectorSize]byte
type ramDisk struct{}
func (r *ramDisk) ReadAt(p []byte, off int64) (int, error) {
return copy(p, diskData[off:]), nil
}
func (r *ramDisk) WriteAt(p []byte, off int64) (int, error) {
return copy(diskData[off:], p), nil
}
func (r *ramDisk) Size() int64 { return int64(diskSectors * sectorSize) }
func (r *ramDisk) WriteBlockSize() int64 { return sectorSize }
func (r *ramDisk) EraseBlockSize() int64 { return sectorSize }
func (r *ramDisk) EraseBlocks(start, len int64) error { return nil }
func init() {
formatFAT12(diskData[:])
}
// formatFAT12 writes a minimal FAT12 volume boot record and FAT tables so the
// host OS can mount the disk without reformatting.
func formatFAT12(d []byte) {
// --- Sector 0: Volume Boot Record ---
s := d[0:]
s[0] = 0xEB
s[1] = 0x3C
s[2] = 0x90 // short JMP + NOP
copy(s[3:11], "MSDOS5.0")
// BPB fields (little-endian)
s[11] = 0x00
s[12] = 0x02 // bytesPerSector = 512
s[13] = 0x01 // sectorsPerCluster = 1
s[14] = 0x01
s[15] = 0x00 // reservedSectors = 1
s[16] = 0x02 // numFATs = 2
s[17] = 0x20
s[18] = 0x00 // rootEntryCount = 32
s[19] = 0x80
s[20] = 0x00 // totalSectors16 = 128
s[21] = 0xF8 // mediaType = fixed disk
s[22] = 0x01
s[23] = 0x00 // sectorsPerFAT = 1
s[24] = 0x80
s[25] = 0x00 // sectorsPerTrack = 128
s[26] = 0x01
s[27] = 0x00 // numHeads = 1
// hiddenSectors[28:32] = 0
// totalSectors32[32:36] = 0
s[38] = 0x29 // extBootSig
s[39] = 0x47
s[40] = 0x4F
s[41] = 0x30
s[42] = 0x31 // volumeID "GO01"
copy(s[43:54], "TINYGO ") // volumeLabel (11 bytes)
copy(s[54:62], "FAT12 ") // fsType
s[510] = 0x55
s[511] = 0xAA // boot sector signature
// --- Sector 1: FAT1 ---
// Entry 0 = 0xFF8 (media byte), entry 1 = 0xFFF (EOC); all others = free.
d[512] = 0xF8
d[513] = 0xFF
d[514] = 0xFF
// --- Sector 2: FAT2 (identical copy) ---
copy(d[1024:1027], d[512:515])
}
func main() {
msc.Port(machine.Flash)
msc.Port(&ramDisk{})
machine.USBDev.Configure(machine.UARTConfig{})
for {
time.Sleep(2 * time.Second)
+5 -8
View File
@@ -45,11 +45,8 @@ func Compare(a, b []byte) int {
// This function was copied from the Go 1.23 source tree (with runtime_cmpstring
// manually inlined).
func CompareString(a, b string) int {
l := len(a)
if len(b) < l {
l = len(b)
}
for i := 0; i < l; i++ {
l := min(len(b), len(a))
for i := range l {
c1, c2 := a[i], b[i]
if c1 < c2 {
return -1
@@ -170,7 +167,7 @@ const PrimeRK = 16777619
// This function was removed in Go 1.22.
func HashStrBytes(sep []byte) (uint32, uint32) {
hash := uint32(0)
for i := 0; i < len(sep); i++ {
for i := range sep {
hash = hash*PrimeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, PrimeRK
@@ -249,7 +246,7 @@ func IndexRabinKarpBytes(s, sep []byte) int {
hashsep, pow := HashStrBytes(sep)
n := len(sep)
var h uint32
for i := 0; i < n; i++ {
for i := range n {
h = h*PrimeRK + uint32(s[i])
}
if h == hashsep && Equal(s[:n], sep) {
@@ -276,7 +273,7 @@ func IndexRabinKarp[T string | []byte](s, sep T) int {
hashss, pow := HashStr(sep)
n := len(sep)
var h uint32
for i := 0; i < n; i++ {
for i := range n {
h = h*PrimeRK + uint32(s[i])
}
if h == hashss && string(s[:n]) == string(sep) {
+2 -2
View File
@@ -12,8 +12,8 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te
return &emptyTextError{}
}
s := string(text)
for i := 0; i < len(cases); i++ {
if cases[i] == s {
for i, c := range cases {
if c == s {
*v = T(i)
return nil
}
+1
View File
@@ -51,6 +51,7 @@ type stackState struct {
// The new goroutine is immediately started.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
addLiveTask(t)
t.state.initialize(fn, args, stackSize)
scheduleTask(t)
}
+42
View File
@@ -0,0 +1,42 @@
//go:build scheduler.tasks || scheduler.asyncify || scheduler.cores
package task
import "sync/atomic"
var (
mainTask *Task
liveTasks uint32
mainExitedByGoexit uint32
)
func addLiveTask(t *Task) {
if mainTask == nil {
mainTask = t
}
atomic.AddUint32(&liveTasks, 1)
}
// Exit exits the current task because runtime.Goexit was called.
func Exit() {
exit(true)
}
func exit(goexit bool) {
t := Current()
remaining := atomic.AddUint32(&liveTasks, ^uint32(0))
if t == mainTask {
if goexit {
if remaining == 0 {
runtimePanic("all goroutines are asleep - deadlock!")
}
atomic.StoreUint32(&mainExitedByGoexit, 1)
}
} else if atomic.LoadUint32(&mainExitedByGoexit) != 0 && remaining == 0 {
runtimePanic("all goroutines are asleep - deadlock!")
}
// TODO: explicitly free the stack after switching back to the scheduler.
Pause()
runtimePanic("unreachable")
}
+2 -2
View File
@@ -33,8 +33,7 @@ type state struct {
//export tinygo_task_exit
func taskExit() {
// TODO: explicitly free the stack after switching back to the scheduler.
Pause()
exit(false)
}
// initialize the state and prepare to call the specified function with the specified argument bundle.
@@ -73,6 +72,7 @@ var startTask [0]uint8
// The new goroutine is scheduled to run later.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
addLiveTask(t)
t.state.initialize(fn, args, stackSize)
scheduleTask(t)
}
+5
View File
@@ -145,6 +145,11 @@ void* tinygo_task_current(void) {
return current_task;
}
// Exit the current thread.
void tinygo_task_exit(void) {
pthread_exit(NULL);
}
// Send a signal to cause the task to pause for the GC mark phase.
void tinygo_task_send_gc_signal(pthread_t thread) {
pthread_kill(thread, taskPauseSignal);
+45 -7
View File
@@ -43,7 +43,9 @@ var mainTask Task
// Queue of tasks (see QueueNext) that currently exist in the program.
var activeTasks = &mainTask
var activeTaskCount uint32 = 1
var activeTaskLock PMutex
var mainExitedByGoexit bool
func OnSystemStack() bool {
runtimePanic("todo: task.OnSystemStack")
@@ -95,9 +97,6 @@ func (t *Task) Resume() {
t.state.pauseSem.Post()
}
// otherGoroutines is the total number of live goroutines minus one.
var otherGoroutines uint32
// Start a new OS thread.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
@@ -117,7 +116,7 @@ func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
}
t.state.QueueNext = activeTasks
activeTasks = t
otherGoroutines++
activeTaskCount++
activeTaskLock.Unlock()
}
@@ -127,6 +126,12 @@ func taskExited(t *Task) {
println("*** exit:", t.state.id)
}
if exit(t) {
runtimePanic("all goroutines are asleep - deadlock!")
}
}
func exit(t *Task) bool {
// Remove from the queue.
// TODO: this can be made more efficient by using a doubly linked list.
activeTaskLock.Lock()
@@ -135,16 +140,46 @@ func taskExited(t *Task) {
if *q == t {
*q = t.state.QueueNext
found = true
activeTaskCount--
break
}
}
otherGoroutines--
deadlocked := mainExitedByGoexit && activeTaskCount == 0
activeTaskLock.Unlock()
// Sanity check.
if !found {
runtimePanic("taskExited failed")
}
return deadlocked
}
func otherTasks(current *Task) uint32 {
if activeTaskCount == 0 {
return 0
}
return activeTaskCount - 1
}
// Exit exits the current task. If this is the main task and there are no other
// goroutines, it reports a deadlock.
func Exit() {
t := Current()
if t == &mainTask {
activeTaskLock.Lock()
noOtherTasks := otherTasks(t) == 0
if !noOtherTasks {
mainExitedByGoexit = true
}
activeTaskLock.Unlock()
if noOtherTasks {
runtimePanic("all goroutines are asleep - deadlock!")
}
}
if exit(t) {
runtimePanic("all goroutines are asleep - deadlock!")
}
tinygo_task_exit()
}
// scanWaitGroup is used to wait on until all threads have finished the current state transition.
@@ -206,7 +241,7 @@ func GCStopWorldAndScan() {
gcState.Store(gcStateStopped)
// Set the number of threads to wait for.
scanWaitGroup = initWaitGroup(otherGoroutines)
scanWaitGroup = initWaitGroup(otherTasks(current))
// Pause all other threads.
for t := activeTasks; t != nil; t = t.state.QueueNext {
@@ -242,7 +277,7 @@ func GCResumeWorld() {
}
// Set the wait group to track resume progress.
scanWaitGroup = initWaitGroup(otherGoroutines)
scanWaitGroup = initWaitGroup(otherTasks(Current()))
// Set the state to resumed.
gcState.Store(gcStateResumed)
@@ -304,6 +339,9 @@ func tinygo_task_init(t *Task, thread *threadID, numCPU *int32)
//go:linkname tinygo_task_start tinygo_task_start
func tinygo_task_start(fn uintptr, args unsafe.Pointer, t *Task, thread *threadID, stackTop *uintptr, stackSize uintptr) int32
//go:linkname tinygo_task_exit tinygo_task_exit
func tinygo_task_exit()
// Pause the thread by sending it a signal.
//
//export tinygo_task_send_gc_signal
+136
View File
@@ -0,0 +1,136 @@
//go:build xiao_ble_plus
// This file contains the pin mappings for the Seeed XIAO BLE nRF52840 [Plus, Sense Plus] boards.
//
// Seeed XIAO BLE is an ultra-small size, ultra-low power Bluetooth development board based on the Nordic nRF52840.
// It features an onboard Bluetooth antenna, onboard battery charging chip, and 21*17.5mm thumb size, which makes it ideal for IoT projects.
//
// Seeed XIAO BLE nRF52840 Sense is a tiny Bluetooth LE development board designed for IoT and AI applications.
// It features an onboard antenna, 6 Dof IMU, microphone, all of which make it an ideal board to run AI using TinyML and TensorFlow Lite.
//
// SoftDevice (s140v7) is pre-flashed on this board already.
// See https://github.com/tinygo-org/bluetooth
//
// - https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html
// - https://www.seeedstudio.com/Seeed-XIAO-BLE-Sense-nRF52840-p-5253.html
// - https://www.seeedstudio.com/Seeed-Studio-XIAO-nRF52840-Sense-Plus-p-6360.html
//
// - https://wiki.seeedstudio.com/XIAO_BLE/
// - https://github.com/Seeed-Studio/ArduinoCore-mbed/tree/master/variants/SEEED_XIAO_NRF52840_SENSE
package machine
const HasLowFrequencyCrystal = true
// Digital Pins
const (
D0 Pin = P0_02
D1 Pin = P0_03
D2 Pin = P0_28
D3 Pin = P0_29
D4 Pin = P0_04
D5 Pin = P0_05
D6 Pin = P1_11
D7 Pin = P1_12
D8 Pin = P1_13
D9 Pin = P1_14
D10 Pin = P1_15
D11 Pin = P0_15
D12 Pin = P0_19
D13 Pin = P1_01
D14 Pin = P0_09
D15 Pin = P0_10
D16 Pin = P0_31
D17 Pin = P1_03
D18 Pin = P1_05
D19 Pin = P1_07
)
// Analog pins
const (
A0 Pin = P0_02
A1 Pin = P0_03
A2 Pin = P0_28
A3 Pin = P0_29
A4 Pin = P0_04
A5 Pin = P0_05
)
// Onboard LEDs
const (
LED = LED_CHG
LED1 = LED_RED
LED2 = LED_GREEN
LED3 = LED_BLUE
LED_CHG = P0_17
LED_RED = P0_26
LED_GREEN = P0_30
LED_BLUE = P0_06
)
// UART pins
const (
UART_RX_PIN = P1_12
UART_TX_PIN = P1_11
UART1_RX_PIN = P0_09
UART1_TX_PIN = P0_10
)
// I2C pins
const (
// Defaults to internal
SDA_PIN = SDA1_PIN
SCL_PIN = SCL1_PIN
// I2C0 (external) pins
SDA0_PIN = P0_04
SCL0_PIN = P0_05
// I2C1 (internal) pins
SDA1_PIN = P0_07
SCL1_PIN = P0_27
)
// I2S pins
const (
I2S_SCK_PIN = P0_19
I2S_WS_PIN = P1_01
I2S_SD_PIN = P0_15
)
// SPI pins
const (
SPI0_SCK_PIN = P1_13
SPI0_SDO_PIN = P1_14
SPI0_SDI_PIN = P1_15
)
// Peripherals
const (
LSM_PWR = P1_08 // IMU (LSM6DS3TR) power
LSM_INT = P0_11 // IMU (LSM6DS3TR) interrupt
MIC_PWR = P1_10 // Microphone (MSM261D3526H1CPM) power
MIC_CLK = P1_00
MIC_DIN = P0_16
)
// NFC pins
const (
NFC1_PIN = P0_09
NFC2_PIN = P0_10
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "XIAO nRF52840 Sense"
usb_STRING_MANUFACTURER = "Seeed"
)
var (
usb_VID uint16 = 0x2886
usb_PID uint16 = 0x8045
)
var (
DefaultUART = UART0
)
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build esp32c3 || nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350
//go:build esp32c3 || nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32f7 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350
package machine
+392
View File
@@ -0,0 +1,392 @@
//go:build esp32c6
package machine
import (
"device/esp"
"errors"
"runtime/volatile"
"unsafe"
)
// newRegI2C returns the regI2C configured for ESP32-C6: hostID=0, drefInit=1.
// I2C_SAR_ADC_HOSTID = 0 per soc/esp32c6/include/soc/regi2c_saradc.h.
func newRegI2C() regI2C { return regI2C{hostID: 0, drefInit: 1} }
const (
// ADC attenuation values for ESP32-C6 APB_SARADC.
// 0 dB : ~0 .. 1.1 V
// 11 dB : ~0 .. 3.3 V (matches typical VDD)
atten0dB = 0
atten11dB = 3
)
// InitADC initialises the APB_SARADC peripheral on ESP32-C6.
// On C6 the clock/reset gating moved to PCR (not SYSTEM as on C3), and the
// SARADC CLKM divider configuration also lives in PCR.
func InitADC() {
// Reset and enable the SARADC bus clock via PCR.
esp.PCR.SetSARADC_CONF_SARADC_RST_EN(1)
esp.PCR.SetSARADC_CONF_SARADC_CLK_EN(1)
esp.PCR.SetSARADC_CONF_SARADC_RST_EN(0)
// Select clock source 2 (PLL_F80M), divider = 1, no fractional.
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_SEL(2)
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_NUM(1)
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_B(0)
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_A(0)
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_EN(1)
// Power up the SAR ADC and configure FSM timing (same register layout as C3).
esp.APB_SARADC.SetCTRL_SARADC_XPD_SAR_FORCE(1)
esp.APB_SARADC.SetFSM_WAIT_SARADC_XPD_WAIT(8)
esp.APB_SARADC.SetFSM_WAIT_SARADC_RSTB_WAIT(8)
esp.APB_SARADC.SetFSM_WAIT_SARADC_STANDBY_WAIT(100)
adcSelfCalibrate()
}
// ESP32-C6 ADC pin mapping: ADC1 = GPIO0GPIO6 (ch 06). There is no ADC2.
// (The machine_esp32c6.go file defines ADC0..ADC6 as GPIO0..GPIO6.)
func (a ADC) Configure(config ADCConfig) error {
if a.Pin > 6 {
return errors.New("invalid ADC pin for ESP32-C6")
}
a.Pin.Configure(PinConfig{Mode: PinAnalog})
return nil
}
// Get performs a single ADC1 conversion and returns a 16-bit value.
// The raw 12-bit result (0..4095) is left-shifted by 4 to fill 16 bits.
func (a ADC) Get() uint16 {
if a.Pin > 6 {
return 0
}
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB)
esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(uint32(a.Pin))
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1)
for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 {
}
raw := esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA()
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0)
return uint16(raw&0xfff) << 4
}
// ── regI2C: internal I2C-bus (LP_I2C_ANA_MST) for SAR ADC calibration ───────
//
// On ESP32-C6 the "REGI2C" master moved from the embedded SENS/APB_SARADC
// controller (0x6000_E000) used on C3/S3 to the dedicated LP_I2C_ANA_MST
// peripheral at 0x600b_2400. The SAR ADC block address and register layout
// (DREF, ENCAL_GND, INIT_CODE) remain identical to C3.
//
// LP_I2C_ANA_MST.I2C0_CTRL bit layout (25-bit command field):
// [7:0] = slave block address (0x69 for I2C_SAR_ADC)
// [15:8] = register address within the block
// [23:16]= write data (8 bits)
// [24] = WR_CNTL: 0=read, 1=write
// [25] = BUSY (read-only, set by hardware while processing)
//
// Source: components/esp_rom/patches/esp_rom_regi2c_esp32c6.c in esp-idf
// regI2C wraps the internal I2C bus used for SAR ADC calibration registers.
// Fields hold chip-specific parameters.
type regI2C struct {
// hostID is the I2C_SAR_ADC_HOSTID (0 for ESP32-C6, matching regi2c_saradc.h).
hostID uint8
// drefInit is the DREF reference value written during calibrationInit (1 for C6).
drefInit uint8
}
// SAR ADC I2C register layout — identical to ESP32-C3 / ESP32-S3.
// Source: soc/esp32c6/include/soc/regi2c_saradc.h
const (
i2cSarADC = uint8(0x69)
adc1DrefAddr = uint8(0x2)
adc1DrefMSB = uint8(6)
adc1DrefLSB = uint8(4)
adc2DrefAddr = uint8(0x5)
adc2DrefMSB = uint8(6)
adc2DrefLSB = uint8(4)
adc1EncalGndAddr = uint8(0x7)
adc1EncalGndMSB = uint8(5)
adc1EncalGndLSB = uint8(5)
adc2EncalGndAddr = uint8(0x7)
adc2EncalGndMSB = uint8(7)
adc2EncalGndLSB = uint8(7)
adc1InitCodeHighAddr = uint8(0x1)
adc1InitCodeHighMSB = uint8(3)
adc1InitCodeHighLSB = uint8(0)
adc1InitCodeLowAddr = uint8(0x0)
adc1InitCodeLowMSB = uint8(7)
adc1InitCodeLowLSB = uint8(0)
adc2InitCodeHighAddr = uint8(0x4)
adc2InitCodeHighMSB = uint8(3)
adc2InitCodeHighLSB = uint8(0)
adc2InitCodeLowAddr = uint8(0x3)
adc2InitCodeLowMSB = uint8(7)
adc2InitCodeLowLSB = uint8(0)
// adcCalOffsetRange is the binary search upper bound (12-bit full scale).
adcCalOffsetRange = uint32(4096)
// adcCalMaxIterations caps binary search iterations.
adcCalMaxIterations = 16
)
// LP_I2C_ANA_MST I2C0_CTRL bit-field shifts (see file header comment).
const (
c6SlaveIDShift = 0 // bits [7:0]
c6AddrShift = 8 // bits [15:8]
c6DataShift = 16 // bits [23:16]
c6WrCntlShift = 24 // bit [24]
c6BusyBit = uint32(1 << 25)
// c6SarI2CDeviceEn is BIT(7) in LP_I2C_ANA_MST.DEVICE_EN for I2C_SAR_ADC (0x69).
c6SarI2CDeviceEn = uint32(1 << 7)
)
// ANA_CONFIG / ANA_CONFIG2 register addresses and bits for the internal SAR I2C
// domain on ESP32-C6. These differ from C3's SENS block (0x6000_E044/048).
// Source: soc/esp32c6/include/soc/regi2c_defs.h
const (
c6AnaConfigReg = uintptr(0x600AF81C) // clear ANA_I2C_SAR_FORCE_PD (bit 18)
c6AnaConfig2Reg = uintptr(0x600AF820) // set ANA_I2C_SAR_FORCE_PU (bit 16)
c6SarForcePD = uint32(1 << 18)
c6SarForcePU = uint32(1 << 16)
)
// sarEnable powers up the internal SAR I2C domain and enables the LP_I2C_ANA_MST
// clock and SAR slave device before any regI2C access.
// Matches regi2c_ctrl_ll_i2c_saradc_enable() + regi2c_enable_block(REGI2C_SAR_I2C).
func (r regI2C) sarEnable() {
cfg := (*volatile.Register32)(unsafe.Pointer(c6AnaConfigReg))
cfg2 := (*volatile.Register32)(unsafe.Pointer(c6AnaConfig2Reg))
cfg.Set(cfg.Get() &^ c6SarForcePD)
cfg2.Set(cfg2.Get() | c6SarForcePU)
// Enable the LP_I2C_ANA_MST master clock (MODEM_LPCON.CLK_CONF bit 2).
esp.MODEM_LPCON.SetCLK_CONF_CLK_I2C_MST_EN(1)
// Enable the master's own clock gate (LP_I2C_ANA_MST.DATE bit 28).
esp.LP_I2C_ANA_MST.SetDATE_LP_I2C_ANA_MAST_I2C_MAT_CLK_EN(1)
// Enable the SAR ADC slave device (DEVICE_EN bit 7).
dev := esp.LP_I2C_ANA_MST.GetDEVICE_EN_LP_I2C_ANA_MAST_I2C_DEVICE_EN()
esp.LP_I2C_ANA_MST.SetDEVICE_EN_LP_I2C_ANA_MAST_I2C_DEVICE_EN(dev | c6SarI2CDeviceEn)
}
// writeMask implements the REGI2C_WRITE_MASK macro for ESP32-C6 via LP_I2C_ANA_MST.
// It reads the current byte at regAddr, updates the [msb:lsb] bitfield, and writes
// it back. Matches esp_rom_regi2c_write_mask() in esp_rom_regi2c_esp32c6.c.
func (r regI2C) writeMask(regAddr, msb, lsb, data uint8) {
ctrl := &esp.LP_I2C_ANA_MST.I2C0_CTRL
rdata := &esp.LP_I2C_ANA_MST.I2C0_DATA
// Issue a read command: slave_id | (reg_addr << 8), no WR_CNTL bit.
readCmd := (uint32(i2cSarADC) << c6SlaveIDShift) | (uint32(regAddr) << c6AddrShift)
volatile.StoreUint32(&ctrl.Reg, readCmd)
for volatile.LoadUint32(&ctrl.Reg)&c6BusyBit != 0 {
}
cur := volatile.LoadUint32(&rdata.Reg) & 0xFF
// Modify the [msb:lsb] bitfield.
mask := uint32(1<<(msb-lsb+1)-1) << lsb
cur &^= mask
cur |= uint32(data&(1<<(msb-lsb+1)-1)) << lsb
// Issue a write command: slave_id | (reg_addr<<8) | WR_CNTL | (data<<16).
writeCmd := (uint32(i2cSarADC) << c6SlaveIDShift) |
(uint32(regAddr) << c6AddrShift) |
(uint32(1) << c6WrCntlShift) |
((cur & 0xFF) << c6DataShift)
volatile.StoreUint32(&ctrl.Reg, writeCmd)
for volatile.LoadUint32(&ctrl.Reg)&c6BusyBit != 0 {
}
}
// calibrationInit sets the DREF reference for the selected ADC unit.
func (r regI2C) calibrationInit(adcN uint8) {
if adcN == 0 {
r.writeMask(adc1DrefAddr, adc1DrefMSB, adc1DrefLSB, r.drefInit)
} else {
r.writeMask(adc2DrefAddr, adc2DrefMSB, adc2DrefLSB, r.drefInit)
}
}
// calibrationPrepare enables ENCAL_GND so the ADC input is shorted to ground.
func (r regI2C) calibrationPrepare(adcN uint8) {
if adcN == 0 {
r.writeMask(adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 1)
} else {
r.writeMask(adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 1)
}
}
// calibrationFinish clears ENCAL_GND to reconnect the ADC input to the pad.
func (r regI2C) calibrationFinish(adcN uint8) {
if adcN == 0 {
r.writeMask(adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 0)
} else {
r.writeMask(adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 0)
}
}
// setCalibrationParam writes the INIT_CODE (offset trim) for the selected ADC unit.
func (r regI2C) setCalibrationParam(adcN uint8, param uint32) {
msb := uint8(param >> 8)
lsb := uint8(param & 0xFF)
if adcN == 0 {
r.writeMask(adc1InitCodeHighAddr, adc1InitCodeHighMSB, adc1InitCodeHighLSB, msb)
r.writeMask(adc1InitCodeLowAddr, adc1InitCodeLowMSB, adc1InitCodeLowLSB, lsb)
} else {
r.writeMask(adc2InitCodeHighAddr, adc2InitCodeHighMSB, adc2InitCodeHighLSB, msb)
r.writeMask(adc2InitCodeLowAddr, adc2InitCodeLowMSB, adc2InitCodeLowLSB, lsb)
}
}
// calibrateBinarySearch runs the ADC self-calibration binary search loop.
// It performs 'iterations' rounds, drops the min/max outliers, and returns
// the rounded mean of the remaining values. Matches adc_hal_self_calibration().
func (r regI2C) calibrateBinarySearch(adcN uint8, iterations int, readADC func() uint32) uint32 {
if iterations > adcCalMaxIterations {
iterations = adcCalMaxIterations
}
var codeList [adcCalMaxIterations]uint32
var codeSum uint32
for rpt := 0; rpt < iterations; rpt++ {
codeH := adcCalOffsetRange
codeL := uint32(0)
chkCode := (codeH + codeL) / 2
r.setCalibrationParam(adcN, chkCode)
selfCal := readADC()
for codeH-codeL > 1 {
if selfCal == 0 {
codeH = chkCode
} else {
codeL = chkCode
}
chkCode = (codeH + codeL) / 2
r.setCalibrationParam(adcN, chkCode)
selfCal = readADC()
if codeH-codeL == 1 {
chkCode++
r.setCalibrationParam(adcN, chkCode)
selfCal = readADC()
}
}
codeList[rpt] = chkCode
codeSum += chkCode
}
codeMin := codeList[0]
codeMax := codeList[0]
for i := 0; i < iterations; i++ {
if codeList[i] < codeMin {
codeMin = codeList[i]
}
if codeList[i] > codeMax {
codeMax = codeList[i]
}
}
remaining := codeSum - codeMax - codeMin
divisor := uint32(iterations - 2)
finalCode := remaining / divisor
if remaining%divisor >= 4 {
finalCode++
}
return finalCode
}
// ── Self-calibration ──────────────────────────────────────────────────────────
const (
adcCalTimesC6 = 15
adcCalRtcMagicC6 = uint32(0xADC1C601) // magic distinguishes C6 from C3
adcCalInitMinC6 = uint32(1000)
adcCalInitMaxC6 = uint32(4096)
)
// adcSelfCalibrate runs a self-calibration for ADC1 (the only ADC unit on C6).
// The calibration code is cached in LP_AON scratch registers to survive sleep.
// eFuse calibration is not used: the fields are often unprogrammed.
func adcSelfCalibrate() {
reg := newRegI2C()
reg.sarEnable()
var adc1Code uint32
if saved, ok := c6RestoreFromLP(); ok {
adc1Code = saved
} else {
c6CalSetupADC1()
reg.calibrationInit(0)
reg.calibrationPrepare(0)
adc1Code = reg.calibrateBinarySearch(0, adcCalTimesC6, readADC1)
if adc1Code < adcCalInitMinC6 {
adc1Code = adcCalInitMinC6
}
if adc1Code > adcCalInitMaxC6 {
adc1Code = adcCalInitMaxC6
}
c6SaveToLP(adc1Code)
reg.calibrationFinish(0)
}
c6ApplyADC1Code(reg, adc1Code)
}
// c6CalSetupADC1 configures APB_SARADC for oneshot ADC1 ch0 with fixed attenuation.
func c6CalSetupADC1() {
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1)
}
// readADC1 performs a single ADC1 conversion and returns the raw 12-bit result.
func readADC1() uint32 {
esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1)
for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 {
}
raw := esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA() & 0xfff
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
return uint32(raw)
}
// c6RestoreFromLP reads the saved calibration code from LP_AON scratch registers.
// On C6, LP_AON replaces the C3's RTC_CNTL for scratch storage.
func c6RestoreFromLP() (uint32, bool) {
if esp.LP_AON.GetSTORE0() != adcCalRtcMagicC6 {
return 0, false
}
code := esp.LP_AON.GetSTORE1()
if code < adcCalInitMinC6 || code > adcCalInitMaxC6 {
return 0, false
}
return code, true
}
// c6SaveToLP stores the calibration code in LP_AON scratch registers.
func c6SaveToLP(code uint32) {
if code < adcCalInitMinC6 || code > adcCalInitMaxC6 {
return
}
esp.LP_AON.SetSTORE0(adcCalRtcMagicC6)
esp.LP_AON.SetSTORE1(code)
}
// c6ApplyADC1Code sets ADC1 init code and finishes calibration.
// ESP32-C6 has no ADC2 so only ADC1 (adcN=0) needs to be configured.
func c6ApplyADC1Code(reg regI2C, code uint32) {
c6CalSetupADC1()
reg.calibrationInit(0)
reg.calibrationPrepare(0)
reg.setCalibrationParam(0, code)
reg.calibrationFinish(0)
}
+38
View File
@@ -470,3 +470,41 @@ func initADCClock() {
esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_DIV(1)
esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1)
}
// ReadTemperature reads the on-chip temperature sensor (TSENS) and returns
// a value in millicelsius (°C × 1000). Uses the default measurement range
// (offset = 0, approximately 10 °C to 80 °C, ±3 °C accuracy).
//
// The conversion uses the same formula as ESP-IDF with no eFuse calibration:
//
// T = (0.4386 × raw 20.52) °C
func ReadTemperature() int32 {
// Enable TSENS peripheral clock.
esp.SENS.SetSAR_PERI_CLK_GATE_CONF_TSENS_CLK_EN(1)
// Set clock divider to default (6).
esp.SENS.SetSAR_TSENS_CTRL_SAR_TSENS_CLK_DIV(6)
// Power up the temperature sensor.
esp.SENS.SetSAR_TSENS_CTRL_SAR_TSENS_POWER_UP_FORCE(1)
esp.SENS.SetSAR_TSENS_CTRL2_SAR_TSENS_XPD_FORCE(1)
esp.SENS.SetSAR_TSENS_CTRL_SAR_TSENS_POWER_UP(1)
// Trigger a conversion.
esp.SENS.SetSAR_TSENS_CTRL_SAR_TSENS_DUMP_OUT(1)
// Wait for data ready.
for esp.SENS.GetSAR_TSENS_CTRL_SAR_TSENS_READY() == 0 {
}
// Read the 8-bit raw value.
raw := int32(esp.SENS.GetSAR_TSENS_CTRL_SAR_TSENS_OUT())
// Stop the conversion.
esp.SENS.SetSAR_TSENS_CTRL_SAR_TSENS_DUMP_OUT(0)
// Convert to millicelsius using the ESP-IDF integer formula (offset=0):
// T_celsius = (4386 * raw - 205200) / 10000
// T_millicelsius = (4386 * raw - 205200) / 10
return (4386*raw - 205200) / 10
}
+6
View File
@@ -86,6 +86,12 @@ func (p Pin) PortMaskClear() (*uint32, uint32) {
return &port.BSRR.Reg, 1 << (pin + 16)
}
// EnterBootloader resets the chip into the bootloader.
// This is currently a stub for STM32, required to satisfy machine.EnterBootloader
// called by machine/usb/cdc.
func EnterBootloader() {
}
var deviceID [12]byte
// DeviceID returns an identifier that is unique within
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5
//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5 && !stm32u0
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5
//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx && !stm32g0 && !stm32u5 && !stm32u0
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx || stm32g0 || stm32u5
//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx || stm32g0 || stm32u0 || stm32u5
package machine
+828
View File
@@ -0,0 +1,828 @@
//go:build stm32f4 || stm32f7
package machine
import (
"device/stm32"
"machine/usb"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
// NumberOfUSBEndpoints is sized to cover TinyGo's full endpoint index space
// (0=control, 1=CDC ACM, 2=CDC OUT, 3=CDC IN, 4=HID IN, 5=HID OUT, 6=MIDI IN, 7=MIDI OUT).
// Physical OTG FS hardware has 4 IN + 4 OUT endpoints (03).
const NumberOfUSBEndpoints = 8
// Default USB identifiers; board files with USB support should override these.
const (
usb_VID = uint16(0x239A)
usb_PID = uint16(0x0001)
usb_STRING_MANUFACTURER = "TinyGo"
usb_STRING_PRODUCT = "STM32 USB Device"
)
// OTG FS register blocks.
var (
otgDevice = (*usbDeviceRegs)(unsafe.Pointer(stm32.OTG_FS_DEVICE))
otgPower = (*usbPowerRegs)(unsafe.Pointer(stm32.OTG_FS_PWRCLK))
)
// usbDeviceRegs represents the USB device-mode control block at base+0x800.
type usbDeviceRegs struct {
DCFG volatile.Register32 // 0x800
DCTL volatile.Register32 // 0x804
DSTS volatile.Register32 // 0x808
_ uint32 // 0x80C
DIEPMSK volatile.Register32 // 0x810
DOEPMSK volatile.Register32 // 0x814
DAINT volatile.Register32 // 0x818
DAINTMSK volatile.Register32 // 0x81C
_ [5]uint32 // 0x820 - 0x830
DIEPEMPMSK volatile.Register32 // 0x834
}
// usbInEndpointRegs represents the registers for a single IN endpoint at 0x900 + ep*0x20.
type usbInEndpointRegs struct {
CTL volatile.Register32 // 0x00
_ uint32 // 0x04
INT volatile.Register32 // 0x08
_ uint32 // 0x0C
TSIZ volatile.Register32 // 0x10
_ uint32 // 0x14
TXFST volatile.Register32 // 0x18
_ uint32 // 0x1C
}
// usbOutEndpointRegs represents the registers for a single OUT endpoint at 0xB00 + ep*0x20.
type usbOutEndpointRegs struct {
CTL volatile.Register32 // 0x00
_ uint32 // 0x04
INT volatile.Register32 // 0x08
_ uint32 // 0x0C
TSIZ volatile.Register32 // 0x10
_ [3]uint32 // 0x14 - 0x1C
}
// usbPowerRegs represents the power and clock gating block at base+0xE00.
type usbPowerRegs struct {
PCGCCTL volatile.Register32 // 0xE00
}
// otgInEP returns the IN endpoint registers for physical endpoint ep.
func otgInEP(ep uint32) *usbInEndpointRegs {
return (*usbInEndpointRegs)(unsafe.Pointer(uintptr(unsafe.Pointer(stm32.OTG_FS_GLOBAL)) + 0x900 + uintptr(ep)*0x20))
}
// otgOutEP returns the OUT endpoint registers for physical endpoint ep.
func otgOutEP(ep uint32) *usbOutEndpointRegs {
return (*usbOutEndpointRegs)(unsafe.Pointer(uintptr(unsafe.Pointer(stm32.OTG_FS_GLOBAL)) + 0xB00 + uintptr(ep)*0x20))
}
// otgDFIFO returns a volatile pointer to the data FIFO for physical endpoint ep.
// DFIFO[ep] is located at 0x50001000 + ep*0x1000.
func otgDFIFO(ep uint32) *volatile.Register32 {
return (*volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(stm32.OTG_FS_GLOBAL)) + 0x1000 + uintptr(ep)*0x1000))
}
// DCFG bit positions and masks.
const (
dcfgDSPD = uint32(0x3) // FS PHY speed (bits [1:0] = 0b11)
dcfgDAD_Pos = uint32(4) // device address field start bit
dcfgDAD_Msk = uint32(0x7F << 4)
)
// DCTL bits.
const (
dctlSDIS = uint32(1 << 1) // soft disconnect
dctlSGINAK = uint32(1 << 7) // set global IN NAK
dctlCGINAK = uint32(1 << 8) // clear global IN NAK
dctlSGONAK = uint32(1 << 9) // set global OUT NAK
dctlCGONAK = uint32(1 << 10) // clear global OUT NAK
)
// DSTS bits.
const (
dstsSUSPSTS = uint32(1 << 0)
dstsENUMSPD_Pos = uint32(1)
dstsENUMSPD_Msk = uint32(0x3 << 1)
)
// DIEPCTLn / DOEPCTLn bits (shared between IN and OUT endpoint control registers).
const (
depctlEPENA = uint32(1 << 31) // endpoint enable
depctlEPDIS = uint32(1 << 30) // endpoint disable
depctlSD0PID = uint32(1 << 28) // set DATA0 PID
depctlSNAK = uint32(1 << 27) // set NAK
depctlCNAK = uint32(1 << 26) // clear NAK
depctlSTALL = uint32(1 << 21) // STALL handshake
depctlETYP_Pos = uint32(18) // endpoint type field start
depctlUSBAEP = uint32(1 << 15) // USB active endpoint
depctlTXFNUM_Pos = uint32(22) // TX FIFO number field start (IN eps only)
depctlMPSIZ_Pos = uint32(0) // max packet size field start
)
// EP0 max packet size encoding in DIEPCTL0 / DOEPCTL0 bits [1:0].
const ep0Mps64 = uint32(0x0) // 64 bytes (FS default)
// DIEPINTn / DOEPINTn bits.
const (
depintXFRC = uint32(1 << 0) // transfer complete
depintSTUP = uint32(1 << 3) // SETUP phase done (DOEPINTn)
)
// GRXSTSP_Device PKTSTS field values.
const (
rxPktstsGNAK = uint32(0x1) // global OUT NAK
rxPktstsOUTData = uint32(0x2) // OUT data packet received
rxPktstsOUTDone = uint32(0x3) // OUT transfer complete
rxPktstsSetupDone = uint32(0x4) // SETUP transaction complete
rxPktstsSetupData = uint32(0x6) // SETUP data packet received (always 8 bytes)
)
// GINTSTS / GINTMSK bits (values taken from stm32f405 SVD constants).
const (
gintRXFLVL = uint32(stm32.USB_OTG_FS_GINTSTS_RXFLVL) // 0x10
gintUSBRST = uint32(stm32.USB_OTG_FS_GINTSTS_USBRST) // 0x1000
gintENUMDNE = uint32(stm32.USB_OTG_FS_GINTSTS_ENUMDNE) // 0x2000
gintUSBSUSP = uint32(stm32.USB_OTG_FS_GINTSTS_USBSUSP) // 0x800
gintWKUPINT = uint32(stm32.USB_OTG_FS_GINTSTS_WKUPINT) // 0x80000000
gintIEPINT = uint32(stm32.USB_OTG_FS_GINTSTS_IEPINT) // 0x40000
gintOEPINT = uint32(stm32.USB_OTG_FS_GINTSTS_OEPINT) // 0x80000
)
// GRSTCTL bits.
const (
grstCSRST = uint32(stm32.USB_OTG_FS_GRSTCTL_CSRST) // core soft reset
grstRXFFLSH = uint32(stm32.USB_OTG_FS_GRSTCTL_RXFFLSH) // RX FIFO flush
grstTXFFLSH = uint32(stm32.USB_OTG_FS_GRSTCTL_TXFFLSH) // TX FIFO flush
grstTXFNUM_Pos = uint32(stm32.USB_OTG_FS_GRSTCTL_TXFNUM_Pos)
grstAHBIDL = uint32(stm32.USB_OTG_FS_GRSTCTL_AHBIDL) // AHB master idle
)
// FIFO size layout in 32-bit words (total budget = 320 words).
const (
rxFIFODepth = uint32(128) // shared RX FIFO
ep0TxFIFODepth = uint32(16) // EP0 TX FIFO
ep1TxFIFODepth = uint32(64) // EP1 TX FIFO
ep2TxFIFODepth = uint32(64) // EP2 TX FIFO
ep3TxFIFODepth = uint32(48) // EP3 TX FIFO
ep0TxFIFOStart = rxFIFODepth
ep1TxFIFOStart = ep0TxFIFOStart + ep0TxFIFODepth
ep2TxFIFOStart = ep1TxFIFOStart + ep1TxFIFODepth
ep3TxFIFOStart = ep2TxFIFOStart + ep2TxFIFODepth
)
// Driver state.
var (
// sendOnEP0DATADONE tracks multi-chunk EP0 IN transfers.
sendOnEP0DATADONE struct {
ptr *byte
count int
offset int
}
// usbSetupBuf holds the 8-byte SETUP packet from the RX FIFO.
usbSetupBuf [8]byte
// usbRxBufLen tracks the byte count of the most recently received OUT packet
// per physical endpoint (index 03).
usbRxBufLen [4]uint32
)
// Configure initialises the OTG FS USB peripheral in device mode.
// The config parameter is unused (present for interface compatibility).
func (dev *USBDevice) Configure(config UARTConfig) {
if dev.initcomplete {
return
}
// ---- 1. Enable peripheral clocks ----------------------------------------
// GPIOA clock (PA11 = D-, PA12 = D+)
stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOAEN)
// OTG FS peripheral clock
stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_OTGFSEN)
// ---- 2. Configure GPIO pins (PA11 D-, PA12 D+) as AF, very high speed ----
for _, pin := range [2]Pin{PA11, PA12} {
pos := uint8(pin%16) * 2
port := pin.getPort()
port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos)
port.OSPEEDR.ReplaceBits(gpioOutputSpeedVeryHigh, gpioOutputSpeedMask, pos)
port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos)
// OTYPER remains 0 (push-pull)
pin.SetAltFunc(10) // AF10 = OTG FS on both F4 and F7
}
// ---- 3. OTG core reset --------------------------------------------------
// Wait for AHB master idle before core reset.
for stm32.OTG_FS_GLOBAL.GRSTCTL.Get()&grstAHBIDL == 0 {
}
// Core soft reset
stm32.OTG_FS_GLOBAL.GRSTCTL.SetBits(grstCSRST)
for stm32.OTG_FS_GLOBAL.GRSTCTL.HasBits(grstCSRST) {
}
// Wait for AHB idle again after reset
for stm32.OTG_FS_GLOBAL.GRSTCTL.Get()&grstAHBIDL == 0 {
}
// ---- 4. Force device mode, set turnaround time --------------------------
gusbcfg := stm32.OTG_FS_GLOBAL.GUSBCFG.Get()
gusbcfg &^= stm32.USB_OTG_FS_GUSBCFG_FHMOD |
stm32.USB_OTG_FS_GUSBCFG_FDMOD |
stm32.USB_OTG_FS_GUSBCFG_TRDT_Msk
gusbcfg |= stm32.USB_OTG_FS_GUSBCFG_FDMOD |
(9 << stm32.USB_OTG_FS_GUSBCFG_TRDT_Pos) // turnaround time = 9 for 216MHz HCLK
stm32.OTG_FS_GLOBAL.GUSBCFG.Set(gusbcfg)
// ---- 5. PHY / VBUS configuration (platform-specific) --------------------
initOTGFSPHY()
// ---- 6. Enable PHY clock, soft-disconnect before further init -----------
// Clear stop-clock / stop-phy-clock bits so the PHY clock runs.
// If a bootloader left these set, USB would hang silently.
otgPower.PCGCCTL.Set(0)
// Soft-disconnect now (after CSRST reset DCTL to its default connected state).
otgDevice.DCTL.SetBits(dctlSDIS)
// ---- 7. Configure data FIFOs --------------------------------------------
// RX FIFO (shared for all OUT + SETUP packets)
stm32.OTG_FS_GLOBAL.GRXFSIZ.Set(rxFIFODepth)
// EP0 TX FIFO: start = rxFIFODepth, depth = ep0TxFIFODepth
stm32.OTG_FS_GLOBAL.DIEPTXF0.Set(
(ep0TxFIFODepth << 16) | ep0TxFIFOStart,
)
// EP13 TX FIFOs
stm32.OTG_FS_GLOBAL.DIEPTXF1.Set(
(ep1TxFIFODepth << 16) | ep1TxFIFOStart,
)
stm32.OTG_FS_GLOBAL.DIEPTXF2.Set(
(ep2TxFIFODepth << 16) | ep2TxFIFOStart,
)
stm32.OTG_FS_GLOBAL.DIEPTXF3.Set(
(ep3TxFIFODepth << 16) | ep3TxFIFOStart,
)
// ---- 8. Flush FIFOs ----------------------------------------------------
flushRxFIFO()
flushTxFIFO(0x10) // flush all TX FIFOs (TXFNUM = 0x10 = all)
// ---- 9. Configure device: full-speed, no SOF output --------------------
otgDevice.DCFG.Set(dcfgDSPD) // FS PHY speed
// Clear any stale interrupts
stm32.OTG_FS_GLOBAL.GINTSTS.Set(0xFFFFFFFF)
// ---- 10. Enable interrupts ----------------------------------------------
stm32.OTG_FS_GLOBAL.GINTMSK.Set(
gintUSBRST | gintENUMDNE | gintRXFLVL | gintIEPINT | gintOEPINT |
gintUSBSUSP | gintWKUPINT,
)
// Enable device-level IN and OUT endpoint interrupt masks
otgDevice.DIEPMSK.Set(depintXFRC)
otgDevice.DOEPMSK.Set(depintXFRC | depintSTUP)
// Enable global interrupt
stm32.OTG_FS_GLOBAL.GAHBCFG.SetBits(stm32.USB_OTG_FS_GAHBCFG_GINT)
// ---- 11. Register and enable NVIC interrupt -----------------------------
intr := interrupt.New(stm32.IRQ_OTG_FS, handleUSBIRQ)
intr.SetPriority(0) // Highest priority
intr.Enable()
// ---- 12. Connect to host (clear soft-disconnect) -----------------------
otgDevice.DCTL.ClearBits(dctlSDIS)
dev.initcomplete = true
}
// handleUSBIRQ is the OTG FS interrupt handler, dispatching on GINTSTS bits.
func handleUSBIRQ(intr interrupt.Interrupt) {
status := stm32.OTG_FS_GLOBAL.GINTSTS.Get() &
stm32.OTG_FS_GLOBAL.GINTMSK.Get()
if status&gintUSBSUSP != 0 {
stm32.OTG_FS_GLOBAL.GINTSTS.Set(gintUSBSUSP)
// Stop PHY clock during suspend. Only STPPCLK (bit 0); do NOT set
// GATEHCLK (bit 1) — that gates the AHB bus, which prevents the ISR
// from reading GINTSTS when WKUPINT fires.
otgPower.PCGCCTL.SetBits(1) // STPPCLK
}
if status&gintWKUPINT != 0 {
stm32.OTG_FS_GLOBAL.GINTSTS.Set(gintWKUPINT)
// Restart clocks before any endpoint activity can resume.
otgPower.PCGCCTL.ClearBits(1 | 2)
}
if status&gintUSBRST != 0 {
stm32.OTG_FS_GLOBAL.GINTSTS.Set(gintUSBRST)
otgPower.PCGCCTL.ClearBits(1 | 2) // ensure clocks running after reset-from-suspend
handleUSBReset()
}
if status&gintRXFLVL != 0 {
// RXFLVL is level-triggered; drain entire FIFO in a loop.
stm32.OTG_FS_GLOBAL.GINTMSK.ClearBits(gintRXFLVL)
handleRxFIFO()
stm32.OTG_FS_GLOBAL.GINTMSK.SetBits(gintRXFLVL)
}
if status&gintENUMDNE != 0 {
stm32.OTG_FS_GLOBAL.GINTSTS.Set(gintENUMDNE)
handleEnumDone()
}
if status&gintIEPINT != 0 {
handleInEndpoints()
}
if status&gintOEPINT != 0 {
handleOutEndpoints()
}
}
// handleUSBReset is called on USB bus reset (USBRST interrupt).
func handleUSBReset() {
// Set NAK on all OUT endpoints.
for ep := uint32(0); ep < 4; ep++ {
otgOutEP(ep).CTL.SetBits(depctlSNAK)
}
// Flush RX and all TX FIFOs.
flushRxFIFO()
flushTxFIFO(0x10)
// Clear all endpoint interrupts.
otgDevice.DAINT.Set(0xFFFFFFFF)
otgDevice.DAINTMSK.Set(0)
// Enable EP0 IN and OUT interrupt sources.
otgDevice.DAINTMSK.Set((1 << 0) | (1 << 16)) // DIEP0 + DOEP0
// Re-arm EP0 OUT for up to 3 back-to-back SETUP packets.
armEP0Out()
// Signal upper layer: device is no longer configured.
usbConfiguration = 0
USBDev.InitEndpointComplete = false
}
// handleEnumDone is called after USB enumeration speed is detected (ENUMDNE).
func handleEnumDone() {
// Activate EP0 (max packet 64, type control, TX FIFO 0).
ep0Ctl := ep0Mps64 | depctlUSBAEP | (0 << depctlETYP_Pos) // control type
otgInEP(0).CTL.SetBits(ep0Ctl)
otgOutEP(0).CTL.SetBits(ep0Mps64 | depctlUSBAEP)
// Clear global IN NAK so EP0 IN can send.
otgDevice.DCTL.SetBits(dctlCGINAK)
}
// handleRxFIFO drains the RX FIFO completely, processing each pop via GRXSTSP.
// RXFLVL is level-triggered, so this must loop until the FIFO is empty.
func handleRxFIFO() {
for stm32.OTG_FS_GLOBAL.GINTSTS.HasBits(gintRXFLVL) {
status := stm32.OTG_FS_GLOBAL.GRXSTSP_Device.Get()
ep := status & stm32.USB_OTG_FS_GRXSTSP_Device_EPNUM_Msk
bcnt := (status & stm32.USB_OTG_FS_GRXSTSP_Device_BCNT_Msk) >>
stm32.USB_OTG_FS_GRXSTSP_Device_BCNT_Pos
pktsts := (status >> stm32.USB_OTG_FS_GRXSTSP_Device_PKTSTS_Pos) & 0xF
pep := ep // GRXSTSP.EPNUM is already a physical endpoint (03)
switch pktsts {
case rxPktstsSetupData:
// 8-byte SETUP packet: read exactly 2 words from DFIFO[0].
w0 := otgDFIFO(0).Get()
w1 := otgDFIFO(0).Get()
usbSetupBuf[0] = byte(w0)
usbSetupBuf[1] = byte(w0 >> 8)
usbSetupBuf[2] = byte(w0 >> 16)
usbSetupBuf[3] = byte(w0 >> 24)
usbSetupBuf[4] = byte(w1)
usbSetupBuf[5] = byte(w1 >> 8)
usbSetupBuf[6] = byte(w1 >> 16)
usbSetupBuf[7] = byte(w1 >> 24)
case rxPktstsSetupDone:
// SETUP transaction complete: process the buffered SETUP packet.
setup := usb.Setup{
BmRequestType: usbSetupBuf[0],
BRequest: usbSetupBuf[1],
WValueL: usbSetupBuf[2],
WValueH: usbSetupBuf[3],
WIndex: uint16(usbSetupBuf[4]) | (uint16(usbSetupBuf[5]) << 8),
WLength: uint16(usbSetupBuf[6]) | (uint16(usbSetupBuf[7]) << 8),
}
ok := false
if setup.BmRequestType&usb.REQUEST_TYPE == usb.REQUEST_STANDARD {
ok = handleStandardSetup(setup)
} else {
if setup.WIndex < uint16(len(usbSetupHandler)) &&
usbSetupHandler[setup.WIndex] != nil {
ok = usbSetupHandler[setup.WIndex](setup)
}
}
if !ok {
// Stall EP0 IN and OUT on unrecognised requests.
otgInEP(0).CTL.SetBits(depctlSTALL)
otgOutEP(0).CTL.SetBits(depctlSTALL)
}
// Re-arm EP0 OUT for the next SETUP.
armEP0Out()
case rxPktstsOUTData:
// OUT data: read bcnt bytes from DFIFO[pep] into cache buffer.
if bcnt > 0 && pep < 4 {
readFIFO(pep, bcnt)
usbRxBufLen[pep] = bcnt
}
case rxPktstsOUTDone:
// OUT transfer complete: nothing to do here; handled in handleOutEndpoints.
}
}
}
// readFIFO reads bcnt bytes from the shared RX FIFO (DFIFO 0) into udd_ep_out_cache_buffer[ep].
func readFIFO(ep, bcnt uint32) {
buf := udd_ep_out_cache_buffer[ep][:]
words := (bcnt + 3) / 4
for i := uint32(0); i < words; i++ {
w := otgDFIFO(0).Get() // Always read from FIFO 0
b := i * 4
buf[b] = byte(w)
if b+1 < bcnt {
buf[b+1] = byte(w >> 8)
}
if b+2 < bcnt {
buf[b+2] = byte(w >> 16)
}
if b+3 < bcnt {
buf[b+3] = byte(w >> 24)
}
}
}
// handleInEndpoints handles IEPINT: checks each active IN endpoint for XFRC.
func handleInEndpoints() {
daint := otgDevice.DAINT.Get() & 0x0000FFFF // lower 16 bits = IN EPs
daintmsk := otgDevice.DAINTMSK.Get() & 0x0000FFFF
active := daint & daintmsk
for ep := uint32(0); ep < 4; ep++ {
if active&(1<<ep) == 0 {
continue
}
diep := otgInEP(ep)
diepint := diep.INT.Get()
diepintmsk := otgDevice.DIEPMSK.Get()
fired := diepint & diepintmsk
if fired&depintXFRC != 0 {
// Clear XFRC.
diep.INT.Set(depintXFRC)
if ep == 0 {
// EP0 IN transfer complete.
if sendOnEP0DATADONE.ptr != nil {
// More data to send.
ptr := sendOnEP0DATADONE.ptr
count := sendOnEP0DATADONE.count
if count > usb.EndpointPacketSize {
sendOnEP0DATADONE.offset += usb.EndpointPacketSize
sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset]
count = usb.EndpointPacketSize
}
sendOnEP0DATADONE.count -= count
sendViaEPIn(0, ptr, count)
if sendOnEP0DATADONE.count == 0 {
sendOnEP0DATADONE.ptr = nil
sendOnEP0DATADONE.offset = 0
}
} else {
// All EP0 IN data sent; arm EP0 OUT for the status ZLP from host.
armEP0Out()
}
} else {
// Non-EP0 IN: find the virtual endpoint(s) mapped to this physical EP
// and call the registered TX handler. Multiple virtual EPs may share
// a physical EP (e.g., HID_IN=4 and CDC_IN=3 both → physical 1 or 3).
for vep := uint32(0); vep < NumberOfUSBEndpoints; vep++ {
if vep == ep && usbTxHandler[vep] != nil {
usbTxHandler[vep]()
}
}
}
}
}
}
// handleOutEndpoints handles OEPINT: checks each active OUT endpoint for STUP / XFRC.
func handleOutEndpoints() {
daint := otgDevice.DAINT.Get() >> 16 // upper 16 bits = OUT EPs
daintmsk := otgDevice.DAINTMSK.Get() >> 16
active := daint & daintmsk
for ep := uint32(0); ep < 4; ep++ {
if active&(1<<ep) == 0 {
continue
}
doep := otgOutEP(ep)
doepint := doep.INT.Get()
doepintmsk := otgDevice.DOEPMSK.Get()
fired := doepint & doepintmsk
if fired&depintSTUP != 0 {
// EP0 SETUP phase done (already processed in handleRxFIFO).
doep.INT.Set(depintSTUP)
}
if fired&depintXFRC != 0 {
doep.INT.Set(depintXFRC)
if ep > 0 {
buf := handleEndpointRx(ep)
// Find the virtual endpoint(s) mapped to this physical EP and call the RX handler.
for vep := uint32(0); vep < NumberOfUSBEndpoints; vep++ {
if vep == ep && usbRxHandler[vep] != nil {
if usbRxHandler[vep](buf) {
AckUsbOutTransfer(ep)
}
break
}
}
}
}
}
}
// initEndpoint configures a USB endpoint for the given type and direction.
// MPS is hardcoded to 64 bytes; the caller (usb.go) does not pass a descriptor.
func initEndpoint(ep, config uint32) {
pep := ep
if pep == 0 {
return // EP0 is always active; configured in handleEnumDone
}
txFIFONum := pep // TX FIFO number matches physical EP
switch config {
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
ctl := (64 << depctlMPSIZ_Pos) | depctlUSBAEP |
(txFIFONum << depctlTXFNUM_Pos) |
(3 << depctlETYP_Pos) | // interrupt type
depctlSD0PID
otgInEP(pep).CTL.Set(ctl)
otgDevice.DAINTMSK.SetBits(1 << pep)
case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn:
ctl := (64 << depctlMPSIZ_Pos) | depctlUSBAEP |
(txFIFONum << depctlTXFNUM_Pos) |
(2 << depctlETYP_Pos) | // bulk type
depctlSD0PID
otgInEP(pep).CTL.Set(ctl)
otgDevice.DAINTMSK.SetBits(1 << pep)
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut:
ctl := uint32(64) | depctlUSBAEP | depctlSD0PID |
(3 << depctlETYP_Pos) // interrupt type
otgOutEP(pep).CTL.Set(ctl)
otgOutEP(pep).TSIZ.Set((1 << 19) | 64)
otgOutEP(pep).CTL.SetBits(depctlEPENA | depctlCNAK)
otgDevice.DAINTMSK.SetBits(1 << (pep + 16))
case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut:
ctl := uint32(64) | depctlUSBAEP | depctlSD0PID |
(2 << depctlETYP_Pos) // bulk type
otgOutEP(pep).CTL.Set(ctl)
otgOutEP(pep).TSIZ.Set((1 << 19) | 64)
otgOutEP(pep).CTL.SetBits(depctlEPENA | depctlCNAK)
otgDevice.DAINTMSK.SetBits(1 << (pep + 16))
case usb.ENDPOINT_TYPE_CONTROL:
// EP0 activated in handleEnumDone.
}
}
// SendUSBInPacket sends data on a USB IN endpoint (interrupt or bulk).
func SendUSBInPacket(ep uint32, data []byte) bool {
sendUSBPacket(ep, data)
return true
}
// sendUSBPacket copies data into the endpoint cache buffer then initiates the transfer.
//
//go:noinline
func sendUSBPacket(ep uint32, data []byte) {
count := len(data)
var buf []byte
if ep == 0 {
buf = udd_ep_control_cache_buffer[:]
if count > usb.EndpointPacketSize {
// Large response: queue continuation via sendOnEP0DATADONE.
sendOnEP0DATADONE.offset = usb.EndpointPacketSize
sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[usb.EndpointPacketSize]
sendOnEP0DATADONE.count = count - usb.EndpointPacketSize
count = usb.EndpointPacketSize
}
} else {
pep := ep
buf = udd_ep_in_cache_buffer[pep][:]
}
copy(buf[:len(data)], data)
sendViaEPIn(ep, &buf[0], count)
}
// sendViaEPIn arms the IN endpoint and writes count bytes from ptr into the TX FIFO.
func sendViaEPIn(ep uint32, ptr *byte, count int) {
pep := ep
diep := otgInEP(pep)
// Verify TX FIFO has enough space before writing.
// DTXFSTS[15:0] = INEPTFSAV: available words. Stall if insufficient.
if count > 0 {
need := uint32((count + 3) / 4)
avail := diep.TXFST.Get() & 0xFFFF
if avail < need {
return
}
}
// Program transfer size: 1 packet, count bytes.
diep.TSIZ.Set(
uint32(count) | (1 << 19), // XFRSIZ = count, PKTCNT = 1
)
// Enable endpoint and clear NAK (starts transfer).
diep.CTL.SetBits(depctlEPENA | depctlCNAK)
// Write bytes to FIFO in 32-bit words (last word padded if needed).
fifo := otgDFIFO(pep)
data := unsafe.Slice(ptr, count)
words := (count + 3) / 4
for i := 0; i < words; i++ {
b := i * 4
var w uint32
w = uint32(data[b])
if b+1 < count {
w |= uint32(data[b+1]) << 8
}
if b+2 < count {
w |= uint32(data[b+2]) << 16
}
if b+3 < count {
w |= uint32(data[b+3]) << 24
}
fifo.Set(w)
}
}
// SendZlp sends a zero-length packet on EP0 IN (status stage for OUT control transfers).
func SendZlp() {
// PKTCNT=1, XFRSIZ=0
otgInEP(0).TSIZ.Set(1 << 19)
otgInEP(0).CTL.SetBits(depctlEPENA | depctlCNAK)
}
// handleEndpointRx returns the bytes received on the given physical endpoint.
func handleEndpointRx(ep uint32) []byte {
pep := ep
return udd_ep_out_cache_buffer[pep][:usbRxBufLen[pep]]
}
// AckUsbOutTransfer re-arms the OUT endpoint to receive the next packet.
func AckUsbOutTransfer(ep uint32) {
pep := ep
usbRxBufLen[pep] = 0
doep := otgOutEP(pep)
doep.TSIZ.Set(
(1 << 19) | 64, // PKTCNT=1, XFRSIZ=64
)
doep.CTL.SetBits(depctlEPENA | depctlCNAK)
}
// handleUSBSetAddress applies the new device address from a SET_ADDRESS request
// and sends the status ZLP. The address is written to DCFG before the ZLP is
// enqueued: the OTG FS core has already committed the current IN token to
// address 0, so the ZLP goes out at the old address while the new address is
// already in DCFG and ready for the host's next transaction.
func handleUSBSetAddress(setup usb.Setup) bool {
addr := uint8(setup.WValueL) & 0x7F
dcfg := otgDevice.DCFG.Get()
dcfg &^= dcfgDAD_Msk
dcfg |= uint32(addr) << dcfgDAD_Pos
otgDevice.DCFG.Set(dcfg)
SendZlp()
return true
}
// ReceiveUSBControlPacket synchronously receives a CDC control OUT packet on EP0.
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
var b [cdcLineInfoSize]byte
// Arm EP0 OUT for up to 64 bytes.
armEP0Out()
// Busy-wait for data to arrive. We call handleRxFIFO() manually to drain
// the shared RX FIFO because we are currently in an interrupt context
// (this is called from the setup handler) and the hardware-triggered
// handleRxFIFO loop is blocked waiting for us to return.
const timeout = 300000
for i := 0; i < timeout; i++ {
if stm32.OTG_FS_GLOBAL.GINTSTS.HasBits(gintRXFLVL) {
handleRxFIFO()
}
if usbRxBufLen[0] > 0 {
n := usbRxBufLen[0]
if n > cdcLineInfoSize {
n = cdcLineInfoSize
}
copy(b[:n], udd_ep_out_cache_buffer[0][:n])
usbRxBufLen[0] = 0
SendZlp()
return b, nil
}
}
return b, ErrUSBReadTimeout
}
// SetStallEPIn stalls an IN endpoint.
func (dev *USBDevice) SetStallEPIn(ep uint32) {
pep := ep
otgInEP(pep).CTL.SetBits(depctlSTALL)
}
// ClearStallEPIn clears the stall condition on an IN endpoint.
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
pep := ep
// Clear STALL and reset DATA0 PID.
ctl := &otgInEP(pep).CTL
ctl.ClearBits(depctlSTALL)
ctl.SetBits(depctlSD0PID)
}
// SetStallEPOut stalls an OUT endpoint.
func (dev *USBDevice) SetStallEPOut(ep uint32) {
pep := ep
otgOutEP(pep).CTL.SetBits(depctlSTALL)
}
// ClearStallEPOut clears the stall condition on an OUT endpoint.
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
pep := ep
ctl := &otgOutEP(pep).CTL
ctl.ClearBits(depctlSTALL)
ctl.SetBits(depctlSD0PID)
}
// armEP0Out re-arms EP0 OUT to receive the next SETUP or status ZLP from the host.
func armEP0Out() {
// STUPCNT=3 (bits[30:29]=11): accept up to 3 back-to-back SETUPs.
// PKTCNT=1 (bit[19]): one packet.
// XFRSIZ=64 (bits[6:0]): max 64 bytes.
otgOutEP(0).TSIZ.Set((3 << 29) | (1 << 19) | 64)
otgOutEP(0).CTL.SetBits(depctlEPENA | depctlCNAK)
}
// flushTxFIFO flushes the selected TX FIFO(s).
// txfnum: 03 for a specific FIFO, 0x10 to flush all TX FIFOs.
func flushTxFIFO(txfnum uint32) {
stm32.OTG_FS_GLOBAL.GRSTCTL.Set(
grstTXFFLSH | (txfnum << grstTXFNUM_Pos),
)
for stm32.OTG_FS_GLOBAL.GRSTCTL.HasBits(grstTXFFLSH) {
}
}
// flushRxFIFO flushes the shared RX FIFO.
func flushRxFIFO() {
stm32.OTG_FS_GLOBAL.GRSTCTL.Set(grstRXFFLSH)
for stm32.OTG_FS_GLOBAL.GRSTCTL.HasBits(grstRXFFLSH) {
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32 && !(stm32f103 || stm32l0x1 || stm32g0)
//go:build stm32 && !(stm32f103 || stm32l0x1 || stm32g0 || stm32u0)
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 && !stm32u5
//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 && !stm32u5 && !stm32u0
package machine
@@ -0,0 +1,12 @@
//go:build stm32f4 && !(stm32f429 || stm32f427 || stm32f411 || stm32f407 || stm32f405 || stm32f401)
package machine
import "device/stm32"
// initOTGFSPHY enables the STM32F4 OTG FS PHY and bypasses VBUS sensing.
// GCCFG.PWRDWN deactivates the PHY power-down; NOVBUSSENS skips the VBUS pin
// check so boards without PA9 connected to VBUS still enumerate.
func initOTGFSPHY() {
stm32.OTG_FS_GLOBAL.GCCFG.Set(stm32.USB_OTG_FS_GCCFG_PWRDWN)
}
+15
View File
@@ -0,0 +1,15 @@
//go:build stm32f4 && (stm32f429 || stm32f427 || stm32f411 || stm32f407 || stm32f405 || stm32f401)
package machine
import "device/stm32"
// initOTGFSPHY enables the STM32F4 OTG FS PHY and bypasses VBUS sensing.
// GCCFG.PWRDWN deactivates the PHY power-down; NOVBUSSENS skips the VBUS pin
// check so boards without PA9 connected to VBUS still enumerate.
func initOTGFSPHY() {
stm32.OTG_FS_GLOBAL.GCCFG.Set(
stm32.USB_OTG_FS_GCCFG_PWRDWN | // enable FS PHY
stm32.USB_OTG_FS_GCCFG_NOVBUSSENS, // bypass VBUS sensing
)
}
+10
View File
@@ -0,0 +1,10 @@
//go:build stm32f7
package machine
// eraseBlockSize returns the smallest erasable unit for the STM32F7 internal
// flash. The first sectors are 32 KB; return that as the nominal page size.
// Flash write/erase via machine.Flash is not implemented for STM32F7; this
// stub satisfies the flash.go interface so that BlockDevice (needed by MSC)
// compiles on this target.
func eraseBlockSize() int64 { return 32768 }
+34
View File
@@ -0,0 +1,34 @@
//go:build stm32f7
package machine
import (
"device/arm"
"device/stm32"
)
// initOTGFSPHY enables the STM32F7 OTG FS PHY and overrides B-session VBUS
// detection via GOTGCTL so boards without VBUS sensing still enumerate.
func initOTGFSPHY() {
// Enable USB voltage regulator (specific to F72x/F73x).
// Bit 14 in PWR_CR2 is USBREGEN.
stm32.PWR.CR2.SetBits(1 << 14)
// Stabilization delay for the regulator (~100us is plenty).
for i := 0; i < 10000; i++ {
arm.Asm("nop")
}
// Enable FS PHY.
stm32.OTG_FS_GLOBAL.GCCFG.SetBits(stm32.USB_OTG_FS_GCCFG_PWRDWN)
// Disable hardware VBUS detection (F7 uses VBDEN, opposite polarity to F4's NOVBUSSENS).
// Clearing this prevents the peripheral from gating enumeration on PA9 VBUS level.
stm32.OTG_FS_GLOBAL.GCCFG.ClearBits(stm32.USB_OTG_FS_GCCFG_VBDEN)
// Override B-session valid so GOTGCTL-based detection reports device connected.
stm32.OTG_FS_GLOBAL.GOTGCTL.SetBits(
stm32.USB_OTG_FS_GOTGCTL_BVALOEN | // enable B-valid override
stm32.USB_OTG_FS_GOTGCTL_BVALOVAL, // set B-valid = 1
)
}
+351
View File
@@ -0,0 +1,351 @@
//go:build stm32u031
package machine
import (
"device/stm32"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
var deviceIDAddr = []uintptr{0x1FFF3E50, 0x1FFF3E54, 0x1FFF3E58}
// Pin constants for all stm32u0 package sizes
const (
PA0 = portA + 0
PA1 = portA + 1
PA2 = portA + 2
PA3 = portA + 3
PA4 = portA + 4
PA5 = portA + 5
PA6 = portA + 6
PA7 = portA + 7
PA8 = portA + 8
PA9 = portA + 9
PA10 = portA + 10
PA11 = portA + 11
PA12 = portA + 12
PA13 = portA + 13
PA14 = portA + 14
PA15 = portA + 15
PB0 = portB + 0
PB1 = portB + 1
PB2 = portB + 2
PB3 = portB + 3
PB4 = portB + 4
PB5 = portB + 5
PB6 = portB + 6
PB7 = portB + 7
PB8 = portB + 8
PB9 = portB + 9
PB10 = portB + 10
PB11 = portB + 11
PB12 = portB + 12
PB13 = portB + 13
PB14 = portB + 14
PB15 = portB + 15
PC0 = portC + 0
PC1 = portC + 1
PC2 = portC + 2
PC3 = portC + 3
PC4 = portC + 4
PC5 = portC + 5
PC6 = portC + 6
PC7 = portC + 7
PC8 = portC + 8
PC9 = portC + 9
PC10 = portC + 10
PC11 = portC + 11
PC12 = portC + 12
PC13 = portC + 13
PC14 = portC + 14
PC15 = portC + 15
PD0 = portD + 0
PD1 = portD + 1
PD2 = portD + 2
PD3 = portD + 3
PD4 = portD + 4
PD5 = portD + 5
PD6 = portD + 6
PD7 = portD + 7
PD8 = portD + 8
PD9 = portD + 9
PD10 = portD + 10
PD11 = portD + 11
PD12 = portD + 12
PD13 = portD + 13
PD14 = portD + 14
PD15 = portD + 15
PE0 = portE + 0
PE1 = portE + 1
PE2 = portE + 2
PE3 = portE + 3
PE4 = portE + 4
PE5 = portE + 5
PE6 = portE + 6
PE7 = portE + 7
PE8 = portE + 8
PE9 = portE + 9
PE10 = portE + 10
PE11 = portE + 11
PE12 = portE + 12
PE13 = portE + 13
PE14 = portE + 14
PE15 = portE + 15
PF0 = portF + 0
PF1 = portF + 1
PF2 = portF + 2
PF3 = portF + 3
PF4 = portF + 4
PF5 = portF + 5
PF6 = portF + 6
PF7 = portF + 7
PF8 = portF + 8
PF9 = portF + 9
PF10 = portF + 10
PF11 = portF + 11
PF12 = portF + 12
PF13 = portF + 13
PF14 = portF + 14
PF15 = portF + 15
)
func (p Pin) getPort() *stm32.GPIO_Type {
switch p / 16 {
case 0:
return stm32.GPIOA
case 1:
return stm32.GPIOB
case 2:
return stm32.GPIOC
case 3:
return stm32.GPIOD
case 4:
return stm32.GPIOE
case 5:
return stm32.GPIOF
default:
panic("machine: unknown port")
}
}
// enableClock enables the clock for this desired GPIO port.
func (p Pin) enableClock() {
switch p / 16 {
case 0:
stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_GPIOAEN)
case 1:
stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_GPIOBEN)
case 2:
stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_GPIOCEN)
case 3:
stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_GPIODEN)
case 4:
stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_GPIOEEN)
case 5:
stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_GPIOFEN)
default:
panic("machine: unknown port")
}
}
func enableAltFuncClock(bus unsafe.Pointer) {
switch bus {
case unsafe.Pointer(stm32.I2C1):
stm32.RCC.APBENR1.SetBits(stm32.RCC_APBENR1_I2C1EN)
case unsafe.Pointer(stm32.I2C2):
stm32.RCC.APBENR1.SetBits(stm32.RCC_APBENR1_I2C2EN)
case unsafe.Pointer(stm32.I2C3):
stm32.RCC.APBENR1.SetBits(stm32.RCC_APBENR1_I2C3EN)
case unsafe.Pointer(stm32.USART1):
stm32.RCC.APBENR2.SetBits(stm32.RCC_APBENR2_USART1EN)
case unsafe.Pointer(stm32.USART2):
stm32.RCC.APBENR1.SetBits(stm32.RCC_APBENR1_USART2EN)
case unsafe.Pointer(stm32.USART3):
stm32.RCC.APBENR1.SetBits(stm32.RCC_APBENR1_USART3EN)
case unsafe.Pointer(stm32.USART4):
stm32.RCC.APBENR1.SetBits(stm32.RCC_APBENR1_USART4EN)
}
}
var (
// TODO: implement output channels
TIM1 = TIM{
EnableRegister: &stm32.RCC.APBENR2,
EnableFlag: stm32.RCC_APBENR2_TIM1EN,
Device: stm32.TIM1,
Channels: [4]TimerChannel{
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
},
busFreq: 4e6,
}
TIM2 = TIM{
EnableRegister: &stm32.RCC.APBENR1,
EnableFlag: stm32.RCC_APBENR1_TIM2EN,
Device: stm32.TIM2,
Channels: [4]TimerChannel{
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
},
busFreq: 4e6,
}
TIM3 = TIM{
EnableRegister: &stm32.RCC.APBENR1,
EnableFlag: stm32.RCC_APBENR1_TIM3EN,
Device: stm32.TIM3,
Channels: [4]TimerChannel{
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
},
busFreq: 4e6,
}
TIM6 = TIM{
EnableRegister: &stm32.RCC.APBENR1,
EnableFlag: stm32.RCC_APBENR1_TIM6EN,
Device: stm32.TIM6,
Channels: [4]TimerChannel{
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
},
busFreq: 4e6,
}
TIM7 = TIM{
EnableRegister: &stm32.RCC.APBENR1,
EnableFlag: stm32.RCC_APBENR1_TIM7EN,
Device: stm32.TIM7,
Channels: [4]TimerChannel{
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
},
busFreq: 4e6,
}
TIM15 = TIM{
EnableRegister: &stm32.RCC.APBENR2,
EnableFlag: stm32.RCC_APBENR2_TIM15EN,
Device: stm32.TIM15,
Channels: [4]TimerChannel{
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
},
busFreq: 4e6,
}
TIM16 = TIM{
EnableRegister: &stm32.RCC.APBENR2,
EnableFlag: stm32.RCC_APBENR2_TIM16EN,
Device: stm32.TIM16,
Channels: [4]TimerChannel{
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
TimerChannel{Pins: []PinFunction{}},
},
busFreq: 4e6,
}
)
func (t *TIM) registerUPInterrupt() interrupt.Interrupt {
switch t {
case &TIM1:
return interrupt.New(stm32.IRQ_TIM1_BRK_UP_TRG_COM, TIM1.handleUPInterrupt)
case &TIM2:
return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt)
case &TIM3:
return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt)
case &TIM6:
return interrupt.New(stm32.IRQ_TIM6_DAC_LPTIM1, TIM6.handleUPInterrupt)
case &TIM7:
return interrupt.New(stm32.IRQ_TIM7_LPTIM2, TIM7.handleUPInterrupt)
case &TIM15:
return interrupt.New(stm32.IRQ_TIM15_LPTIM3, TIM15.handleUPInterrupt)
case &TIM16:
return interrupt.New(stm32.IRQ_TIM16, TIM16.handleUPInterrupt)
}
return interrupt.Interrupt{}
}
func (t *TIM) registerOCInterrupt() interrupt.Interrupt {
switch t {
case &TIM1:
return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt)
case &TIM2:
return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt)
case &TIM3:
return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt)
case &TIM6:
return interrupt.New(stm32.IRQ_TIM6_DAC_LPTIM1, TIM6.handleOCInterrupt)
case &TIM7:
return interrupt.New(stm32.IRQ_TIM7_LPTIM2, TIM7.handleOCInterrupt)
case &TIM15:
return interrupt.New(stm32.IRQ_TIM15_LPTIM3, TIM15.handleOCInterrupt)
case &TIM16:
return interrupt.New(stm32.IRQ_TIM16, TIM16.handleOCInterrupt)
}
return interrupt.Interrupt{}
}
func (t *TIM) enableMainOutput() {
t.Device.BDTR.SetBits(stm32.TIM_BDTR_MOE)
}
type arrtype = uint16
type psctype = uint16
type arrRegType = volatile.Register16
const (
ARR_MAX = 0x10000
PSC_MAX = 0x10000
)
const (
UART_TX_PIN = NoPin
UART_RX_PIN = NoPin
)
func (uart *UART) configurePins(config UARTConfig) {
panic("unimplemented: UART")
}
func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {
panic("unimplemented: UART")
}
func (uart *UART) setRegisters() uint32 {
panic("unimplemented: UART")
}
const (
I2C0_SCL_PIN = NoPin
I2C0_SDA_PIN = NoPin
)
func (i2c I2C) getFreqRange(br uint32) uint32 {
panic("unimplemented: I2C")
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !baremetal || atmega || attiny85 || esp32 || esp32c3 || esp32s3 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2)
//go:build !baremetal || atmega || attiny85 || esp32 || esp32c3 || esp32s3 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2 && !stm32u0)
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build atmega || attiny85 || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2)
//go:build atmega || attiny85 || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2 && !stm32u0)
// This file implements the SPI Tx function for targets that don't have a custom
// (faster) implementation for it.
+8 -2
View File
@@ -1,4 +1,4 @@
//go:build sam || nrf52840 || rp2040 || rp2350
//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7
package machine
@@ -184,7 +184,9 @@ func sendDescriptor(setup usb.Setup) {
return
}
case descriptor.TypeDeviceQualifier:
// skip
// Full-speed-only device: STALL to signal no high-speed capability (USB 2.0 §9.6.2).
USBDev.SetStallEPIn(0)
return
default:
}
@@ -369,6 +371,10 @@ func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.S
})
}
// PhysicalEndpoint maps a virtual endpoint index to the physical endpoint number
// used by the hardware. This is an identity mapping on all currently supported platforms.
func PhysicalEndpoint(ep uint32) uint32 { return ep }
func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) {
usbDescriptor = desc
+5 -8
View File
@@ -268,7 +268,7 @@ func TestRing512_PutOversize(t *testing.T) {
func TestRing512_MultiplePutPeekDiscard(t *testing.T) {
var r ring512
for i := 0; i < 2000; i++ {
for i := range 2000 {
msg := []byte(fmt.Sprintf("msg%04d", i))
if !r.Put(msg) {
t.Fatalf("Put failed at iteration %d, Free=%d, Used=%d", i, r.Free(), r.Used())
@@ -290,7 +290,7 @@ func TestRing512_HeadTailOverflow(t *testing.T) {
t.Fatalf("Used=%d Free=%d, want 0/512", r.Used(), r.Free())
}
for i := 0; i < 300; i++ {
for i := range 300 {
data := []byte{byte(i), byte(i + 1), byte(i + 2)}
if !r.Put(data) {
t.Fatalf("Put failed at iter %d (head=%d tail=%d)", i, r.head.Load(), r.tail.Load())
@@ -370,7 +370,7 @@ func TestRing512_PeekTotalEqualsUsed(t *testing.T) {
// --- Concurrent SPSC Test ---
func TestRing512_SPSC(t *testing.T) {
for trial := 0; trial < 20; trial++ {
for trial := range 20 {
var r ring512
const totalBytes = 1 << 18
produced := make([]byte, totalBytes)
@@ -431,7 +431,7 @@ func TestRing512_SPSCSmallChunks(t *testing.T) {
go func() {
defer wg.Done()
for i := 0; i < totalBytes; i++ {
for i := range totalBytes {
for !r.Put([]byte{byte(i)}) {
}
}
@@ -494,10 +494,7 @@ func FuzzRing512(f *testing.F) {
switch op {
case 0: // Put
size := int(arg)
if size > 512 {
size = 512
}
size := min(int(arg), 512)
data := make([]byte, size)
for j := range data {
data[j] = byte(j)
+1
View File
@@ -165,6 +165,7 @@ func (m *msc) sendCSW(status csw.Status) {
residue = expected - m.sentBytes
}
m.cbw.CSW(status, residue, m.cswBuf)
m.queuedBytes = csw.MsgLen
m.state = mscStateStatusSent
m.queuedBytes = csw.MsgLen
m.sendUSBPacket(m.cswBuf)
+1
View File
@@ -51,6 +51,7 @@ func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool {
// (b) a Clear Feature HALT to the Bulk-In endpoint (clear stall IN)
// (c) a Clear Feature HALT to the Bulk-Out endpoint (clear stall OUT)
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
if m.state == mscStateNeedReset {
wIndex := uint8(setup.WIndex & 0x7F)
if wIndex == usb.MSC_ENDPOINT_IN {
+1 -1
Submodule src/net updated: 1026408a38...d5da3ddeef
+16
View File
@@ -138,6 +138,22 @@ func Symlink(oldname, newname string) error {
return ErrNotImplemented
}
func Chmod(name string, mode FileMode) error {
return ErrNotImplemented
}
func Chown(name string, uid, gid int) error {
return ErrNotImplemented
}
func Link(oldname, newname string) error {
return ErrNotImplemented
}
func (f *File) Chown(uid, gid int) error {
return ErrNotImplemented
}
func Readlink(name string) (string, error) {
return "", ErrNotImplemented
}

Some files were not shown because too many files have changed in this diff Show More