mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c78cf05cd | |||
| e79edb58b2 | |||
| bf80e9b446 | |||
| 30355cec9f | |||
| 9111927f85 | |||
| 10224a4bf8 | |||
| 33ddee76db | |||
| 7fa50a55cf | |||
| 1c0c26b8b7 | |||
| 3abe58448b | |||
| 364ac2dcb2 | |||
| e111b489ec | |||
| f31b39775f | |||
| 6c30583062 | |||
| d29e8e5f22 | |||
| a93640662e | |||
| 3fb0a90854 | |||
| df09dbdf0f | |||
| 134de98ba5 | |||
| 221ea6a54c | |||
| 7ffbcbc666 | |||
| 5f66260c3a | |||
| ba95eb3e1b | |||
| f16697624d | |||
| cb311ed25e | |||
| 6f686d12af | |||
| 6a0acfc735 | |||
| 27f8ec17fc | |||
| a683881eb7 | |||
| 335b085fd2 | |||
| 11c2b76e29 | |||
| 6605b6e43f | |||
| ec18e89365 | |||
| 39cde9f9a3 | |||
| 2747027ef2 | |||
| 4465360f3d | |||
| c33113ab5f | |||
| eab43851ac | |||
| c3f1832d9a | |||
| 2328d1e799 | |||
| 5fabc203db | |||
| d9d19e812e | |||
| 45dd431f19 | |||
| 088a660c00 | |||
| 1ff348fb1f | |||
| cd364a9315 | |||
| ca584de84a | |||
| 594be6db63 | |||
| aef70a5839 | |||
| e6e7250a47 | |||
| 7b3dc19445 | |||
| 8e36d2758b | |||
| 7cbbfd6fc5 | |||
| e8bb38f7c9 | |||
| 1b9bb143bf | |||
| 41f666c670 | |||
| 469e2434fd | |||
| 278aa09819 | |||
| dded832238 | |||
| 8ffabbea64 | |||
| 29b4c6723f | |||
| 039f48f3d2 | |||
| eed4afda63 | |||
| ce888e1042 | |||
| f5d0ec93ef | |||
| 3a0d72457b | |||
| c980e48ad4 | |||
| f5bd750a4d | |||
| 0a5875ab2e | |||
| 54ecf74261 | |||
| 9b83acffe6 | |||
| 67aa15e92e | |||
| 0d6efc6429 | |||
| 1bd3ade825 | |||
| fb8ed8fde8 | |||
| c698eeacba | |||
| 906a20d3bf | |||
| 1be377c47d | |||
| 4dbb5dd4da | |||
| 997974376d | |||
| 7d51044892 | |||
| 37c080083b | |||
| 70a0dee8c8 | |||
| de83d67364 | |||
| 7a5a50d949 | |||
| d348d8b4cf | |||
| 443de9fe1c | |||
| c72f1d5e62 | |||
| ca8fcc9479 | |||
| d01a932201 |
@@ -40,7 +40,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Build TinyGo (LLVM ${{ matrix.version }})
|
||||
run: go install -tags=llvm${{ matrix.version }}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# This CI job checks whether at least the smoke tests pass for the oldest
|
||||
# Go/LLVM version we claim to support.
|
||||
|
||||
name: Version compatibility test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- release
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test-compat:
|
||||
runs-on: ubuntu-22.04 # this must be a specific version for the apt install below
|
||||
env:
|
||||
# Oldest versions currently supported by TinyGo
|
||||
LLVM: "15"
|
||||
Go: "1.24" # when updating this, also update minorMin in builder/config.go
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.Go }}
|
||||
cache: true
|
||||
- name: Install LLVM
|
||||
run: |
|
||||
echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-${{ env.LLVM }} main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y \
|
||||
llvm-${{ env.LLVM }}-dev \
|
||||
clang-${{ env.LLVM }} \
|
||||
libclang-${{ env.LLVM }}-dev \
|
||||
lld-${{ env.LLVM }} \
|
||||
binaryen
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-linux-compat
|
||||
path: llvm-project/compiler-rt
|
||||
- name: Download LLVM source
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
run: make llvm-source
|
||||
- name: Save LLVM source cache
|
||||
uses: actions/cache/save@v5
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
with:
|
||||
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
|
||||
path: llvm-project/compiler-rt
|
||||
- name: Go cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: go-build-${{ env.Go }}-llvm${{ env.LLVM }}-${{ hashFiles('go.sum') }}
|
||||
- name: Build TinyGo
|
||||
run: go install -tags=llvm${{ env.LLVM }}
|
||||
- run: tinygo version
|
||||
- run: make gen-device -j4
|
||||
- run: go test -tags=llvm${{ env.LLVM }} -short -skip=TestErrors
|
||||
- run: make smoketest XTENSA=0
|
||||
@@ -12,6 +12,24 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
go-mod-tidy:
|
||||
# Check that go.sum is up to date.
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Run go mod tidy
|
||||
run: go mod tidy
|
||||
- name: Check go.mod and go.sum are up to date
|
||||
run: git diff --exit-code
|
||||
|
||||
build-linux:
|
||||
# Build Linux binaries, ready for release.
|
||||
# This runs inside an Alpine Linux container so we can more easily create a
|
||||
@@ -142,7 +160,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Install wasmtime
|
||||
uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
@@ -186,7 +204,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
@@ -266,6 +284,8 @@ jobs:
|
||||
- run: make smoketest
|
||||
- run: make wasmtest
|
||||
- run: make tinygo-test-baremetal
|
||||
- name: Check Go code formatting
|
||||
run: make fmt-check lint
|
||||
build-linux-cross:
|
||||
# Build ARM Linux binaries, ready for release.
|
||||
# This intentionally uses an older Linux image, so that we compile against
|
||||
@@ -303,7 +323,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
|
||||
@@ -17,12 +17,6 @@ jobs:
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 24GB
|
||||
disk-root: "C:"
|
||||
- uses: MinoruSekine/setup-scoop@v4
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
@@ -40,13 +34,13 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore cached LLVM source
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-windows-v1
|
||||
key: llvm-source-20-windows-v3
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
@@ -71,7 +65,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-20-windows-v2
|
||||
key: llvm-build-20-windows-v4
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
@@ -93,7 +87,7 @@ jobs:
|
||||
- name: Cache Go cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: go-cache-windows-v2-${{ hashFiles('go.mod') }}
|
||||
key: go-cache-windows-v3-${{ hashFiles('go.mod') }}
|
||||
path: |
|
||||
C:/Users/runneradmin/AppData/Local/go-build
|
||||
C:/Users/runneradmin/go/pkg/mod
|
||||
@@ -124,12 +118,6 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
needs: build-windows
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 24GB
|
||||
disk-root: "C:"
|
||||
- uses: MinoruSekine/setup-scoop@v4
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
@@ -141,7 +129,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -157,18 +145,12 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
needs: build-windows
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 24GB
|
||||
disk-root: "C:"
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -183,12 +165,6 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
needs: build-windows
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 24GB
|
||||
disk-root: "C:"
|
||||
- uses: MinoruSekine/setup-scoop@v4
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
@@ -200,7 +176,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
+30
-10
@@ -194,6 +194,12 @@ NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,
|
||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
|
||||
CGO_CPPFLAGS+=$(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CXXFLAGS=-std=c++17
|
||||
ifneq ($(uname),Windows_NT)
|
||||
# Disable GCC DWARF compression: lld built without zlib cannot link
|
||||
# object files with ELFCOMPRESS_ZLIB debug sections.
|
||||
CGO_CFLAGS+=-gz=none
|
||||
CGO_CXXFLAGS+=-gz=none
|
||||
endif
|
||||
CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
||||
endif
|
||||
|
||||
@@ -310,7 +316,7 @@ check-nodejs-version:
|
||||
|
||||
tinygo: ## Build the TinyGo compiler
|
||||
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
|
||||
test: check-nodejs-version
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
|
||||
|
||||
@@ -379,22 +385,22 @@ TEST_PACKAGES_FAST = \
|
||||
# archive/zip requires os.ReadAt, which is not yet supported on windows
|
||||
# bytes requires mmap
|
||||
# compress/flate appears to hang on wasi
|
||||
# crypto/aes fails on wasi, needs panic()/recover()
|
||||
# crypto/aes needs reflect.Type.Method(), not yet implemented
|
||||
# crypto/des fails on wasi, needs panic()/recover()
|
||||
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic
|
||||
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
|
||||
# encoding/xml takes a minute on linux and gives a stack overflow on wasi
|
||||
# image requires recover(), which is not yet supported on wasi
|
||||
# image fails on wasi, needs panic()/recover()
|
||||
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
|
||||
# mime: fail on wasi; neds panic()/recover()
|
||||
# mime: fails on wasi, needs panic()/recover()
|
||||
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# mime/quotedprintable requires syscall.Faccessat
|
||||
# net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# regexp/syntax: fails on wasip1; needs panic()/recover()
|
||||
# strconv requires recover() which is not yet supported on wasi
|
||||
# text/tabwriter requires recover(), which is not yet supported on wasi
|
||||
# text/template/parse requires recover(), which is not yet supported on wasi
|
||||
# regexp/syntax: fails on wasip1, needs panic()/recover()
|
||||
# strconv: fails on wasi, needs panic()/recover()
|
||||
# text/tabwriter: fails on wasi, needs panic()/recover()
|
||||
# text/template/parse: fails on wasi, needs panic()/recover()
|
||||
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi
|
||||
|
||||
# Additional standard library packages that pass tests on individual platforms
|
||||
@@ -419,6 +425,7 @@ TEST_PACKAGES_LINUX := \
|
||||
os/user \
|
||||
regexp/syntax \
|
||||
strconv \
|
||||
testing/fstest \
|
||||
text/tabwriter \
|
||||
text/template/parse
|
||||
|
||||
@@ -429,7 +436,11 @@ TEST_PACKAGES_WINDOWS := \
|
||||
compress/flate \
|
||||
crypto/des \
|
||||
crypto/hmac \
|
||||
image \
|
||||
mime \
|
||||
regexp/syntax \
|
||||
strconv \
|
||||
text/tabwriter \
|
||||
text/template/parse \
|
||||
$(nil)
|
||||
|
||||
@@ -778,6 +789,8 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico -gc=leaking examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
|
||||
@@ -947,6 +960,11 @@ ifneq ($(XTENSA), 0)
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
|
||||
@$(MD5SUM) test.bin
|
||||
# xiao-esp32c6
|
||||
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c6 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c6 examples/blinkm
|
||||
@$(MD5SUM) test.bin
|
||||
# xiao-esp32s3
|
||||
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
@@ -967,6 +985,8 @@ ifneq ($(XTENSA), 0)
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/adc
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32s3-box-3 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
endif
|
||||
# esp32c3-supermini
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1
|
||||
@@ -1163,8 +1183,8 @@ endif
|
||||
@cp -rp lib/wasi-libc/libc-top-half/musl/src/unistd build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
|
||||
@cp -rp lib/wasi-libc/libc-top-half/sources build/release/tinygo/lib/wasi-libc/libc-top-half
|
||||
@cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit
|
||||
@cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins
|
||||
@cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins
|
||||
@cp -rp ${LLVM_PROJECTDIR}/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins
|
||||
@cp -rp ${LLVM_PROJECTDIR}/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins
|
||||
@cp -rp src build/release/tinygo/src
|
||||
@cp -rp targets build/release/tinygo/targets
|
||||
|
||||
|
||||
+15
-1
@@ -1076,7 +1076,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
|
||||
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp32c6", "esp8266":
|
||||
// Special format for the ESP family of chips (parsed by the ROM
|
||||
// bootloader).
|
||||
result.Binary = filepath.Join(tmpdir, "main"+outext)
|
||||
@@ -1352,6 +1352,14 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
|
||||
}
|
||||
baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize()
|
||||
|
||||
// Account for the bytes that tinygo_swapTask pushes onto the goroutine stack
|
||||
// on every context switch. The static analysis correctly traces Go calls,
|
||||
// but it cannot see into the assembly-level register push.
|
||||
var contextSwitchOverhead uint64
|
||||
if swapFuncs, ok := functions["tinygo_swapTask"]; ok && len(swapFuncs) == 1 {
|
||||
contextSwitchOverhead = swapFuncs[0].FrameSize
|
||||
}
|
||||
|
||||
sizes := make(map[string]functionStackSize)
|
||||
|
||||
// Add the reset handler function, for convenience. The reset handler runs
|
||||
@@ -1400,6 +1408,12 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
|
||||
// overflow will occur even before the goroutine is started.
|
||||
stackSize = baseStackSize
|
||||
}
|
||||
if stackSizeType == stacksize.Bounded {
|
||||
// Add the overhead of context switching. This is needed because the
|
||||
// context switch (tinygo_swapTask) pushes callee-saved registers
|
||||
// onto the current stack, which is not seen by the static analysis.
|
||||
stackSize += contextSwitchOverhead
|
||||
}
|
||||
sizes[name] = functionStackSize{
|
||||
stackSize: stackSize,
|
||||
stackSizeType: stackSizeType,
|
||||
|
||||
@@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) {
|
||||
"cortex-m4",
|
||||
"cortex-m7",
|
||||
"esp32c3",
|
||||
"esp32c6",
|
||||
"esp32s3",
|
||||
"fe310",
|
||||
"gameboy-advance",
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
}
|
||||
|
||||
// Version range supported by TinyGo.
|
||||
const minorMin = 19
|
||||
const minorMin = 24 // when updating the min version, also update .github/workflows/compat.yml
|
||||
const minorMax = 26
|
||||
|
||||
// Check that we support this Go toolchain version.
|
||||
|
||||
+15
-3
@@ -100,12 +100,24 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
chip_id := map[string]uint16{
|
||||
"esp32": 0x0000,
|
||||
"esp32c3": 0x0005,
|
||||
"esp32c6": 0x000d,
|
||||
"esp32s3": 0x0009,
|
||||
}[chip]
|
||||
|
||||
// SPI flash speed/size byte (byte 3 of header):
|
||||
// Upper nibble = flash size, lower nibble = flash frequency.
|
||||
// The espflasher auto-detects and patches the flash size (upper nibble),
|
||||
// but the frequency (lower nibble) must be correct per chip.
|
||||
spiSpeedSize := map[string]uint8{
|
||||
"esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
"esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
"esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding)
|
||||
"esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
}[chip]
|
||||
|
||||
// Image header.
|
||||
switch chip {
|
||||
case "esp32", "esp32c3", "esp32s3":
|
||||
case "esp32", "esp32c3", "esp32s3", "esp32c6":
|
||||
// Header format:
|
||||
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
|
||||
// Note: not adding a SHA256 hash as the binary is modified by
|
||||
@@ -126,8 +138,8 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
}{
|
||||
magic: 0xE9,
|
||||
segment_count: byte(len(segments)),
|
||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
||||
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
|
||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
||||
spi_speed_size: spiSpeedSize,
|
||||
entry_addr: uint32(inf.Entry),
|
||||
wp_pin: 0xEE, // disable WP pin
|
||||
chip_id: chip_id,
|
||||
|
||||
+1
-1
@@ -218,7 +218,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
|
||||
return err
|
||||
}
|
||||
// Store this archive in the cache.
|
||||
return os.Rename(f.Name(), archiveFilePath)
|
||||
return robustRename(f.Name(), archiveFilePath)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !windows
|
||||
|
||||
package builder
|
||||
|
||||
import "os"
|
||||
|
||||
func robustRename(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const robustRenameTimeout = 2 * time.Second
|
||||
|
||||
func robustRename(oldpath, newpath string) error {
|
||||
var bestErr error
|
||||
start := time.Now()
|
||||
nextSleep := time.Millisecond
|
||||
for {
|
||||
err := os.Rename(oldpath, newpath)
|
||||
if err == nil || !isEphemeralRenameError(err) {
|
||||
return err
|
||||
}
|
||||
if bestErr == nil {
|
||||
bestErr = err
|
||||
}
|
||||
if d := time.Since(start) + nextSleep; d >= robustRenameTimeout {
|
||||
return bestErr
|
||||
}
|
||||
time.Sleep(nextSleep)
|
||||
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
|
||||
}
|
||||
}
|
||||
|
||||
func isEphemeralRenameError(err error) bool {
|
||||
var errno syscall.Errno
|
||||
if errors.As(err, &errno) {
|
||||
switch errno {
|
||||
case syscall.Errno(2), // ERROR_FILE_NOT_FOUND
|
||||
syscall.Errno(5), // ERROR_ACCESS_DENIED
|
||||
syscall.Errno(32): // ERROR_SHARING_VIOLATION
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+8
-2
@@ -501,8 +501,9 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
|
||||
Align: section.Addralign,
|
||||
Type: memoryStack,
|
||||
})
|
||||
} else {
|
||||
// Regular .bss section.
|
||||
} else if section.Flags&elf.SHF_WRITE != 0 {
|
||||
// Regular .bss section. Zero-initialized RAM is always
|
||||
// writable, so require SHF_WRITE here.
|
||||
sections = append(sections, memorySection{
|
||||
Address: section.Addr,
|
||||
Size: section.Size,
|
||||
@@ -510,6 +511,11 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
|
||||
Type: memoryBSS,
|
||||
})
|
||||
}
|
||||
// Other (non-writable) SHT_NOBITS sections are address-space
|
||||
// placeholders that occupy no RAM, such as the ESP linker
|
||||
// script's .irom_dummy / .rodata_dummy sections which reserve
|
||||
// the flash-mapped XIP virtual address ranges. They must not be
|
||||
// counted as bss/RAM usage.
|
||||
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 {
|
||||
// .text
|
||||
sections = append(sections, memorySection{
|
||||
|
||||
@@ -42,9 +42,9 @@ 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", 3680, 280, 0, 2252},
|
||||
{"microbit", "examples/serial", 2694, 342, 8, 2248},
|
||||
{"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},
|
||||
{"hifive1b", "examples/echo", 3699, 297, 0, 2252},
|
||||
{"microbit", "examples/serial", 2736, 356, 8, 2248},
|
||||
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
|
||||
|
||||
// TODO: also check wasm. Right now this is difficult, because
|
||||
// wasm binaries are run through wasm-opt and therefore the
|
||||
@@ -99,7 +99,7 @@ func TestSizeFull(t *testing.T) {
|
||||
t.Fatal("could not read program size:", err)
|
||||
}
|
||||
for _, pkg := range sizes.sortedPackageNames() {
|
||||
if pkg == "(padding)" || pkg == "(unknown)" {
|
||||
if pkg == "(padding)" || pkg == "(unknown)" || pkg == "Go types" {
|
||||
// TODO: correctly attribute all unknown binary size.
|
||||
continue
|
||||
}
|
||||
|
||||
+4
-2
@@ -121,9 +121,11 @@ func parseLLDErrors(text string) error {
|
||||
if matches != nil {
|
||||
parsedError = true
|
||||
line, _ := strconv.Atoi(matches[3])
|
||||
// TODO: detect common mistakes like -gc=none?
|
||||
msg := "linker could not find symbol " + symbolName
|
||||
if symbolName == "runtime.alloc_noheap" {
|
||||
switch symbolName {
|
||||
case "runtime.alloc":
|
||||
msg = "object allocated on the heap with -gc=none"
|
||||
case "runtime.alloc_noheap":
|
||||
msg = "object allocated on the heap in //go:noheap function"
|
||||
}
|
||||
linkErrors = append(linkErrors, scanner.Error{
|
||||
|
||||
-22
@@ -26,10 +26,6 @@ import (
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// Function that's only defined in Go 1.22.
|
||||
var setASTFileFields = func(f *ast.File, start, end token.Pos) {
|
||||
}
|
||||
|
||||
// cgoPackage holds all CGo-related information of a package.
|
||||
type cgoPackage struct {
|
||||
generated *ast.File
|
||||
@@ -654,7 +650,6 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
|
||||
X: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "union",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: pos,
|
||||
@@ -708,7 +703,6 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
|
||||
X: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: typeName,
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -764,7 +758,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
@@ -811,11 +804,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "s",
|
||||
Decl: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.StarExpr{
|
||||
@@ -823,7 +811,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: typeName,
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -881,7 +868,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
@@ -964,11 +950,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "s",
|
||||
Decl: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.StarExpr{
|
||||
@@ -976,7 +957,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: typeName,
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -997,7 +977,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "value",
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
Type: bitfield.field.Type,
|
||||
@@ -1015,7 +994,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//go:build go1.22
|
||||
|
||||
package cgo
|
||||
|
||||
// Code specifically for Go 1.22.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
func init() {
|
||||
setASTFileFields = func(f *ast.File, start, end token.Pos) {
|
||||
f.FileStart = start
|
||||
f.FileEnd = end
|
||||
}
|
||||
}
|
||||
+2
-60
@@ -217,10 +217,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
exportName := name
|
||||
localName := name
|
||||
var stringSignature string
|
||||
@@ -258,7 +254,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Name: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + localName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: pos,
|
||||
@@ -295,11 +290,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
{
|
||||
NamePos: pos,
|
||||
Name: argName,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: argName,
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: f.makeDecayingASTType(argType, pos),
|
||||
@@ -315,7 +305,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
},
|
||||
}
|
||||
}
|
||||
obj.Decl = decl
|
||||
return decl, stringSignature
|
||||
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
|
||||
typ := f.makeASTRecordType(c, pos)
|
||||
@@ -325,39 +314,27 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
// Convert to a single-field struct type.
|
||||
typeExpr = f.makeUnionField(typ)
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: typ.pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typeExpr,
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
return typeSpec, typ
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typeName := "_Cgo_" + name
|
||||
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: f.makeASTType(underlyingType, pos),
|
||||
}
|
||||
if underlyingType.kind != C.CXType_Enum {
|
||||
typeSpec.Assign = pos
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
return typeSpec, nil
|
||||
case C.CXCursor_VarDecl:
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
@@ -376,19 +353,13 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
},
|
||||
},
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: typeExpr,
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
return gen, nil
|
||||
case C.CXCursor_MacroDefinition:
|
||||
@@ -405,26 +376,16 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Con,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Values: []ast.Expr{expr},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
return gen, nil
|
||||
case C.CXCursor_EnumDecl:
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
underlying := C.tinygo_clang_getEnumDeclIntegerType(c)
|
||||
// TODO: gc's CGo implementation uses types such as `uint32` for enums
|
||||
// instead of types such as C.int, which are used here.
|
||||
@@ -432,12 +393,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Name: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + name,
|
||||
Obj: obj,
|
||||
},
|
||||
Assign: pos,
|
||||
Type: f.makeASTType(underlying, pos),
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
return typeSpec, nil
|
||||
case C.CXCursor_EnumConstantDecl:
|
||||
value := C.tinygo_clang_getEnumConstantDeclValue(c)
|
||||
@@ -452,19 +411,13 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Con,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Values: []ast.Expr{expr},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
return gen, nil
|
||||
default:
|
||||
@@ -639,7 +592,8 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
|
||||
Package: f.Pos(0),
|
||||
Name: ast.NewIdent(p.packageName),
|
||||
}
|
||||
setASTFileFields(astFile, f.Pos(0), f.Pos(int(size)))
|
||||
astFile.FileStart = f.Pos(0)
|
||||
astFile.FileEnd = f.Pos(int(size))
|
||||
p.cgoFiles = append(p.cgoFiles, astFile)
|
||||
}
|
||||
positionFile := p.tokenFiles[filename]
|
||||
@@ -956,22 +910,16 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
|
||||
}
|
||||
|
||||
// Construct an *ast.TypeSpec for this type.
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: name,
|
||||
}
|
||||
spec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: name,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: goName,
|
||||
},
|
||||
}
|
||||
obj.Decl = spec
|
||||
return spec
|
||||
}
|
||||
|
||||
@@ -1104,7 +1052,6 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
||||
pos: prevField.Names[0].NamePos,
|
||||
})
|
||||
prevField.Names[0].Name = bitfieldName
|
||||
prevField.Names[0].Obj.Name = bitfieldName
|
||||
}
|
||||
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
|
||||
prevBitfield.endBit = bitfieldOffset
|
||||
@@ -1121,11 +1068,6 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
||||
{
|
||||
NamePos: pos,
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: name,
|
||||
Decl: field,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append(fieldList.List, field)
|
||||
|
||||
@@ -539,7 +539,7 @@ func (c *Config) Programmer() (method, openocdInterface string) {
|
||||
case "openocd", "msd", "command", "adb":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, c.Target.OpenOCDInterface
|
||||
case "bmp":
|
||||
case "bmp", "probe-rs":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, ""
|
||||
default:
|
||||
|
||||
@@ -47,6 +47,7 @@ type Options struct {
|
||||
Nobounds bool
|
||||
PrintSizes string
|
||||
PrintAllocs *regexp.Regexp // regexp string
|
||||
PrintAllocsCover bool // emit allocs in go coverage tool format
|
||||
PrintStacks bool
|
||||
Tags []string
|
||||
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
||||
|
||||
@@ -67,6 +67,7 @@ type TargetSpec struct {
|
||||
ADBPreCommands []string `json:"adb-pre-commands,omitempty"`
|
||||
ADBPushRemote string `json:"adb-push-remote,omitempty"`
|
||||
ADBPostCommands []string `json:"adb-post-commands,omitempty"`
|
||||
ProbeRSChip string `json:"probe-rs-chip,omitempty"`
|
||||
CodeModel string `json:"code-model,omitempty"`
|
||||
RelocationModel string `json:"relocation-model,omitempty"`
|
||||
WITPackage string `json:"wit-package,omitempty"`
|
||||
@@ -484,6 +485,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
||||
"--no-insert-timestamp",
|
||||
"--no-dynamicbase",
|
||||
)
|
||||
spec.ExtraFiles = append(spec.ExtraFiles,
|
||||
"src/runtime/runtime_windows.c")
|
||||
case "wasm", "wasip1", "wasip2":
|
||||
return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS)
|
||||
default:
|
||||
|
||||
+21
-8
@@ -241,24 +241,37 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
|
||||
}
|
||||
}
|
||||
|
||||
// Put the fault block at the end of the function and the next block at the
|
||||
// current insert position.
|
||||
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
|
||||
faultBlock := b.getRuntimeAssertBlock(blockPrefix, assertFunc)
|
||||
nextBlock := b.insertBasicBlock(blockPrefix + ".next")
|
||||
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now branch to the out-of-bounds or the regular block.
|
||||
b.CreateCondBr(assert, faultBlock, nextBlock)
|
||||
|
||||
// Fail: the assert triggered so panic.
|
||||
b.SetInsertPointAtEnd(faultBlock)
|
||||
b.createRuntimeCall(assertFunc, nil, "")
|
||||
b.CreateUnreachable()
|
||||
|
||||
// Ok: assert didn't trigger so continue normally.
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
func (b *builder) getRuntimeAssertBlock(blockPrefix, assertFunc string) llvm.BasicBlock {
|
||||
if b.runtimeAssertBlocks == nil {
|
||||
b.runtimeAssertBlocks = make(map[string]llvm.BasicBlock)
|
||||
}
|
||||
if block := b.runtimeAssertBlocks[assertFunc]; !block.IsNil() {
|
||||
return block
|
||||
}
|
||||
savedBlock := b.GetInsertBlock()
|
||||
block := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
|
||||
b.runtimeAssertBlocks[assertFunc] = block
|
||||
b.SetInsertPointAtEnd(block)
|
||||
if b.hasDeferFrame() {
|
||||
b.createFaultCheckpoint()
|
||||
}
|
||||
b.createRuntimeCall(assertFunc, nil, "")
|
||||
b.CreateUnreachable()
|
||||
b.SetInsertPointAtEnd(savedBlock)
|
||||
return block
|
||||
}
|
||||
|
||||
// extendInteger extends the value to at least targetType using a zero or sign
|
||||
// extend. The resulting value is not truncated: it may still be bigger than
|
||||
// targetType.
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
|
||||
|
||||
// Do the send.
|
||||
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
|
||||
// End the lifetime of the allocas.
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
@@ -101,7 +101,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
|
||||
// createChanClose closes the given channel.
|
||||
func (b *builder) createChanClose(ch llvm.Value) {
|
||||
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
||||
b.createRuntimeInvoke("chanClose", []llvm.Value{ch}, "")
|
||||
}
|
||||
|
||||
// createSelect emits all IR necessary for a select statements. That's a
|
||||
|
||||
+27
-15
@@ -92,6 +92,7 @@ type compilerContext struct {
|
||||
pkg *types.Package
|
||||
packageDir string // directory for this package
|
||||
runtimePkg *types.Package
|
||||
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
|
||||
}
|
||||
|
||||
// newCompilerContext returns a new compiler context ready for use, most
|
||||
@@ -176,6 +177,9 @@ type builder struct {
|
||||
deferBuiltinFuncs map[ssa.Value]deferBuiltin
|
||||
runDefersBlock []llvm.BasicBlock
|
||||
afterDefersBlock []llvm.BasicBlock
|
||||
|
||||
runtimeAssertBlocks map[string]llvm.BasicBlock
|
||||
interfaceAssertBlock llvm.BasicBlock
|
||||
}
|
||||
|
||||
func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder {
|
||||
@@ -192,16 +196,6 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
|
||||
}
|
||||
}
|
||||
|
||||
// Return the runtime.alloc function variant.
|
||||
// This is normally just "alloc", but is "alloc_noheap" if the //go:noheap
|
||||
// pragma is used.
|
||||
func (b *builder) allocFunc() string {
|
||||
if b.info.noheap {
|
||||
return "alloc_noheap"
|
||||
}
|
||||
return "alloc"
|
||||
}
|
||||
|
||||
type blockInfo struct {
|
||||
// entry is the LLVM basic block corresponding to the start of this *ssa.Block.
|
||||
entry llvm.BasicBlock
|
||||
@@ -310,6 +304,11 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
|
||||
// Convert AST to SSA.
|
||||
ssaPkg.Build()
|
||||
|
||||
// Assign names to function-local named types before compiling the
|
||||
// package, so that types declared in different functions (or in
|
||||
// different instantiations of a generic function) do not collide.
|
||||
c.scanLocalTypes(ssaPkg)
|
||||
|
||||
// Initialize debug information.
|
||||
if c.Debug {
|
||||
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||
@@ -869,10 +868,9 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
|
||||
}
|
||||
// Create the function definition.
|
||||
b := newBuilder(c, irbuilder, member)
|
||||
if _, ok := mathToLLVMMapping[member.RelString(nil)]; ok {
|
||||
if ok := b.defineMathOp(); ok {
|
||||
// The body of this function (if there is one) is ignored and
|
||||
// replaced with a LLVM intrinsic call.
|
||||
b.defineMathOp()
|
||||
continue
|
||||
}
|
||||
if ok := b.defineMathBitsIntrinsic(); ok {
|
||||
@@ -1356,6 +1354,15 @@ func (b *builder) createFunctionStart(intrinsic bool) {
|
||||
// diagnostic.
|
||||
func (b *builder) createFunction() {
|
||||
b.createFunctionStart(false)
|
||||
// createFunctionStart returns early (leaving blockInfo unset) when it hits
|
||||
// an error such as a redeclared symbol. Don't plow into the block loop and
|
||||
// panic on an empty blockInfo — the error was already recorded.
|
||||
if b.blockInfo == nil {
|
||||
// TINYGO-DEBUG: surface the redeclaration collision.
|
||||
fmt.Printf("TINYGO-DEBUG skipping fn (redeclared/errored): %s pkg=%s\n",
|
||||
b.fn.RelString(nil), b.fn.Pkg.Pkg.Path())
|
||||
return
|
||||
}
|
||||
|
||||
// Fill blocks with instructions.
|
||||
for _, block := range b.fn.DomPreorder() {
|
||||
@@ -1895,6 +1902,12 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
|
||||
// not of the current function.
|
||||
useParentFrame = 1
|
||||
}
|
||||
// Prevent inlining of functions that call recover(), matching the
|
||||
// Go compiler's behavior. If this function were inlined into a
|
||||
// deferred function, recover() would incorrectly succeed because
|
||||
// the inlined code runs in the deferred function's context.
|
||||
noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
|
||||
b.llvmFn.AddFunctionAttr(noinline)
|
||||
return b.createRuntimeCall("_recover", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), useParentFrame, false)}, ""), nil
|
||||
case "ssa:wrapnilchk":
|
||||
// TODO: do an actual nil check?
|
||||
@@ -2183,9 +2196,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
layoutValue := b.createObjectLayout(typ, expr.Pos())
|
||||
buf := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, layoutValue}, expr.Comment)
|
||||
align := b.targetData.ABITypeAlignment(typ)
|
||||
buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
buf := b.createAlloc(sizeValue, layoutValue, align, expr.Comment)
|
||||
return buf, nil
|
||||
} else {
|
||||
buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment)
|
||||
@@ -2415,7 +2427,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
|
||||
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
|
||||
slicePtr := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
|
||||
slicePtr := b.createAlloc(sliceSize, layoutValue, 0, "makeslice.buf")
|
||||
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))
|
||||
|
||||
// Extend or truncate if necessary. This is safe as we've already done
|
||||
|
||||
+22
-5
@@ -60,10 +60,6 @@ func (b *builder) deferInitFunc() {
|
||||
b.deferExprFuncs = make(map[ssa.Value]int)
|
||||
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
|
||||
|
||||
// Create defer list pointer.
|
||||
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
|
||||
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
|
||||
|
||||
if b.hasDeferFrame() {
|
||||
// Set up the defer frame with the current stack pointer.
|
||||
// This assumes that the stack pointer doesn't move outside of the
|
||||
@@ -73,12 +69,22 @@ func (b *builder) deferInitFunc() {
|
||||
// in the setjmp-like inline assembly.
|
||||
deferFrameType := b.getLLVMRuntimeType("deferFrame")
|
||||
b.deferFrame = b.CreateAlloca(deferFrameType, "deferframe.buf")
|
||||
// The field index must match the DeferPtr field in runtime.deferFrame,
|
||||
// defined in src/runtime/panic.go.
|
||||
b.deferPtr = b.CreateInBoundsGEP(deferFrameType, b.deferFrame, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 6, false), // DeferPtr field
|
||||
}, "deferPtr")
|
||||
stackPointer := b.readStackPointer()
|
||||
b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "")
|
||||
|
||||
// Create the landing pad block, which is where control transfers after
|
||||
// a panic.
|
||||
b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad")
|
||||
} else {
|
||||
// Create defer list pointer.
|
||||
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
|
||||
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +243,17 @@ func (b *builder) createInvokeCheckpoint() {
|
||||
b.currentBlockInfo.exit = continueBB
|
||||
}
|
||||
|
||||
// createFaultCheckpoint is like createInvokeCheckpoint but for use in fault
|
||||
// blocks (e.g., bounds check failures). Unlike createInvokeCheckpoint, it does
|
||||
// not update currentBlockInfo.exit because the fault block is a dead-end that
|
||||
// does not participate in phi node resolution.
|
||||
func (b *builder) createFaultCheckpoint() {
|
||||
isZero := b.createCheckpoint(b.deferFrame)
|
||||
continueBB := b.insertBasicBlock("")
|
||||
b.CreateCondBr(isZero, continueBB, b.landingpad)
|
||||
b.SetInsertPointAtEnd(continueBB)
|
||||
}
|
||||
|
||||
// isInLoop checks if there is a path from the current block to itself.
|
||||
// Use Tarjan's strongly connected components algorithm to search for cycles.
|
||||
// A one-node SCC is a cycle iff there is an edge from the node to itself.
|
||||
@@ -488,7 +505,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
size := b.targetData.TypeAllocSize(deferredCallType)
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
nilPtr := llvm.ConstNull(b.dataPtrType)
|
||||
alloca = b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
|
||||
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call")
|
||||
}
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(alloca)
|
||||
|
||||
@@ -10,6 +10,32 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Heap-allocate a buffer of the given size. This will typically call
|
||||
// runtime.alloc.
|
||||
func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comment string) llvm.Value {
|
||||
// Normally allocate using "runtime.alloc", but use "runtime.alloc_noheap"
|
||||
// if the //go:noheap pragma is used.
|
||||
allocFunc := "alloc"
|
||||
if b.info.noheap {
|
||||
allocFunc = "alloc_noheap"
|
||||
}
|
||||
|
||||
// Allocs that don't allocate anything can return an architecture-specific
|
||||
// sentinel value.
|
||||
if !sizeValue.IsAConstantInt().IsNil() && sizeValue.ZExtValue() == 0 {
|
||||
allocFunc = "alloc_zero"
|
||||
}
|
||||
|
||||
// Make the runtime call.
|
||||
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
|
||||
if align != 0 {
|
||||
// TODO: make sure all callsites set the correct alignment.
|
||||
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
}
|
||||
|
||||
return call
|
||||
}
|
||||
|
||||
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
|
||||
// one of the expressions that need this.
|
||||
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
|
||||
|
||||
+328
-46
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -165,7 +166,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
}
|
||||
}
|
||||
|
||||
typeCodeName, isLocal := getTypeCodeName(typ)
|
||||
typeCodeName, isLocal := c.getTypeCodeName(typ)
|
||||
globalName := "reflect/types.type:" + typeCodeName
|
||||
var global llvm.Value
|
||||
if isLocal {
|
||||
@@ -580,20 +581,50 @@ var basicTypeNames = [...]string{
|
||||
// getTypeCodeName returns a name for this type that can be used in the
|
||||
// interface lowering pass to assign type codes as expected by the reflect
|
||||
// package. See getTypeCodeNum.
|
||||
func getTypeCodeName(t types.Type) (string, bool) {
|
||||
//
|
||||
// isLocal is true when the type is declared inside a function body.
|
||||
// Such types need a per-declaration (or per instantiation) suffix
|
||||
// because their printed names are not unique.
|
||||
//
|
||||
// Ordinary function-local types (TypeName.Parent() != nil) are
|
||||
// disambiguated lazily from their declaration position: every
|
||||
// declaration in the package has a distinct (file, line, column)
|
||||
// triple, and the position is taken un-//line-adjusted so it is
|
||||
// stable across builds. Such types are only nameable inside their
|
||||
// declaring package, so the name does not need to agree with anything
|
||||
// computed in another package.
|
||||
//
|
||||
// Synthetic locals (TypeName.Parent() == nil), produced by generic
|
||||
// instantiation, are pre-registered by scanLocalTypes because their
|
||||
// names must agree across packages that materialize the same instance.
|
||||
func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bool) {
|
||||
switch t := types.Unalias(t).(type) {
|
||||
case *types.Named:
|
||||
if t.Obj().Parent() != t.Obj().Pkg().Scope() {
|
||||
return "named:" + t.String() + "$local", true
|
||||
tn := t.Obj()
|
||||
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
|
||||
// Package-scope or builtin: the printed name is unique.
|
||||
return "named:" + t.String(), false
|
||||
}
|
||||
return "named:" + t.String(), false
|
||||
if tn.Parent() != nil {
|
||||
// Ordinary function-local type. Use the un-//line-adjusted
|
||||
// declaration position as the disambiguator.
|
||||
pos := c.program.Fset.PositionFor(tn.Pos(), false)
|
||||
return fmt.Sprintf("named:%s$%s:%d:%d", t.String(), filepath.Base(pos.Filename), pos.Line, pos.Column), true
|
||||
}
|
||||
// Synthetic local from generic instantiation: must have been
|
||||
// pre-registered by scanLocalTypes.
|
||||
v := c.localTypeNames.At(t)
|
||||
if v == nil {
|
||||
panic("compiler: synthetic local type " + tn.Name() + " was not registered by scanLocalTypes")
|
||||
}
|
||||
return "named:" + v.(string), true
|
||||
case *types.Array:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal
|
||||
case *types.Basic:
|
||||
return "basic:" + basicTypeNames[t.Kind()], false
|
||||
case *types.Chan:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
var dir string
|
||||
switch t.Dir() {
|
||||
case types.SendOnly:
|
||||
@@ -613,7 +644,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
if !token.IsExported(name) {
|
||||
name = t.Method(i).Pkg().Path() + "." + name
|
||||
}
|
||||
s, local := getTypeCodeName(t.Method(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Method(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -621,17 +652,17 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal
|
||||
case *types.Map:
|
||||
keyType, keyLocal := getTypeCodeName(t.Key())
|
||||
elemType, elemLocal := getTypeCodeName(t.Elem())
|
||||
keyType, keyLocal := c.getTypeCodeName(t.Key())
|
||||
elemType, elemLocal := c.getTypeCodeName(t.Elem())
|
||||
return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal
|
||||
case *types.Pointer:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "pointer:" + s, isLocal
|
||||
case *types.Signature:
|
||||
isLocal := false
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
s, local := getTypeCodeName(t.Params().At(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Params().At(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -639,7 +670,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
results := make([]string, t.Results().Len())
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
s, local := getTypeCodeName(t.Results().At(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Results().At(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -647,7 +678,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal
|
||||
case *types.Slice:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "slice:" + s, isLocal
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
@@ -657,7 +688,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
if t.Field(i).Embedded() {
|
||||
embedded = "#"
|
||||
}
|
||||
s, local := getTypeCodeName(t.Field(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Field(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -672,6 +703,232 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// scanLocalTypes assigns names to every synthetic *types.TypeName
|
||||
// (TypeName.Parent() == nil) reachable from this package and stores
|
||||
// them in c.localTypeNames.
|
||||
//
|
||||
// Synthetic TypeNames are produced by generic instantiation: two
|
||||
// instantiations of the same generic function (e.g. F[int] and
|
||||
// F[string]) produce TypeNames with the same printed name and the
|
||||
// same source position, so each is named with the enclosing
|
||||
// instance's RelString as prefix. RelString encodes the type
|
||||
// arguments, matching Go's runtime behavior, where F[int].Inner and
|
||||
// F[string].Inner are distinct types even when Inner does not mention
|
||||
// the type parameter.
|
||||
//
|
||||
// A given instance may be materialized by several packages (the body
|
||||
// of F[int] is compiled in every package that calls F[int]); its
|
||||
// reflect/types.type:* global has LinkOnceODRLinkage and is merged by
|
||||
// name at link time. The chosen name therefore depends only on
|
||||
// intrinsic SSA properties (RelString and the raw token.Pos used as a
|
||||
// sort key), so any package compiling the same instance produces the
|
||||
// same identifier.
|
||||
//
|
||||
// Ordinary function-local TypeNames (TypeName.Parent() != nil) are
|
||||
// not handled here: they are nameable only inside their declaring
|
||||
// package, and getTypeCodeName derives a stable per-declaration name
|
||||
// for them directly from their source position.
|
||||
func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
|
||||
// Locate every generic instance reachable from this package
|
||||
// (including instances declared in imported packages and any
|
||||
// function reached through an instance subtree).
|
||||
var instances []*ssa.Function
|
||||
seen := map[*ssa.Function]struct{}{}
|
||||
var walk func(fn *ssa.Function, inInstance bool)
|
||||
walk = func(fn *ssa.Function, inInstance bool) {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[fn]; ok {
|
||||
return
|
||||
}
|
||||
// fn belongs to an instance subtree if it is itself an
|
||||
// instantiation or if we reached it from one.
|
||||
//
|
||||
// len(TypeArgs()) is used instead of fn.Origin() because
|
||||
// Origin() may call Build() on fn's declaring package, which
|
||||
// would defeat per-package compilation.
|
||||
isInstanceRoot := len(fn.TypeArgs()) > 0
|
||||
if !isInstanceRoot && !inInstance && fn.Pkg != ssaPkg {
|
||||
return
|
||||
}
|
||||
if fn.Blocks == nil && fn.AnonFuncs == nil {
|
||||
return
|
||||
}
|
||||
seen[fn] = struct{}{}
|
||||
isInInstance := inInstance || isInstanceRoot
|
||||
if isInInstance {
|
||||
instances = append(instances, fn)
|
||||
}
|
||||
for _, anon := range fn.AnonFuncs {
|
||||
walk(anon, isInInstance)
|
||||
}
|
||||
var ops [10]*ssa.Value
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
for _, op := range instr.Operands(ops[:0]) {
|
||||
if op == nil || *op == nil {
|
||||
continue
|
||||
}
|
||||
if callee, ok := (*op).(*ssa.Function); ok {
|
||||
walk(callee, isInInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, member := range ssaPkg.Members {
|
||||
switch m := member.(type) {
|
||||
case *ssa.Function:
|
||||
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)
|
||||
}
|
||||
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
|
||||
for i := 0; i < pmset.Len(); i++ {
|
||||
walk(c.program.MethodValue(pmset.At(i)), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Registration is first-writer-wins (a synthetic TypeName may be
|
||||
// reachable from several instances), so visit instances in a
|
||||
// deterministic order. Pos() is a defensive tiebreaker.
|
||||
sort.Slice(instances, func(i, j int) bool {
|
||||
ri, rj := instances[i].RelString(nil), instances[j].RelString(nil)
|
||||
if ri != rj {
|
||||
return ri < rj
|
||||
}
|
||||
return instances[i].Pos() < instances[j].Pos()
|
||||
})
|
||||
for _, fn := range instances {
|
||||
c.registerSyntheticLocalTypes(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// registerSyntheticLocalTypes walks every type reachable from fn's
|
||||
// body and records each synthetic *types.Named (TypeName.Parent() ==
|
||||
// nil) in c.localTypeNames. Each is named with fn.RelString as the
|
||||
// owning function plus a per-function counter assigned in source
|
||||
// order.
|
||||
//
|
||||
// First-writer-wins: a *types.Named already present in
|
||||
// c.localTypeNames is left alone, so a synthetic type reachable from
|
||||
// several instances keeps the name assigned by the first (in
|
||||
// scanLocalTypes' deterministic order).
|
||||
func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
|
||||
var found []*types.Named
|
||||
seen := map[types.Type]struct{}{}
|
||||
var visit func(t types.Type)
|
||||
visit = func(t types.Type) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[t]; ok {
|
||||
return
|
||||
}
|
||||
seen[t] = struct{}{}
|
||||
switch t := t.(type) {
|
||||
case *types.Alias:
|
||||
visit(types.Unalias(t))
|
||||
case *types.Named:
|
||||
tn := t.Obj()
|
||||
if tn.Pkg() != nil && tn.Parent() == nil {
|
||||
if c.localTypeNames.At(t) == nil {
|
||||
// Reserve the slot so later calls within this
|
||||
// scanLocalTypes invocation skip it; the final
|
||||
// name is filled in after sorting, before any
|
||||
// getTypeCodeName lookups happen.
|
||||
c.localTypeNames.Set(t, "")
|
||||
found = append(found, t)
|
||||
}
|
||||
}
|
||||
targs := t.TypeArgs()
|
||||
for i := 0; i < targs.Len(); i++ {
|
||||
visit(targs.At(i))
|
||||
}
|
||||
visit(t.Underlying())
|
||||
case *types.Pointer:
|
||||
visit(t.Elem())
|
||||
case *types.Slice:
|
||||
visit(t.Elem())
|
||||
case *types.Array:
|
||||
visit(t.Elem())
|
||||
case *types.Chan:
|
||||
visit(t.Elem())
|
||||
case *types.Map:
|
||||
visit(t.Key())
|
||||
visit(t.Elem())
|
||||
case *types.Struct:
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
visit(t.Field(i).Type())
|
||||
}
|
||||
case *types.Signature:
|
||||
if p := t.Params(); p != nil {
|
||||
for i := 0; i < p.Len(); i++ {
|
||||
visit(p.At(i).Type())
|
||||
}
|
||||
}
|
||||
if r := t.Results(); r != nil {
|
||||
for i := 0; i < r.Len(); i++ {
|
||||
visit(r.At(i).Type())
|
||||
}
|
||||
}
|
||||
case *types.Tuple:
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
visit(t.At(i).Type())
|
||||
}
|
||||
case *types.Interface:
|
||||
// A synthetic local type can be reachable only through a
|
||||
// local interface's method signature, so descend into
|
||||
// them. getTypeCodeName encodes those signatures into
|
||||
// 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 _, p := range fn.Params {
|
||||
visit(p.Type())
|
||||
}
|
||||
for _, fv := range fn.FreeVars {
|
||||
visit(fv.Type())
|
||||
}
|
||||
for _, l := range fn.Locals {
|
||||
visit(l.Type())
|
||||
}
|
||||
var ops [10]*ssa.Value
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if v, ok := instr.(ssa.Value); ok {
|
||||
visit(v.Type())
|
||||
}
|
||||
for _, op := range instr.Operands(ops[:0]) {
|
||||
if op != nil && *op != nil {
|
||||
visit((*op).Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(found) == 0 {
|
||||
return
|
||||
}
|
||||
// Sort by raw token.Pos: this gives a total order on declarations
|
||||
// that is stable across builds and unaffected by //line directives
|
||||
// (which only adjust the human-facing position from Fset.Position).
|
||||
sort.Slice(found, func(i, j int) bool {
|
||||
return found[i].Obj().Pos() < found[j].Obj().Pos()
|
||||
})
|
||||
enclosing := fn.RelString(nil)
|
||||
for i, named := range found {
|
||||
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||
// method set should be unreferenced after the interface lowering pass.
|
||||
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
@@ -769,7 +1026,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum)
|
||||
}
|
||||
} else {
|
||||
name, _ := getTypeCodeName(expr.AssertedType)
|
||||
name, _ := b.getTypeCodeName(expr.AssertedType)
|
||||
globalName := "reflect/types.typeid:" + name
|
||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||
if assertedTypeCodeGlobal.IsNil() {
|
||||
@@ -796,43 +1053,68 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
|
||||
prevBlock := b.GetInsertBlock()
|
||||
okBlock := b.insertBasicBlock("typeassert.ok")
|
||||
nextBlock := b.insertBasicBlock("typeassert.next")
|
||||
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
|
||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
|
||||
// Retrieve the value from the interface if the type assert was
|
||||
// successful.
|
||||
b.SetInsertPointAtEnd(okBlock)
|
||||
var valueOk llvm.Value
|
||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||
// Type assert on interface type. Easy: just return the same
|
||||
// interface value.
|
||||
valueOk = itf
|
||||
} else {
|
||||
// Type assert on concrete type. Extract the underlying type from
|
||||
// the interface (but only after checking it matches).
|
||||
valueOk = b.extractValueFromInterface(itf, assertedType)
|
||||
}
|
||||
b.CreateBr(nextBlock)
|
||||
|
||||
// Continue after the if statement.
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
phi := b.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
|
||||
if expr.CommaOk {
|
||||
nextBlock := b.insertBasicBlock("typeassert.next")
|
||||
b.currentBlockInfo.exit = nextBlock
|
||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
|
||||
// Retrieve the value from the interface if the type assert was
|
||||
// successful.
|
||||
b.SetInsertPointAtEnd(okBlock)
|
||||
var valueOk llvm.Value
|
||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||
// Type assert on interface type. Easy: just return the same
|
||||
// interface value.
|
||||
valueOk = itf
|
||||
} else {
|
||||
// Type assert on concrete type. Extract the underlying type from
|
||||
// the interface (but only after checking it matches).
|
||||
valueOk = b.extractValueFromInterface(itf, assertedType)
|
||||
}
|
||||
b.CreateBr(nextBlock)
|
||||
|
||||
// Continue after the if statement.
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
phi := b.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
|
||||
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
|
||||
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
|
||||
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
||||
return tuple
|
||||
} else {
|
||||
// This is kind of dirty as the branch above becomes mostly useless,
|
||||
// but hopefully this gets optimized away.
|
||||
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||
return phi
|
||||
// Type assert without comma-ok. If it fails, panic.
|
||||
faultBlock := b.getInterfaceAssertBlock()
|
||||
b.currentBlockInfo.exit = okBlock
|
||||
b.CreateCondBr(commaOk, okBlock, faultBlock)
|
||||
|
||||
// OK: extract the value from the interface.
|
||||
b.SetInsertPointAtEnd(okBlock)
|
||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||
return itf
|
||||
}
|
||||
return b.extractValueFromInterface(itf, assertedType)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) getInterfaceAssertBlock() llvm.BasicBlock {
|
||||
if !b.interfaceAssertBlock.IsNil() {
|
||||
return b.interfaceAssertBlock
|
||||
}
|
||||
savedBlock := b.GetInsertBlock()
|
||||
block := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.throw")
|
||||
b.interfaceAssertBlock = block
|
||||
b.SetInsertPointAtEnd(block)
|
||||
if b.hasDeferFrame() {
|
||||
b.createFaultCheckpoint()
|
||||
}
|
||||
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "")
|
||||
b.CreateUnreachable()
|
||||
b.SetInsertPointAtEnd(savedBlock)
|
||||
return block
|
||||
}
|
||||
|
||||
// getMethodsString returns a string to be used in the "tinygo-methods" string
|
||||
// attribute for interface functions.
|
||||
func (c *compilerContext) getMethodsString(itf *types.Interface) string {
|
||||
@@ -857,7 +1139,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||
if !token.IsExported(name) {
|
||||
name = method.Pkg().Path() + "." + name
|
||||
}
|
||||
s, _ := getTypeCodeName(method.Type())
|
||||
s, _ := c.getTypeCodeName(method.Type())
|
||||
globalName := "reflect/types.signature:" + name + ":" + s
|
||||
value := c.mod.NamedGlobal(globalName)
|
||||
if value.IsNil() {
|
||||
@@ -901,7 +1183,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||
// thunk is declared, not defined: it will be defined by the interface lowering
|
||||
// pass.
|
||||
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
s, _ := getTypeCodeName(instr.Value.Type().Underlying())
|
||||
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
|
||||
fnName := s + "." + instr.Method.Name() + "$invoke"
|
||||
llvmFn := c.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
@@ -926,7 +1208,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
// by the interface lowering pass as a type-ID comparison chain, avoiding the
|
||||
// need for runtime.typeImplementsMethodSet at compile time.
|
||||
func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value {
|
||||
s, _ := getTypeCodeName(intf)
|
||||
s, _ := b.getTypeCodeName(intf)
|
||||
fnName := s + ".$typeassert"
|
||||
llvmFn := b.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
|
||||
+42
-8
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -166,12 +167,22 @@ func (b *builder) createMachineKeepAliveImpl() {
|
||||
}
|
||||
|
||||
var mathToLLVMMapping = map[string]string{
|
||||
"math.Acos": "llvm.acos.f64",
|
||||
"math.Asin": "llvm.asin.f64",
|
||||
"math.Atan": "llvm.atan.f64",
|
||||
"math.Atan2": "llvm.atan2.f64",
|
||||
"math.Ceil": "llvm.ceil.f64",
|
||||
"math.Cos": "llvm.cos.f64",
|
||||
"math.Cosh": "llvm.cosh.f64",
|
||||
"math.Exp": "llvm.exp.f64",
|
||||
"math.Exp2": "llvm.exp2.f64",
|
||||
"math.Floor": "llvm.floor.f64",
|
||||
"math.Log": "llvm.log.f64",
|
||||
"math.Sin": "llvm.sin.f64",
|
||||
"math.Sinh": "llvm.sinh.f64",
|
||||
"math.Sqrt": "llvm.sqrt.f64",
|
||||
"math.Tan": "llvm.tan.f64",
|
||||
"math.Tanh": "llvm.tanh.f64",
|
||||
"math.Trunc": "llvm.trunc.f64",
|
||||
}
|
||||
|
||||
@@ -185,18 +196,40 @@ var mathToLLVMMapping = map[string]string{
|
||||
// float32(math.Sqrt(float64(v))) to a 32-bit floating point operation, which is
|
||||
// beneficial on architectures where 64-bit floating point operations are (much)
|
||||
// more expensive than 32-bit ones.
|
||||
func (b *builder) defineMathOp() {
|
||||
b.createFunctionStart(true)
|
||||
llvmName := mathToLLVMMapping[b.fn.RelString(nil)]
|
||||
if llvmName == "" {
|
||||
panic("unreachable: unknown math operation") // sanity check
|
||||
func (b *builder) defineMathOp() bool {
|
||||
llvmName, ok := mathToLLVMMapping[b.fn.RelString(nil)]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if strings.HasSuffix(b.Triple, "-wasi") || llvmutil.Version() < 19 {
|
||||
// We don't have a real libc for wasip2. Until that is fixed, we need to
|
||||
// limit math intrinsics on WASI to a subset supported natively in
|
||||
// WebAssembly.
|
||||
// Also, since we don't know the specific libc we will target, disallow
|
||||
// these for all WASI targets.
|
||||
//
|
||||
// We also need to limit ourselves to LLVM 19 and above for the extended
|
||||
// set of math intrinsics, see:
|
||||
// https://discourse.llvm.org/t/rfc-all-the-math-intrinsics/78294
|
||||
switch b.fn.Name() {
|
||||
case "Ceil", "Exp", "Exp2", "Floor", "Log", "Sqrt", "Trunc":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
b.createFunctionStart(true)
|
||||
llvmFn := b.mod.NamedFunction(llvmName)
|
||||
if llvmFn.IsNil() {
|
||||
// The intrinsic doesn't exist yet, so declare it.
|
||||
// At the moment, all supported intrinsics have the form "double
|
||||
// foo(double %x)" so we can hardcode the signature here.
|
||||
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
|
||||
var llvmType llvm.Type
|
||||
switch b.fn.Name() {
|
||||
case "Atan2":
|
||||
// double atan2(double %y, double %x)
|
||||
llvmType = llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)
|
||||
default:
|
||||
// double foo(double %x)
|
||||
llvmType = llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
|
||||
}
|
||||
llvmFn = llvm.AddFunction(b.mod, llvmName, llvmType)
|
||||
}
|
||||
// Create a call to the intrinsic.
|
||||
@@ -206,6 +239,7 @@ func (b *builder) defineMathOp() {
|
||||
}
|
||||
result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "")
|
||||
b.CreateRet(result)
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *builder) defineCryptoIntrinsic() bool {
|
||||
|
||||
+1
-5
@@ -129,11 +129,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
align := b.targetData.ABITypeAlignment(packedType)
|
||||
packedAlloc := b.createRuntimeCall(b.allocFunc(), []llvm.Value{
|
||||
sizeValue,
|
||||
llvm.ConstNull(b.dataPtrType),
|
||||
}, "")
|
||||
packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "")
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(packedAlloc)
|
||||
}
|
||||
|
||||
+2
-2
@@ -133,7 +133,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key, valueAlloca}
|
||||
b.createRuntimeCall("hashmapStringSet", params, "")
|
||||
b.createRuntimeInvoke("hashmapStringSet", params, "")
|
||||
} else {
|
||||
// Key stored at actual type.
|
||||
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
@@ -143,7 +143,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
|
||||
fnName = "hashmapGenericSet"
|
||||
}
|
||||
params := []llvm.Value{m, keyAlloca, valueAlloca}
|
||||
b.createRuntimeCall(fnName, params, "")
|
||||
b.createRuntimeInvoke(fnName, params, "")
|
||||
b.emitLifetimeEnd(keyAlloca, keySize)
|
||||
}
|
||||
b.emitLifetimeEnd(valueAlloca, valueSize)
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.alloc", "runtime.alloc_noheap":
|
||||
case "runtime.alloc", "runtime.alloc_noheap", "runtime.alloc_zero":
|
||||
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
|
||||
// returns values that are never null and never alias to an existing value.
|
||||
for _, attrName := range []string{"noalias", "nonnull"} {
|
||||
|
||||
+14
-17
@@ -3,7 +3,7 @@ source_filename = "defer.go"
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "thumbv7m-unknown-unknown-eabi"
|
||||
|
||||
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface }
|
||||
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr }
|
||||
%runtime._interface = type { ptr, ptr }
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -18,14 +18,14 @@ declare void @main.external(ptr) #1
|
||||
define hidden void @main.deferSimple(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%defer.alloca = alloca { i32, ptr }, align 4
|
||||
%deferPtr = alloca ptr, align 4
|
||||
store ptr null, ptr %deferPtr, align 4
|
||||
%deferframe.buf = alloca %runtime.deferFrame, align 4
|
||||
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
|
||||
%0 = call ptr @llvm.stacksave.p0()
|
||||
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
|
||||
%defer.next = load ptr, ptr %deferPtr, align 4
|
||||
store i32 0, ptr %defer.alloca, align 4
|
||||
%defer.alloca.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
|
||||
store ptr null, ptr %defer.alloca.repack15, align 4
|
||||
store ptr %defer.next, ptr %defer.alloca.repack15, align 4
|
||||
store ptr %defer.alloca, ptr %deferPtr, align 4
|
||||
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5
|
||||
%setjmp.result = icmp eq i32 %setjmp, 0
|
||||
@@ -111,9 +111,9 @@ rundefers.end3: ; preds = %rundefers.loophead6
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn
|
||||
declare ptr @llvm.stacksave.p0() #2
|
||||
|
||||
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #1
|
||||
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(28), ptr, ptr) #1
|
||||
|
||||
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #1
|
||||
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(28), ptr) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #0 {
|
||||
@@ -135,18 +135,18 @@ define hidden void @main.deferMultiple(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%defer.alloca2 = alloca { i32, ptr }, align 4
|
||||
%defer.alloca = alloca { i32, ptr }, align 4
|
||||
%deferPtr = alloca ptr, align 4
|
||||
store ptr null, ptr %deferPtr, align 4
|
||||
%deferframe.buf = alloca %runtime.deferFrame, align 4
|
||||
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
|
||||
%0 = call ptr @llvm.stacksave.p0()
|
||||
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
|
||||
%defer.next = load ptr, ptr %deferPtr, align 4
|
||||
store i32 0, ptr %defer.alloca, align 4
|
||||
%defer.alloca.repack22 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
|
||||
store ptr null, ptr %defer.alloca.repack22, align 4
|
||||
store ptr %defer.next, ptr %defer.alloca.repack22, align 4
|
||||
store ptr %defer.alloca, ptr %deferPtr, align 4
|
||||
store i32 1, ptr %defer.alloca2, align 4
|
||||
%defer.alloca2.repack23 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4
|
||||
store ptr %defer.alloca, ptr %defer.alloca2.repack23, align 4
|
||||
%defer.alloca2.repack24 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4
|
||||
store ptr %defer.alloca, ptr %defer.alloca2.repack24, align 4
|
||||
store ptr %defer.alloca2, ptr %deferPtr, align 4
|
||||
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5
|
||||
%setjmp.result = icmp eq i32 %setjmp, 0
|
||||
@@ -270,9 +270,8 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.deferInfiniteLoop(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%deferPtr = alloca ptr, align 4
|
||||
store ptr null, ptr %deferPtr, align 4
|
||||
%deferframe.buf = alloca %runtime.deferFrame, align 4
|
||||
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
|
||||
%0 = call ptr @llvm.stacksave.p0()
|
||||
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
|
||||
br label %for.body
|
||||
@@ -318,9 +317,8 @@ declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.deferLoop(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%deferPtr = alloca ptr, align 4
|
||||
store ptr null, ptr %deferPtr, align 4
|
||||
%deferframe.buf = alloca %runtime.deferFrame, align 4
|
||||
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
|
||||
%0 = call ptr @llvm.stacksave.p0()
|
||||
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
|
||||
br label %for.loop
|
||||
@@ -408,9 +406,8 @@ rundefers.end1: ; preds = %rundefers.loophead4
|
||||
define hidden void @main.deferBetweenLoops(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%defer.alloca = alloca { i32, ptr, i32 }, align 4
|
||||
%deferPtr = alloca ptr, align 4
|
||||
store ptr null, ptr %deferPtr, align 4
|
||||
%deferframe.buf = alloca %runtime.deferFrame, align 4
|
||||
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
|
||||
%0 = call ptr @llvm.stacksave.p0()
|
||||
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
|
||||
br label %for.loop
|
||||
|
||||
Vendored
+4
-1
@@ -75,7 +75,7 @@ entry:
|
||||
define hidden void @main.newStruct(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
%new = call align 1 ptr @runtime.alloc_zero(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store ptr %new, ptr @main.struct1, align 4
|
||||
%new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
@@ -93,6 +93,9 @@ entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||
declare noalias nonnull ptr @runtime.alloc_zero(i32, ptr, ptr) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
|
||||
Vendored
+30
-60
@@ -29,13 +29,13 @@ entry:
|
||||
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store float %a.X, ptr %a, align 4
|
||||
%a.repack9 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store float %a.Y, ptr %a.repack9, align 4
|
||||
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store float %a.Y, ptr %a.repack5, align 4
|
||||
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store float %b.X, ptr %b, align 4
|
||||
%b.repack11 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store float %b.Y, ptr %b.repack11, align 4
|
||||
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store float %b.Y, ptr %b.repack7, align 4
|
||||
call void @main.checkSize(i32 4, ptr undef) #3
|
||||
call void @main.checkSize(i32 8, ptr undef) #3
|
||||
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
@@ -43,29 +43,29 @@ entry:
|
||||
br i1 false, label %deref.throw, label %deref.next
|
||||
|
||||
deref.next: ; preds = %entry
|
||||
br i1 false, label %deref.throw1, label %deref.next2
|
||||
br i1 false, label %deref.throw, label %deref.next1
|
||||
|
||||
deref.next2: ; preds = %deref.next
|
||||
deref.next1: ; preds = %deref.next
|
||||
%0 = load float, ptr %a, align 4
|
||||
%1 = load float, ptr %b, align 4
|
||||
%2 = fadd float %0, %1
|
||||
br i1 false, label %deref.throw3, label %deref.next4
|
||||
br i1 false, label %deref.throw, label %deref.next2
|
||||
|
||||
deref.next4: ; preds = %deref.next2
|
||||
br i1 false, label %deref.throw5, label %deref.next6
|
||||
deref.next2: ; preds = %deref.next1
|
||||
br i1 false, label %deref.throw, label %deref.next3
|
||||
|
||||
deref.next6: ; preds = %deref.next4
|
||||
deref.next3: ; preds = %deref.next2
|
||||
%3 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
%4 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
%5 = load float, ptr %4, align 4
|
||||
%6 = load float, ptr %3, align 4
|
||||
br i1 false, label %store.throw, label %store.next
|
||||
br i1 false, label %deref.throw, label %store.next
|
||||
|
||||
store.next: ; preds = %deref.next6
|
||||
store.next: ; preds = %deref.next3
|
||||
store float %2, ptr %complit, align 4
|
||||
br i1 false, label %store.throw7, label %store.next8
|
||||
br i1 false, label %deref.throw, label %store.next4
|
||||
|
||||
store.next8: ; preds = %store.next
|
||||
store.next4: ; preds = %store.next
|
||||
%7 = getelementptr inbounds nuw i8, ptr %complit, i32 4
|
||||
%8 = fadd float %5, %6
|
||||
store float %8, ptr %7, align 4
|
||||
@@ -74,22 +74,7 @@ store.next8: ; preds = %store.next
|
||||
%10 = insertvalue %"main.Point[float32]" %9, float %8, 1
|
||||
ret %"main.Point[float32]" %10
|
||||
|
||||
deref.throw: ; preds = %entry
|
||||
unreachable
|
||||
|
||||
deref.throw1: ; preds = %deref.next
|
||||
unreachable
|
||||
|
||||
deref.throw3: ; preds = %deref.next2
|
||||
unreachable
|
||||
|
||||
deref.throw5: ; preds = %deref.next4
|
||||
unreachable
|
||||
|
||||
store.throw: ; preds = %deref.next6
|
||||
unreachable
|
||||
|
||||
store.throw7: ; preds = %store.next
|
||||
deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -107,13 +92,13 @@ entry:
|
||||
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store i32 %a.X, ptr %a, align 4
|
||||
%a.repack9 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store i32 %a.Y, ptr %a.repack9, align 4
|
||||
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store i32 %a.Y, ptr %a.repack5, align 4
|
||||
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store i32 %b.X, ptr %b, align 4
|
||||
%b.repack11 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store i32 %b.Y, ptr %b.repack11, align 4
|
||||
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store i32 %b.Y, ptr %b.repack7, align 4
|
||||
call void @main.checkSize(i32 4, ptr undef) #3
|
||||
call void @main.checkSize(i32 8, ptr undef) #3
|
||||
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
@@ -121,29 +106,29 @@ entry:
|
||||
br i1 false, label %deref.throw, label %deref.next
|
||||
|
||||
deref.next: ; preds = %entry
|
||||
br i1 false, label %deref.throw1, label %deref.next2
|
||||
br i1 false, label %deref.throw, label %deref.next1
|
||||
|
||||
deref.next2: ; preds = %deref.next
|
||||
deref.next1: ; preds = %deref.next
|
||||
%0 = load i32, ptr %a, align 4
|
||||
%1 = load i32, ptr %b, align 4
|
||||
%2 = add i32 %0, %1
|
||||
br i1 false, label %deref.throw3, label %deref.next4
|
||||
br i1 false, label %deref.throw, label %deref.next2
|
||||
|
||||
deref.next4: ; preds = %deref.next2
|
||||
br i1 false, label %deref.throw5, label %deref.next6
|
||||
deref.next2: ; preds = %deref.next1
|
||||
br i1 false, label %deref.throw, label %deref.next3
|
||||
|
||||
deref.next6: ; preds = %deref.next4
|
||||
deref.next3: ; preds = %deref.next2
|
||||
%3 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
%4 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
%5 = load i32, ptr %4, align 4
|
||||
%6 = load i32, ptr %3, align 4
|
||||
br i1 false, label %store.throw, label %store.next
|
||||
br i1 false, label %deref.throw, label %store.next
|
||||
|
||||
store.next: ; preds = %deref.next6
|
||||
store.next: ; preds = %deref.next3
|
||||
store i32 %2, ptr %complit, align 4
|
||||
br i1 false, label %store.throw7, label %store.next8
|
||||
br i1 false, label %deref.throw, label %store.next4
|
||||
|
||||
store.next8: ; preds = %store.next
|
||||
store.next4: ; preds = %store.next
|
||||
%7 = getelementptr inbounds nuw i8, ptr %complit, i32 4
|
||||
%8 = add i32 %5, %6
|
||||
store i32 %8, ptr %7, align 4
|
||||
@@ -152,22 +137,7 @@ store.next8: ; preds = %store.next
|
||||
%10 = insertvalue %"main.Point[int]" %9, i32 %8, 1
|
||||
ret %"main.Point[int]" %10
|
||||
|
||||
deref.throw: ; preds = %entry
|
||||
unreachable
|
||||
|
||||
deref.throw1: ; preds = %deref.next
|
||||
unreachable
|
||||
|
||||
deref.throw3: ; preds = %deref.next2
|
||||
unreachable
|
||||
|
||||
deref.throw5: ; preds = %deref.next4
|
||||
unreachable
|
||||
|
||||
store.throw: ; preds = %deref.next6
|
||||
unreachable
|
||||
|
||||
store.throw7: ; preds = %store.next
|
||||
deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry
|
||||
unreachable
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/tinygo-org/tinygo
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
|
||||
@@ -18,11 +18,11 @@ require (
|
||||
go.bug.st/serial v1.6.4
|
||||
go.bytecodealliance.org v0.6.2
|
||||
go.bytecodealliance.org/cm v0.2.2
|
||||
golang.org/x/net v0.35.0
|
||||
golang.org/x/sys v0.30.0
|
||||
golang.org/x/tools v0.30.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/tools v0.42.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
tinygo.org/x/espflasher v0.6.0
|
||||
tinygo.org/x/espflasher v0.6.1
|
||||
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6
|
||||
)
|
||||
|
||||
@@ -48,6 +48,6 @@ require (
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1 // indirect
|
||||
golang.org/x/mod v0.23.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
)
|
||||
|
||||
@@ -23,6 +23,8 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
|
||||
github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
@@ -93,24 +95,24 @@ go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ=
|
||||
go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA=
|
||||
go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA=
|
||||
go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
@@ -118,7 +120,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
tinygo.org/x/espflasher v0.6.0 h1:CHbGMHAIWq1tB8FKd/QwBpNFw1jvHHxpmvZiF+QOYUo=
|
||||
tinygo.org/x/espflasher v0.6.0/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U=
|
||||
tinygo.org/x/espflasher v0.6.1 h1:9jfyAP9jGjxF63FQUY2Bml9TFb5fCZYxVgbgR2IjUGs=
|
||||
tinygo.org/x/espflasher v0.6.1/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U=
|
||||
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6 h1:QSnqFgNV2Ij0T4hM2qKv53fcDAFElxClPjVUZXzYkWU=
|
||||
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
|
||||
|
||||
@@ -18,7 +18,9 @@ func TestInterp(t *testing.T) {
|
||||
"copy",
|
||||
"interface",
|
||||
"revert",
|
||||
"store",
|
||||
"alloc",
|
||||
"slicedata",
|
||||
} {
|
||||
name := name // make local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
||||
@@ -299,7 +299,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
// means that monotonic time in the time package is counted from
|
||||
// time.Time{}.Sub(1), which should be fine.
|
||||
locals[inst.localIndex] = literalValue{uint64(0)}
|
||||
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap":
|
||||
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap" || callFn.name == "runtime.alloc_zero":
|
||||
// Allocate heap memory. At compile time, this is instead done
|
||||
// by creating a global variable.
|
||||
|
||||
|
||||
+34
-13
@@ -299,7 +299,8 @@ func (mv *memoryView) put(index uint32, obj object) {
|
||||
}
|
||||
|
||||
// Load the value behind the given pointer. Returns nil if the pointer points to
|
||||
// an external global.
|
||||
// an external global or if the load is out of bounds of the object (in which
|
||||
// case the caller defers the load to runtime).
|
||||
func (mv *memoryView) load(p pointerValue, size uint32) value {
|
||||
if checks && mv.hasExternalStore(p) {
|
||||
panic("interp: load from object with external store")
|
||||
@@ -312,14 +313,23 @@ func (mv *memoryView) load(p pointerValue, size uint32) value {
|
||||
if p.offset() == 0 && size == obj.size {
|
||||
return obj.buffer.clone()
|
||||
}
|
||||
if checks && p.offset()+size > obj.size {
|
||||
panic("interp: load out of bounds")
|
||||
if p.offset()+size > obj.size {
|
||||
// The load is out of bounds of the object. This can happen for valid
|
||||
// Go programs, for example when dereferencing the pointer returned by
|
||||
// unsafe.SliceData on a zero-capacity slice (which points to a
|
||||
// zero-sized object). Return nil so the caller defers this load to
|
||||
// runtime instead of crashing the compiler.
|
||||
return nil
|
||||
}
|
||||
v := obj.buffer.asRawValue(mv.r)
|
||||
loadedValue := rawValue{
|
||||
buf: v.buf[p.offset() : p.offset()+size],
|
||||
loadedBuf := v.buf[p.offset() : p.offset()+size]
|
||||
if _, writable := mv.objects[p.index()]; writable {
|
||||
// This object's buffer is owned by this view, which means a later
|
||||
// store may mutate it in place (see store below). Copy the loaded
|
||||
// slice so the returned value is not aliased with the live buffer.
|
||||
loadedBuf = append([]uint64(nil), loadedBuf...)
|
||||
}
|
||||
return loadedValue
|
||||
return rawValue{buf: loadedBuf}
|
||||
}
|
||||
|
||||
// Store to the value behind the given pointer. This overwrites the value in the
|
||||
@@ -330,26 +340,37 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
|
||||
if checks && mv.hasExternalLoadOrStore(p) {
|
||||
panic("interp: store to object with external load/store")
|
||||
}
|
||||
obj := mv.get(p.index())
|
||||
index := p.index()
|
||||
var obj object
|
||||
writable := false
|
||||
if mv.objects != nil {
|
||||
obj, writable = mv.objects[index]
|
||||
}
|
||||
if !writable {
|
||||
obj = mv.get(index)
|
||||
}
|
||||
if obj.buffer == nil {
|
||||
// External global, return false (for a failure).
|
||||
return false
|
||||
}
|
||||
if checks && p.offset()+v.len(mv.r) > obj.size {
|
||||
valueLen := v.len(mv.r)
|
||||
if checks && p.offset()+valueLen > obj.size {
|
||||
panic("interp: store out of bounds")
|
||||
}
|
||||
if p.offset() == 0 && v.len(mv.r) == obj.buffer.len(mv.r) {
|
||||
obj.buffer = v
|
||||
if p.offset() == 0 && valueLen == obj.buffer.len(mv.r) {
|
||||
obj.buffer = v.clone()
|
||||
} else {
|
||||
obj = obj.clone()
|
||||
if !writable {
|
||||
obj = obj.clone()
|
||||
}
|
||||
buffer := obj.buffer.asRawValue(mv.r)
|
||||
obj.buffer = buffer
|
||||
v := v.asRawValue(mv.r)
|
||||
for i := uint32(0); i < v.len(mv.r); i++ {
|
||||
for i := uint32(0); i < valueLen; i++ {
|
||||
buffer.buf[p.offset()+i] = v.buf[i]
|
||||
}
|
||||
}
|
||||
mv.put(p.index(), obj)
|
||||
mv.put(index, obj)
|
||||
return true // success
|
||||
}
|
||||
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
; Reproduction of https://github.com/tinygo-org/tinygo/issues/4214.
|
||||
; Dereferencing the pointer returned by unsafe.SliceData on a zero-capacity
|
||||
; slice produces a load that is out of bounds of a zero-sized object. The
|
||||
; interp must defer this load to runtime instead of crashing the compiler.
|
||||
|
||||
@main.zeroSized = global {} zeroinitializer
|
||||
@main.v = global i64 0
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init(ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
%val = load i64, ptr @main.zeroSized
|
||||
store i64 %val, ptr @main.v
|
||||
ret void
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.zeroSized = local_unnamed_addr global {} zeroinitializer
|
||||
@main.v = local_unnamed_addr global i64 0
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
%val = load i64, ptr @main.zeroSized, align 8
|
||||
store i64 %val, ptr @main.v, align 8
|
||||
ret void
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@overlap.buf = global [4 x i8] c"\01\02\03\04"
|
||||
@alias.src = global [4 x i8] c"\05\06\07\08"
|
||||
@alias.dst = global [2 x i8] zeroinitializer
|
||||
@reload.buf = global [4 x i8] c"\01\02\03\04"
|
||||
@reload.out = global [2 x i8] zeroinitializer
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @overlap.init(ptr undef)
|
||||
call void @alias.init(ptr undef)
|
||||
call void @reload.init(ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @overlap.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
%tail = getelementptr [4 x i8], ptr @overlap.buf, i32 0, i32 3
|
||||
store i8 9, ptr %tail
|
||||
%val = load i16, ptr @overlap.buf
|
||||
%dst = getelementptr [4 x i8], ptr @overlap.buf, i32 0, i32 1
|
||||
store i16 %val, ptr %dst
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @alias.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
%src = getelementptr [4 x i8], ptr @alias.src, i32 0, i32 1
|
||||
%val = load i16, ptr %src
|
||||
store i16 %val, ptr @alias.dst
|
||||
store i8 9, ptr @alias.dst
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @reload.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
; First store makes reload.buf writable in the current memory view.
|
||||
%tail = getelementptr [4 x i8], ptr @reload.buf, i32 0, i32 3
|
||||
store i8 9, ptr %tail
|
||||
; Partial load whose result may share the underlying buffer.
|
||||
%val = load i16, ptr @reload.buf
|
||||
; Subsequent in-place partial store; this must not corrupt %val.
|
||||
store i8 99, ptr @reload.buf
|
||||
; Write the originally-loaded value to a separate global.
|
||||
store i16 %val, ptr @reload.out
|
||||
ret void
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@overlap.buf = local_unnamed_addr global [4 x i8] c"\01\01\02\09"
|
||||
@alias.src = local_unnamed_addr global [4 x i8] c"\05\06\07\08"
|
||||
@alias.dst = local_unnamed_addr global [2 x i8] c"\09\07"
|
||||
@reload.buf = local_unnamed_addr global [4 x i8] c"c\02\03\09"
|
||||
@reload.out = local_unnamed_addr global [2 x i8] c"\01\02"
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
@@ -246,6 +246,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
|
||||
"internal/futex/": false,
|
||||
"internal/fuzz/": false,
|
||||
"internal/itoa/": false,
|
||||
"internal/poll/": false,
|
||||
"internal/reflectlite/": false,
|
||||
"internal/gclayout": false,
|
||||
"internal/task/": false,
|
||||
|
||||
+2
-4
@@ -27,8 +27,6 @@ import (
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
var initFileVersions = func(info *types.Info) {}
|
||||
|
||||
// Program holds all packages and some metadata about the program as a whole.
|
||||
type Program struct {
|
||||
config *compileopts.Config
|
||||
@@ -435,7 +433,7 @@ func (p *Package) Check() error {
|
||||
}
|
||||
checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor)
|
||||
}
|
||||
initFileVersions(&p.info)
|
||||
p.info.FileVersions = make(map[*ast.File]string)
|
||||
|
||||
// Do typechecking of the package.
|
||||
packageName := p.ImportPath
|
||||
@@ -485,7 +483,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
if !filepath.IsAbs(file) {
|
||||
file = filepath.Join(p.Dir, file)
|
||||
}
|
||||
f, err := p.parseFile(file, parser.ParseComments)
|
||||
f, err := p.parseFile(file, parser.ParseComments|parser.SkipObjectResolution)
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
return
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//go:build go1.22
|
||||
|
||||
// types.Info.FileVersions was added in Go 1.22, so we can only initialize it
|
||||
// when built with Go 1.22.
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initFileVersions = func(info *types.Info) {
|
||||
info.FileVersions = make(map[*ast.File]string)
|
||||
}
|
||||
}
|
||||
@@ -209,6 +209,8 @@ func Build(pkgName, outpath string, config *compileopts.Config) error {
|
||||
// Test runs the tests in the given package. Returns whether the test passed and
|
||||
// possibly an error if the test failed to run.
|
||||
func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, outpath string) (bool, error) {
|
||||
optionsCopy := *options
|
||||
options = &optionsCopy
|
||||
options.TestConfig.CompileTestBinary = true
|
||||
config, err := builder.NewConfig(options)
|
||||
if err != nil {
|
||||
@@ -395,7 +397,7 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error {
|
||||
fileExt = filepath.Ext(config.Target.FlashFilename)
|
||||
case "openocd":
|
||||
fileExt = ".hex"
|
||||
case "bmp":
|
||||
case "bmp", "probe-rs":
|
||||
fileExt = ".elf"
|
||||
case "adb":
|
||||
fileExt = ".hex"
|
||||
@@ -535,6 +537,16 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error {
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", result.Binary, err}
|
||||
}
|
||||
case "probe-rs":
|
||||
// TODO: this halts the target after flashing.
|
||||
// See: https://github.com/probe-rs/probe-rs/discussions/4005
|
||||
cmd := executeCommand(config.Options, "probe-rs", "download", "--chip="+config.Target.ProbeRSChip, "--verify", result.Binary)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", result.Binary, err}
|
||||
}
|
||||
case "adb":
|
||||
// Run pre-flash adb shell commands.
|
||||
for _, preCmd := range config.Target.ADBPreCommands {
|
||||
@@ -697,6 +709,21 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
|
||||
daemon.Stdout = w
|
||||
daemon.Stderr = w
|
||||
}
|
||||
case "probe-rs":
|
||||
port = ":1337"
|
||||
gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt")
|
||||
|
||||
daemon = executeCommand(config.Options, "probe-rs", "gdb", "--chip="+config.Target.ProbeRSChip)
|
||||
if ocdOutput {
|
||||
// Make it clear which output is from the daemon.
|
||||
w := &ColorWriter{
|
||||
Out: colorable.NewColorableStderr(),
|
||||
Prefix: "probe-rs: ",
|
||||
Color: TermColorYellow,
|
||||
}
|
||||
daemon.Stdout = w
|
||||
daemon.Stderr = w
|
||||
}
|
||||
case "jlink":
|
||||
port = ":2331"
|
||||
gdbCommands = append(gdbCommands, "load", "monitor reset halt")
|
||||
@@ -1753,6 +1780,7 @@ func main() {
|
||||
printSize := flag.String("size", "", "print sizes (none, short, full, html)")
|
||||
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
|
||||
printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
|
||||
printAllocsCoverString := flag.String("print-allocs-cover", "", "like -print-allocs, but in go coverage tool format")
|
||||
printCommands := flag.Bool("x", false, "Print commands")
|
||||
flagJSON := flag.Bool("json", false, "print output in JSON format")
|
||||
parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel")
|
||||
@@ -1833,8 +1861,14 @@ func main() {
|
||||
}
|
||||
|
||||
var printAllocs *regexp.Regexp
|
||||
if *printAllocsString != "" {
|
||||
printAllocs, err = regexp.Compile(*printAllocsString)
|
||||
printAllocsCover := false
|
||||
printAllocsPattern := *printAllocsString
|
||||
if *printAllocsCoverString != "" {
|
||||
printAllocsPattern = *printAllocsCoverString
|
||||
printAllocsCover = true
|
||||
}
|
||||
if printAllocsPattern != "" {
|
||||
printAllocs, err = regexp.Compile(printAllocsPattern)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
@@ -1882,6 +1916,7 @@ func main() {
|
||||
PrintSizes: *printSize,
|
||||
PrintStacks: *printStacks,
|
||||
PrintAllocs: printAllocs,
|
||||
PrintAllocsCover: printAllocsCover,
|
||||
Tags: []string(tags),
|
||||
TestConfig: testConfig,
|
||||
GlobalValues: globalVarValues,
|
||||
|
||||
+2
-9
@@ -46,15 +46,6 @@ var supportedLinuxArches = map[string]string{
|
||||
"WASIp1": "wasip1/wasm",
|
||||
}
|
||||
|
||||
func init() {
|
||||
major, _, _ := goenv.GetGorootVersion()
|
||||
if major < 21 {
|
||||
// Go 1.20 backwards compatibility.
|
||||
// Should be removed once we drop support for Go 1.20.
|
||||
delete(supportedLinuxArches, "WASIp1")
|
||||
}
|
||||
}
|
||||
|
||||
var sema = make(chan struct{}, runtime.NumCPU())
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
@@ -76,6 +67,8 @@ func TestBuild(t *testing.T) {
|
||||
"init_multi.go",
|
||||
"interface.go",
|
||||
"json.go",
|
||||
"localtypes/",
|
||||
"localtypes.go",
|
||||
"map.go",
|
||||
"map_bigkey.go",
|
||||
"math.go",
|
||||
|
||||
+1
-1
@@ -278,7 +278,7 @@ func ListSerialPorts() ([]SerialPortInfo, error) {
|
||||
return serialPortInfo, nil
|
||||
}
|
||||
|
||||
var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `)
|
||||
var addressMatch = regexp.MustCompile(`panic: runtime error at 0x([0-9a-f]+): `)
|
||||
|
||||
// Extract the address from the "panic: runtime error at" message.
|
||||
func extractPanicAddress(line []byte) uint64 {
|
||||
|
||||
@@ -20,7 +20,7 @@ type reader struct {
|
||||
|
||||
func (r *reader) Read(b []byte) (n int, err error) {
|
||||
if len(b) != 0 {
|
||||
libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b)))
|
||||
libc_arc4random_buf(unsafe.Pointer(unsafe.SliceData(b)), uint(len(b)))
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func (r *reader) Read(b []byte) (n int, err error) {
|
||||
// See for example: https://github.com/golang/go/issues/33542
|
||||
// For Windows 7 and newer, we might switch to ProcessPrng in the future
|
||||
// (which is a documented function and might be a tiny bit faster).
|
||||
ok := libc_RtlGenRandom(unsafe.Pointer(&b[0]), len(b))
|
||||
ok := libc_RtlGenRandom(unsafe.Pointer(unsafe.SliceData(b)), len(b))
|
||||
if !ok {
|
||||
return 0, errRandom
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// This is a very minimal bootloader for the ESP32-C6. It only initializes the
|
||||
// flash and then continues with the generic RISC-V initialization code, which
|
||||
// in turn will call runtime.main.
|
||||
// It is written in assembly (and not in a higher level language) to make sure
|
||||
// it is entirely loaded into IRAM and doesn't accidentally call functions
|
||||
// stored in IROM.
|
||||
//
|
||||
// The ESP32-C6 has a unified IRAM/DRAM address space at 0x40800000, and
|
||||
// separate DROM (0x42800000) / IROM (0x42000000) flash-mapped regions.
|
||||
|
||||
.section .init
|
||||
.global call_start_cpu0
|
||||
.type call_start_cpu0,@function
|
||||
call_start_cpu0:
|
||||
// At this point:
|
||||
// - The ROM bootloader is finished and has jumped to here.
|
||||
// - We're running from IRAM: both IRAM and DRAM segments have been loaded
|
||||
// by the ROM bootloader.
|
||||
// - We have a usable stack (but not the one we would like to use).
|
||||
// - No flash mappings (MMU) are set up yet.
|
||||
|
||||
// Reset MMU, see bootloader_reset_mmu in the ESP-IDF.
|
||||
call Cache_Suspend_ICache
|
||||
mv s0, a0 // autoload value
|
||||
call Cache_Invalidate_ICache_All
|
||||
call Cache_MMU_Init
|
||||
|
||||
// Set up flash mapping (both IROM and DROM).
|
||||
// On ESP32-C6, Cache_Dbus_MMU_Set is replaced by Cache_MSPI_MMU_Set
|
||||
// which has an extra "sensitive" parameter.
|
||||
// C equivalent:
|
||||
// Cache_MSPI_MMU_Set(0, 0, 0x42000000, 0, 64, 256, 0)
|
||||
// Maps 16MB starting at 0x42000000, covering both IROM and DROM.
|
||||
li a0, 0 // sensitive: no flash encryption
|
||||
li a1, 0 // ext_ram: MMU_ACCESS_FLASH
|
||||
li a2, 0x42000000 // vaddr: start of flash-mapped region
|
||||
li a3, 0 // paddr: physical address in the flash chip
|
||||
li a4, 64 // psize: always 64 (kilobytes)
|
||||
li a5, 256 // num: pages (16MB / 64K = 256, covers IROM+DROM)
|
||||
li a6, 0 // fixed
|
||||
call Cache_MSPI_MMU_Set
|
||||
|
||||
// Enable the flash cache.
|
||||
mv a0, s0 // restore autoload value from Cache_Suspend_ICache call
|
||||
call Cache_Resume_ICache
|
||||
|
||||
// Jump to generic RISC-V initialization, which initializes the stack
|
||||
// pointer and globals register. It should not return.
|
||||
j _start
|
||||
|
||||
.section .text.exception_vectors
|
||||
.global _vector_table
|
||||
.type _vector_table,@function
|
||||
|
||||
_vector_table:
|
||||
|
||||
.option push
|
||||
.option norvc
|
||||
|
||||
.rept 32
|
||||
j handleInterruptASM /* interrupt handler */
|
||||
.endr
|
||||
|
||||
.option pop
|
||||
|
||||
.size _vector_table, .-_vector_table
|
||||
@@ -85,6 +85,9 @@ var (
|
||||
SIO = (*SIO_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0134)))
|
||||
|
||||
INTERRUPT = (*INTERRUPT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0200)))
|
||||
|
||||
// BIOS interrupt flags
|
||||
BIOS_IF = (*volatile.Register16)(unsafe.Pointer(uintptr(0x03007FF8)))
|
||||
)
|
||||
|
||||
// Main memory sections
|
||||
@@ -825,3 +828,60 @@ const (
|
||||
OAMOBJ_ATT2_PB_Pos = 0xC
|
||||
OAMOBJ_ATT2_PB_Msk = 0xF
|
||||
)
|
||||
|
||||
// Constants for SOUND: sound control
|
||||
const (
|
||||
SSW_INC = 0x0 // Increasing sweep rate
|
||||
SSW_DEC = 0x0008 // Decreasing sweep rate
|
||||
SSW_OFF = 0x0008 // Disable sweep altogether
|
||||
|
||||
SSQR_DUTY1_8 = 0x0 // 12.5% duty cycle (#-------)
|
||||
SSQR_DUTY1_4 = 0x0040 // 25% duty cycle (##------)
|
||||
SSQR_DUTY1_2 = 0x0080 // 50% duty cycle (####----)
|
||||
SSQR_DUTY3_4 = 0x00C0 // 75% duty cycle (######--) Equivalent to 25%
|
||||
SSQR_INC = 0x0 // Increasing volume
|
||||
SSQR_DEC = 0x0800 // Decreasing volume
|
||||
|
||||
SFREQ_HOLD = 0x0 // Continuous play
|
||||
SFREQ_TIMED = 0x4000 // Timed play
|
||||
SFREQ_RESET = 0x8000 // Reset sound
|
||||
|
||||
SDMG_SQR1 = 0x01
|
||||
SDMG_SQR2 = 0x02
|
||||
SDMG_WAVE = 0x04
|
||||
SDMG_NOISE = 0x08
|
||||
|
||||
SDMG_LSQR1 = 0x0100 // Enable channel 1 on left
|
||||
SDMG_LSQR2 = 0x0200 // Enable channel 2 on left
|
||||
SDMG_LWAVE = 0x0400 // Enable channel 3 on left
|
||||
SDMG_LNOISE = 0x0800 // Enable channel 4 on left
|
||||
SDMG_RSQR1 = 0x1000 // Enable channel 1 on right
|
||||
SDMG_RSQR2 = 0x2000 // Enable channel 2 on right
|
||||
SDMG_RWAVE = 0x4000 // Enable channel 3 on right
|
||||
SDMG_RNOISE = 0x8000 // Enable channel 4 on right
|
||||
|
||||
SDS_DMG25 = 0x0 // Tone generators at 25% volume
|
||||
SDS_DMG50 = 0x0001 // Tone generators at 50% volume
|
||||
SDS_DMG100 = 0x0002 // Tone generators at 100% volume
|
||||
SDS_A50 = 0x0 // Direct Sound A at 50% volume
|
||||
SDS_A100 = 0x0004 // Direct Sound A at 100% volume
|
||||
SDS_B50 = 0x0 // Direct Sound B at 50% volume
|
||||
SDS_B100 = 0x0008 // Direct Sound B at 100% volume
|
||||
SDS_AR = 0x0100 // Enable Direct Sound A on right
|
||||
SDS_AL = 0x0200 // Enable Direct Sound A on left
|
||||
SDS_ATMR0 = 0x0 // Direct Sound A to use timer 0
|
||||
SDS_ATMR1 = 0x0400 // Direct Sound A to use timer 1
|
||||
SDS_ARESET = 0x0800 // Reset FIFO of Direct Sound A
|
||||
SDS_BR = 0x1000 // Enable Direct Sound B on right
|
||||
SDS_BL = 0x2000 // Enable Direct Sound B on left
|
||||
SDS_BTMR0 = 0x0 // Direct Sound B to use timer 0
|
||||
SDS_BTMR1 = 0x4000 // Direct Sound B to use timer 1
|
||||
SDS_BRESET = 0x8000 // Reset FIFO of Direct Sound B
|
||||
|
||||
SSTAT_SQR1 = 0x0001 // (R) Channel 1 status
|
||||
SSTAT_SQR2 = 0x0002 // (R) Channel 2 status
|
||||
SSTAT_WAVE = 0x0004 // (R) Channel 3 status
|
||||
SSTAT_NOISE = 0x0008 // (R) Channel 4 status
|
||||
SSTAT_DISABLE = 0 // Disable sound
|
||||
SSTAT_ENABLE = 0x0080 // Enable sound. NOTE: enable before using any other sound regs
|
||||
)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build go1.23
|
||||
|
||||
package cm
|
||||
|
||||
import "structs"
|
||||
@@ -1,11 +0,0 @@
|
||||
//go:build !go1.23
|
||||
|
||||
package cm
|
||||
|
||||
// HostLayout marks a struct as using host memory layout.
|
||||
// See [structs.HostLayout] in Go 1.23 or later.
|
||||
type HostLayout struct {
|
||||
_ hostLayout // prevent accidental conversion with plain struct{}
|
||||
}
|
||||
|
||||
type hostLayout struct{}
|
||||
@@ -0,0 +1,45 @@
|
||||
//go:build wasip1
|
||||
|
||||
// Internal-test helpers exposed via go:linkname so user code can drive
|
||||
// the deadline-aware Read/Write loop without becoming a stdlib package.
|
||||
// Not part of the public API; the names are intentionally awkward to
|
||||
// signal "for tests only".
|
||||
|
||||
package poll
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// pollTestReadWithDeadline opens a pollable FD wrapper for sysfd, sets
|
||||
// a read deadline d into the future, calls Read once, and returns
|
||||
// (n, err). Caller is responsible for closing sysfd.
|
||||
//
|
||||
//go:linkname pollTestReadWithDeadline
|
||||
func pollTestReadWithDeadline(sysfd int, d time.Duration, p []byte) (int, error) {
|
||||
fd := &FD{Sysfd: sysfd, IsStream: true}
|
||||
// Best-effort init; ignore error so a caller using a not-fcntl-able FD
|
||||
// (stdin under wazero, etc.) still gets to test the deadline path on
|
||||
// whatever park behaviour the runtime gives.
|
||||
_ = fd.Init("test", true)
|
||||
if err := fd.SetReadDeadline(time.Now().Add(d)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fd.Read(p)
|
||||
}
|
||||
|
||||
// pollTestSetNonblock toggles O_NONBLOCK on a raw sysfd. Useful in
|
||||
// tests when the caller wants to ensure the FD is in nonblocking mode
|
||||
// before calling pollTestReadWithDeadline (Init is best-effort and may
|
||||
// silently skip).
|
||||
//
|
||||
//go:linkname pollTestSetNonblock
|
||||
func pollTestSetNonblock(sysfd int) error {
|
||||
flags, err := syscall.Fcntl(sysfd, syscall.F_GETFL, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = syscall.Fcntl(sysfd, syscall.F_SETFL, flags|syscall.O_NONBLOCK)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
//go:build wasip1
|
||||
|
||||
// Package poll is a minimal subset of upstream Go's internal/poll, scoped
|
||||
// to what is needed to back a wasip1 net implementation on top of
|
||||
// TinyGo's cooperative-scheduler netpoll integration.
|
||||
//
|
||||
// On wasip1 the cooperative scheduler integrates poll_oneoff with FD
|
||||
// waiters (see runtime/netpoll_wasip1.go and syscall/syscall_libc_wasip1.go).
|
||||
// This package wraps the syscall layer to:
|
||||
//
|
||||
// - own the O_NONBLOCK policy decision (set on Init for pollable FDs),
|
||||
// unblocking the EAGAIN→park retry loop that syscall.Read already has;
|
||||
// - provide a Go-shaped FD type that net.* can use without reaching
|
||||
// into syscall directly;
|
||||
// - thread per-FD read/write deadlines through a runtime helper that
|
||||
// lets a time.AfterFunc callback wake the parked goroutine;
|
||||
// - dispatch socket FDs through wasi sock_recv / sock_send / sock_accept
|
||||
// / sock_shutdown so net.Conn/net.Listener (via upstream Go's
|
||||
// net/file_wasip1.go) work end-to-end.
|
||||
package poll
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"internal/task"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ErrFileClosing is returned when a Read or Write is started on a closed FD.
|
||||
var ErrFileClosing = errors.New("use of closed file")
|
||||
|
||||
// ErrNetClosing is returned for network operations on a closed FD.
|
||||
var ErrNetClosing = errors.New("use of closed network connection")
|
||||
|
||||
// ErrDeadlineExceeded is returned by Read/Write when a deadline expired.
|
||||
// Matches the error returned by os.IsTimeout-style helpers.
|
||||
var ErrDeadlineExceeded = errors.New("i/o timeout")
|
||||
|
||||
// ErrNoDeadline is returned if SetDeadline is called on an FD whose
|
||||
// underlying type does not support deadlines.
|
||||
var ErrNoDeadline = errors.New("file type does not support deadline")
|
||||
|
||||
// pollMode constants must mirror runtime/netpoll_wasip1.go's pollRead/
|
||||
// pollWrite values.
|
||||
const (
|
||||
pollModeRead uint8 = 1
|
||||
pollModeWrite uint8 = 2
|
||||
)
|
||||
|
||||
//go:linkname runtime_netpoll_addwait runtime.runtime_netpoll_addwait
|
||||
func runtime_netpoll_addwait(fd uint32, mode uint8) uintptr
|
||||
|
||||
//go:linkname runtime_netpoll_done runtime.runtime_netpoll_done
|
||||
func runtime_netpoll_done(pd uintptr)
|
||||
|
||||
//go:linkname runtime_netpoll_pdfired runtime.runtime_netpoll_pdfired
|
||||
func runtime_netpoll_pdfired(pd uintptr) bool
|
||||
|
||||
//go:linkname runtime_netpoll_wake runtime.runtime_netpoll_wake
|
||||
func runtime_netpoll_wake(pd uintptr)
|
||||
|
||||
//go:linkname fd_fdstat_get_type syscall.fd_fdstat_get_type
|
||||
func fd_fdstat_get_type(fd int) (syscall.Filetype, error)
|
||||
|
||||
// wasiIovec / wasiCiovec mirror wasi-snapshot-preview1's iovec / ciovec
|
||||
// records: a buffer pointer plus a 32-bit length. Marshalled inline with
|
||||
// each fd_read / fd_write / sock_recv / sock_send call.
|
||||
type wasiIovec struct {
|
||||
buf *byte
|
||||
bufLen uint32
|
||||
}
|
||||
|
||||
// fd_read / fd_write are bound directly here rather than going through
|
||||
// wasi-libc so the deadline-aware Read/Write loop can observe EAGAIN
|
||||
// cleanly. Outside package runtime, TinyGo's wasmimport binding
|
||||
// restricts us to unsafe.Pointer for struct args and uint32 for the
|
||||
// errno result; the wasi spec uses *iovec and uint16 respectively but
|
||||
// the wire layout is identical.
|
||||
//
|
||||
//go:wasmimport wasi_snapshot_preview1 fd_read
|
||||
func wasi_fd_read(fd int32, iovs unsafe.Pointer, iovsLen uint32, nread unsafe.Pointer) uint32
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 fd_write
|
||||
func wasi_fd_write(fd int32, iovs unsafe.Pointer, iovsLen uint32, nwritten unsafe.Pointer) uint32
|
||||
|
||||
// sock_recv / sock_send are the socket-data wasi syscalls used when the
|
||||
// FD's Filetype indicates a socket. We bind them here for the same
|
||||
// reason as fd_read / fd_write: the deadline-aware loop wants direct
|
||||
// access to the EAGAIN signal without libc's translation layer.
|
||||
//
|
||||
//go:wasmimport wasi_snapshot_preview1 sock_recv
|
||||
func wasi_sock_recv(fd int32, riData unsafe.Pointer, riDataLen uint32, riFlags uint32, roDatalen unsafe.Pointer, roFlags unsafe.Pointer) uint32
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 sock_send
|
||||
func wasi_sock_send(fd int32, siData unsafe.Pointer, siDataLen uint32, siFlags uint32, soDatalen unsafe.Pointer) uint32
|
||||
|
||||
// SysFile carries per-FD bookkeeping that upstream Go's poll.FD uses to
|
||||
// share an underlying syscall FD between an os.File and a net.Conn (see
|
||||
// Copy below). RefCountPtr / RefCount handle the shared-ownership case;
|
||||
// Filetype caches the wasi filetype so socket-vs-file dispatch in Read /
|
||||
// Write is a single integer compare on the hot path.
|
||||
//
|
||||
// wasip1 is single-threaded, so the refcount is a plain int — no atomics
|
||||
// needed. Match upstream's field naming for source-level compatibility
|
||||
// with code that constructs FDs via struct literal (e.g. upstream's
|
||||
// net/fd_fake.go).
|
||||
type SysFile struct {
|
||||
RefCountPtr *int32
|
||||
RefCount int32
|
||||
Filetype uint32
|
||||
}
|
||||
|
||||
// init lazily allocates the refcount the first time it's needed (i.e.
|
||||
// the first Init or Copy on this FD). A zero SysFile starts at refcount
|
||||
// 1 — the FD's sole owner is the caller.
|
||||
func (s *SysFile) init() {
|
||||
if s.RefCountPtr == nil {
|
||||
s.RefCount = 1
|
||||
s.RefCountPtr = &s.RefCount
|
||||
}
|
||||
}
|
||||
|
||||
// ref increments the shared refcount and returns a SysFile that points
|
||||
// at the same counter. Used by FD.Copy.
|
||||
func (s *SysFile) ref() SysFile {
|
||||
s.init()
|
||||
*s.RefCountPtr++
|
||||
return SysFile{RefCountPtr: s.RefCountPtr, Filetype: s.Filetype}
|
||||
}
|
||||
|
||||
// destroy decrements the refcount and reports whether the underlying
|
||||
// syscall FD should now be closed (i.e. this was the last owner).
|
||||
func (s *SysFile) destroy() bool {
|
||||
if s.RefCountPtr == nil {
|
||||
return true
|
||||
}
|
||||
*s.RefCountPtr--
|
||||
return *s.RefCountPtr <= 0
|
||||
}
|
||||
|
||||
// FD is the wasip1 file/socket descriptor wrapped with the bookkeeping
|
||||
// that net and os rely on. It owns the lifecycle of the underlying
|
||||
// syscall FD (modulo the Copy / refcount handoff between os.File and
|
||||
// net.Conn).
|
||||
//
|
||||
// The struct mirrors upstream Go's internal/poll.FD field naming
|
||||
// (Sysfd, IsStream, ZeroReadIsEOF, SysFile) so that upstream's
|
||||
// net/file_wasip1.go and net/fd_fake.go can construct one via struct
|
||||
// literal without modification.
|
||||
type FD struct {
|
||||
Sysfd int
|
||||
SysFile SysFile
|
||||
IsStream bool
|
||||
ZeroReadIsEOF bool
|
||||
closed bool
|
||||
|
||||
// Per-FD deadlines, zero means "no deadline". Subsequent Read/Write
|
||||
// calls observe whatever the current value is at call time; an
|
||||
// in-flight call uses the deadline it captured at its start.
|
||||
rDeadline time.Time
|
||||
wDeadline time.Time
|
||||
}
|
||||
|
||||
// Init readies the FD for use. When pollable is true (i.e. the FD might
|
||||
// block — sockets, pipes, FIFOs), Init sets O_NONBLOCK so that
|
||||
// Read/Write enter the EAGAIN→park retry loop instead of blocking the
|
||||
// entire wasm module.
|
||||
//
|
||||
// Init also caches the wasi filetype so the Read/Write hot path can
|
||||
// dispatch socket vs file with a single integer compare.
|
||||
//
|
||||
// The net argument is currently ignored but kept for parity with
|
||||
// upstream Go.
|
||||
func (fd *FD) Init(net string, pollable bool) error {
|
||||
_ = net
|
||||
fd.SysFile.init()
|
||||
if ft, err := fd_fdstat_get_type(fd.Sysfd); err == nil {
|
||||
fd.SysFile.Filetype = uint32(ft)
|
||||
}
|
||||
if !pollable {
|
||||
return nil
|
||||
}
|
||||
flags, err := syscall.Fcntl(fd.Sysfd, syscall.F_GETFL, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flags&syscall.O_NONBLOCK != 0 {
|
||||
return nil
|
||||
}
|
||||
if _, err := syscall.Fcntl(fd.Sysfd, syscall.F_SETFL, flags|syscall.O_NONBLOCK); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy returns a duplicate FD that shares the underlying Sysfd through
|
||||
// the SysFile refcount. The original and the copy can independently
|
||||
// call Close — only the last one actually issues the syscall. Used by
|
||||
// upstream net/file_wasip1.go to hand a socket FD off from an os.File
|
||||
// to a net.Listener / net.Conn.
|
||||
func (fd *FD) Copy() FD {
|
||||
return FD{
|
||||
Sysfd: fd.Sysfd,
|
||||
SysFile: fd.SysFile.ref(),
|
||||
IsStream: fd.IsStream,
|
||||
ZeroReadIsEOF: fd.ZeroReadIsEOF,
|
||||
}
|
||||
}
|
||||
|
||||
// Close marks the FD closed. The underlying syscall FD is only released
|
||||
// when the refcount drops to zero — earlier Close calls (e.g. on the
|
||||
// os.File side after a successful net.FileConn handoff) just decrement.
|
||||
func (fd *FD) Close() error {
|
||||
if fd.closed {
|
||||
return ErrFileClosing
|
||||
}
|
||||
fd.closed = true
|
||||
if !fd.SysFile.destroy() {
|
||||
return nil
|
||||
}
|
||||
return syscall.Close(fd.Sysfd)
|
||||
}
|
||||
|
||||
// Exist reports whether fd points to an actual FD wrapper (non-nil).
|
||||
// Callers in package os hold a *FD via a per-target alias and need to
|
||||
// nil-check without depending on the concrete type being a pointer.
|
||||
func (fd *FD) Exist() bool { return fd != nil }
|
||||
|
||||
// CloseFunc is the hook upstream net's poll package exposes so tests
|
||||
// can intercept Close. We just point at syscall.Close.
|
||||
var CloseFunc func(int) error = syscall.Close
|
||||
|
||||
// String is an internal string definition for methods/functions that
|
||||
// shouldn't be used outside the stdlib. Upstream net references it
|
||||
// from rawconn.go to mark methods as not-for-external-use.
|
||||
type String string
|
||||
|
||||
// RawControl / RawRead / RawWrite back syscall.RawConn's three callback
|
||||
// methods. They invoke f with the underlying FD; the bool return of
|
||||
// RawRead / RawWrite controls retry-on-EAGAIN, which we implement by
|
||||
// parking the goroutine on the netpoll registry and retrying until f
|
||||
// returns true (the same loop upstream uses).
|
||||
func (fd *FD) RawControl(f func(uintptr)) error {
|
||||
if fd.closed {
|
||||
return ErrFileClosing
|
||||
}
|
||||
f(uintptr(fd.Sysfd))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fd *FD) RawRead(f func(uintptr) bool) error {
|
||||
if fd.closed {
|
||||
return ErrFileClosing
|
||||
}
|
||||
for {
|
||||
if f(uintptr(fd.Sysfd)) {
|
||||
return nil
|
||||
}
|
||||
wait(fd.Sysfd, pollModeRead)
|
||||
}
|
||||
}
|
||||
|
||||
func (fd *FD) RawWrite(f func(uintptr) bool) error {
|
||||
if fd.closed {
|
||||
return ErrFileClosing
|
||||
}
|
||||
for {
|
||||
if f(uintptr(fd.Sysfd)) {
|
||||
return nil
|
||||
}
|
||||
wait(fd.Sysfd, pollModeWrite)
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown calls wasi sock_shutdown. how is one of syscall.SHUT_RD,
|
||||
// SHUT_WR, SHUT_RDWR.
|
||||
func (fd *FD) Shutdown(how int) error {
|
||||
return syscall.Shutdown(fd.Sysfd, how)
|
||||
}
|
||||
|
||||
// Accept loops over wasi sock_accept, parking the goroutine on EAGAIN
|
||||
// (waiting for a new connection) through the netpoll registry. Returns
|
||||
// (newfd, sockaddr=nil, errcall, error) — sockaddr is always nil because
|
||||
// wasi sock_accept doesn't return one.
|
||||
func (fd *FD) Accept() (int, syscall.Sockaddr, string, error) {
|
||||
deadline := fd.rDeadline
|
||||
for {
|
||||
if !deadline.IsZero() && !time.Now().Before(deadline) {
|
||||
return -1, nil, "accept", ErrDeadlineExceeded
|
||||
}
|
||||
nfd, _, err := syscall.Accept(fd.Sysfd)
|
||||
if err == nil {
|
||||
return nfd, nil, "", nil
|
||||
}
|
||||
if err == syscall.EINTR {
|
||||
continue
|
||||
}
|
||||
if err != syscall.EAGAIN {
|
||||
return -1, nil, "accept", err
|
||||
}
|
||||
if deadline.IsZero() {
|
||||
wait(fd.Sysfd, pollModeRead)
|
||||
} else {
|
||||
if perr := fd.parkUntil(pollModeRead, deadline); perr != nil {
|
||||
return -1, nil, "accept", perr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isSocket reports whether the cached Filetype is a stream or datagram
|
||||
// socket. False if Init was never called (Filetype stays 0 = UNKNOWN).
|
||||
func (fd *FD) isSocket() bool {
|
||||
ft := syscall.Filetype(fd.SysFile.Filetype)
|
||||
return ft == syscall.FILETYPE_SOCKET_STREAM || ft == syscall.FILETYPE_SOCKET_DGRAM
|
||||
}
|
||||
|
||||
// Read reads from the FD into p. Sockets dispatch to sock_recv (which
|
||||
// honours fd.rDeadline directly); regular files/pipes go through
|
||||
// syscall.Read for the no-deadline fast path or readWithDeadline when
|
||||
// a deadline is set.
|
||||
func (fd *FD) Read(p []byte) (int, error) {
|
||||
if fd.closed {
|
||||
return 0, ErrFileClosing
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if fd.isSocket() {
|
||||
return fd.sockRecv(p)
|
||||
}
|
||||
if fd.rDeadline.IsZero() {
|
||||
return syscall.Read(fd.Sysfd, p)
|
||||
}
|
||||
return fd.readWithDeadline(p)
|
||||
}
|
||||
|
||||
// Write writes p to the FD. Sockets dispatch to sock_send. Regular
|
||||
// files / pipes go through syscall.Write or writeWithDeadline.
|
||||
func (fd *FD) Write(p []byte) (int, error) {
|
||||
if fd.closed {
|
||||
return 0, ErrFileClosing
|
||||
}
|
||||
if fd.isSocket() {
|
||||
return fd.sockSend(p)
|
||||
}
|
||||
if fd.wDeadline.IsZero() {
|
||||
return syscall.Write(fd.Sysfd, p)
|
||||
}
|
||||
return fd.writeWithDeadline(p)
|
||||
}
|
||||
|
||||
// Pread reads from the FD at the given offset. Always file semantics —
|
||||
// sockets aren't seekable so this never goes through sock_recv.
|
||||
func (fd *FD) Pread(p []byte, off int64) (int, error) {
|
||||
if fd.closed {
|
||||
return 0, ErrFileClosing
|
||||
}
|
||||
return syscall.Pread(fd.Sysfd, p, off)
|
||||
}
|
||||
|
||||
// Pwrite writes to the FD at the given offset.
|
||||
func (fd *FD) Pwrite(p []byte, off int64) (int, error) {
|
||||
if fd.closed {
|
||||
return 0, ErrFileClosing
|
||||
}
|
||||
return syscall.Pwrite(fd.Sysfd, p, off)
|
||||
}
|
||||
|
||||
// SetDeadline sets both the read and write deadlines.
|
||||
func (fd *FD) SetDeadline(t time.Time) error {
|
||||
fd.rDeadline = t
|
||||
fd.wDeadline = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the deadline for future Read calls. A zero t
|
||||
// clears the deadline.
|
||||
func (fd *FD) SetReadDeadline(t time.Time) error {
|
||||
fd.rDeadline = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the deadline for future Write calls.
|
||||
func (fd *FD) SetWriteDeadline(t time.Time) error {
|
||||
fd.wDeadline = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// readWithDeadline implements the EAGAIN→park retry loop with deadline
|
||||
// cancellation for non-socket FDs. The deadline is captured at function
|
||||
// entry; later SetReadDeadline calls don't affect this in-flight Read.
|
||||
func (fd *FD) readWithDeadline(p []byte) (int, error) {
|
||||
deadline := fd.rDeadline
|
||||
iov := wasiIovec{buf: &p[0], bufLen: uint32(len(p))}
|
||||
for {
|
||||
if !time.Now().Before(deadline) {
|
||||
return 0, ErrDeadlineExceeded
|
||||
}
|
||||
var n uint32
|
||||
errno := wasi_fd_read(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, unsafe.Pointer(&n))
|
||||
switch errno {
|
||||
case 0:
|
||||
return int(n), nil
|
||||
case wasiErrnoIntr:
|
||||
continue
|
||||
case wasiErrnoAgain:
|
||||
if err := fd.parkUntil(pollModeRead, deadline); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, syscall.Errno(errno)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fd *FD) writeWithDeadline(p []byte) (int, error) {
|
||||
deadline := fd.wDeadline
|
||||
var nn int
|
||||
for {
|
||||
if !time.Now().Before(deadline) {
|
||||
return nn, ErrDeadlineExceeded
|
||||
}
|
||||
if nn == len(p) {
|
||||
return nn, nil
|
||||
}
|
||||
buf := p[nn:]
|
||||
iov := wasiIovec{buf: &buf[0], bufLen: uint32(len(buf))}
|
||||
var n uint32
|
||||
errno := wasi_fd_write(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, unsafe.Pointer(&n))
|
||||
switch errno {
|
||||
case 0:
|
||||
nn += int(n)
|
||||
case wasiErrnoIntr:
|
||||
// retry
|
||||
case wasiErrnoAgain:
|
||||
if err := fd.parkUntil(pollModeWrite, deadline); err != nil {
|
||||
return nn, err
|
||||
}
|
||||
default:
|
||||
return nn, syscall.Errno(errno)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sockRecv is the socket sibling of readWithDeadline / syscall.Read.
|
||||
// Always issues sock_recv, regardless of deadline state, so callers
|
||||
// that don't want fd_read on a socket get a guaranteed sock_recv path.
|
||||
func (fd *FD) sockRecv(p []byte) (int, error) {
|
||||
deadline := fd.rDeadline
|
||||
iov := wasiIovec{buf: &p[0], bufLen: uint32(len(p))}
|
||||
for {
|
||||
if !deadline.IsZero() && !time.Now().Before(deadline) {
|
||||
return 0, ErrDeadlineExceeded
|
||||
}
|
||||
var n uint32
|
||||
var roFlags uint32
|
||||
errno := wasi_sock_recv(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, 0, unsafe.Pointer(&n), unsafe.Pointer(&roFlags))
|
||||
switch errno {
|
||||
case 0:
|
||||
return int(n), nil
|
||||
case wasiErrnoIntr:
|
||||
continue
|
||||
case wasiErrnoAgain:
|
||||
if deadline.IsZero() {
|
||||
wait(fd.Sysfd, pollModeRead)
|
||||
} else if err := fd.parkUntil(pollModeRead, deadline); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, syscall.Errno(errno)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fd *FD) sockSend(p []byte) (int, error) {
|
||||
deadline := fd.wDeadline
|
||||
var nn int
|
||||
for {
|
||||
if !deadline.IsZero() && !time.Now().Before(deadline) {
|
||||
return nn, ErrDeadlineExceeded
|
||||
}
|
||||
if nn == len(p) {
|
||||
return nn, nil
|
||||
}
|
||||
buf := p[nn:]
|
||||
iov := wasiIovec{buf: &buf[0], bufLen: uint32(len(buf))}
|
||||
var n uint32
|
||||
errno := wasi_sock_send(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, 0, unsafe.Pointer(&n))
|
||||
switch errno {
|
||||
case 0:
|
||||
nn += int(n)
|
||||
case wasiErrnoIntr:
|
||||
// retry
|
||||
case wasiErrnoAgain:
|
||||
if deadline.IsZero() {
|
||||
wait(fd.Sysfd, pollModeWrite)
|
||||
} else if err := fd.parkUntil(pollModeWrite, deadline); err != nil {
|
||||
return nn, err
|
||||
}
|
||||
default:
|
||||
return nn, syscall.Errno(errno)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wasi snapshot preview1 errno values, kept here so the read/write loops
|
||||
// can compare without dragging in the full syscall errno table for a
|
||||
// switch on three constants. These match the wasi spec exactly; see also
|
||||
// syscall_libc_wasi.go in package syscall.
|
||||
const (
|
||||
wasiErrnoAgain uint32 = 6
|
||||
wasiErrnoIntr uint32 = 27
|
||||
)
|
||||
|
||||
// wait parks the current goroutine until the FD becomes ready in the
|
||||
// given direction. No deadline; mirrors the helper of the same name in
|
||||
// package syscall (intentionally duplicated rather than linknamed
|
||||
// across the package boundary — see project memory on shim avoidance).
|
||||
func wait(fd int, mode uint8) {
|
||||
pd := runtime_netpoll_addwait(uint32(fd), mode)
|
||||
task.Pause()
|
||||
runtime_netpoll_done(pd)
|
||||
}
|
||||
|
||||
// parkUntil parks the current goroutine on (fd, mode) with a deadline.
|
||||
// Returns nil if the FD became ready or the timer fired (caller's loop
|
||||
// re-checks the deadline at top); ErrDeadlineExceeded if the deadline
|
||||
// was already in the past.
|
||||
//
|
||||
// Race handling: the deadline timer's callback and pollIO's event walk
|
||||
// can both target the same pollDesc. The pd.fired flag guards against
|
||||
// double-pushing the task to the run queue; whichever arrives second
|
||||
// is a no-op.
|
||||
func (fd *FD) parkUntil(mode uint8, deadline time.Time) error {
|
||||
d := time.Until(deadline)
|
||||
if d <= 0 {
|
||||
return ErrDeadlineExceeded
|
||||
}
|
||||
pd := runtime_netpoll_addwait(uint32(fd.Sysfd), mode)
|
||||
timer := time.AfterFunc(d, func() {
|
||||
runtime_netpoll_wake(pd)
|
||||
})
|
||||
task.Pause()
|
||||
timer.Stop()
|
||||
runtime_netpoll_done(pd)
|
||||
return nil
|
||||
}
|
||||
@@ -754,6 +754,98 @@ func (t *RawType) FieldAlign() int {
|
||||
return t.Align()
|
||||
}
|
||||
|
||||
// ConvertibleTo returns whether a value of type t can be converted to a variable of type u
|
||||
func (r *RawType) ConvertibleTo(u *RawType) bool {
|
||||
|
||||
// This logic is mostly copied from Value.CanConvert
|
||||
|
||||
switch r.Kind() {
|
||||
case Int, Int8, Int16, Int32, Int64:
|
||||
switch u.Kind() {
|
||||
case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
|
||||
return true
|
||||
case Float32, Float64:
|
||||
return true
|
||||
case String:
|
||||
return true
|
||||
}
|
||||
|
||||
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
|
||||
switch u.Kind() {
|
||||
case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
|
||||
return true
|
||||
case Float32, Float64:
|
||||
return true
|
||||
case String:
|
||||
return true
|
||||
}
|
||||
|
||||
case Float32, Float64:
|
||||
switch u.Kind() {
|
||||
case Int, Int8, Int16, Int32, Int64:
|
||||
return true
|
||||
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
|
||||
return true
|
||||
case Float32, Float64:
|
||||
return true
|
||||
}
|
||||
|
||||
case Complex64, Complex128:
|
||||
switch u.Kind() {
|
||||
case Complex64, Complex128:
|
||||
return true
|
||||
}
|
||||
|
||||
case Slice:
|
||||
switch u.Kind() {
|
||||
case Array:
|
||||
// This may fail at runtime if there isn't room
|
||||
if r.elem() == u.elem() {
|
||||
return true
|
||||
}
|
||||
|
||||
case Pointer:
|
||||
// This may fail at runtime if there isn't room
|
||||
if u.elem().Kind() == Array && r.elem() == u.elem().elem() {
|
||||
return true
|
||||
}
|
||||
|
||||
case String:
|
||||
// bytes or runes
|
||||
if !r.elem().isNamed() && (r.elem().Kind() == Uint8 || r.elem().Kind() == Int32) {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case String:
|
||||
// bytes or runes
|
||||
if u.Kind() == Slice && !u.elem().isNamed() && (u.elem().Kind() == Uint8 || u.elem().Kind() == Int32) {
|
||||
return true
|
||||
}
|
||||
|
||||
case Pointer:
|
||||
if !r.isNamed() && u.Kind() == Pointer && !u.isNamed() && r.elem().underlying() == u.elem().underlying() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if r.underlying() == u.underlying() {
|
||||
return true
|
||||
}
|
||||
|
||||
if u.Kind() == Interface && u.NumMethod() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO(dgryski): Unimplemented
|
||||
// struct types
|
||||
// channels
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
// AssignableTo returns whether a value of type t can be assigned to a variable
|
||||
// of type u.
|
||||
func (t *RawType) AssignableTo(u Type) bool {
|
||||
|
||||
@@ -51,6 +51,22 @@ func (v Value) isRO() bool {
|
||||
return v.flags&(valueFlagRO) != 0
|
||||
}
|
||||
|
||||
// These are package methods, not methods on Value, since the reflect.Value embeds a reflectlite.Value, so any
|
||||
// methods added to reflectlite.Value are visible to the user.
|
||||
|
||||
func IsRO(v Value) bool {
|
||||
return v.isRO()
|
||||
}
|
||||
|
||||
func MakeRO(v Value, ro bool) Value {
|
||||
if ro {
|
||||
v.flags |= valueFlagRO
|
||||
} else {
|
||||
v.flags &^= valueFlagRO
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Value) checkRO() {
|
||||
if v.isRO() {
|
||||
panic("reflect: value is not settable")
|
||||
@@ -1485,13 +1501,11 @@ func convertOp(src Value, typ Type) (Value, bool) {
|
||||
return cvtFloat(src, rtype), true
|
||||
}
|
||||
|
||||
/*
|
||||
case Complex64, Complex128:
|
||||
switch src.Kind() {
|
||||
case Complex64, Complex128:
|
||||
return cvtComplex
|
||||
}
|
||||
*/
|
||||
case Complex64, Complex128:
|
||||
switch rtype := typ.(*RawType); rtype.Kind() {
|
||||
case Complex64, Complex128:
|
||||
return cvtComplex(src, rtype), true
|
||||
}
|
||||
|
||||
case Slice:
|
||||
switch rtype := typ.(*RawType); rtype.Kind() {
|
||||
@@ -1534,6 +1548,12 @@ func convertOp(src Value, typ Type) (Value, bool) {
|
||||
return cvtStringRunes(src, rtype), true
|
||||
}
|
||||
}
|
||||
|
||||
case Pointer:
|
||||
rtype := typ.(*RawType)
|
||||
if rtype.Kind() == Pointer && !src.typecode.isNamed() && !rtype.isNamed() && src.typecode.elem().underlying() == rtype.elem().underlying() {
|
||||
return cvtDirect(src, rtype), true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(dgryski): Unimplemented:
|
||||
@@ -1578,6 +1598,18 @@ func cvtFloat(v Value, t *RawType) Value {
|
||||
return makeFloat(v.flags, v.Float(), t)
|
||||
}
|
||||
|
||||
func cvtDirect(v Value, t *RawType) Value {
|
||||
return Value{
|
||||
typecode: t,
|
||||
value: v.value,
|
||||
flags: v.flags,
|
||||
}
|
||||
}
|
||||
|
||||
func cvtComplex(v Value, t *RawType) Value {
|
||||
return makeComplex(v.flags, v.Complex(), t)
|
||||
}
|
||||
|
||||
//go:linkname stringToBytes runtime.stringToBytes
|
||||
func stringToBytes(x string) []byte
|
||||
|
||||
@@ -1661,20 +1693,76 @@ func makeFloat32(flags valueFlags, f float32, t *RawType) Value {
|
||||
return v
|
||||
}
|
||||
|
||||
func cvtIntString(src Value, t *RawType) Value {
|
||||
panic("cvtUintString: unimplemented")
|
||||
func makeComplex(flags valueFlags, f complex128, t *RawType) Value {
|
||||
size := t.Size()
|
||||
|
||||
v := Value{
|
||||
typecode: t,
|
||||
flags: flags,
|
||||
}
|
||||
|
||||
ptr := unsafe.Pointer(&v.value)
|
||||
if size > unsafe.Sizeof(uintptr(0)) {
|
||||
ptr = alloc(size, nil)
|
||||
v.value = ptr
|
||||
}
|
||||
|
||||
switch size {
|
||||
case 8:
|
||||
*(*complex64)(ptr) = complex64(f)
|
||||
case 16:
|
||||
*(*complex128)(ptr) = f
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func cvtUintString(src Value, t *RawType) Value {
|
||||
panic("cvtUintString: unimplemented")
|
||||
func cvtIntString(v Value, t *RawType) Value {
|
||||
s := "\uFFFD"
|
||||
if x := v.Int(); int64(rune(x)) == x {
|
||||
s = string(rune(x))
|
||||
}
|
||||
return Value{
|
||||
typecode: t,
|
||||
value: unsafe.Pointer(&s),
|
||||
flags: v.flags,
|
||||
}
|
||||
}
|
||||
|
||||
func cvtStringRunes(src Value, t *RawType) Value {
|
||||
panic("cvsStringRunes: unimplemented")
|
||||
func cvtUintString(v Value, t *RawType) Value {
|
||||
s := "\uFFFD"
|
||||
if x := v.Uint(); uint64(rune(x)) == x {
|
||||
s = string(rune(x))
|
||||
}
|
||||
|
||||
return Value{
|
||||
typecode: t,
|
||||
value: unsafe.Pointer(&s),
|
||||
flags: v.flags,
|
||||
}
|
||||
}
|
||||
|
||||
func cvtRunesString(src Value, t *RawType) Value {
|
||||
panic("cvsRunesString: unimplemented")
|
||||
//go:linkname stringToRunes runtime.stringToRunes
|
||||
func stringToRunes(s string) []rune
|
||||
|
||||
func cvtStringRunes(v Value, t *RawType) Value {
|
||||
b := stringToRunes(*(*string)(v.value))
|
||||
return Value{
|
||||
typecode: t,
|
||||
value: unsafe.Pointer(&b),
|
||||
flags: v.flags,
|
||||
}
|
||||
}
|
||||
|
||||
//go:linkname stringFromRunes runtime.stringFromRunes
|
||||
func stringFromRunes(r []rune) string
|
||||
|
||||
func cvtRunesString(v Value, t *RawType) Value {
|
||||
s := stringFromRunes(*(*[]rune)(v.value))
|
||||
return Value{
|
||||
typecode: t,
|
||||
value: unsafe.Pointer(&s),
|
||||
flags: v.flags,
|
||||
}
|
||||
}
|
||||
|
||||
//go:linkname slicePanic runtime.slicePanic
|
||||
|
||||
@@ -15,7 +15,7 @@ func GetRandom(p []byte, flags GetRandomFlag) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
libc_arc4random_buf(unsafe.Pointer(&p[0]), uint(len(p)))
|
||||
libc_arc4random_buf(unsafe.Pointer(unsafe.SliceData(p)), uint(len(p)))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -42,3 +42,8 @@ func SystemStack() uintptr {
|
||||
runtimePanic("scheduler is disabled")
|
||||
return 0 // unreachable
|
||||
}
|
||||
|
||||
func GoroutineStack() uintptr {
|
||||
// No separate goroutine stack without a scheduler.
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -11,3 +11,14 @@ uintptr_t SystemStack() {
|
||||
);
|
||||
return sp;
|
||||
}
|
||||
|
||||
uintptr_t GoroutineStack() {
|
||||
uintptr_t sp;
|
||||
asm volatile(
|
||||
"mrs %0, PSP"
|
||||
: "=r"(sp)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
return sp;
|
||||
}
|
||||
|
||||
@@ -70,3 +70,10 @@ func (s *state) pause() {
|
||||
//
|
||||
//export SystemStack
|
||||
func SystemStack() uintptr
|
||||
|
||||
// GoroutineStack returns the PSP (goroutine stack pointer). When a fault
|
||||
// occurs in goroutine context, the hardware saves the exception frame to PSP;
|
||||
// reading PSP+0x18 gives the actual faulting PC.
|
||||
//
|
||||
//export GoroutineStack
|
||||
func GoroutineStack() uintptr
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
//go:build badger2350
|
||||
|
||||
// Chip: RP2350A (QFN-60, 30 GPIO) -> target inherits from "rp2350".
|
||||
// Pin source: https://github.com/pimoroni/badger2350/blob/main/board/pins.csv
|
||||
//
|
||||
// NOTES (verify before first flash):
|
||||
// - POWER_EN (GPIO27) likely switches peripheral/display power.
|
||||
// Verify polarity against the schematic; pull HIGH early if needed,
|
||||
// otherwise the display will remain blank.
|
||||
// - CHARGE_STAT is connected to EXT_GPIO2 (I2C IO expander) according to pins.csv,
|
||||
// NOT the RP2350 -> intentionally not defined as a pin here.
|
||||
|
||||
package machine
|
||||
|
||||
// Crystal oscillator frequency.
|
||||
const xoscFreq = 12 // MHz
|
||||
|
||||
// User buttons.
|
||||
const (
|
||||
BUTTON_A Pin = GPIO7
|
||||
BUTTON_B Pin = GPIO9
|
||||
BUTTON_C Pin = GPIO10
|
||||
BUTTON_UP Pin = GPIO11
|
||||
BUTTON_DOWN Pin = GPIO6
|
||||
BUTTON_HOME Pin = GPIO22
|
||||
BUTTON_BOOT Pin = BUTTON_HOME
|
||||
|
||||
// System / RTC-related buttons.
|
||||
BUTTON_RESET Pin = GPIO14
|
||||
BUTTON_INT Pin = GPIO15
|
||||
)
|
||||
|
||||
// Case LEDs (4-zone mono illumination), named CL0..CL3 in the docs.
|
||||
const (
|
||||
CL0 Pin = GPIO0
|
||||
CL1 Pin = GPIO1
|
||||
CL2 Pin = GPIO2
|
||||
CL3 Pin = GPIO3
|
||||
|
||||
// Convention: first zone as default LED.
|
||||
LED = CL0
|
||||
)
|
||||
|
||||
// E-ink display (Inky), on SPI0.
|
||||
const (
|
||||
INKY_BUSY Pin = GPIO16
|
||||
INKY_CS Pin = GPIO17
|
||||
INKY_SCLK Pin = GPIO18
|
||||
INKY_MOSI Pin = GPIO19
|
||||
INKY_DC Pin = GPIO20
|
||||
INKY_RES Pin = GPIO21
|
||||
)
|
||||
|
||||
// SPI0 default pins == Inky bus. SDI is unused by the EPD,
|
||||
// but GPIO16 is a valid SPI0 RX line on the RP2350.
|
||||
const (
|
||||
SPI0_SCK_PIN = GPIO18 // INKY_SCLK
|
||||
SPI0_SDO_PIN = GPIO19 // INKY_MOSI
|
||||
SPI0_SDI_PIN = GPIO16 // (unused by the EPD)
|
||||
)
|
||||
|
||||
// SPI1 is not routed to any header; NoPin satisfies the machine package.
|
||||
const (
|
||||
SPI1_SCK_PIN Pin = NoPin
|
||||
SPI1_SDO_PIN Pin = NoPin
|
||||
SPI1_SDI_PIN Pin = NoPin
|
||||
)
|
||||
|
||||
// UART pins - default RP2350 UART0 mapping.
|
||||
// Note: GPIO0/GPIO1 are also CL0/CL1 (case LEDs)
|
||||
// Do not use UART0 and LEDs simultaneously.
|
||||
const (
|
||||
UART0_TX_PIN = GPIO0
|
||||
UART0_RX_PIN = GPIO1
|
||||
UART_TX_PIN = UART0_TX_PIN
|
||||
UART_RX_PIN = UART0_RX_PIN
|
||||
)
|
||||
|
||||
// I2C0 (Qwiic/STEMMA QT + RTC PCF85063A).
|
||||
const (
|
||||
I2C0_SDA_PIN = GPIO4
|
||||
I2C0_SCL_PIN = GPIO5
|
||||
)
|
||||
|
||||
// I2C1 is not routed to any header; NoPin satisfies the machine package.
|
||||
const (
|
||||
I2C1_SDA_PIN Pin = NoPin
|
||||
I2C1_SCL_PIN Pin = NoPin
|
||||
)
|
||||
|
||||
// Power / Sensing.
|
||||
const (
|
||||
VBUS_DETECT Pin = GPIO12
|
||||
RTC_ALARM Pin = GPIO13
|
||||
VBAT_SENSE Pin = GPIO26
|
||||
POWER_EN Pin = GPIO27
|
||||
SENSE_1V1 Pin = GPIO28
|
||||
|
||||
BATTERY = VBAT_SENSE
|
||||
)
|
||||
|
||||
var DefaultUART = UART0
|
||||
|
||||
// USB identifiers
|
||||
const (
|
||||
usb_STRING_PRODUCT = "Badger 2350"
|
||||
usb_STRING_MANUFACTURER = "Pimoroni"
|
||||
)
|
||||
|
||||
var (
|
||||
usb_VID uint16 = 0x2E8A
|
||||
usb_PID uint16 = 0x0005
|
||||
)
|
||||
@@ -0,0 +1,112 @@
|
||||
//go:build blinky2350
|
||||
|
||||
// Chip: RP2350A (QFN-60, 30 GPIO) -> target inherits from "rp2350".
|
||||
// Pin source: https://github.com/pimoroni/blinky2350/blob/main/board/pins.csv
|
||||
//
|
||||
// NOTES (verify before first flash):
|
||||
// - POWER_EN (GPIO27) likely switches peripheral power.
|
||||
// Verify polarity against the schematic; pull HIGH early if needed,
|
||||
// otherwise the display will remain blank.
|
||||
// - CHARGE_STAT is connected to EXT_GPIO2 (I2C IO expander) according to pins.csv,
|
||||
// NOT the RP2350 -> intentionally not defined as a pin here.
|
||||
// - The dot matrix is NOT driven via SPI (Latch/Blank/
|
||||
// Row-Clock are not SPI signals) -> use PIO or bit-banging.
|
||||
// SPI0 and SPI1 are therefore set to NoPin (required by the machine package).
|
||||
|
||||
package machine
|
||||
|
||||
// Crystal oscillator frequency.
|
||||
const xoscFreq = 12 // MHz
|
||||
|
||||
// User buttons.
|
||||
const (
|
||||
BUTTON_A Pin = GPIO7
|
||||
BUTTON_B Pin = GPIO9
|
||||
BUTTON_C Pin = GPIO10
|
||||
BUTTON_UP Pin = GPIO11
|
||||
BUTTON_DOWN Pin = GPIO6
|
||||
BUTTON_HOME Pin = GPIO22
|
||||
BUTTON_BOOT Pin = BUTTON_HOME
|
||||
|
||||
BUTTON_RESET Pin = GPIO14
|
||||
BUTTON_INT Pin = GPIO15
|
||||
)
|
||||
|
||||
// Case LEDs (4-zone mono illumination), named CL0..CL3 in the docs.
|
||||
const (
|
||||
CL0 Pin = GPIO0
|
||||
CL1 Pin = GPIO1
|
||||
CL2 Pin = GPIO2
|
||||
CL3 Pin = GPIO3
|
||||
|
||||
LED = CL0
|
||||
)
|
||||
|
||||
// LED dot matrix: column shift register + row driver.
|
||||
// NOT SPI -> drive via PIO or bit-banging.
|
||||
const (
|
||||
DISPLAY_COL_SCLK Pin = GPIO16
|
||||
DISPLAY_COL_DATA Pin = GPIO17
|
||||
DISPLAY_COL_LATCH Pin = GPIO18
|
||||
DISPLAY_COL_BLANK Pin = GPIO19
|
||||
DISPLAY_ROW_DATA Pin = GPIO20
|
||||
DISPLAY_ROW_CLK Pin = GPIO21
|
||||
)
|
||||
|
||||
// I2C0 (Qwiic/STEMMA QT + RTC).
|
||||
const (
|
||||
I2C0_SDA_PIN = GPIO4
|
||||
I2C0_SCL_PIN = GPIO5
|
||||
)
|
||||
|
||||
// I2C1 is not routed to any header; NoPin satisfies the machine package.
|
||||
const (
|
||||
I2C1_SDA_PIN Pin = NoPin
|
||||
I2C1_SCL_PIN Pin = NoPin
|
||||
)
|
||||
|
||||
// SPI pins - the LED matrix is driven via PIO/bit-banging, not SPI.
|
||||
// NoPin satisfies the machine package requirements.
|
||||
const (
|
||||
SPI0_SCK_PIN Pin = NoPin
|
||||
SPI0_SDO_PIN Pin = NoPin
|
||||
SPI0_SDI_PIN Pin = NoPin
|
||||
|
||||
SPI1_SCK_PIN Pin = NoPin
|
||||
SPI1_SDO_PIN Pin = NoPin
|
||||
SPI1_SDI_PIN Pin = NoPin
|
||||
)
|
||||
|
||||
// UART pins - default RP2350 UART0 mapping.
|
||||
// Note: GPIO0/GPIO1 are also CL0/CL1 (case LEDs)
|
||||
// Do not use UART0 and LEDs simultaneously.
|
||||
const (
|
||||
UART0_TX_PIN = GPIO0
|
||||
UART0_RX_PIN = GPIO1
|
||||
UART_TX_PIN = UART0_TX_PIN
|
||||
UART_RX_PIN = UART0_RX_PIN
|
||||
)
|
||||
|
||||
// Power / sensing.
|
||||
const (
|
||||
VBUS_DETECT Pin = GPIO12
|
||||
RTC_ALARM Pin = GPIO13
|
||||
VBAT_SENSE Pin = GPIO26
|
||||
POWER_EN Pin = GPIO27
|
||||
SENSE_1V1 Pin = GPIO28
|
||||
|
||||
BATTERY = VBAT_SENSE
|
||||
)
|
||||
|
||||
var DefaultUART = UART0
|
||||
|
||||
// USB identifiers
|
||||
const (
|
||||
usb_STRING_PRODUCT = "Blinky 2350"
|
||||
usb_STRING_MANUFACTURER = "Pimoroni"
|
||||
)
|
||||
|
||||
var (
|
||||
usb_VID uint16 = 0x2E8A
|
||||
usb_PID uint16 = 0x0005
|
||||
)
|
||||
@@ -0,0 +1,144 @@
|
||||
//go:build esp32s3_box3
|
||||
|
||||
// This file contains the pin mappings for the ESP32-S3-BOX-3 board.
|
||||
//
|
||||
// ESP32-S3-BOX-3 is an AI voice development kit with 2.4" display.
|
||||
// - https://github.com/espressif/esp-box
|
||||
//
|
||||
// Based on ESP-BOX-3 BSP from Espressif.
|
||||
|
||||
package machine
|
||||
|
||||
// CPUFrequency returns the current CPU frequency for ESP32-S3-BOX-3.
|
||||
// Returns 240MHz (max speed for ESP32-S3 with PSRAM)
|
||||
func CPUFrequency() uint32 {
|
||||
return 240_000_000 // 240MHz
|
||||
}
|
||||
|
||||
const (
|
||||
// GPIO pins available on ESP32-S3-BOX-3
|
||||
// Based on ESP-BOX-3 BSP pin definitions
|
||||
IO0 = GPIO0 // Button Config
|
||||
IO1 = GPIO1 // Button Mute / Mute Status
|
||||
IO2 = GPIO2 // I2S MCLK
|
||||
IO3 = GPIO3 // LCD Touch Interrupt
|
||||
IO4 = GPIO4 // LCD DC
|
||||
IO5 = GPIO5 // LCD CS
|
||||
IO6 = GPIO6 // LCD DATA0 (MOSI)
|
||||
IO7 = GPIO7 // LCD PCLK (SCK)
|
||||
IO8 = GPIO8 // I2C SDA
|
||||
IO9 = GPIO9 // SD Card D0
|
||||
IO10 = GPIO10
|
||||
IO11 = GPIO11 // SD Card CLK
|
||||
IO12 = GPIO12 // SD Card D3
|
||||
IO13 = GPIO13 // SD Card D1
|
||||
IO14 = GPIO14 // SD Card CMD
|
||||
IO15 = GPIO15 // I2S DOUT (Speaker)
|
||||
IO16 = GPIO16 // I2S DSIN (Microphone)
|
||||
IO17 = GPIO17 // I2S SCLK
|
||||
IO18 = GPIO18 // I2C SCL
|
||||
IO19 = GPIO19 // USB_NEG
|
||||
IO20 = GPIO20 // USB_POS
|
||||
IO21 = GPIO21
|
||||
IO38 = GPIO38
|
||||
IO39 = GPIO39
|
||||
IO40 = GPIO40 // I2C Dock SCL
|
||||
IO41 = GPIO41 // I2C Dock SDA
|
||||
IO42 = GPIO42 // SD Card D2
|
||||
IO43 = GPIO43 // SD Card Power
|
||||
IO44 = GPIO44 // (Unused)
|
||||
IO45 = GPIO45 // I2S LCLK (LRCK)
|
||||
IO46 = GPIO46 // Power Amp Control
|
||||
IO47 = GPIO47 // LCD Backlight
|
||||
IO48 = GPIO48 // LCD Reset
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
// SPI1 - used for LCD
|
||||
SPI1_SCK_PIN = IO7 // LCD_PCLK
|
||||
SPI1_MOSI_PIN = IO6 // LCD_DATA0 (MOSI)
|
||||
SPI1_MISO_PIN = NoPin // Not used for LCD
|
||||
SPI1_CS_PIN = IO5 // LCD_CS
|
||||
|
||||
// SPI2 (not used on this board)
|
||||
SPI2_SCK_PIN = NoPin
|
||||
SPI2_MOSI_PIN = NoPin
|
||||
SPI2_MISO_PIN = NoPin
|
||||
SPI2_CS_PIN = NoPin
|
||||
)
|
||||
|
||||
// LCD pins (ST7789/ILI9341)
|
||||
const (
|
||||
LCD_SCK_PIN = SPI1_SCK_PIN
|
||||
LCD_SDO_PIN = SPI1_MOSI_PIN
|
||||
LCD_SDI_PIN = NoPin
|
||||
LCD_SS_PIN = SPI1_CS_PIN
|
||||
LCD_DC_PIN = IO4
|
||||
LCD_RST_PIN = IO48
|
||||
LCD_BL_PIN = IO47
|
||||
)
|
||||
|
||||
// I2C pins (Internal I2C for sensors)
|
||||
const (
|
||||
SDA0_PIN = IO8
|
||||
SCL0_PIN = IO18
|
||||
|
||||
SDA_PIN = SDA0_PIN
|
||||
SCL_PIN = SCL0_PIN
|
||||
)
|
||||
|
||||
// Dock I2C pins (for expansion dock)
|
||||
const (
|
||||
SDA1_PIN = IO41
|
||||
SCL1_PIN = IO40
|
||||
)
|
||||
|
||||
// UART pins (USB CDC - native USB on ESP32-S3)
|
||||
// Note: Native USB doesn't require GPIO pins, these are kept for compatibility
|
||||
const (
|
||||
UART_TX_PIN = NoPin
|
||||
UART_RX_PIN = NoPin
|
||||
)
|
||||
|
||||
// Built-in LED
|
||||
// Using LCD backlight as LED indicator
|
||||
const LED = GPIO47
|
||||
|
||||
// Buttons
|
||||
const (
|
||||
BUTTON_CONFIG = GPIO0
|
||||
BUTTON_MUTE = GPIO1
|
||||
)
|
||||
|
||||
// Audio pins (I2S)
|
||||
const (
|
||||
I2S_SCLK_PIN = IO17 // Bit Clock
|
||||
I2S_MCLK_PIN = IO2 // Master Clock
|
||||
I2S_LRCK_PIN = IO45 // Left/Right Clock (Frame Sync)
|
||||
I2S_DOUT_PIN = IO15 // Data Out to Speaker (ES8311)
|
||||
I2S_DSIN_PIN = IO16 // Data In from Microphone (ES7210)
|
||||
)
|
||||
|
||||
// SD Card pins (SD/MMC mode)
|
||||
const (
|
||||
SDCARD_CMD_PIN = IO14
|
||||
SDCARD_CLK_PIN = IO11
|
||||
SDCARD_D0_PIN = IO9
|
||||
SDCARD_D1_PIN = IO13
|
||||
SDCARD_D2_PIN = IO42
|
||||
SDCARD_D3_PIN = IO12
|
||||
SDCARD_PWR_PIN = IO43 // SD Card Power
|
||||
)
|
||||
|
||||
// USB pins (native USB)
|
||||
const (
|
||||
USB_DPPIN = IO20 // USB D+
|
||||
USB_DMPIN = IO19 // USB D-
|
||||
)
|
||||
|
||||
// Touch interrupt
|
||||
const TOUCH_INT_PIN = IO3
|
||||
|
||||
// Power amplifier control
|
||||
const POWER_AMP_PIN = IO46
|
||||
@@ -41,6 +41,40 @@ const (
|
||||
D29 Pin = GPIO29
|
||||
)
|
||||
|
||||
// GPIO pin aliases
|
||||
const (
|
||||
GP0 Pin = GPIO0
|
||||
GP1 Pin = GPIO1
|
||||
GP2 Pin = GPIO2
|
||||
GP3 Pin = GPIO3
|
||||
GP4 Pin = GPIO4
|
||||
GP5 Pin = GPIO5
|
||||
GP6 Pin = GPIO6
|
||||
GP7 Pin = GPIO7
|
||||
GP8 Pin = GPIO8
|
||||
GP9 Pin = GPIO9
|
||||
GP10 Pin = GPIO10
|
||||
GP11 Pin = GPIO11
|
||||
GP12 Pin = GPIO12
|
||||
GP13 Pin = GPIO13
|
||||
GP14 Pin = GPIO14
|
||||
GP15 Pin = GPIO15
|
||||
GP16 Pin = GPIO16
|
||||
GP17 Pin = GPIO17
|
||||
GP18 Pin = GPIO18
|
||||
GP19 Pin = GPIO19
|
||||
GP20 Pin = GPIO20
|
||||
GP21 Pin = GPIO21
|
||||
GP22 Pin = GPIO22
|
||||
GP23 Pin = GPIO23
|
||||
GP24 Pin = GPIO24
|
||||
GP25 Pin = GPIO25
|
||||
GP26 Pin = GPIO26
|
||||
GP27 Pin = GPIO27
|
||||
GP28 Pin = GPIO28
|
||||
GP29 Pin = GPIO29
|
||||
)
|
||||
|
||||
// Analog pins
|
||||
const (
|
||||
A0 Pin = D26
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//go:build xiao_esp32c6
|
||||
|
||||
// This file contains the pin mappings for the Seeed XIAO ESP32C6 board.
|
||||
//
|
||||
// Seeed Studio XIAO ESP32C6 is an IoT mini development board based on
|
||||
// the Espressif ESP32-C6 WiFi 6 / Bluetooth 5 / 802.15.4 chip.
|
||||
//
|
||||
// - https://www.seeedstudio.com/Seeed-Studio-XIAO-ESP32C6-p-5884.html
|
||||
// - https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/
|
||||
|
||||
package machine
|
||||
|
||||
// Digital Pins
|
||||
const (
|
||||
D0 = GPIO0
|
||||
D1 = GPIO1
|
||||
D2 = GPIO2
|
||||
D3 = GPIO21
|
||||
D4 = GPIO22
|
||||
D5 = GPIO23
|
||||
D6 = GPIO16
|
||||
D7 = GPIO17
|
||||
D8 = GPIO19
|
||||
D9 = GPIO20
|
||||
D10 = GPIO18
|
||||
)
|
||||
|
||||
// Analog pins
|
||||
const (
|
||||
A0 = GPIO0
|
||||
A1 = GPIO1
|
||||
A2 = GPIO2
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
UART_TX_PIN = GPIO16
|
||||
UART_RX_PIN = GPIO17
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = GPIO22
|
||||
SCL_PIN = GPIO23
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI_SCK_PIN = GPIO19
|
||||
SPI_SDI_PIN = GPIO20
|
||||
SPI_SDO_PIN = GPIO18
|
||||
)
|
||||
|
||||
// Onboard LEDs
|
||||
const (
|
||||
LED = GPIO15
|
||||
)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 || esp32s3
|
||||
//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 || esp32c6 || esp32s3
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -23,19 +23,6 @@ const (
|
||||
NumberOfUSBEndpoints = 8
|
||||
)
|
||||
|
||||
var (
|
||||
endPoints = []uint32{
|
||||
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
|
||||
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
|
||||
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
|
||||
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
|
||||
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
|
||||
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
|
||||
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
|
||||
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
|
||||
}
|
||||
)
|
||||
|
||||
// Configure the USB peripheral. The config is here for compatibility with the UART interface.
|
||||
func (dev *USBDevice) Configure(config UARTConfig) {
|
||||
if dev.initcomplete {
|
||||
@@ -188,7 +175,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
|
||||
// Now the actual transfer handlers, ignore endpoint number 0 (setup)
|
||||
var i uint32
|
||||
for i = 1; i < uint32(len(endPoints)); i++ {
|
||||
for i = 1; i < NumberOfUSBEndpoints; i++ {
|
||||
// Check if endpoint has a pending interrupt
|
||||
epFlags := getEPINTFLAG(i)
|
||||
setEPINTFLAG(i, epFlags)
|
||||
@@ -206,6 +193,8 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
}
|
||||
|
||||
func initEndpoint(ep, config uint32) {
|
||||
// Note: Both IN (Bank 1) and OUT (Bank 0) configurations share the same EPCFG register.
|
||||
// We must use getEPCFG(ep) | ... to avoid clearing/disabling the opposite direction.
|
||||
switch config {
|
||||
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
|
||||
// set packet size
|
||||
@@ -215,7 +204,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
|
||||
|
||||
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT1)
|
||||
|
||||
@@ -227,7 +216,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
|
||||
|
||||
// receive interrupts when current transfer complete
|
||||
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0)
|
||||
@@ -246,7 +235,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
|
||||
|
||||
// receive interrupts when current transfer complete
|
||||
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0)
|
||||
@@ -265,7 +254,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
|
||||
|
||||
// NAK on endpoint IN, the bank is not yet filled in.
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
|
||||
@@ -663,3 +652,19 @@ func setEPINTENSET(ep uint32, val uint8) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (dev *USBDevice) SetStallEPIn(ep uint32) {
|
||||
setEPSTATUSSET(ep, sam.USB_DEVICE_EPSTATUSSET_STALLRQ1)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) SetStallEPOut(ep uint32) {
|
||||
setEPSTATUSSET(ep, sam.USB_DEVICE_EPSTATUSSET_STALLRQ0)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_STALLRQ1)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_STALLRQ0)
|
||||
}
|
||||
|
||||
@@ -23,19 +23,6 @@ const (
|
||||
NumberOfUSBEndpoints = 8
|
||||
)
|
||||
|
||||
var (
|
||||
endPoints = []uint32{
|
||||
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
|
||||
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
|
||||
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
|
||||
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
|
||||
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
|
||||
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
|
||||
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
|
||||
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
|
||||
}
|
||||
)
|
||||
|
||||
// Configure the USB peripheral. The config is here for compatibility with the UART interface.
|
||||
func (dev *USBDevice) Configure(config UARTConfig) {
|
||||
if dev.initcomplete {
|
||||
@@ -191,7 +178,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
|
||||
// Now the actual transfer handlers, ignore endpoint number 0 (setup)
|
||||
var i uint32
|
||||
for i = 1; i < uint32(len(endPoints)); i++ {
|
||||
for i = 1; i < NumberOfUSBEndpoints; i++ {
|
||||
// Check if endpoint has a pending interrupt
|
||||
epFlags := getEPINTFLAG(i)
|
||||
setEPINTFLAG(i, epFlags)
|
||||
@@ -209,6 +196,8 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
}
|
||||
|
||||
func initEndpoint(ep, config uint32) {
|
||||
// Note: Both IN (Bank 1) and OUT (Bank 0) configurations share the same EPCFG register.
|
||||
// We must use getEPCFG(ep) | ... to avoid clearing/disabling the opposite direction.
|
||||
switch config {
|
||||
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
|
||||
// set packet size
|
||||
@@ -218,7 +207,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
|
||||
|
||||
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT1)
|
||||
|
||||
@@ -230,7 +219,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
|
||||
|
||||
// receive interrupts when current transfer complete
|
||||
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0)
|
||||
@@ -249,7 +238,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
|
||||
|
||||
// receive interrupts when current transfer complete
|
||||
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0)
|
||||
@@ -268,7 +257,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
|
||||
|
||||
// NAK on endpoint IN, the bank is not yet filled in.
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK1RDY)
|
||||
@@ -494,3 +483,19 @@ func setEPINTENCLR(ep uint32, val uint8) {
|
||||
func setEPINTENSET(ep uint32, val uint8) {
|
||||
sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTENSET.Set(val)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) SetStallEPIn(ep uint32) {
|
||||
setEPSTATUSSET(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSSET_STALLRQ1)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) SetStallEPOut(ep uint32) {
|
||||
setEPSTATUSSET(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSSET_STALLRQ0)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_STALLRQ1)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_STALLRQ0)
|
||||
}
|
||||
|
||||
@@ -295,16 +295,33 @@ var DefaultUART = UART0
|
||||
|
||||
var (
|
||||
UART0 = &_UART0
|
||||
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
|
||||
_UART0 = UART{
|
||||
Bus: esp.UART0,
|
||||
Buffer: NewRingBuffer(),
|
||||
TXRXSignal: 14,
|
||||
RTSCTSSignal: 15,
|
||||
}
|
||||
UART1 = &_UART1
|
||||
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
|
||||
_UART1 = UART{
|
||||
Bus: esp.UART1,
|
||||
Buffer: NewRingBuffer(),
|
||||
TXRXSignal: 17,
|
||||
RTSCTSSignal: 18,
|
||||
}
|
||||
UART2 = &_UART2
|
||||
_UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()}
|
||||
_UART2 = UART{
|
||||
Bus: esp.UART2,
|
||||
Buffer: NewRingBuffer(),
|
||||
TXRXSignal: 198,
|
||||
RTSCTSSignal: 199,
|
||||
}
|
||||
)
|
||||
|
||||
type UART struct {
|
||||
Bus *esp.UART_Type
|
||||
Buffer *RingBuffer
|
||||
Bus *esp.UART_Type
|
||||
Buffer *RingBuffer
|
||||
TXRXSignal uint32
|
||||
RTSCTSSignal uint32
|
||||
}
|
||||
|
||||
func (uart *UART) Configure(config UARTConfig) {
|
||||
@@ -312,6 +329,22 @@ func (uart *UART) Configure(config UARTConfig) {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate)
|
||||
|
||||
if config.RX != NoPin {
|
||||
config.RX.configure(PinConfig{Mode: PinInputPullup}, uart.TXRXSignal)
|
||||
}
|
||||
|
||||
if config.TX != NoPin {
|
||||
config.TX.configure(PinConfig{Mode: PinOutput}, uart.TXRXSignal)
|
||||
}
|
||||
|
||||
if config.RTS != NoPin {
|
||||
config.RTS.configure(PinConfig{Mode: PinOutput}, uart.RTSCTSSignal)
|
||||
}
|
||||
|
||||
if config.CTS != NoPin {
|
||||
config.CTS.configure(PinConfig{Mode: PinInputPullup}, uart.RTSCTSSignal)
|
||||
}
|
||||
}
|
||||
|
||||
func (uart *UART) writeByte(b byte) error {
|
||||
@@ -319,6 +352,7 @@ func (uart *UART) writeByte(b byte) error {
|
||||
// Read UART_TXFIFO_CNT from the status register, which indicates how
|
||||
// many bytes there are in the transmit buffer. Wait until there are
|
||||
// less than 128 bytes in this buffer (the default buffer size).
|
||||
gosched()
|
||||
}
|
||||
// Write to the TX_FIFO register.
|
||||
(*volatile.Register8)(unsafe.Add(unsafe.Pointer(uart.Bus), 0x200C0000)).Set(b)
|
||||
|
||||
@@ -19,3 +19,10 @@ var (
|
||||
useExt1: false,
|
||||
}
|
||||
)
|
||||
|
||||
// enableI2C0PeriphClock enables the I2C0 peripheral clock via SYSTEM.
|
||||
func enableI2C0PeriphClock() {
|
||||
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
|
||||
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
|
||||
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,546 @@
|
||||
//go:build esp32c6
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"device/riscv"
|
||||
"errors"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const deviceName = esp.Device
|
||||
const maxPin = 31
|
||||
const cpuInterruptFromPin = 6
|
||||
|
||||
// CPUFrequency returns the current CPU frequency of the chip.
|
||||
// Currently it is a fixed frequency but it may allow changing in the future.
|
||||
func CPUFrequency() uint32 {
|
||||
return 160e6 // 160MHz
|
||||
}
|
||||
|
||||
const (
|
||||
PinOutput PinMode = iota
|
||||
PinInput
|
||||
PinInputPullup
|
||||
PinInputPulldown
|
||||
PinAnalog
|
||||
)
|
||||
|
||||
const (
|
||||
GPIO0 Pin = 0
|
||||
GPIO1 Pin = 1
|
||||
GPIO2 Pin = 2
|
||||
GPIO3 Pin = 3
|
||||
GPIO4 Pin = 4
|
||||
GPIO5 Pin = 5
|
||||
GPIO6 Pin = 6
|
||||
GPIO7 Pin = 7
|
||||
GPIO8 Pin = 8
|
||||
GPIO9 Pin = 9
|
||||
GPIO10 Pin = 10
|
||||
GPIO11 Pin = 11
|
||||
GPIO12 Pin = 12
|
||||
GPIO13 Pin = 13
|
||||
GPIO14 Pin = 14
|
||||
GPIO15 Pin = 15
|
||||
GPIO16 Pin = 16
|
||||
GPIO17 Pin = 17
|
||||
GPIO18 Pin = 18
|
||||
GPIO19 Pin = 19
|
||||
GPIO20 Pin = 20
|
||||
GPIO21 Pin = 21
|
||||
GPIO22 Pin = 22
|
||||
GPIO23 Pin = 23
|
||||
GPIO24 Pin = 24
|
||||
GPIO25 Pin = 25
|
||||
GPIO26 Pin = 26
|
||||
GPIO27 Pin = 27
|
||||
GPIO28 Pin = 28
|
||||
GPIO29 Pin = 29
|
||||
GPIO30 Pin = 30
|
||||
)
|
||||
|
||||
const (
|
||||
ADC0 Pin = GPIO0
|
||||
ADC1 Pin = GPIO1
|
||||
ADC2 Pin = GPIO2
|
||||
ADC3 Pin = GPIO3
|
||||
ADC4 Pin = GPIO4
|
||||
ADC5 Pin = GPIO5
|
||||
ADC6 Pin = GPIO6
|
||||
)
|
||||
|
||||
type PinChange uint8
|
||||
|
||||
// Pin change interrupt constants for SetInterrupt.
|
||||
const (
|
||||
PinRising PinChange = iota + 1
|
||||
PinFalling
|
||||
PinToggle
|
||||
)
|
||||
|
||||
// Configure this pin with the given configuration.
|
||||
func (p Pin) Configure(config PinConfig) {
|
||||
if p == NoPin {
|
||||
// This simplifies pin configuration in peripherals such as SPI.
|
||||
return
|
||||
}
|
||||
|
||||
var muxConfig uint32
|
||||
|
||||
// Configure this pin as a GPIO pin.
|
||||
const function = 1 // function 1 is GPIO for every pin
|
||||
muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos
|
||||
|
||||
// FUN_IE: disable for PinAnalog (high-Z for ADC)
|
||||
if config.Mode != PinAnalog {
|
||||
muxConfig |= esp.IO_MUX_GPIO_FUN_IE
|
||||
}
|
||||
|
||||
// Set drive strength: 0 is lowest, 3 is highest.
|
||||
muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos
|
||||
|
||||
// Select pull mode (no pulls for PinAnalog).
|
||||
if config.Mode == PinInputPullup {
|
||||
muxConfig |= esp.IO_MUX_GPIO_FUN_WPU
|
||||
} else if config.Mode == PinInputPulldown {
|
||||
muxConfig |= esp.IO_MUX_GPIO_FUN_WPD
|
||||
}
|
||||
|
||||
// Configure the pad with the given IO mux configuration.
|
||||
p.mux().Set(muxConfig)
|
||||
|
||||
// Set the output signal to the simple GPIO output.
|
||||
p.outFunc().Set(0x80)
|
||||
|
||||
switch config.Mode {
|
||||
case PinOutput:
|
||||
// Set the 'output enable' bit.
|
||||
esp.GPIO.ENABLE_W1TS.Set(1 << p)
|
||||
case PinInput, PinInputPullup, PinInputPulldown, PinAnalog:
|
||||
// Clear the 'output enable' bit.
|
||||
esp.GPIO.ENABLE_W1TC.Set(1 << p)
|
||||
}
|
||||
}
|
||||
|
||||
// configure is the same as Configure, but allows setting a specific GPIO matrix signal.
|
||||
func (p Pin) configure(config PinConfig, signal uint32) {
|
||||
p.Configure(config)
|
||||
if signal == 256 {
|
||||
return
|
||||
}
|
||||
if config.Mode == PinOutput {
|
||||
p.outFunc().Set(signal)
|
||||
} else {
|
||||
inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p))
|
||||
}
|
||||
}
|
||||
|
||||
func initI2CExt1Clock() {}
|
||||
|
||||
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
|
||||
// output function selection.
|
||||
func (p Pin) outFunc() *volatile.Register32 {
|
||||
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4))
|
||||
}
|
||||
|
||||
func (p Pin) pinReg() *volatile.Register32 {
|
||||
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4)))
|
||||
}
|
||||
|
||||
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
|
||||
// function selection.
|
||||
func inFunc(signal uint32) *volatile.Register32 {
|
||||
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4))
|
||||
}
|
||||
|
||||
// mux returns the I/O mux configuration register corresponding to the given
|
||||
// GPIO pin.
|
||||
func (p Pin) mux() *volatile.Register32 {
|
||||
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4))
|
||||
}
|
||||
|
||||
// pin returns the PIN register corresponding to the given GPIO pin.
|
||||
func (p Pin) pin() *volatile.Register32 {
|
||||
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4))
|
||||
}
|
||||
|
||||
// Set the pin to high or low.
|
||||
// Warning: only use this on an output pin!
|
||||
func (p Pin) Set(value bool) {
|
||||
if value {
|
||||
reg, mask := p.portMaskSet()
|
||||
reg.Set(mask)
|
||||
} else {
|
||||
reg, mask := p.portMaskClear()
|
||||
reg.Set(mask)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin when configured as an input or as
|
||||
// an output.
|
||||
func (p Pin) Get() bool {
|
||||
reg := &esp.GPIO.IN
|
||||
return (reg.Get()>>p)&1 > 0
|
||||
}
|
||||
|
||||
// Return the register and mask to enable a given GPIO pin. This can be used to
|
||||
// implement bit-banged drivers.
|
||||
//
|
||||
// Warning: only use this on an output pin!
|
||||
func (p Pin) PortMaskSet() (*uint32, uint32) {
|
||||
reg, mask := p.portMaskSet()
|
||||
return ®.Reg, mask
|
||||
}
|
||||
|
||||
// Return the register and mask to disable a given GPIO pin. This can be used to
|
||||
// implement bit-banged drivers.
|
||||
//
|
||||
// Warning: only use this on an output pin!
|
||||
func (p Pin) PortMaskClear() (*uint32, uint32) {
|
||||
reg, mask := p.portMaskClear()
|
||||
return ®.Reg, mask
|
||||
}
|
||||
|
||||
func (p Pin) portMaskSet() (*volatile.Register32, uint32) {
|
||||
return &esp.GPIO.OUT_W1TS, 1 << p
|
||||
}
|
||||
|
||||
func (p Pin) portMaskClear() (*volatile.Register32, uint32) {
|
||||
return &esp.GPIO.OUT_W1TC, 1 << p
|
||||
}
|
||||
|
||||
// SetInterrupt sets an interrupt to be executed when a particular pin changes
|
||||
// state. The pin should already be configured as an input, including a pull up
|
||||
// or down if no external pull is provided.
|
||||
//
|
||||
// You can pass a nil func to unset the pin change interrupt. If you do so,
|
||||
// the change parameter is ignored and can be set to any value (such as 0).
|
||||
// If the pin is already configured with a callback, you must first unset
|
||||
// this pins interrupt before you can set a new callback.
|
||||
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) {
|
||||
if p >= maxPin {
|
||||
return ErrInvalidInputPin
|
||||
}
|
||||
|
||||
if callback == nil {
|
||||
// Disable this pin interrupt
|
||||
p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk)
|
||||
|
||||
if pinCallbacks[p] != nil {
|
||||
pinCallbacks[p] = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if pinCallbacks[p] != nil {
|
||||
// The pin was already configured.
|
||||
// To properly re-configure a pin, unset it first and set a new
|
||||
// configuration.
|
||||
return ErrNoPinChangeChannel
|
||||
}
|
||||
pinCallbacks[p] = callback
|
||||
|
||||
onceSetupPinInterrupt.Do(func() {
|
||||
err = setupPinInterrupt()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.pin().Set(
|
||||
(p.pin().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) |
|
||||
uint32(change)<<esp.GPIO_PIN_INT_TYPE_Pos | uint32(1)<<esp.GPIO_PIN_INT_ENA_Pos)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pinCallbacks [maxPin]func(Pin)
|
||||
onceSetupPinInterrupt sync.Once
|
||||
)
|
||||
|
||||
func setupPinInterrupt() error {
|
||||
esp.INTERRUPT_CORE0.GPIO_INTERRUPT_PRO_MAP.Set(cpuInterruptFromPin)
|
||||
return interrupt.New(cpuInterruptFromPin, func(interrupt.Interrupt) {
|
||||
status := esp.GPIO.STATUS.Get()
|
||||
// Clear before processing so new edges during callbacks are not lost.
|
||||
esp.GPIO.STATUS_W1TC.Set(status)
|
||||
for i, mask := 0, uint32(1); i < maxPin; i, mask = i+1, mask<<1 {
|
||||
if (status&mask) != 0 && pinCallbacks[i] != nil {
|
||||
pinCallbacks[i](Pin(i))
|
||||
}
|
||||
}
|
||||
}).Enable()
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultUART = UART0
|
||||
|
||||
UART0 = &_UART0
|
||||
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
|
||||
UART1 = &_UART1
|
||||
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
|
||||
|
||||
onceUart = sync.Once{}
|
||||
errSamePins = errors.New("UART: invalid pin combination")
|
||||
errWrongUART = errors.New("UART: unsupported UARTn")
|
||||
errWrongBitSize = errors.New("UART: invalid data size")
|
||||
errWrongStopBitSize = errors.New("UART: invalid bit size")
|
||||
)
|
||||
|
||||
type UART struct {
|
||||
Bus *esp.UART_Type
|
||||
Buffer *RingBuffer
|
||||
ParityErrorDetected bool // set when parity error detected
|
||||
DataErrorDetected bool // set when data corruption detected
|
||||
DataOverflowDetected bool // set when data overflow detected in UART FIFO buffer or RingBuffer
|
||||
}
|
||||
|
||||
const (
|
||||
defaultDataBits = 8
|
||||
defaultStopBit = 1
|
||||
defaultParity = ParityNone
|
||||
|
||||
uartInterrupts = esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA |
|
||||
esp.UART_INT_ENA_PARITY_ERR_INT_ENA |
|
||||
esp.UART_INT_ENA_FRM_ERR_INT_ENA |
|
||||
esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA |
|
||||
esp.UART_INT_ENA_GLITCH_DET_INT_ENA
|
||||
|
||||
pplClockFreq = 80e6
|
||||
)
|
||||
|
||||
type registerSet struct {
|
||||
interruptMapReg *volatile.Register32
|
||||
gpioMatrixSignal uint32
|
||||
}
|
||||
|
||||
func (uart *UART) Configure(config UARTConfig) error {
|
||||
if config.BaudRate == 0 {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
if config.TX == config.RX {
|
||||
return errSamePins
|
||||
}
|
||||
switch {
|
||||
case uart.Bus == esp.UART0:
|
||||
return uart.configure(config, registerSet{
|
||||
interruptMapReg: &esp.INTERRUPT_CORE0.UART0_INTR_MAP,
|
||||
gpioMatrixSignal: 6,
|
||||
})
|
||||
case uart.Bus == esp.UART1:
|
||||
return uart.configure(config, registerSet{
|
||||
interruptMapReg: &esp.INTERRUPT_CORE0.UART1_INTR_MAP,
|
||||
gpioMatrixSignal: 9,
|
||||
})
|
||||
}
|
||||
return errWrongUART
|
||||
}
|
||||
|
||||
func (uart *UART) configure(config UARTConfig, regs registerSet) error {
|
||||
initUARTClock(uart.Bus)
|
||||
|
||||
// - disable TX/RX clock to make sure the UART transmitter or receiver is not at work during configuration
|
||||
uart.Bus.SetCLK_CONF_TX_SCLK_EN(0)
|
||||
uart.Bus.SetCLK_CONF_RX_SCLK_EN(0)
|
||||
|
||||
// - default clock source: 1=XTAL_CLK, 2=RC_FAST_CLK, 3=FOSC_CLK
|
||||
// On C6, SCLK_SEL=1 selects XTAL (40 MHz). Use FOSC (80 MHz) for
|
||||
// better baud-rate accuracy.
|
||||
uart.Bus.SetCLK_CONF_SCLK_SEL(3) // FOSC_CLK (80 MHz)
|
||||
// reset divisor of the divider via UART_SCLK_DIV_NUM, UART_SCLK_DIV_A, and UART_SCLK_DIV_B
|
||||
uart.Bus.SetCLK_CONF_SCLK_DIV_NUM(0)
|
||||
uart.Bus.SetCLK_CONF_SCLK_DIV_A(0)
|
||||
uart.Bus.SetCLK_CONF_SCLK_DIV_B(0)
|
||||
|
||||
uart.SetBaudRate(config.BaudRate)
|
||||
uart.SetFormat(defaultDataBits, defaultStopBit, defaultParity)
|
||||
|
||||
// - set UART mode
|
||||
uart.Bus.SetRS485_CONF_RS485_EN(0)
|
||||
uart.Bus.SetRS485_CONF_RS485TX_RX_EN(0)
|
||||
uart.Bus.SetRS485_CONF_RS485RXBY_TX_EN(0)
|
||||
uart.Bus.SetCONF0_IRDA_EN(0)
|
||||
// - disable hw-flow control
|
||||
uart.Bus.SetCONF0_TX_FLOW_EN(0)
|
||||
|
||||
// synchronize values into Core Clock
|
||||
uart.Bus.SetREG_UPDATE(1)
|
||||
|
||||
uart.setupPins(config, regs)
|
||||
uart.configureInterrupt(regs.interruptMapReg)
|
||||
uart.enableTransmitter()
|
||||
uart.enableReceiver()
|
||||
|
||||
// Start TX/RX
|
||||
uart.Bus.SetCLK_CONF_TX_SCLK_EN(1)
|
||||
uart.Bus.SetCLK_CONF_RX_SCLK_EN(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uart *UART) SetFormat(dataBits, stopBits int, parity UARTParity) error {
|
||||
if dataBits < 5 {
|
||||
return errWrongBitSize
|
||||
}
|
||||
if stopBits > 1 {
|
||||
return errWrongStopBitSize
|
||||
}
|
||||
// - data length
|
||||
uart.Bus.SetCONF0_BIT_NUM(uint32(dataBits - 5))
|
||||
// - stop bit
|
||||
uart.Bus.SetCONF0_STOP_BIT_NUM(uint32(stopBits))
|
||||
// - parity check
|
||||
switch parity {
|
||||
case ParityNone:
|
||||
uart.Bus.SetCONF0_PARITY_EN(0)
|
||||
case ParityEven:
|
||||
uart.Bus.SetCONF0_PARITY_EN(1)
|
||||
uart.Bus.SetCONF0_PARITY(0)
|
||||
case ParityOdd:
|
||||
uart.Bus.SetCONF0_PARITY_EN(1)
|
||||
uart.Bus.SetCONF0_PARITY(1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initUARTClock(bus *esp.UART_Type) {
|
||||
// On ESP32-C6, UART clock is controlled via PCR (Peripheral Clock Reset).
|
||||
switch bus {
|
||||
case esp.UART0:
|
||||
esp.PCR.SetUART0_CONF_UART0_CLK_EN(1)
|
||||
esp.PCR.SetUART0_CONF_UART0_RST_EN(0) // ensure not in reset
|
||||
bus.SetCLK_CONF_RST_CORE(1)
|
||||
esp.PCR.SetUART0_CONF_UART0_RST_EN(1)
|
||||
esp.PCR.SetUART0_CONF_UART0_RST_EN(0)
|
||||
bus.SetCLK_CONF_RST_CORE(0)
|
||||
// Configure SCLK divisor in PCR
|
||||
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(0)
|
||||
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_A(0)
|
||||
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_B(0)
|
||||
case esp.UART1:
|
||||
esp.PCR.SetUART1_CONF_UART1_CLK_EN(1)
|
||||
esp.PCR.SetUART1_CONF_UART1_RST_EN(0)
|
||||
bus.SetCLK_CONF_RST_CORE(1)
|
||||
esp.PCR.SetUART1_CONF_UART1_RST_EN(1)
|
||||
esp.PCR.SetUART1_CONF_UART1_RST_EN(0)
|
||||
bus.SetCLK_CONF_RST_CORE(0)
|
||||
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(0)
|
||||
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_A(0)
|
||||
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_B(0)
|
||||
}
|
||||
// wait for Core Clock to ready for configuration
|
||||
for bus.GetREG_UPDATE() > 0 {
|
||||
riscv.Asm("nop")
|
||||
}
|
||||
}
|
||||
|
||||
func (uart *UART) SetBaudRate(baudRate uint32) {
|
||||
// based on esp-idf
|
||||
max_div := uint32((1 << 12) - 1)
|
||||
sclk_div := (pplClockFreq + (max_div * baudRate) - 1) / (max_div * baudRate)
|
||||
clk_div := (pplClockFreq << 4) / (baudRate * sclk_div)
|
||||
uart.Bus.SetCLKDIV(clk_div >> 4)
|
||||
uart.Bus.SetCLKDIV_FRAG(clk_div & 0xf)
|
||||
// On C6, the SCLK divisor is in the PCR register.
|
||||
switch uart.Bus {
|
||||
case esp.UART0:
|
||||
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(sclk_div - 1)
|
||||
case esp.UART1:
|
||||
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(sclk_div - 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (uart *UART) setupPins(config UARTConfig, regs registerSet) {
|
||||
config.RX.Configure(PinConfig{Mode: PinInputPullup})
|
||||
config.TX.Configure(PinConfig{Mode: PinInputPullup})
|
||||
|
||||
// link TX with GPIO signal X (technical reference manual, GPIO matrix)
|
||||
config.TX.outFunc().Set(regs.gpioMatrixSignal)
|
||||
// link RX with GPIO signal X and route signals via GPIO matrix
|
||||
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX))
|
||||
}
|
||||
|
||||
func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) {
|
||||
// Disable all UART interrupts
|
||||
uart.Bus.INT_ENA.ClearBits(0x0ffff)
|
||||
|
||||
intrMapReg.Set(7)
|
||||
onceUart.Do(func() {
|
||||
_ = interrupt.New(7, func(i interrupt.Interrupt) {
|
||||
UART0.serveInterrupt(0)
|
||||
UART1.serveInterrupt(1)
|
||||
}).Enable()
|
||||
})
|
||||
}
|
||||
|
||||
func (uart *UART) serveInterrupt(num int) {
|
||||
// get interrupt status
|
||||
interrutFlag := uart.Bus.INT_ST.Get()
|
||||
if (interrutFlag & uartInterrupts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// block UART interrupts while processing
|
||||
uart.Bus.INT_ENA.ClearBits(uartInterrupts)
|
||||
|
||||
if interrutFlag&esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA > 0 {
|
||||
for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 {
|
||||
b := uart.Bus.GetFIFO_RXFIFO_RD_BYTE()
|
||||
if !uart.Buffer.Put(byte(b & 0xff)) {
|
||||
uart.DataOverflowDetected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if interrutFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 {
|
||||
uart.ParityErrorDetected = true
|
||||
}
|
||||
if 0 != interrutFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA {
|
||||
uart.DataErrorDetected = true
|
||||
}
|
||||
if 0 != interrutFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA {
|
||||
uart.DataOverflowDetected = true
|
||||
}
|
||||
if 0 != interrutFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA {
|
||||
uart.DataErrorDetected = true
|
||||
}
|
||||
|
||||
// Clear the UART interrupt status
|
||||
uart.Bus.INT_CLR.SetBits(interrutFlag)
|
||||
uart.Bus.INT_CLR.ClearBits(interrutFlag)
|
||||
// Enable interrupts
|
||||
uart.Bus.INT_ENA.Set(uartInterrupts)
|
||||
}
|
||||
|
||||
const uart_empty_thresh_default = 10
|
||||
|
||||
func (uart *UART) enableTransmitter() {
|
||||
uart.Bus.SetCONF0_TXFIFO_RST(1)
|
||||
uart.Bus.SetCONF0_TXFIFO_RST(0)
|
||||
uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default)
|
||||
}
|
||||
|
||||
func (uart *UART) enableReceiver() {
|
||||
uart.Bus.SetCONF0_RXFIFO_RST(1)
|
||||
uart.Bus.SetCONF0_RXFIFO_RST(0)
|
||||
uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1)
|
||||
uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1)
|
||||
}
|
||||
|
||||
func (uart *UART) writeByte(b byte) error {
|
||||
for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 {
|
||||
// Wait until there is space in the transmit buffer.
|
||||
}
|
||||
uart.Bus.FIFO.Set(uint32(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uart *UART) flush() {}
|
||||
@@ -0,0 +1,37 @@
|
||||
//go:build esp32c6
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
)
|
||||
|
||||
// GPIO matrix signal indices for I2C0 on ESP32-C6.
|
||||
const (
|
||||
I2CEXT0_SCL_OUT_IDX = 45
|
||||
I2CEXT0_SDA_OUT_IDX = 46
|
||||
)
|
||||
|
||||
var (
|
||||
I2C0 = &I2C{
|
||||
Bus: esp.I2C0,
|
||||
funcSCL: I2CEXT0_SCL_OUT_IDX,
|
||||
funcSDA: I2CEXT0_SDA_OUT_IDX,
|
||||
useExt1: false,
|
||||
}
|
||||
)
|
||||
|
||||
// enableI2C0PeriphClock enables the I2C0 peripheral clock via PCR.
|
||||
func enableI2C0PeriphClock() {
|
||||
// Enable the APB/bus clock for the I2C0 registers and pulse the reset.
|
||||
esp.PCR.SetI2C0_CONF_I2C0_CLK_EN(1)
|
||||
esp.PCR.SetI2C0_CONF_I2C0_RST_EN(1)
|
||||
esp.PCR.SetI2C0_CONF_I2C0_RST_EN(0)
|
||||
|
||||
// On the ESP32-C6 the I2C functional (source) clock is gated and selected
|
||||
// in PCR.
|
||||
// Select the XTAL (40 MHz) source and enable the clock gate, otherwise SCL
|
||||
// is never driven and no bytes are clocked onto the bus.
|
||||
esp.PCR.SetI2C_SCLK_CONF_I2C_SCLK_SEL(i2cClkSource)
|
||||
esp.PCR.SetI2C_SCLK_CONF_I2C_SCLK_EN(1)
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
//go:build esp32c6
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"errors"
|
||||
"machine/usb"
|
||||
"machine/usb/descriptor"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// USB Serial/JTAG Controller
|
||||
// The ESP32-C6 has the same built-in USB Serial/JTAG hardware IP as the
|
||||
// ESP32-C3. The only difference at the machine level is how the peripheral
|
||||
// clock is enabled: C3 uses SYSTEM.PERIP_CLK_EN0, while C6 uses the PCR
|
||||
// (Peripheral Clock Reset) block.
|
||||
|
||||
const cpuInterruptFromUSB = 10
|
||||
|
||||
type USB_DEVICE struct {
|
||||
Bus *esp.USB_DEVICE_Type
|
||||
Buffer *RingBuffer
|
||||
txPending bool // unflushed data in the EP1 TX FIFO
|
||||
txStalled bool // set when flushAndWait fails (no host reading); cleared when FIFO becomes writable
|
||||
}
|
||||
|
||||
var (
|
||||
_USBCDC = &USB_DEVICE{
|
||||
Bus: esp.USB_DEVICE,
|
||||
Buffer: NewRingBuffer(),
|
||||
}
|
||||
|
||||
USBCDC Serialer = _USBCDC
|
||||
)
|
||||
|
||||
var (
|
||||
errUSBWrongSize = errors.New("USB: invalid write size")
|
||||
errUSBCouldNotWriteAllData = errors.New("USB: could not write all data")
|
||||
)
|
||||
|
||||
type Serialer interface {
|
||||
WriteByte(c byte) error
|
||||
Write(data []byte) (n int, err error)
|
||||
Configure(config UARTConfig) error
|
||||
Buffered() int
|
||||
ReadByte() (byte, error)
|
||||
DTR() bool
|
||||
RTS() bool
|
||||
}
|
||||
|
||||
var usbConfigured bool
|
||||
|
||||
// USBDevice provides a stub USB device for the ESP32-C6. The hardware
|
||||
// only supports a fixed-function CDC-ACM serial port, so the programmable
|
||||
// USB device features are no-ops.
|
||||
type USBDevice struct {
|
||||
initcomplete bool
|
||||
InitEndpointComplete bool
|
||||
}
|
||||
|
||||
var USBDev = &USBDevice{}
|
||||
|
||||
func (dev *USBDevice) SetStallEPIn(ep uint32) {}
|
||||
func (dev *USBDevice) SetStallEPOut(ep uint32) {}
|
||||
func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
|
||||
func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
|
||||
|
||||
// initUSB is intentionally empty — the interp phase evaluates init()
|
||||
// functions at compile time and cannot access hardware registers.
|
||||
// Actual hardware setup is deferred to the first Configure() call.
|
||||
func initUSB() {}
|
||||
|
||||
// Configure initialises the USB Serial/JTAG controller clock, pads, and
|
||||
// interrupt so that received data is buffered automatically.
|
||||
func (usbdev *USB_DEVICE) Configure(config UARTConfig) error {
|
||||
if usbConfigured {
|
||||
return nil
|
||||
}
|
||||
usbConfigured = true
|
||||
|
||||
// Enable the USB_DEVICE peripheral clock via PCR (C6 uses PCR instead
|
||||
// of SYSTEM.PERIP_CLK_EN0 that C3 used).
|
||||
esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_CLK_EN(1)
|
||||
esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_RST_EN(0)
|
||||
|
||||
// Ensure internal PHY is selected and USB pads are enabled.
|
||||
usbdev.Bus.SetCONF0_PHY_SEL(0)
|
||||
usbdev.Bus.SetCONF0_USB_PAD_ENABLE(1)
|
||||
usbdev.Bus.SetCONF0_DP_PULLUP(1)
|
||||
|
||||
// Clear any pending interrupts.
|
||||
usbdev.Bus.INT_CLR.Set(0xFFFFFFFF)
|
||||
|
||||
// Enable the RX-packet-received interrupt.
|
||||
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
|
||||
|
||||
// Map the USB peripheral interrupt to CPU interrupt cpuInterruptFromUSB.
|
||||
esp.INTERRUPT_CORE0.SetUSB_INTR_MAP(cpuInterruptFromUSB)
|
||||
|
||||
_ = interrupt.New(cpuInterruptFromUSB, func(interrupt.Interrupt) {
|
||||
_USBCDC.handleInterrupt()
|
||||
}).Enable()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureConfigured triggers lazy initialization on first use.
|
||||
func (usbdev *USB_DEVICE) ensureConfigured() {
|
||||
if !usbConfigured {
|
||||
usbdev.Configure(UARTConfig{})
|
||||
}
|
||||
}
|
||||
|
||||
// handleInterrupt drains the hardware RX FIFO into the software ring buffer.
|
||||
func (usbdev *USB_DEVICE) handleInterrupt() {
|
||||
intStatus := usbdev.Bus.INT_ST.Get()
|
||||
|
||||
// Disable the RX interrupt to prevent re-triggering while we drain.
|
||||
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(0)
|
||||
|
||||
if intStatus&esp.USB_DEVICE_INT_ST_SERIAL_OUT_RECV_PKT_INT_ST != 0 {
|
||||
for usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 {
|
||||
b := byte(usbdev.Bus.EP1.Get())
|
||||
usbdev.Buffer.Put(b)
|
||||
}
|
||||
usbdev.Bus.SetINT_CLR_SERIAL_OUT_RECV_PKT_INT_CLR(1)
|
||||
}
|
||||
|
||||
// Re-enable the RX interrupt.
|
||||
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
|
||||
}
|
||||
|
||||
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
|
||||
usbdev.ensureConfigured()
|
||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
||||
if usbdev.txStalled {
|
||||
return errUSBCouldNotWriteAllData
|
||||
}
|
||||
if !usbdev.flushAndWait() {
|
||||
usbdev.txStalled = true
|
||||
return errUSBCouldNotWriteAllData
|
||||
}
|
||||
}
|
||||
usbdev.txStalled = false
|
||||
|
||||
usbdev.Bus.EP1.Set(uint32(c))
|
||||
|
||||
if c == '\n' {
|
||||
usbdev.flush()
|
||||
usbdev.txPending = false
|
||||
} else {
|
||||
usbdev.txPending = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) {
|
||||
usbdev.ensureConfigured()
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
for i, c := range data {
|
||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
||||
if usbdev.txStalled {
|
||||
return i, errUSBCouldNotWriteAllData
|
||||
}
|
||||
if !usbdev.flushAndWait() {
|
||||
usbdev.txStalled = true
|
||||
return i, errUSBCouldNotWriteAllData
|
||||
}
|
||||
}
|
||||
usbdev.txStalled = false
|
||||
usbdev.Bus.EP1.Set(uint32(c))
|
||||
}
|
||||
|
||||
usbdev.flush()
|
||||
usbdev.txPending = false
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// Buffered returns the number of bytes waiting in the receive ring buffer.
|
||||
func (usbdev *USB_DEVICE) Buffered() int {
|
||||
usbdev.ensureConfigured()
|
||||
if usbdev.txPending {
|
||||
usbdev.flush()
|
||||
usbdev.txPending = false
|
||||
}
|
||||
return int(usbdev.Buffer.Used())
|
||||
}
|
||||
|
||||
// ReadByte returns a byte from the receive ring buffer.
|
||||
func (usbdev *USB_DEVICE) ReadByte() (byte, error) {
|
||||
b, ok := usbdev.Buffer.Get()
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (usbdev *USB_DEVICE) DTR() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (usbdev *USB_DEVICE) RTS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// flush signals WR_DONE to tell the hardware to send the data that has
|
||||
// been written to the EP1 FIFO.
|
||||
func (usbdev *USB_DEVICE) flush() {
|
||||
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
|
||||
}
|
||||
|
||||
// flushAndWait signals WR_DONE and waits for the EP1 FIFO to become
|
||||
// writable again. Returns false if the FIFO is still locked after the
|
||||
// timeout (no host reading).
|
||||
func (usbdev *USB_DEVICE) flushAndWait() bool {
|
||||
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
|
||||
for i := 0; i < 50000; i++ {
|
||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FlushSerial flushes any pending USB serial TX data.
|
||||
func FlushSerial() {
|
||||
if _USBCDC.txPending {
|
||||
_USBCDC.flush()
|
||||
_USBCDC.txPending = false
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigureUSBEndpoint is a no-op on ESP32-C6 — the hardware does not
|
||||
// support programmable USB endpoints.
|
||||
func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) {
|
||||
}
|
||||
|
||||
// SendZlp is a no-op on ESP32-C6.
|
||||
func SendZlp() {
|
||||
}
|
||||
|
||||
// SendUSBInPacket is a no-op on ESP32-C6.
|
||||
func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
return false
|
||||
}
|
||||
@@ -33,3 +33,10 @@ func initI2CExt1Clock() {
|
||||
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT1_CLK_EN(1)
|
||||
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT1_RST(0)
|
||||
}
|
||||
|
||||
// enableI2C0PeriphClock enables the I2C0 peripheral clock via SYSTEM.
|
||||
func enableI2C0PeriphClock() {
|
||||
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
|
||||
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
|
||||
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build (esp32c3 || esp32s3) && !m5stamp_c3
|
||||
//go:build (esp32c3 || esp32c6 || esp32s3) && !m5stamp_c3
|
||||
|
||||
package machine
|
||||
|
||||
@@ -53,9 +53,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
|
||||
//go:inline
|
||||
func (i2c *I2C) initClock(config I2CConfig) {
|
||||
if !i2c.useExt1 {
|
||||
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
|
||||
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
|
||||
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
|
||||
enableI2C0PeriphClock()
|
||||
} else {
|
||||
initI2CExt1Clock()
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
|
||||
|
||||
// Set and enable the GPIOTE interrupt. It's not a problem if this happens
|
||||
// more than once.
|
||||
interrupt.New(nrf.IRQ_GPIOTE, func(interrupt.Interrupt) {
|
||||
intr := interrupt.New(nrf.IRQ_GPIOTE, func(interrupt.Interrupt) {
|
||||
for i := range nrf.GPIOTE.EVENTS_IN {
|
||||
if nrf.GPIOTE.EVENTS_IN[i].Get() != 0 {
|
||||
nrf.GPIOTE.EVENTS_IN[i].Set(0)
|
||||
@@ -153,7 +153,9 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
|
||||
pinCallbacks[i](pin)
|
||||
}
|
||||
}
|
||||
}).Enable()
|
||||
})
|
||||
intr.SetPriority(0x40) // interrupt priority 2 (highest priority not reserved by the SoftDevice)
|
||||
intr.Enable()
|
||||
|
||||
// Everything was configured correctly.
|
||||
return nil
|
||||
|
||||
@@ -22,17 +22,6 @@ var (
|
||||
epinen uint32
|
||||
epouten uint32
|
||||
easyDMABusy volatile.Register8
|
||||
|
||||
endPoints = []uint32{
|
||||
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
|
||||
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
|
||||
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
|
||||
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
|
||||
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
|
||||
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
|
||||
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
|
||||
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
|
||||
}
|
||||
)
|
||||
|
||||
// enterCriticalSection is used to protect access to easyDMA - only one thing
|
||||
@@ -183,7 +172,7 @@ func handleUSBIRQ(interrupt.Interrupt) {
|
||||
epDataStatus := nrf.USBD.EPDATASTATUS.Get()
|
||||
nrf.USBD.EPDATASTATUS.Set(epDataStatus)
|
||||
var i uint32
|
||||
for i = 1; i < uint32(len(endPoints)); i++ {
|
||||
for i = 1; i < NumberOfUSBEndpoints; i++ {
|
||||
// Check if endpoint has a pending interrupt
|
||||
inDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPIN1<<(i-1)) > 0
|
||||
outDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPOUT1<<(i-1)) > 0
|
||||
@@ -202,7 +191,7 @@ func handleUSBIRQ(interrupt.Interrupt) {
|
||||
}
|
||||
|
||||
// ENDEPOUT[n] events
|
||||
for i := 0; i < len(endPoints); i++ {
|
||||
for i := 0; i < NumberOfUSBEndpoints; i++ {
|
||||
if nrf.USBD.EVENTS_ENDEPOUT[i].Get() > 0 {
|
||||
nrf.USBD.EVENTS_ENDEPOUT[i].Set(0)
|
||||
buf := handleEndpointRx(uint32(i))
|
||||
@@ -378,3 +367,19 @@ func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (dev *USBDevice) SetStallEPIn(ep uint32) {
|
||||
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_IO | nrf.USBD_EPSTALL_STALL)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) SetStallEPOut(ep uint32) {
|
||||
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_STALL)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
|
||||
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_IO)
|
||||
}
|
||||
|
||||
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
|
||||
nrf.USBD.EPSTALL.Set(ep)
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
}
|
||||
|
||||
s2 := rp.USBCTRL_REGS.BUFF_STATUS.Get()
|
||||
rp.USBCTRL_REGS.BUFF_STATUS.Set(s2)
|
||||
|
||||
// OUT (PC -> rp2040)
|
||||
for i := 0; i < 16; i++ {
|
||||
@@ -110,7 +111,6 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
}
|
||||
}
|
||||
|
||||
rp.USBCTRL_REGS.BUFF_STATUS.Set(s2)
|
||||
}
|
||||
|
||||
// Bus is reset
|
||||
@@ -128,7 +128,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
|
||||
const ackTimeout = 570
|
||||
|
||||
rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC)
|
||||
sendUSBPacket(0, []byte{})
|
||||
sendUSBPacket(0, nil)
|
||||
|
||||
// Wait for transfer to complete with a timeout.
|
||||
t := timer.timeElapsed()
|
||||
|
||||
@@ -155,9 +155,7 @@ static bool pico_processor_state_is_nonsecure(void) {
|
||||
|
||||
#define BOOTROM_FUNC_TABLE_OFFSET 0x14
|
||||
|
||||
// todo remove this (or #ifdef it for A1/A2)
|
||||
#define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2)
|
||||
#define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4)
|
||||
#define BOOTROM_WELL_KNOWN_PTR_SIZE 2
|
||||
|
||||
#define BOOTROM_VTABLE_OFFSET 0x00
|
||||
#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE)
|
||||
@@ -229,11 +227,12 @@ void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interf
|
||||
// https://github.com/raspberrypi/pico-sdk
|
||||
// src/rp2350/hardware_regs/include/hardware/regs/qmi.h
|
||||
|
||||
#define QMI_DIRECT_CSR_EN_BITS 0x00000001
|
||||
#define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000
|
||||
#define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400
|
||||
#define QMI_M1_WFMT_RESET 0x00001000
|
||||
#define QMI_M1_WCMD_RESET 0x0000a002
|
||||
#define QMI_DIRECT_CSR_EN_BITS 0x00000001
|
||||
#define QMI_DIRECT_CSR_ASSERT_CS0N_BITS 0x00000004
|
||||
#define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000
|
||||
#define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400
|
||||
#define QMI_M1_WFMT_RESET 0x00001000
|
||||
#define QMI_M1_WCMD_RESET 0x0000a002
|
||||
|
||||
|
||||
// https://github.com/raspberrypi/pico-sdk
|
||||
@@ -406,13 +405,11 @@ static ram_func void flash_rp2350_restore_qmi_cs1(const flash_rp2350_qmi_save_st
|
||||
}
|
||||
|
||||
void ram_func flash_cs_force(bool high) {
|
||||
uint32_t field_val = high ?
|
||||
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH :
|
||||
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW;
|
||||
hw_write_masked(&io_qspi_hw->io[1].ctrl,
|
||||
field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB,
|
||||
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS
|
||||
);
|
||||
if (high) {
|
||||
hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_ASSERT_CS0N_BITS);
|
||||
} else {
|
||||
hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_ASSERT_CS0N_BITS);
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from flash_range_program()
|
||||
|
||||
@@ -93,6 +93,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
}
|
||||
|
||||
s2 := rp.USB.BUFF_STATUS.Get()
|
||||
rp.USB.BUFF_STATUS.Set(s2)
|
||||
|
||||
// OUT (PC -> rp2350)
|
||||
for i := 0; i < 16; i++ {
|
||||
@@ -113,7 +114,6 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
}
|
||||
}
|
||||
|
||||
rp.USB.BUFF_STATUS.Set(s2)
|
||||
}
|
||||
|
||||
// Bus is reset
|
||||
|
||||
@@ -130,11 +130,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
|
||||
var okSDI, okSDO, okSCK bool
|
||||
switch spi.Bus {
|
||||
case rp.SPI0:
|
||||
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
|
||||
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
|
||||
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
|
||||
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
|
||||
case rp.SPI1:
|
||||
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
|
||||
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
|
||||
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
|
||||
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
|
||||
var okSDI, okSDO, okSCK bool
|
||||
switch spi.Bus {
|
||||
case rp.SPI0:
|
||||
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
|
||||
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
|
||||
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
|
||||
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
|
||||
case rp.SPI1:
|
||||
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
|
||||
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
|
||||
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
|
||||
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
|
||||
var okSDI, okSDO, okSCK bool
|
||||
switch spi.Bus {
|
||||
case rp.SPI0:
|
||||
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 || config.SDI == 32 || config.SDI == 36
|
||||
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 || config.SDI == 32 || config.SDI == 36
|
||||
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23 || config.SDO == 35 || config.SDO == 39
|
||||
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22 || config.SCK == 34 || config.SCK == 38
|
||||
case rp.SPI1:
|
||||
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 || config.SDI == 40 || config.SDI == 44
|
||||
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 || config.SDI == 40 || config.SDI == 44
|
||||
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27 || config.SDO == 31 || config.SDO == 43 || config.SDO == 47
|
||||
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26 || config.SCK == 30 || config.SCK == 42 || config.SCK == 46
|
||||
}
|
||||
|
||||
@@ -174,7 +174,9 @@ func (spi *SPI) Configure(config SPIConfig) error {
|
||||
// SPI pin configuration
|
||||
config.SCK.setFunc(fnSPI)
|
||||
config.SDO.setFunc(fnSPI)
|
||||
config.SDI.setFunc(fnSPI)
|
||||
if config.SDI != NoPin {
|
||||
config.SDI.setFunc(fnSPI)
|
||||
}
|
||||
|
||||
return spi.initSPI(config)
|
||||
}
|
||||
|
||||
@@ -16,53 +16,60 @@ var (
|
||||
data []byte
|
||||
pid uint32
|
||||
}
|
||||
|
||||
endPoints = []uint32{
|
||||
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
|
||||
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
|
||||
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
|
||||
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
|
||||
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
|
||||
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
|
||||
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
|
||||
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
|
||||
}
|
||||
)
|
||||
|
||||
func initEndpoint(ep, config uint32) {
|
||||
val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff)
|
||||
|
||||
// Each endpoint has 128 bytes of DPRAM buffer space allocated (2 * usbBufferLen).
|
||||
// To support bidirectional configurations using the same endpoint number,
|
||||
// we allocate the first 64 bytes (Buffer0) to OUT transfers, and the remaining
|
||||
// 64 bytes (Buffer1) to IN transfers by shifting the IN offset by usbBufferLen.
|
||||
offset := ep*2*usbBufferLen + 0x100
|
||||
val |= offset
|
||||
|
||||
// Bulk and interrupt endpoints must have their Packet ID reset to DATA0 when un-stalled.
|
||||
epXPIDReset[ep] = false // Default to false in case an endpoint is re-initialized.
|
||||
// Since both directions share the same ep, we reset their PID flags independently.
|
||||
if (config & usb.EndpointIn) != 0 {
|
||||
epXPIDResetIn[ep] = false
|
||||
} else {
|
||||
epXPIDResetOut[ep] = false
|
||||
}
|
||||
|
||||
switch config {
|
||||
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
|
||||
epXPIDResetIn[ep] = true
|
||||
epXdata0In[ep] = false
|
||||
val |= offset + usbBufferLen
|
||||
val |= usbEpControlEndpointTypeInterrupt
|
||||
_usbDPSRAM.EPxControl[ep].In.Set(val)
|
||||
epXPIDReset[ep] = true
|
||||
|
||||
case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut:
|
||||
epXPIDResetOut[ep] = true
|
||||
epXdata0Out[ep] = false
|
||||
val |= offset
|
||||
val |= usbEpControlEndpointTypeBulk
|
||||
_usbDPSRAM.EPxControl[ep].Out.Set(val)
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask)
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
|
||||
epXPIDReset[ep] = true
|
||||
|
||||
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut:
|
||||
epXPIDResetOut[ep] = true
|
||||
epXdata0Out[ep] = false
|
||||
val |= offset
|
||||
val |= usbEpControlEndpointTypeInterrupt
|
||||
_usbDPSRAM.EPxControl[ep].Out.Set(val)
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask)
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
|
||||
epXPIDReset[ep] = true
|
||||
|
||||
case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn:
|
||||
epXPIDResetIn[ep] = true
|
||||
epXdata0In[ep] = false
|
||||
val |= offset + usbBufferLen
|
||||
val |= usbEpControlEndpointTypeBulk
|
||||
_usbDPSRAM.EPxControl[ep].In.Set(val)
|
||||
epXPIDReset[ep] = true
|
||||
|
||||
case usb.ENDPOINT_TYPE_CONTROL:
|
||||
val |= offset
|
||||
val |= usbEpControlEndpointTypeControl
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid)
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
|
||||
@@ -90,7 +97,7 @@ func sendUSBPacket(ep uint32, data []byte) {
|
||||
} else {
|
||||
sendOnEP0DATADONE.offset = 0
|
||||
}
|
||||
epXdata0[ep] = true
|
||||
epXdata0In[ep] = true
|
||||
}
|
||||
|
||||
sendViaEPIn(ep, data, count)
|
||||
@@ -127,21 +134,31 @@ func handleEndpointRx(ep uint32) []byte {
|
||||
// AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer.
|
||||
func AckUsbOutTransfer(ep uint32) {
|
||||
ep = ep & 0x7F
|
||||
setEPDataPID(ep, !epXdata0[ep])
|
||||
setEPDataPIDOut(ep, !epXdata0Out[ep])
|
||||
}
|
||||
|
||||
// Set the USB endpoint Packet ID to DATA0 or DATA1.
|
||||
func setEPDataPID(ep uint32, dataOne bool) {
|
||||
epXdata0[ep] = dataOne
|
||||
if epXdata0[ep] || ep == 0 {
|
||||
// Set the USB endpoint Packet ID to DATA0 or DATA1 for OUT direction.
|
||||
func setEPDataPIDOut(ep uint32, dataOne bool) {
|
||||
epXdata0Out[ep] = dataOne
|
||||
if epXdata0Out[ep] || ep == 0 {
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid)
|
||||
}
|
||||
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
|
||||
}
|
||||
|
||||
// Set the USB endpoint Packet ID to DATA0 or DATA1 for IN direction.
|
||||
func setEPDataPIDIn(ep uint32, dataOne bool) {
|
||||
epXdata0In[ep] = dataOne
|
||||
if epXdata0In[ep] || ep == 0 {
|
||||
_usbDPSRAM.EPxBufferControl[ep].In.SetBits(usbBuf0CtrlData1Pid)
|
||||
}
|
||||
|
||||
_usbDPSRAM.EPxBufferControl[ep].In.SetBits(usbBuf0CtrlAvail)
|
||||
}
|
||||
|
||||
func SendZlp() {
|
||||
sendUSBPacket(0, []byte{})
|
||||
sendUSBPacket(0, nil)
|
||||
}
|
||||
|
||||
func sendViaEPIn(ep uint32, data []byte, count int) {
|
||||
@@ -149,15 +166,19 @@ func sendViaEPIn(ep uint32, data []byte, count int) {
|
||||
val := uint32(count) | usbBuf0CtrlAvail
|
||||
|
||||
// DATA0 or DATA1
|
||||
epXdata0[ep&0x7F] = !epXdata0[ep&0x7F]
|
||||
if !epXdata0[ep&0x7F] {
|
||||
epXdata0In[ep&0x7F] = !epXdata0In[ep&0x7F]
|
||||
if !epXdata0In[ep&0x7F] {
|
||||
val |= usbBuf0CtrlData1Pid
|
||||
}
|
||||
|
||||
// Mark as full
|
||||
val |= usbBuf0CtrlFull
|
||||
|
||||
copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count])
|
||||
if (ep & 0x7F) == 0 {
|
||||
copy(_usbDPSRAM.EPxBuffer[0].Buffer0[:], data[:count])
|
||||
} else {
|
||||
copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer1[:], data[:count])
|
||||
}
|
||||
_usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val)
|
||||
}
|
||||
|
||||
@@ -189,9 +210,9 @@ func (dev *USBDevice) ClearStallEPIn(ep uint32) {
|
||||
ep = ep & 0x7F
|
||||
val := uint32(usbBuf0CtrlStall)
|
||||
_usbDPSRAM.EPxBufferControl[ep].In.ClearBits(val)
|
||||
if epXPIDReset[ep] {
|
||||
if epXPIDResetIn[ep] {
|
||||
// Reset the PID to DATA0
|
||||
setEPDataPID(ep, false)
|
||||
setEPDataPIDIn(ep, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,9 +221,9 @@ func (dev *USBDevice) ClearStallEPOut(ep uint32) {
|
||||
ep = ep & 0x7F
|
||||
val := uint32(usbBuf0CtrlStall)
|
||||
_usbDPSRAM.EPxBufferControl[ep].Out.ClearBits(val)
|
||||
if epXPIDReset[ep] {
|
||||
if epXPIDResetOut[ep] {
|
||||
// Reset the PID to DATA0
|
||||
setEPDataPID(ep, false)
|
||||
setEPDataPIDOut(ep, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,10 +251,12 @@ type usbBuffer struct {
|
||||
}
|
||||
|
||||
var (
|
||||
_usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000)))
|
||||
epXdata0 [16]bool
|
||||
epXPIDReset [16]bool
|
||||
setupBytes [8]byte
|
||||
_usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000)))
|
||||
epXdata0In [16]bool
|
||||
epXdata0Out [16]bool
|
||||
epXPIDResetIn [16]bool
|
||||
epXPIDResetOut [16]bool
|
||||
setupBytes [8]byte
|
||||
)
|
||||
|
||||
func (d *usbDPSRAM) setupBytes() []byte {
|
||||
|
||||
@@ -76,13 +76,15 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
|
||||
uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
|
||||
}
|
||||
|
||||
// Clear overrun error (ORE, bit 3) to prevent an interrupt storm.
|
||||
if s&0x8 != 0 {
|
||||
// Clear error flags (ORE=bit3, NE=bit2, FE=bit1, PE=bit0) to prevent
|
||||
// an interrupt storm and ensure the USART can continue receiving.
|
||||
if s&0xF != 0 {
|
||||
if uart.errClearReg != nil {
|
||||
// Newer USART peripherals: clear ORE via the ICR register.
|
||||
uart.errClearReg.Set(0x8) // ORECF
|
||||
// Newer USART peripherals (L0, L4, L5, G0, F7, U5, WL, etc.):
|
||||
// clear all error flags via ICR (ORECF|NECF|FECF|PECF = bits 3:0).
|
||||
uart.errClearReg.Set(s & 0xF)
|
||||
} else if s&0x20 == 0 {
|
||||
// Older USART (F1/F4): ORE is cleared by reading SR then DR.
|
||||
// Older USART (F1/F4): errors are cleared by reading SR then DR.
|
||||
// SR was already read above. If RXNE was set, DR was read in
|
||||
// the Receive path. Otherwise do a dummy DR read to complete
|
||||
// the clearing sequence.
|
||||
@@ -91,20 +93,20 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART. Defer to chip-specific
|
||||
// routines for calculation
|
||||
func (uart *UART) SetBaudRate(br uint32) {
|
||||
divider := uart.getBaudRateDivisor(br)
|
||||
uart.Bus.BRR.Set(divider)
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the UART.
|
||||
func (uart *UART) writeByte(c byte) error {
|
||||
uart.txReg.Set(uint32(c))
|
||||
|
||||
// Wait for the transmit data register to be empty before writing, so we
|
||||
// don't overwrite a byte that hasn't moved to the shift register yet.
|
||||
for !uart.statusReg.HasBits(uart.txEmptyFlag) {
|
||||
}
|
||||
uart.txReg.Set(uint32(c))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uart *UART) flush() {}
|
||||
// flush waits until the USART shift register has finished transmitting the
|
||||
// last byte (TC = Transmission Complete, bit 6). Without this, Write() returns
|
||||
// while the final byte is still clocking out on the wire.
|
||||
func (uart *UART) flush() {
|
||||
for !uart.statusReg.HasBits(1 << 6) { // TC bit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//go:build stm32 && !stm32u585
|
||||
|
||||
package machine
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART. Defers to
|
||||
// chip-specific getBaudRateDivisor for the divisor calculation.
|
||||
//
|
||||
// On STM32U585 this function is overridden in machine_stm32u585.go because
|
||||
// the U5 family requires UE=0 to write BRR.
|
||||
func (uart *UART) SetBaudRate(br uint32) {
|
||||
divider := uart.getBaudRateDivisor(br)
|
||||
uart.Bus.BRR.Set(divider)
|
||||
}
|
||||
@@ -256,6 +256,7 @@ func enableAltFuncClock(bus unsafe.Pointer) {
|
||||
stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_I2C4EN)
|
||||
case unsafe.Pointer(stm32.USART1): // USART1 clock enable
|
||||
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN)
|
||||
_ = stm32.RCC.APB2ENR.Get() // readback: ensure clock is active before accessing USART1 registers
|
||||
case unsafe.Pointer(stm32.USART2): // USART2 clock enable
|
||||
stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART2EN)
|
||||
case unsafe.Pointer(stm32.USART3): // USART3 clock enable
|
||||
|
||||
@@ -8,14 +8,14 @@ import (
|
||||
)
|
||||
|
||||
func CPUFrequency() uint32 {
|
||||
return 4_000_000
|
||||
return 160_000_000
|
||||
}
|
||||
|
||||
// Internal use: configured speed of the APB1 and APB2 timers, this should be kept
|
||||
// in sync with any changes to runtime package which configures the oscillators
|
||||
// and clock frequencies
|
||||
const APB1_TIM_FREQ = 4e6 // 4MHz (MSI default)
|
||||
const APB2_TIM_FREQ = 4e6 // 4MHz (MSI default)
|
||||
const APB1_TIM_FREQ = 160e6 // 160MHz (PLL1: MSIS 4MHz × 80 / 1 / 2)
|
||||
const APB2_TIM_FREQ = 160e6 // 160MHz (PLL1: MSIS 4MHz × 80 / 1 / 2)
|
||||
|
||||
//---------- UART related code
|
||||
|
||||
@@ -55,10 +55,21 @@ func (uart *UART) isLPUART1() bool {
|
||||
// NOTE: keep this in sync with the runtime/runtime_stm32u5.go clock init code
|
||||
func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {
|
||||
if uart.isLPUART1() {
|
||||
// LPUART uses BRR = 256 * fclk / baud
|
||||
return (256 * CPUFrequency()) / baudRate
|
||||
// LPUART uses BRR = 256 * fclk / baud.
|
||||
// Use 64-bit arithmetic to avoid overflow: at 160 MHz,
|
||||
// 256 * 160_000_000 = 40_960_000_000 which exceeds uint32 max.
|
||||
return uint32(uint64(256) * uint64(CPUFrequency()) / uint64(baudRate))
|
||||
}
|
||||
return CPUFrequency() / baudRate
|
||||
// USART requires BRR >= 16 for 16x oversampling (OVER8=0).
|
||||
// A divisor below 16 is invalid per the STM32 reference manual and causes
|
||||
// undefined hardware behaviour — in practice the receiver fires ORE/RXNE
|
||||
// interrupts at an impossible rate, completely starving the CPU.
|
||||
const minBRR = 16
|
||||
divisor := CPUFrequency() / baudRate
|
||||
if divisor < minBRR {
|
||||
divisor = minBRR
|
||||
}
|
||||
return divisor
|
||||
}
|
||||
|
||||
// Register names vary by ST processor, these are for STM U5
|
||||
@@ -70,6 +81,24 @@ func (uart *UART) setRegisters() {
|
||||
uart.errClearReg = &uart.Bus.ICR
|
||||
}
|
||||
|
||||
// SetBaudRate overrides the shared implementation for STM32U5. On this
|
||||
// family the BRR register is read-only while UE=1 (USART enabled), so the
|
||||
// USART must be briefly disabled to change the baud rate. This matters when
|
||||
// the servo library (or any code) calls SetBaudRate after Configure has
|
||||
// already enabled the USART.
|
||||
func (uart *UART) SetBaudRate(br uint32) {
|
||||
cr1 := uart.Bus.CR1.Get()
|
||||
if cr1&stm32.USART_CR1_UE != 0 {
|
||||
// Disable the USART so BRR becomes writable.
|
||||
uart.Bus.CR1.Set(cr1 &^ stm32.USART_CR1_UE)
|
||||
}
|
||||
uart.Bus.BRR.Set(uart.getBaudRateDivisor(br))
|
||||
if cr1&stm32.USART_CR1_UE != 0 {
|
||||
// Restore CR1 exactly as it was (re-enables USART, TE, RE, etc.).
|
||||
uart.Bus.CR1.Set(cr1)
|
||||
}
|
||||
}
|
||||
|
||||
//---------- SPI related types and code
|
||||
|
||||
// SPI on the STM32U5 using the new SPIv2 peripheral
|
||||
|
||||
@@ -71,16 +71,18 @@ type rttSerial struct {
|
||||
rttControlBlock
|
||||
}
|
||||
|
||||
var terminalByteString = []byte("Terminal\x00")
|
||||
|
||||
func (s *rttSerial) Configure(config UARTConfig) error {
|
||||
s.maxNumUpBuffers = rttMaxNumUpBuffers
|
||||
s.maxNumDownBuffers = rttMaxNumDownBuffers
|
||||
|
||||
s.buffersUp[0].name = &[]byte("Terminal\x00")[0]
|
||||
s.buffersUp[0].name = &terminalByteString[0]
|
||||
s.buffersUp[0].buffer = &rttBufferUpData[0]
|
||||
s.buffersUp[0].bufferSize = rttBufferSizeUp
|
||||
s.buffersUp[0].flags = rttModeNoBlockSkip
|
||||
|
||||
s.buffersDown[0].name = &[]byte("Terminal\x00")[0]
|
||||
s.buffersDown[0].name = &terminalByteString[0]
|
||||
s.buffersDown[0].buffer = &rttBufferDownData[0]
|
||||
s.buffersDown[0].bufferSize = rttBufferSizeDown
|
||||
s.buffersDown[0].flags = rttModeNoBlockSkip
|
||||
|
||||
+31
-9
@@ -14,9 +14,21 @@ type USBDevice struct {
|
||||
InitEndpointComplete bool
|
||||
}
|
||||
|
||||
type usbEndpointEntry struct {
|
||||
Endpoint uint32
|
||||
Config uint32
|
||||
}
|
||||
|
||||
var (
|
||||
USBDev = &USBDevice{}
|
||||
USBCDC Serialer
|
||||
|
||||
endPoints = []usbEndpointEntry{
|
||||
{
|
||||
Endpoint: usb.CONTROL_ENDPOINT,
|
||||
Config: usb.ENDPOINT_TYPE_CONTROL,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func initUSB() {
|
||||
@@ -196,17 +208,18 @@ func sendDescriptorString(data string, maxLen uint16) {
|
||||
|
||||
// Clamp the length.
|
||||
maxEncBytes := min(len(usb_trans_buffer), len(udd_ep_control_cache_buffer), int(maxLen))
|
||||
data = data[:min(len(data), (maxEncBytes-2)/2)]
|
||||
|
||||
// Write the header.
|
||||
buf := usb_trans_buffer[:2*len(data)+2]
|
||||
buf := usb_trans_buffer[:min(2*len(data)+2, maxEncBytes)]
|
||||
hdr, body := buf[:2], buf[2:]
|
||||
hdr[0] = byte(len(buf))
|
||||
|
||||
// hdr[0] (bLength) should convey the "original total string length" before being limited by the host's maxLen.
|
||||
hdr[0] = byte(2*len(data) + 2)
|
||||
hdr[1] = descriptor.TypeString
|
||||
|
||||
// Convert the string to UTF16.
|
||||
// NOTE: Using range here would cause the length to disagree when multibyte codepoints are present.
|
||||
for i := 0; i < len(data); i++ {
|
||||
limit := min(len(data), len(body)/2)
|
||||
for i := 0; i < limit; i++ {
|
||||
body[2*i] = byte(data[i])
|
||||
body[2*i+1] = 0
|
||||
}
|
||||
@@ -276,8 +289,11 @@ func handleStandardSetup(setup usb.Setup) bool {
|
||||
|
||||
case usb.SET_CONFIGURATION:
|
||||
if setup.BmRequestType&usb.REQUEST_RECIPIENT == usb.REQUEST_DEVICE {
|
||||
for i := 1; i < len(endPoints); i++ {
|
||||
initEndpoint(uint32(i), endPoints[i])
|
||||
for _, entry := range endPoints {
|
||||
if entry.Endpoint == usb.CONTROL_ENDPOINT {
|
||||
continue
|
||||
}
|
||||
initEndpoint(uint32(entry.Endpoint), entry.Config)
|
||||
}
|
||||
|
||||
usbConfiguration = setup.WValueL
|
||||
@@ -358,12 +374,18 @@ func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointC
|
||||
|
||||
for _, ep := range epSettings {
|
||||
if ep.IsIn {
|
||||
endPoints[ep.Index] = uint32(ep.Type | usb.EndpointIn)
|
||||
endPoints = append(endPoints, usbEndpointEntry{
|
||||
Endpoint: uint32(ep.Index),
|
||||
Config: uint32(ep.Type | usb.EndpointIn),
|
||||
})
|
||||
if ep.TxHandler != nil {
|
||||
usbTxHandler[ep.Index] = ep.TxHandler
|
||||
}
|
||||
} else {
|
||||
endPoints[ep.Index] = uint32(ep.Type | usb.EndpointOut)
|
||||
endPoints = append(endPoints, usbEndpointEntry{
|
||||
Endpoint: uint32(ep.Index),
|
||||
Config: uint32(ep.Type | usb.EndpointOut),
|
||||
})
|
||||
if ep.RxHandler != nil {
|
||||
usbRxHandler[ep.Index] = func(b []byte) bool {
|
||||
ep.RxHandler(b)
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
package cdc
|
||||
|
||||
import (
|
||||
"machine/usb"
|
||||
)
|
||||
|
||||
const (
|
||||
cdcEndpointACM = 1
|
||||
cdcEndpointOut = 2
|
||||
cdcEndpointIn = 3
|
||||
cdcEndpointACM = usb.CDC_ENDPOINT_ACM
|
||||
cdcEndpointOut = usb.CDC_ENDPOINT_OUT
|
||||
cdcEndpointIn = usb.CDC_ENDPOINT_IN
|
||||
)
|
||||
|
||||
// New returns USBCDC struct.
|
||||
|
||||
@@ -26,11 +26,23 @@ type cdcLineInfo struct {
|
||||
|
||||
// USBCDC is the USB CDC aka serial over USB interface.
|
||||
type USBCDC struct {
|
||||
tx ring512
|
||||
rx ring512
|
||||
tx ring512
|
||||
rx ring512
|
||||
|
||||
// inflight is the number of bytes currently submitted to the USB IN endpoint.
|
||||
inflight atomic.Uint32
|
||||
rbuf [1]byte
|
||||
wbuf [1]byte
|
||||
|
||||
// txActive is the TX-pump ownership flag: 0 = idle, 1 = a pump owns the TX
|
||||
// path. Claimed once (kickTx, CAS 0->1), held across every in-flight packet
|
||||
// and the TX-complete IRQ, and released only when the ring drains. While it
|
||||
// is set, kickTx's CAS fails and no second pump starts, which serializes the
|
||||
// pump against Write across cores. Same model as Linux NAPI_STATE_SCHED:
|
||||
// held across completion, dropped only with a recheck
|
||||
// (Documentation/networking/napi.rst).
|
||||
txActive atomic.Uint32
|
||||
|
||||
rbuf [1]byte
|
||||
wbuf [1]byte
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -81,7 +93,7 @@ func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error {
|
||||
|
||||
// Flush flushes buffered data.
|
||||
func (usbcdc *USBCDC) Flush() {
|
||||
for usbcdc.tx.Used() > 0 {
|
||||
for usbcdc.tx.Used() > 0 || usbcdc.txActive.Load() != 0 {
|
||||
gosched()
|
||||
}
|
||||
}
|
||||
@@ -105,33 +117,59 @@ func (usbcdc *USBCDC) Write(data []byte) (n int, err error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// kickTx starts a transfer if none is in flight. Called from main context only.
|
||||
// kickTx claims the TX pump for a producer. This CAS is the only start-from-idle
|
||||
// edge; if it fails, a pump already owns the path and will drain what we just
|
||||
// enqueued -- see the recheck in sendFromRing.
|
||||
func (usbcdc *USBCDC) kickTx() {
|
||||
if usbcdc.inflight.Load() > 0 {
|
||||
return // txhandler will chain the next packet.
|
||||
if !usbcdc.txActive.CompareAndSwap(0, 1) {
|
||||
return
|
||||
}
|
||||
usbcdc.sendFromRing()
|
||||
}
|
||||
|
||||
func (usbcdc *USBCDC) txhandler() {
|
||||
// TX-complete IRQ. The pump is still owned here (txActive stayed 1 across the
|
||||
// in-flight packet), so continue WITHOUT re-claiming -- pairs with the CAS in
|
||||
// kickTx. A CAS here would see the flag already set, bail, and stall the chain.
|
||||
inflight := usbcdc.inflight.Load()
|
||||
usbcdc.inflight.Store(0)
|
||||
if inflight == 0 {
|
||||
return
|
||||
}
|
||||
usbcdc.tx.Discard(inflight)
|
||||
usbcdc.inflight.Store(0)
|
||||
usbcdc.sendFromRing()
|
||||
}
|
||||
|
||||
// sendFromRing sends one USB packet from the ring and sets inflight.
|
||||
// Called from kickTx (main) or txhandler (ISR), but never concurrently
|
||||
// because kickTx only runs when inflight==0 and txhandler only runs
|
||||
// when inflight>0.
|
||||
// sendFromRing runs one step of the TX pump: submit one IN packet, or release the
|
||||
// pump if the ring is empty. Precondition: txActive == 1 (from kickTx's CAS, or
|
||||
// still held from the previous packet when entered via txhandler).
|
||||
func (usbcdc *USBCDC) sendFromRing() {
|
||||
d1, _ := usbcdc.tx.Peek()
|
||||
if len(d1) == 0 {
|
||||
return
|
||||
for {
|
||||
d1, _ := usbcdc.tx.Peek()
|
||||
if len(d1) == 0 {
|
||||
// Release the pump, then re-scan the ring: closes the missed-wakeup
|
||||
// race where Write Put()s data and kickTx's CAS then fails (txActive
|
||||
// still set), leaving the data for this pump to drain. The Store(0)
|
||||
// is ordered before the Used() load -- and, in the producer, Put()
|
||||
// before its CAS -- by the sequential consistency of Go's atomics, so
|
||||
// neither side misses the other (assumes the ring's accesses are
|
||||
// atomic too). cf. napi_complete_done() clearing NAPI_STATE_SCHED
|
||||
// then rechecking.
|
||||
usbcdc.txActive.Store(0)
|
||||
if usbcdc.tx.Used() == 0 {
|
||||
return // ring empty and pump released; done
|
||||
}
|
||||
if !usbcdc.txActive.CompareAndSwap(0, 1) {
|
||||
return // another producer re-claimed the pump; let it run
|
||||
}
|
||||
continue // re-claimed; re-peek and keep pumping
|
||||
}
|
||||
|
||||
chunk := d1[:min(usb.EndpointPacketSize, len(d1))]
|
||||
usbcdc.inflight.Store(uint32(len(chunk)))
|
||||
machine.SendUSBInPacket(cdcEndpointIn, chunk)
|
||||
return // in flight; txActive stays set, txhandler continues
|
||||
}
|
||||
chunk := d1[:min(usb.EndpointPacketSize, len(d1))]
|
||||
usbcdc.inflight.Store(uint32(len(chunk)))
|
||||
machine.SendUSBInPacket(cdcEndpointIn, chunk)
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the USB CDC interface.
|
||||
|
||||
@@ -150,6 +150,9 @@ var InterfaceCDCData = InterfaceType{
|
||||
data: interfaceCDCData[:],
|
||||
}
|
||||
|
||||
// EP1 IN : CDC Call Management
|
||||
// EP2 OUT: CDC OUT
|
||||
// EP2 IN : CDC IN
|
||||
var CDC = Descriptor{
|
||||
Device: DeviceCDC.Bytes(),
|
||||
Configuration: Append([][]byte{
|
||||
@@ -160,9 +163,9 @@ var CDC = Descriptor{
|
||||
ClassSpecificCDCCallManagement.Bytes(),
|
||||
ClassSpecificCDCACM.Bytes(),
|
||||
ClassSpecificCDCUnion.Bytes(),
|
||||
EndpointEP1IN.Bytes(),
|
||||
EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
|
||||
InterfaceCDCData.Bytes(),
|
||||
EndpointEP2OUT.Bytes(),
|
||||
EndpointEP3IN.Bytes(),
|
||||
EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
|
||||
EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -15,104 +15,44 @@ const (
|
||||
TransferTypeInterrupt
|
||||
)
|
||||
|
||||
var endpointEP1IN = [endpointTypeLen]byte{
|
||||
endpointTypeLen,
|
||||
TypeEndpoint,
|
||||
0x81, // EndpointAddress
|
||||
0x03, // Attributes
|
||||
0x10, // MaxPacketSizeL
|
||||
0x00, // MaxPacketSizeH
|
||||
0x10, // Interval
|
||||
type EndpointNumber uint8
|
||||
|
||||
const (
|
||||
EndpointEP1 EndpointNumber = iota
|
||||
EndpointEP2
|
||||
EndpointEP3
|
||||
EndpointEP4
|
||||
)
|
||||
|
||||
const (
|
||||
maxEndpoints = 4
|
||||
)
|
||||
|
||||
var (
|
||||
endpointEPIn = [maxEndpoints][endpointTypeLen]byte{}
|
||||
endpointEPOut = [maxEndpoints][endpointTypeLen]byte{}
|
||||
)
|
||||
|
||||
func EndpointIN(ep EndpointNumber, transferType uint8, maxPacketSize uint16, interval uint8) EndpointType {
|
||||
e := EndpointType{data: endpointEPIn[ep][:]}
|
||||
e.Length(endpointTypeLen)
|
||||
e.Type(TypeEndpoint)
|
||||
e.EndpointAddress(uint8(ep+1) | 0x80) // EndpointNumber is 0-based, addresses are 1-based
|
||||
e.Attributes(transferType)
|
||||
e.MaxPacketSize(maxPacketSize)
|
||||
e.Interval(interval)
|
||||
return e
|
||||
}
|
||||
|
||||
var EndpointEP1IN = EndpointType{
|
||||
data: endpointEP1IN[:],
|
||||
}
|
||||
|
||||
var endpointEP2OUT = [endpointTypeLen]byte{
|
||||
endpointTypeLen,
|
||||
TypeEndpoint,
|
||||
0x02, // EndpointAddress
|
||||
0x02, // Attributes
|
||||
0x40, // MaxPacketSizeL
|
||||
0x00, // MaxPacketSizeH
|
||||
0x00, // Interval
|
||||
}
|
||||
|
||||
var EndpointEP2OUT = EndpointType{
|
||||
data: endpointEP2OUT[:],
|
||||
}
|
||||
|
||||
var endpointEP3IN = [endpointTypeLen]byte{
|
||||
endpointTypeLen,
|
||||
TypeEndpoint,
|
||||
0x83, // EndpointAddress
|
||||
0x02, // Attributes
|
||||
0x40, // MaxPacketSizeL
|
||||
0x00, // MaxPacketSizeH
|
||||
0x00, // Interval
|
||||
}
|
||||
|
||||
var EndpointEP3IN = EndpointType{
|
||||
data: endpointEP3IN[:],
|
||||
}
|
||||
|
||||
var endpointEP4IN = [endpointTypeLen]byte{
|
||||
endpointTypeLen,
|
||||
TypeEndpoint,
|
||||
0x84, // EndpointAddress
|
||||
0x03, // Attributes
|
||||
0x40, // MaxPacketSizeL
|
||||
0x00, // MaxPacketSizeH
|
||||
0x01, // Interval
|
||||
}
|
||||
|
||||
var EndpointEP4IN = EndpointType{
|
||||
data: endpointEP4IN[:],
|
||||
}
|
||||
|
||||
var endpointEP5OUT = [endpointTypeLen]byte{
|
||||
endpointTypeLen,
|
||||
TypeEndpoint,
|
||||
0x05, // EndpointAddress
|
||||
0x03, // Attributes
|
||||
0x40, // MaxPacketSizeL
|
||||
0x00, // MaxPacketSizeH
|
||||
0x01, // Interval
|
||||
}
|
||||
|
||||
var EndpointEP5OUT = EndpointType{
|
||||
data: endpointEP5OUT[:],
|
||||
}
|
||||
|
||||
// Mass Storage Class bulk in endpoint
|
||||
var endpointMSCIN = [endpointTypeLen]byte{
|
||||
endpointTypeLen,
|
||||
TypeEndpoint,
|
||||
0x86, // EndpointAddress
|
||||
TransferTypeBulk, // Attributes
|
||||
0x40, // MaxPacketSizeL (64 bytes)
|
||||
0x00, // MaxPacketSizeH
|
||||
0x00, // Interval
|
||||
}
|
||||
|
||||
var EndpointMSCIN = EndpointType{
|
||||
data: endpointMSCIN[:],
|
||||
}
|
||||
|
||||
// Mass Storage Class bulk out endpoint
|
||||
var endpointMSCOUT = [endpointTypeLen]byte{
|
||||
endpointTypeLen,
|
||||
TypeEndpoint,
|
||||
0x07, // EndpointAddress
|
||||
TransferTypeBulk, // Attributes
|
||||
0x40, // MaxPacketSizeL (64 bytes)
|
||||
0x00, // MaxPacketSizeH
|
||||
0x00, // Interval
|
||||
}
|
||||
|
||||
var EndpointMSCOUT = EndpointType{
|
||||
data: endpointMSCOUT[:],
|
||||
func EndpointOUT(ep EndpointNumber, transferType uint8, maxPacketSize uint16, interval uint8) EndpointType {
|
||||
e := EndpointType{data: endpointEPOut[ep][:]}
|
||||
e.Length(endpointTypeLen)
|
||||
e.Type(TypeEndpoint)
|
||||
e.EndpointAddress(uint8(ep + 1)) // EndpointNumber is 0-based, addresses are 1-based
|
||||
e.Attributes(transferType)
|
||||
e.MaxPacketSize(maxPacketSize)
|
||||
e.Interval(interval)
|
||||
return e
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -111,6 +111,11 @@ var ClassHID = ClassHIDType{
|
||||
data: classHID[:],
|
||||
}
|
||||
|
||||
// EP1 IN : CDC Call Management
|
||||
// EP2 OUT: CDC OUT
|
||||
// EP2 IN : CDC IN
|
||||
// EP3 OUT: HID OUT
|
||||
// EP3 IN : HID IN
|
||||
var CDCHID = Descriptor{
|
||||
Device: DeviceCDC.Bytes(),
|
||||
Configuration: Append([][]byte{
|
||||
@@ -121,14 +126,14 @@ var CDCHID = Descriptor{
|
||||
ClassSpecificCDCACM.Bytes(),
|
||||
ClassSpecificCDCUnion.Bytes(),
|
||||
ClassSpecificCDCCallManagement.Bytes(),
|
||||
EndpointEP1IN.Bytes(),
|
||||
EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
|
||||
InterfaceCDCData.Bytes(),
|
||||
EndpointEP2OUT.Bytes(),
|
||||
EndpointEP3IN.Bytes(),
|
||||
EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
|
||||
EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
|
||||
InterfaceHID.Bytes(),
|
||||
ClassHID.Bytes(),
|
||||
EndpointEP4IN.Bytes(),
|
||||
EndpointEP5OUT.Bytes(),
|
||||
EndpointIN(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
|
||||
EndpointOUT(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
|
||||
}),
|
||||
HID: map[uint16][]byte{
|
||||
2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified!
|
||||
|
||||
@@ -121,6 +121,11 @@ var JoystickDefaultHIDReport = Append([][]byte{
|
||||
// CDCJoystick requires that you append the JoystickDescriptor
|
||||
// to the Configuration before using. This is in order to support
|
||||
// custom configurations.
|
||||
// EP1 IN : CDC Call Management
|
||||
// EP2 OUT: CDC OUT
|
||||
// EP2 IN : CDC IN
|
||||
// EP3 OUT: HID OUT
|
||||
// EP3 IN : HID IN
|
||||
var CDCJoystick = Descriptor{
|
||||
Device: DeviceJoystick.Bytes(),
|
||||
Configuration: Append([][]byte{
|
||||
@@ -131,14 +136,14 @@ var CDCJoystick = Descriptor{
|
||||
ClassSpecificCDCACM.Bytes(),
|
||||
ClassSpecificCDCUnion.Bytes(),
|
||||
ClassSpecificCDCCallManagement.Bytes(),
|
||||
EndpointEP1IN.Bytes(),
|
||||
EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
|
||||
InterfaceCDCData.Bytes(),
|
||||
EndpointEP2OUT.Bytes(),
|
||||
EndpointEP3IN.Bytes(),
|
||||
EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
|
||||
EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
|
||||
InterfaceHIDJoystick.Bytes(),
|
||||
ClassHIDJoystick.Bytes(),
|
||||
EndpointEP4IN.Bytes(),
|
||||
EndpointEP5OUT.Bytes(),
|
||||
EndpointIN(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
|
||||
EndpointOUT(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
|
||||
}),
|
||||
HID: map[uint16][]byte{},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user