mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 11:07:46 +00:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c2753e54e | |||
| 0212f0c008 | |||
| f5f4751088 | |||
| 62fb386d57 | |||
| a59f92daf8 | |||
| b7508818ee | |||
| 284e1acd87 | |||
| 37849c4897 | |||
| 213e73ad84 | |||
| 4a240827cb | |||
| 92d9f856ab | |||
| ee5890f36f | |||
| 744574193b | |||
| bbe755fb69 | |||
| 635d322703 | |||
| ee90bdebff | |||
| bcdb3fc681 | |||
| b7b23ace3f | |||
| f366cd53b7 | |||
| da81784ee9 | |||
| 481f60c5ea | |||
| cf39db36fe | |||
| 54c07b7de8 | |||
| 2fb866ca86 | |||
| 4d11d552db | |||
| b43bd9e62a | |||
| 572c22f66a | |||
| 1b66da4dff | |||
| b08ff17f6b | |||
| 41e787d504 | |||
| 6dba16f28e | |||
| b336a1561f | |||
| 4c682680ca | |||
| af76c807e2 | |||
| cdbd850f80 | |||
| d256804af7 | |||
| e3c96803c3 | |||
| f55bb8e030 | |||
| 07fb3a0cad | |||
| 02913563df | |||
| 535e64adbc | |||
| af936f3261 | |||
| d1a45f2efc | |||
| b56a263d0d | |||
| 6435f62dcc | |||
| f4c8c37b7b | |||
| 868812717f | |||
| e82f595f37 | |||
| e5af121553 | |||
| 20fdbc1f9d | |||
| 14fed59827 | |||
| d331aca296 | |||
| 7e8a2e8934 | |||
| e7363966a5 | |||
| 373ab34492 | |||
| 602f35a5ca | |||
| 1d5c5ca2ef | |||
| 90935703e6 | |||
| d8ee520bdc | |||
| e4da35486b | |||
| 4e41e9084a | |||
| ee3af40cab | |||
| 666312f63f | |||
| ccfe92a58c | |||
| 4fa6a132e2 | |||
| 1a67795fd3 | |||
| c70daa2497 | |||
| d28b58e4dd | |||
| 25b03414dc | |||
| 2ab7ee6a8a | |||
| 6a2dd35fe6 | |||
| 4c0fbbfc7f | |||
| 36bd66a858 | |||
| 9d7dd3dd4b | |||
| 9bf5d6a4aa | |||
| 839edec64c | |||
| 7e05c92420 | |||
| 0b2aec1164 | |||
| ae381e74a5 | |||
| 3392827c3e | |||
| 59838338ba | |||
| 76303f9db4 | |||
| 4bf8b618e5 | |||
| c89a684ad2 | |||
| 7aac1a1391 | |||
| af9f19615b | |||
| 79b63dd041 | |||
| 06e34caa5f | |||
| 4e9f9e2773 | |||
| 64957c5254 | |||
| 1bba5f5d7b | |||
| 48ef68dd86 | |||
| 8b9bee4cad | |||
| b4c9b579b8 | |||
| 2b1dc4fee5 | |||
| 0244bed033 | |||
| 60b23a7035 | |||
| 4326c8f10e | |||
| 1672610749 | |||
| e00a2395d9 | |||
| 42175496eb | |||
| 3b4e543f4e | |||
| ad3e9e1a77 | |||
| e0385e48d0 | |||
| feadb9c85c | |||
| 4bf7308d26 | |||
| 19e4db45db | |||
| 71b44e79b3 | |||
| 9e97566b5f | |||
| 7d83e76833 | |||
| 9e7882b1b7 | |||
| 0c4f9d1f19 | |||
| a85cb22193 | |||
| 60bb832c89 | |||
| 6eda52a289 | |||
| 8badf79af9 | |||
| 66d3c4edb8 | |||
| 63aaa43072 | |||
| ee81c31884 |
@@ -0,0 +1,96 @@
|
||||
name: Binary size difference
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
sizediff:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
# Prepare, install tools
|
||||
- name: Add GOBIN to $PATH
|
||||
run: |
|
||||
echo "$HOME/go/bin" >> $GITHUB_PATH
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # fetch all history (no sparse checkout)
|
||||
submodules: true
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 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-15-dev \
|
||||
clang-15 \
|
||||
libclang-15-dev \
|
||||
lld-15
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache@v3
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-15-sizediff-v1
|
||||
path: |
|
||||
llvm-project/compiler-rt
|
||||
- name: Download LLVM source
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
run: make llvm-source
|
||||
- name: Cache Go
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: go-cache-linux-sizediff-v1-${{ hashFiles('go.mod') }}
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
- run: make gen-device -j4
|
||||
- name: Download drivers repo
|
||||
run: git clone https://github.com/tinygo-org/drivers.git
|
||||
- name: Save HEAD
|
||||
run: git branch github-actions-saved-HEAD HEAD
|
||||
|
||||
# Compute sizes for the dev branch
|
||||
- name: Checkout dev branch
|
||||
run: git checkout --no-recurse-submodules `git merge-base HEAD origin/dev`
|
||||
- name: Build tinygo binary for the dev branch
|
||||
run: go install
|
||||
- name: Determine binary sizes on the dev branch
|
||||
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-dev.txt)
|
||||
|
||||
# Compute sizes for the PR branch
|
||||
- name: Checkout PR branch
|
||||
run: git checkout --no-recurse-submodules github-actions-saved-HEAD
|
||||
- name: Build tinygo binary for the PR branch
|
||||
run: go install
|
||||
- name: Determine binary sizes on the PR branch
|
||||
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-pr.txt)
|
||||
|
||||
# Create comment
|
||||
# TODO: add a summary, something like:
|
||||
# - overall size difference (percent)
|
||||
# - number of binaries that grew / shrank / remained the same
|
||||
# - don't show the full diff when no binaries changed
|
||||
- name: Calculate size diff
|
||||
run: ./tools/sizediff drivers/sizes-dev.txt drivers/sizes-pr.txt | tee sizediff.txt
|
||||
- name: Create comment
|
||||
run: |
|
||||
echo "Size difference with the dev branch:" > comment.txt
|
||||
echo "<details><summary>Binary size difference</summary>" >> comment.txt
|
||||
echo "<pre>" >> comment.txt
|
||||
cat sizediff.txt >> comment.txt
|
||||
echo "</pre></details>" >> comment.txt
|
||||
- name: Comment contents
|
||||
run: cat comment.txt
|
||||
- name: Add comment
|
||||
if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
|
||||
uses: thollander/actions-comment-pull-request@v2.3.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
filePath: comment.txt
|
||||
comment_tag: sizediff
|
||||
+137
@@ -1,3 +1,140 @@
|
||||
0.28.0
|
||||
---
|
||||
|
||||
* **general**
|
||||
- fix parallelism in the compiler on Windows by building LLVM with thread support
|
||||
- support qemu-user debugging
|
||||
- make target JSON msd-volume-name an array
|
||||
- print source location when a panic happens in -monitor
|
||||
- `test`: don't print `ok` for a successful compile-only
|
||||
* **compiler**
|
||||
- `builder`: remove non-ThinLTO build mode
|
||||
- `builder`: fail earlier if Go is not available
|
||||
- `builder`: improve `-size=full` in a number of ways
|
||||
- `builder`: implement Nordic DFU file writer in Go
|
||||
- `cgo`: allow `LDFLAGS: --export=...`
|
||||
- `compiler`: support recursive slice types
|
||||
- `compiler`: zero struct padding during map operations
|
||||
- `compiler`: add llvm.ident metadata
|
||||
- `compiler`: remove `unsafe.Pointer(uintptr(v) + idx)` optimization (use `unsafe.Add` instead)
|
||||
- `compiler`: add debug info to `//go:embed` data structures for better `-size` output
|
||||
- `compiler`: add debug info to string constants
|
||||
- `compiler`: fix a minor race condition
|
||||
- `compiler`: emit correct alignment in debug info for global variables
|
||||
- `compiler`: correctly generate reflect data for local named types
|
||||
- `compiler`: add alloc attributes to `runtime.alloc`, reducing flash usage slightly
|
||||
- `compiler`: for interface maps, use the original named type if available
|
||||
- `compiler`: implement most math/bits functions as LLVM intrinsics
|
||||
- `compiler`: ensure all defers have been seen before creating rundefers
|
||||
* **standard library**
|
||||
- `internal/task`: disallow blocking inside an interrupt
|
||||
- `machine`: add `CPUReset`
|
||||
- `machine/usb/hid`: add MediaKey support
|
||||
- `machine/usb/hid/joystick`: move joystick under HID
|
||||
- `machine/usb/hid/joystick`: allow joystick settings override
|
||||
- `machine/usb/hid/joystick`: handle case where we cannot find the correct HID descriptor
|
||||
- `machine/usb/hid/mouse`: add support for mouse back and forward
|
||||
- `machine/usb`: add ability to override default VID, PID, manufacturer name, and product name
|
||||
- `net`: added missing `TCPAddr` and `UDPAddr` implementations
|
||||
- `os`: add IsTimeout function
|
||||
- `os`: fix resource leak in `(*File).Close`
|
||||
- `os`: add `(*File).Sync`
|
||||
- `os`: implement `(*File).ReadDir` for wasi
|
||||
- `os`: implement `(*File).WriteAt`
|
||||
- `reflect`: make sure null bytes are supported in tags
|
||||
- `reflect`: refactor this package to enable many new features
|
||||
- `reflect`: add map type methods: `Elem` and `Key`
|
||||
- `reflect`: add map methods: `MapIndex`, `MapRange`/`MapIter`, `SetMapIndex`, `MakeMap`, `MapKeys`
|
||||
- `reflect`: add slice methods: `Append`, `MakeSlice`, `Slice`, `Slice3`, `Copy`, `Bytes`, `SetLen`
|
||||
- `reflect`: add misc methods: `Zero`, `Addr`, `UnsafeAddr`, `OverflowFloat`, `OverflowInt`, `OverflowUint`, `SetBytes`, `Convert`, `CanInt`, `CanFloat`, `CanComplex`, `Comparable`
|
||||
- `reflect`: add type methods: `String`, `PkgPath`, `FieldByName`, `FieldByIndex`, `NumMethod`
|
||||
- `reflect`: add stubs for `Type.Method`, `CanConvert`, `ArrayOf`, `StructOf`, `MapOf`
|
||||
- `reflect`: add stubs for channel select routines/types
|
||||
- `reflect`: allow nil rawType to call Kind()
|
||||
- `reflect`: ensure all ValueError panics have Kind fields
|
||||
- `reflect`: add support for named types
|
||||
- `reflect`: improve `Value.String()`
|
||||
- `reflect`: set `Index` and `PkgPath` field in `Type.Field`
|
||||
- `reflect`: `Type.AssignableTo`: you can assign anything to `interface{}`
|
||||
- `reflect`: add type check to `Value.Field`
|
||||
- `reflect`: let `TypeOf(nil)` return nil
|
||||
- `reflect`: move `StructField.Anonymous` field to match upstream location
|
||||
- `reflect`: add `UnsafePointer` for Func types
|
||||
- `reflect`: `MapIter.Next` needs to allocate new keys/values every time
|
||||
- `reflect`: fix `IsNil` for interfaces
|
||||
- `reflect`: fix `Type.Name` to return an empty string for non-named types
|
||||
- `reflect`: add `VisibleFields`
|
||||
- `reflect`: properly handle embedded structs
|
||||
- `reflect`: make sure `PointerTo` works for named types
|
||||
- `reflect`: `Set`: convert non-interface to interface
|
||||
- `reflect`: `Set`: fix direction of assignment check
|
||||
- `reflect`: support channel directions
|
||||
- `reflect`: print struct tags in Type.String()
|
||||
- `reflect`: properly handle read-only values
|
||||
- `runtime`: allow custom-gc SetFinalizer and clarify KeepAlive
|
||||
- `runtime`: implement KeepAlive using inline assembly
|
||||
- `runtime`: check for heap allocations inside interrupts
|
||||
- `runtime`: properly turn pointer into empty interface when hashing
|
||||
- `runtime`: improve map size hint usage
|
||||
- `runtime`: zero map key/value on deletion to so GC doesn't see them
|
||||
- `runtime`: print the address where a panic happened
|
||||
- `runtime/debug`: stub `SetGCPercent`, `BuildInfo.Settings`
|
||||
- `runtime/metrics`: add this package as a stub
|
||||
- `syscall`: `Stat_t` timespec fields are Atimespec on darwin
|
||||
- `syscall`: add `Timespec.Unix()` for wasi
|
||||
- `syscall`: add fsync using libc
|
||||
- `testing`: support -test.count
|
||||
- `testing`: make test output unbuffered when verbose
|
||||
- `testing`: add -test.skip
|
||||
- `testing`: move runtime.GC() call to runN to match upstream
|
||||
- `testing`: add -test.shuffle to order randomize test and benchmark order
|
||||
* **targets**
|
||||
- `arm64`: fix register save/restore to include vector registers
|
||||
- `attiny1616`: add support for this chip
|
||||
- `cortexm`: refactor EnableInterrupts and DisableInterrupts to avoid `arm.AsmFull`
|
||||
- `cortexm`: enable functions in RAM for go & cgo
|
||||
- `cortexm`: convert SystemStack from `AsmFull` to C inline assembly
|
||||
- `cortexm`: fix crash due to wrong stack size offset
|
||||
- `nrf`: samd21, stm32: add flash API
|
||||
- `nrf`: fix memory issue in ADC read
|
||||
- `nrf`: new peripheral type for nrf528xx chips
|
||||
- `nrf`: implement target mode
|
||||
- `nrf`: improve ADC and add oversampling, longer sample time, and reference voltage
|
||||
- `rp2040`: change calling order for device enumeration fix to do first
|
||||
- `rp2040`: rtc delayed interrupt
|
||||
- `rp2040`: provide better errors for invalid pins on I2C and SPI
|
||||
- `rp2040`: change uart to allow for a single pin
|
||||
- `rp2040`: implement Flash interface
|
||||
- `rp2040`: remove SPI `DataBits` property
|
||||
- `rp2040`: unify all linker scripts using LDFLAGS
|
||||
- `rp2040`: remove SPI deadline for improved performance
|
||||
- `rp2040`: use 4MHz as default frequency for SPI
|
||||
- `rp2040`: implement target mode
|
||||
- `rp2040`: use DMA for send-only SPI transfers
|
||||
- `samd21`: rearrange switch case for get pin cfg
|
||||
- `samd21`: fix issue with WS2812 driver by making pin accesses faster
|
||||
- `samd51`: enable CMCC cache for greatly improved performance
|
||||
- `samd51`: remove extra BK0RDY clear
|
||||
- `samd51`: implement Flash interface
|
||||
- `samd51`: use correct SPI frequency
|
||||
- `samd51`: remove extra BK0RDY clear
|
||||
- `samd51`: fix ADC multisampling
|
||||
- `wasi`: allow users to set the `runtime_memhash_tsip` or `runtime_memhash_fnv` build tags
|
||||
- `wasi`: set `WASMTIME_BACKTRACE_DETAILS` when running in wasmtime.
|
||||
- `wasm`: implement the `//go:wasmimport` directive
|
||||
* **boards**
|
||||
- `gameboy-advance`: switch to use register definitions in device/gba
|
||||
- `gameboy-advance`: rename display and make pointer receivers
|
||||
- `gopher-badge`: Added Gopher Badge support
|
||||
- `lorae5`: add needed definition for UART2
|
||||
- `lorae5`: correct mapping for I2C bus, add pin mapping to enable power
|
||||
- `pinetime`: update the target file (rename from pinetime-devkit0)
|
||||
- `qtpy`: fix bad pin assignment
|
||||
- `wioterminal`: fix pin definition of BCM13
|
||||
- `xiao`: Pins D4 & D5 are I2C1. Use pins D2 & D3 for I2C0.
|
||||
- `xiao`: add DefaultUART
|
||||
|
||||
|
||||
0.27.0
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved.
|
||||
Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of the Go standard library.
|
||||
Copyright (c) 2009-2022 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2009-2023 The Go Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
|
||||
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
|
||||
|
||||
@@ -482,6 +482,8 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-keyboard
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/i2c-target
|
||||
@$(MD5SUM) test.hex
|
||||
# test simulated boards on play.tinygo.org
|
||||
ifneq ($(WASM), 0)
|
||||
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
|
||||
@@ -526,6 +528,8 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=bluemicro840 examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
|
||||
@@ -562,7 +566,7 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
|
||||
$(TINYGO) build -size short -o test.hex -target=pinetime examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -702,6 +706,8 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=attiny1616 examples/empty
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
|
||||
@@ -715,6 +721,8 @@ ifneq ($(XTENSA), 0)
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target m5stack examples/serial
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target m5stick-c examples/serial
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target mch2022 examples/serial
|
||||
@$(MD5SUM) test.bin
|
||||
endif
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
[](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools.
|
||||
|
||||
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
||||
|
||||
## Embedded
|
||||
|
||||
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
|
||||
|
||||
```go
|
||||
@@ -35,115 +37,62 @@ The above program can be compiled and run without modification on an Arduino Uno
|
||||
tinygo flash -target arduino examples/blinky1
|
||||
```
|
||||
|
||||
## WebAssembly
|
||||
|
||||
TinyGo is very useful for compiling programs both for use in browsers (WASM) as well as for use on servers and other edge devices (WASI).
|
||||
|
||||
TinyGo programs can run in Fastly Compute@Edge (https://developer.fastly.com/learning/compute/go/), Fermyon Spin (https://developer.fermyon.com/spin/go-components), wazero (https://wazero.io/languages/tinygo/) and many other WebAssembly runtimes.
|
||||
|
||||
Here is a small TinyGo program for use by a WASI host application:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
//go:wasm-module yourmodulename
|
||||
//export add
|
||||
func add(x, y uint32) uint32 {
|
||||
return x + y
|
||||
}
|
||||
|
||||
// main is required for the `wasi` target, even if it isn't used.
|
||||
func main() {}
|
||||
```
|
||||
|
||||
This compiles the above TinyGo program for use on any WASI runtime:
|
||||
|
||||
```shell
|
||||
tinygo build -o main.wasm -target=wasi main.go
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
|
||||
|
||||
## Supported boards/targets
|
||||
## Supported targets
|
||||
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
### Embedded
|
||||
|
||||
The following 94 microcontroller boards are currently supported:
|
||||
You can compile TinyGo programs for over 94 different microcontroller boards.
|
||||
|
||||
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
|
||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
|
||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||
* [Adafruit Feather M0 Express](https://www.adafruit.com/product/3403)
|
||||
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
|
||||
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
|
||||
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
|
||||
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
|
||||
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
|
||||
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
|
||||
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
|
||||
* [Adafruit KB2040](https://www.adafruit.com/product/5302)
|
||||
* [Adafruit MacroPad RP2040](https://www.adafruit.com/product/5100)
|
||||
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
|
||||
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
||||
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
|
||||
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
|
||||
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
|
||||
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
|
||||
* [Adafruit QT Py RP2040](https://www.adafruit.com/product/4900)
|
||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||
* [Adafruit Trinkey QT2040](https://adafruit.com/product/5056)
|
||||
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
|
||||
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
|
||||
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
|
||||
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
|
||||
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
|
||||
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
|
||||
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
|
||||
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
|
||||
* [BBC micro:bit](https://microbit.org/)
|
||||
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
|
||||
* [blues wireless Swan](https://blues.io/products/swan/)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Dragino LoRaWAN GPS Tracker LGT-92](http://www.dragino.com/products/lora-lorawan-end-node/item/142-lgt-92.html)
|
||||
* [ESP32 - Core board](https://www.espressif.com/en/products/socs/esp32)
|
||||
* [ESP32 - mini32](https://www.espressif.com/en/products/socs/esp32)
|
||||
* [ESP32-C3-12f](https://www.espressif.com/en/products/socs/esp32-c3)
|
||||
* [ESP8266 - d1mini](https://www.espressif.com/en/products/socs/esp8266)
|
||||
* [ESP8266 - NodeMCU](https://www.espressif.com/en/products/socs/esp8266)
|
||||
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||
* [iLabs Challenger RP2040 LoRa](https://ilabs.se/product/challenger-rp2040-lora/)
|
||||
* [M5Stack](https://docs.m5stack.com/en/core/basic)
|
||||
* [M5Stack Core2](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit)
|
||||
* [M5Stamp C3](https://docs.m5stack.com/en/core/stamp_c3)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
|
||||
* [MCH2022 badge](https://badge.team/docs/badges/mch2022/)
|
||||
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
|
||||
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
|
||||
* [Nintendo Switch](https://www.nintendo.com/switch/)
|
||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
|
||||
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
|
||||
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
|
||||
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
* [Pimoroni Badger2040](https://shop.pimoroni.com/products/badger-2040)
|
||||
* [Pimoroni Tufty2040](https://shop.pimoroni.com/products/tufty-2040)
|
||||
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
||||
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
|
||||
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
|
||||
* [PJRC Teensy 4.1](https://www.pjrc.com/store/teensy41.html)
|
||||
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
|
||||
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
|
||||
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
|
||||
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
|
||||
* [Seeed XIAO BLE](https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html)
|
||||
* [Seeed XIAO ESP32C3](https://www.seeedstudio.com/Seeed-XIAO-ESP32C3-p-5431.html)
|
||||
* [Seeed XIAO RP2040](https://www.seeedstudio.com/XIAO-RP2040-v1-0-p-5026.html)
|
||||
* [Seeed LoRa-E5 Development Kit](https://www.seeedstudio.com/LoRa-E5-Dev-Kit-p-4868.html)
|
||||
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
|
||||
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
|
||||
* [SiFIve HiFive1 Rev B](https://www.sifive.com/boards/hifive1-rev-b)
|
||||
* [Sparkfun Thing Plus RP2040](https://www.sparkfun.com/products/17745)
|
||||
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
|
||||
* [ST Micro "Nucleo" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
|
||||
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
|
||||
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
|
||||
* [ST Micro "Nucleo" WL55JC](https://www.st.com/en/evaluation-tools/nucleo-wl55jc.html)
|
||||
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
|
||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||
* [ST Micro STM32F469 "Discovery"](https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-discovery-kits/32f469idiscovery.html)
|
||||
* [The Things Industries Generic Node Sensor Edition](https://www.genericnode.com/docs/sensor-edition/)
|
||||
* [Waveshare RP2040-Zero](https://www.waveshare.com/wiki/RP2040-Zero)
|
||||
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
|
||||
For more information, please see https://tinygo.org/docs/reference/microcontrollers/
|
||||
|
||||
### WebAssembly
|
||||
|
||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||
TinyGo programs can be compiled for both WASM and WASI targets.
|
||||
|
||||
For more information, see https://tinygo.org/docs/guides/webassembly/
|
||||
|
||||
### Operating Systems
|
||||
|
||||
You can also compile programs for Linux, macOS, and Windows targets.
|
||||
|
||||
For more information:
|
||||
|
||||
- Linux https://tinygo.org/docs/guides/linux/
|
||||
|
||||
- macOS https://tinygo.org/docs/guides/macos/
|
||||
|
||||
- Windows https://tinygo.org/docs/guides/windows/
|
||||
|
||||
## Currently supported features:
|
||||
|
||||
|
||||
+2
-7
@@ -175,7 +175,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
DefaultStackSize: config.StackSize(),
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
Debug: true,
|
||||
Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed
|
||||
}
|
||||
|
||||
// Load the target machine, which is the LLVM object that contains all
|
||||
@@ -909,13 +909,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
}
|
||||
case "nrf-dfu":
|
||||
// special format for nrfutil for Nordic chips
|
||||
tmphexpath := filepath.Join(tmpdir, "main.hex")
|
||||
err := objcopy(result.Executable, tmphexpath, "hex")
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.Binary = filepath.Join(tmpdir, "main"+outext)
|
||||
err = makeDFUFirmwareImage(config.Options, tmphexpath, result.Binary)
|
||||
err = makeDFUFirmwareImage(config.Options, result.Executable, result.Binary)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||
}
|
||||
if major != 1 || minor < 18 || minor > 20 {
|
||||
// Note: when this gets updated, also update the Go compatibility matrix:
|
||||
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md
|
||||
return nil, fmt.Errorf("requires go version 1.18 through 1.20, got go%d.%d", major, minor)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ type espImageSegment struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
// makeESPFirmare converts an input ELF file to an image file for an ESP32 or
|
||||
// makeESPFirmareImage converts an input ELF file to an image file for an ESP32 or
|
||||
// ESP8266 chip. This is a special purpose image format just for the ESP chip
|
||||
// family, and is parsed by the on-chip mask ROM bootloader.
|
||||
//
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ func runJobs(job *compileJob, sema chan struct{}) error {
|
||||
numRunningJobs--
|
||||
<-sema
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
|
||||
fmt.Println("## finished:", completed.description, "(time "+completed.duration.String()+")")
|
||||
}
|
||||
if completed.err != nil {
|
||||
// Wait for any current jobs to finish.
|
||||
|
||||
+109
-19
@@ -1,27 +1,117 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/sigurn/crc16"
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
)
|
||||
|
||||
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
|
||||
|
||||
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
|
||||
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
|
||||
|
||||
if options.PrintCommands != nil {
|
||||
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
|
||||
cmd.Stdout = io.Discard
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
|
||||
}
|
||||
return nil
|
||||
// Structure of the manifest.json file.
|
||||
type jsonManifest struct {
|
||||
Manifest struct {
|
||||
Application struct {
|
||||
BinaryFile string `json:"bin_file"`
|
||||
DataFile string `json:"dat_file"`
|
||||
InitPacketData nrfInitPacket `json:"init_packet_data"`
|
||||
} `json:"application"`
|
||||
DFUVersion float64 `json:"dfu_version"` // yes, this is a JSON number, not a string
|
||||
} `json:"manifest"`
|
||||
}
|
||||
|
||||
// Structure of the init packet.
|
||||
// Source:
|
||||
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/lib/sdk11/components/libraries/bootloader_dfu/dfu_init.h#L47-L57
|
||||
type nrfInitPacket struct {
|
||||
ApplicationVersion uint32 `json:"application_version"`
|
||||
DeviceRevision uint16 `json:"device_revision"`
|
||||
DeviceType uint16 `json:"device_type"`
|
||||
FirmwareCRC16 uint16 `json:"firmware_crc16"`
|
||||
SoftDeviceRequired []uint16 `json:"softdevice_req"` // this is actually a variable length array
|
||||
}
|
||||
|
||||
// Create the init packet (the contents of application.dat).
|
||||
func (p nrfInitPacket) createInitPacket() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
binary.Write(buf, binary.LittleEndian, p.DeviceType) // uint16_t device_type;
|
||||
binary.Write(buf, binary.LittleEndian, p.DeviceRevision) // uint16_t device_rev;
|
||||
binary.Write(buf, binary.LittleEndian, p.ApplicationVersion) // uint32_t app_version;
|
||||
binary.Write(buf, binary.LittleEndian, uint16(len(p.SoftDeviceRequired))) // uint16_t softdevice_len;
|
||||
binary.Write(buf, binary.LittleEndian, p.SoftDeviceRequired) // uint16_t softdevice[1];
|
||||
binary.Write(buf, binary.LittleEndian, p.FirmwareCRC16)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Make a Nordic DFU firmware image from an ELF file.
|
||||
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
|
||||
// Read ELF file as input and convert it to a binary image file.
|
||||
_, data, err := extractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the zip file in memory.
|
||||
// It won't be very large anyway.
|
||||
buf := &bytes.Buffer{}
|
||||
w := zip.NewWriter(buf)
|
||||
|
||||
// Write the application binary to the zip file.
|
||||
binw, err := w.Create("application.bin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = binw.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the init packet.
|
||||
initPacket := nrfInitPacket{
|
||||
ApplicationVersion: 0xffff_ffff, // appears to be unused by the Adafruit bootloader
|
||||
DeviceRevision: 0xffff, // DFU_DEVICE_REVISION_EMPTY
|
||||
DeviceType: 0x0052, // ADAFRUIT_DEVICE_TYPE
|
||||
FirmwareCRC16: crc16.Checksum(data, crc16.MakeTable(crc16.CRC16_CCITT_FALSE)),
|
||||
SoftDeviceRequired: []uint16{0xfffe}, // DFU_SOFTDEVICE_ANY
|
||||
}
|
||||
|
||||
// Write the init packet to the zip file.
|
||||
datw, err := w.Create("application.dat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = datw.Write(initPacket.createInitPacket())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the JSON manifest.
|
||||
manifest := &jsonManifest{}
|
||||
manifest.Manifest.Application.BinaryFile = "application.bin"
|
||||
manifest.Manifest.Application.DataFile = "application.dat"
|
||||
manifest.Manifest.Application.InitPacketData = initPacket
|
||||
manifest.Manifest.DFUVersion = 0.5
|
||||
|
||||
// Write the JSON manifest to the file.
|
||||
jsonw, err := w.Create("manifest.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := json.NewEncoder(jsonw)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finish the zip file.
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(outfile, buf.Bytes(), 0o666)
|
||||
}
|
||||
|
||||
@@ -41,9 +41,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", 4556, 272, 0, 2252},
|
||||
{"microbit", "examples/serial", 2680, 380, 8, 2256},
|
||||
{"wioterminal", "examples/pininterrupt", 6109, 1471, 116, 6816},
|
||||
{"hifive1b", "examples/echo", 4612, 280, 0, 2252},
|
||||
{"microbit", "examples/serial", 2724, 388, 8, 2256},
|
||||
{"wioterminal", "examples/pininterrupt", 6159, 1485, 116, 6816},
|
||||
|
||||
// TODO: also check wasm. Right now this is difficult, because
|
||||
// wasm binaries are run through wasm-opt and therefore the
|
||||
|
||||
@@ -142,6 +142,7 @@ var validLinkerFlags = []*regexp.Regexp{
|
||||
re(`-L([^@\-].*)`),
|
||||
re(`-O`),
|
||||
re(`-O([^@\-].*)`),
|
||||
re(`--export=([^@\-].*)`),
|
||||
re(`-f(no-)?(pic|PIC|pie|PIE)`),
|
||||
re(`-f(no-)?openmp(-simd)?`),
|
||||
re(`-fsanitize=([^@\-].*)`),
|
||||
|
||||
@@ -108,6 +108,7 @@ var goodLinkerFlags = [][]string{
|
||||
{"-Fbar"},
|
||||
{"-lbar"},
|
||||
{"-Lbar"},
|
||||
{"--export=my_symbol"},
|
||||
{"-fpic"},
|
||||
{"-fno-pic"},
|
||||
{"-fPIC"},
|
||||
|
||||
+3
-26
@@ -75,8 +75,7 @@ func (c *Config) GOARM() string {
|
||||
|
||||
// BuildTags returns the complete list of build tags used during this build.
|
||||
func (c *Config) BuildTags() []string {
|
||||
targetTags := filterTags(c.Target.BuildTags, c.Options.Tags)
|
||||
tags := append(targetTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
|
||||
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
|
||||
for i := 1; i <= c.GoMinorVersion; i++ {
|
||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||
}
|
||||
@@ -545,32 +544,10 @@ type TestConfig struct {
|
||||
Verbose bool
|
||||
Short bool
|
||||
RunRegexp string
|
||||
SkipRegexp string
|
||||
Count *int
|
||||
BenchRegexp string
|
||||
BenchTime string
|
||||
BenchMem bool
|
||||
}
|
||||
|
||||
// filterTags removes predefined build tags for a target if a conflicting option
|
||||
// is provided by the user.
|
||||
func filterTags(targetTags []string, userTags []string) []string {
|
||||
var filtered []string
|
||||
for _, t := range targetTags {
|
||||
switch {
|
||||
case strings.HasPrefix(t, "runtime_memhash_"):
|
||||
overridden := false
|
||||
for _, ut := range userTags {
|
||||
if strings.HasPrefix(ut, "runtime_memhash_") {
|
||||
overridden = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !overridden {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
default:
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
Shuffle string
|
||||
}
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
package compileopts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildTags(t *testing.T) {
|
||||
tests := []struct {
|
||||
targetTags []string
|
||||
userTags []string
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
targetTags: []string{},
|
||||
userTags: []string{},
|
||||
result: []string{
|
||||
"tinygo",
|
||||
"math_big_pure_go",
|
||||
"gc.conservative",
|
||||
"scheduler.none",
|
||||
"serial.none",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetTags: []string{"bear"},
|
||||
userTags: []string{},
|
||||
result: []string{
|
||||
"bear",
|
||||
"tinygo",
|
||||
"math_big_pure_go",
|
||||
"gc.conservative",
|
||||
"scheduler.none",
|
||||
"serial.none",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetTags: []string{},
|
||||
userTags: []string{"cat"},
|
||||
result: []string{
|
||||
"tinygo",
|
||||
"math_big_pure_go",
|
||||
"gc.conservative",
|
||||
"scheduler.none",
|
||||
"serial.none",
|
||||
"cat",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetTags: []string{"bear"},
|
||||
userTags: []string{"cat"},
|
||||
result: []string{
|
||||
"bear",
|
||||
"tinygo",
|
||||
"math_big_pure_go",
|
||||
"gc.conservative",
|
||||
"scheduler.none",
|
||||
"serial.none",
|
||||
"cat",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetTags: []string{"bear", "runtime_memhash_leveldb"},
|
||||
userTags: []string{"cat"},
|
||||
result: []string{
|
||||
"bear",
|
||||
"runtime_memhash_leveldb",
|
||||
"tinygo",
|
||||
"math_big_pure_go",
|
||||
"gc.conservative",
|
||||
"scheduler.none",
|
||||
"serial.none",
|
||||
"cat",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetTags: []string{"bear", "runtime_memhash_leveldb"},
|
||||
userTags: []string{"cat", "runtime_memhash_leveldb"},
|
||||
result: []string{
|
||||
"bear",
|
||||
"tinygo",
|
||||
"math_big_pure_go",
|
||||
"gc.conservative",
|
||||
"scheduler.none",
|
||||
"serial.none",
|
||||
"cat",
|
||||
"runtime_memhash_leveldb",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetTags: []string{"bear", "runtime_memhash_leveldb"},
|
||||
userTags: []string{"cat", "runtime_memhash_sip"},
|
||||
result: []string{
|
||||
"bear",
|
||||
"tinygo",
|
||||
"math_big_pure_go",
|
||||
"gc.conservative",
|
||||
"scheduler.none",
|
||||
"serial.none",
|
||||
"cat",
|
||||
"runtime_memhash_sip",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tt := tc
|
||||
t.Run(fmt.Sprintf("%s+%s", strings.Join(tt.targetTags, ","), strings.Join(tt.userTags, ",")), func(t *testing.T) {
|
||||
c := &Config{
|
||||
Target: &TargetSpec{
|
||||
BuildTags: tt.targetTags,
|
||||
},
|
||||
Options: &Options{
|
||||
Tags: tt.userTags,
|
||||
},
|
||||
}
|
||||
|
||||
res := c.BuildTags()
|
||||
|
||||
if len(res) != len(tt.result) {
|
||||
t.Errorf("expected %d tags, got %d", len(tt.result), len(res))
|
||||
}
|
||||
|
||||
for i, tag := range tt.result {
|
||||
if tag != res[i] {
|
||||
t.Errorf("tag %d: expected %s, got %s", i, tt.result[i], tag)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ type Options struct {
|
||||
PrintIR bool
|
||||
DumpSSA bool
|
||||
VerifyIR bool
|
||||
SkipDWARF bool
|
||||
PrintCommands func(cmd string, args ...string) `json:"-"`
|
||||
Semaphore chan struct{} `json:"-"` // -p flag controls cap
|
||||
Debug bool
|
||||
|
||||
@@ -50,7 +50,7 @@ type TargetSpec struct {
|
||||
PortReset string `json:"flash-1200-bps-reset"`
|
||||
SerialPort []string `json:"serial-port"` // serial port IDs in the form "vid:pid"
|
||||
FlashMethod string `json:"flash-method"`
|
||||
FlashVolume string `json:"msd-volume-name"`
|
||||
FlashVolume []string `json:"msd-volume-name"`
|
||||
FlashFilename string `json:"msd-firmware-name"`
|
||||
UF2FamilyID string `json:"uf2-family-id"`
|
||||
BinaryFormat string `json:"binary-format"`
|
||||
|
||||
+1
-1
@@ -229,7 +229,7 @@ func extractSubfield(t types.Type, field int) types.Type {
|
||||
}
|
||||
}
|
||||
|
||||
// flattenAggregateTypeOffset returns the offsets from the start of an object of
|
||||
// flattenAggregateTypeOffsets returns the offsets from the start of an object of
|
||||
// type t if this object were flattened like in flattenAggregate. Used together
|
||||
// with flattenAggregate to know the start indices of each value in the
|
||||
// non-flattened object.
|
||||
|
||||
+28
-8
@@ -82,6 +82,7 @@ type compilerContext struct {
|
||||
uintptrType llvm.Type
|
||||
program *ssa.Program
|
||||
diagnostics []error
|
||||
functionInfos map[*ssa.Function]functionInfo
|
||||
astComments map[string]*ast.CommentGroup
|
||||
embedGlobals map[string][]*loader.EmbedFile
|
||||
pkg *types.Package
|
||||
@@ -93,13 +94,14 @@ type compilerContext struct {
|
||||
// importantly with a newly created LLVM context and module.
|
||||
func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *Config, dumpSSA bool) *compilerContext {
|
||||
c := &compilerContext{
|
||||
Config: config,
|
||||
DumpSSA: dumpSSA,
|
||||
difiles: make(map[string]llvm.Metadata),
|
||||
ditypes: make(map[types.Type]llvm.Metadata),
|
||||
machine: machine,
|
||||
targetData: machine.CreateTargetData(),
|
||||
astComments: map[string]*ast.CommentGroup{},
|
||||
Config: config,
|
||||
DumpSSA: dumpSSA,
|
||||
difiles: make(map[string]llvm.Metadata),
|
||||
ditypes: make(map[types.Type]llvm.Metadata),
|
||||
machine: machine,
|
||||
targetData: machine.CreateTargetData(),
|
||||
functionInfos: map[*ssa.Function]functionInfo{},
|
||||
astComments: map[string]*ast.CommentGroup{},
|
||||
}
|
||||
|
||||
c.ctx = llvm.NewContext()
|
||||
@@ -167,6 +169,8 @@ type builder struct {
|
||||
deferExprFuncs map[ssa.Value]int
|
||||
selectRecvBuf map[*ssa.Select]llvm.Value
|
||||
deferBuiltinFuncs map[ssa.Value]deferBuiltin
|
||||
runDefersBlock []llvm.BasicBlock
|
||||
afterDefersBlock []llvm.BasicBlock
|
||||
}
|
||||
|
||||
func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder {
|
||||
@@ -1349,6 +1353,15 @@ func (b *builder) createFunction() {
|
||||
}
|
||||
}
|
||||
|
||||
// The rundefers instruction needs to be created after all defer
|
||||
// instructions have been created. Otherwise it won't handle all defer
|
||||
// cases.
|
||||
for i, bb := range b.runDefersBlock {
|
||||
b.SetInsertPointAtEnd(bb)
|
||||
b.createRunDefers()
|
||||
b.CreateBr(b.afterDefersBlock[i])
|
||||
}
|
||||
|
||||
if b.hasDeferFrame() {
|
||||
// Create the landing pad block, where execution continues after a
|
||||
// panic.
|
||||
@@ -1503,7 +1516,14 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
|
||||
b.CreateRet(retVal)
|
||||
}
|
||||
case *ssa.RunDefers:
|
||||
b.createRunDefers()
|
||||
// Note where we're going to put the rundefers block
|
||||
run := b.insertBasicBlock("rundefers.block")
|
||||
b.CreateBr(run)
|
||||
b.runDefersBlock = append(b.runDefersBlock, run)
|
||||
|
||||
after := b.insertBasicBlock("rundefers.after")
|
||||
b.SetInsertPointAtEnd(after)
|
||||
b.afterDefersBlock = append(b.afterDefersBlock, after)
|
||||
case *ssa.Send:
|
||||
b.createChanSend(instr)
|
||||
case *ssa.Store:
|
||||
|
||||
+85
-43
@@ -70,52 +70,11 @@ func TestCompiler(t *testing.T) {
|
||||
options := &compileopts.Options{
|
||||
Target: targetString,
|
||||
}
|
||||
target, err := compileopts.LoadTarget(options)
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
if tc.scheduler != "" {
|
||||
options.Scheduler = tc.scheduler
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: options,
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &Config{
|
||||
Triple: config.Triple(),
|
||||
Features: config.Features(),
|
||||
ABI: config.ABI(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
DefaultStackSize: config.StackSize(),
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
}
|
||||
machine, err := NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
defer machine.Dispose()
|
||||
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, "./testdata/"+tc.file, config.ClangHeaders, types.Config{
|
||||
Sizes: Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse test case %s: %s", tc.file, err)
|
||||
}
|
||||
|
||||
// Compile AST to IR.
|
||||
program := lprogram.LoadSSA()
|
||||
pkg := lprogram.MainPkg()
|
||||
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
mod, errs := testCompilePackage(t, options, tc.file)
|
||||
if errs != nil {
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
@@ -123,7 +82,7 @@ func TestCompiler(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
|
||||
err := llvm.VerifyModule(mod, llvm.PrintMessageAction)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -216,3 +175,86 @@ func filterIrrelevantIRLines(lines []string) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestCompilerErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Read expected errors from the test file.
|
||||
var expectedErrors []string
|
||||
errorsFile, err := os.ReadFile("testdata/errors.go")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
|
||||
for _, line := range strings.Split(errorsFileString, "\n") {
|
||||
if strings.HasPrefix(line, "// ERROR: ") {
|
||||
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
|
||||
}
|
||||
}
|
||||
|
||||
// Compile the Go file with errors.
|
||||
options := &compileopts.Options{
|
||||
Target: "wasm",
|
||||
}
|
||||
_, errs := testCompilePackage(t, options, "errors.go")
|
||||
|
||||
// Check whether the actual errors match the expected errors.
|
||||
expectedErrorsIdx := 0
|
||||
for _, err := range errs {
|
||||
err := err.(types.Error)
|
||||
position := err.Fset.Position(err.Pos)
|
||||
position.Filename = "errors.go" // don't use a full path
|
||||
if expectedErrorsIdx >= len(expectedErrors) || expectedErrors[expectedErrorsIdx] != err.Msg {
|
||||
t.Errorf("unexpected compiler error: %s: %s", position.String(), err.Msg)
|
||||
continue
|
||||
}
|
||||
expectedErrorsIdx++
|
||||
}
|
||||
}
|
||||
|
||||
// Build a package given a number of compiler options and a file.
|
||||
func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) {
|
||||
target, err := compileopts.LoadTarget(options)
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: options,
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &Config{
|
||||
Triple: config.Triple(),
|
||||
Features: config.Features(),
|
||||
ABI: config.ABI(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
DefaultStackSize: config.StackSize(),
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
}
|
||||
machine, err := NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
defer machine.Dispose()
|
||||
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, "./testdata/"+file, config.ClangHeaders, types.Config{
|
||||
Sizes: Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse test case %s: %s", file, err)
|
||||
}
|
||||
|
||||
// Compile AST to IR.
|
||||
program := lprogram.LoadSSA()
|
||||
pkg := lprogram.MainPkg()
|
||||
return CompilePackage(file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
}
|
||||
|
||||
+44
-7
@@ -6,6 +6,7 @@ package compiler
|
||||
// interface-lowering.go for more details.
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
@@ -126,6 +127,22 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
if _, ok := typ.Underlying().(*types.Interface); ok {
|
||||
hasMethodSet = false
|
||||
}
|
||||
|
||||
// Short-circuit all the global pointer logic here for pointers to pointers.
|
||||
if typ, ok := typ.(*types.Pointer); ok {
|
||||
if _, ok := typ.Elem().(*types.Pointer); ok {
|
||||
// For a pointer to a pointer, we just increase the pointer by 1
|
||||
ptr := c.getTypeCode(typ.Elem())
|
||||
// if the type is already *****T or higher, we can't make it.
|
||||
if typstr := typ.String(); strings.HasPrefix(typstr, "*****") {
|
||||
c.addError(token.NoPos, fmt.Sprintf("too many levels of pointers for typecode: %s", typstr))
|
||||
}
|
||||
return llvm.ConstGEP(c.ctx.Int8Type(), ptr, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 1, false),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
typeCodeName, isLocal := getTypeCodeName(typ)
|
||||
globalName := "reflect/types.type:" + typeCodeName
|
||||
var global llvm.Value
|
||||
@@ -205,6 +222,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
|
||||
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
|
||||
types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]),
|
||||
types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uint32]),
|
||||
types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]),
|
||||
types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))),
|
||||
)
|
||||
@@ -236,6 +254,16 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
c.interfaceTypes.Set(typ, global)
|
||||
}
|
||||
metabyte := getTypeKind(typ)
|
||||
|
||||
// Precompute these so we don't have to calculate them at runtime.
|
||||
if types.Comparable(typ) {
|
||||
metabyte |= 1 << 6
|
||||
}
|
||||
|
||||
if hashmapIsBinaryKey(typ) {
|
||||
metabyte |= 1 << 7
|
||||
}
|
||||
|
||||
switch typ := typ.(type) {
|
||||
case *types.Basic:
|
||||
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
|
||||
@@ -306,16 +334,21 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
}
|
||||
pkgPathPtr := c.pkgPathPtr(pkgpath)
|
||||
|
||||
llvmStructType := c.getLLVMType(typ)
|
||||
size := c.targetData.TypeStoreSize(llvmStructType)
|
||||
typeFields = []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(ms.Len()), false), // numMethods
|
||||
c.getTypeCode(types.NewPointer(typ)), // ptrTo
|
||||
pkgPathPtr,
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size
|
||||
llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields
|
||||
}
|
||||
structFieldType := c.getLLVMRuntimeType("structField")
|
||||
|
||||
var fields []llvm.Value
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
field := typ.Field(i)
|
||||
offset := c.targetData.ElementOffset(llvmStructType, i)
|
||||
var flags uint8
|
||||
if field.Anonymous() {
|
||||
flags |= structFieldFlagAnonymous
|
||||
@@ -329,7 +362,11 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
if field.Embedded() {
|
||||
flags |= structFieldFlagIsEmbedded
|
||||
}
|
||||
data := string(flags) + field.Name() + "\x00"
|
||||
|
||||
var offsBytes [binary.MaxVarintLen32]byte
|
||||
offLen := binary.PutUvarint(offsBytes[:], offset)
|
||||
|
||||
data := string(flags) + string(offsBytes[:offLen]) + field.Name() + "\x00"
|
||||
if typ.Tag(i) != "" {
|
||||
if len(typ.Tag(i)) > 0xff {
|
||||
c.addError(field.Pos(), fmt.Sprintf("struct tag is %d bytes which is too long, max is 255", len(typ.Tag(i))))
|
||||
@@ -370,6 +407,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
}, typeFields...)
|
||||
}
|
||||
alignment := c.targetData.TypeAllocSize(c.i8ptrType)
|
||||
if alignment < 4 {
|
||||
alignment = 4
|
||||
}
|
||||
globalValue := c.ctx.ConstStruct(typeFields, false)
|
||||
global.SetInitializer(globalValue)
|
||||
if isLocal {
|
||||
@@ -647,11 +687,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
|
||||
|
||||
} else {
|
||||
assertedTypeGlobal := b.getTypeCode(expr.AssertedType)
|
||||
if !assertedTypeGlobal.IsAConstantExpr().IsNil() {
|
||||
assertedTypeGlobal = assertedTypeGlobal.Operand(0) // resolve the GEP operation
|
||||
}
|
||||
globalName := "reflect/types.typeid:" + strings.TrimPrefix(assertedTypeGlobal.Name(), "reflect/types.type:")
|
||||
name, _ := getTypeCodeName(expr.AssertedType)
|
||||
globalName := "reflect/types.typeid:" + name
|
||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||
if assertedTypeCodeGlobal.IsNil() {
|
||||
// Create a new typecode global.
|
||||
@@ -724,7 +761,7 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string {
|
||||
return strings.Join(methods, "; ")
|
||||
}
|
||||
|
||||
// getInterfaceImplementsfunc returns a declared function that works as a type
|
||||
// getInterfaceImplementsFunc returns a declared function that works as a type
|
||||
// switch. The interface lowering pass will define this function.
|
||||
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
|
||||
s, _ := getTypeCodeName(assertedType.Underlying())
|
||||
|
||||
+64
-5
@@ -4,6 +4,7 @@ package compiler
|
||||
// pragmas, determines the link name, etc.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
@@ -239,18 +240,22 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
// present in *ssa.Function, such as the link name and whether it should be
|
||||
// exported.
|
||||
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
if info, ok := c.functionInfos[f]; ok {
|
||||
return info
|
||||
}
|
||||
info := functionInfo{
|
||||
// Pick the default linkName.
|
||||
linkName: f.RelString(nil),
|
||||
}
|
||||
// Check for //go: pragmas, which may change the link name (among others).
|
||||
info.parsePragmas(f)
|
||||
c.parsePragmas(&info, f)
|
||||
c.functionInfos[f] = info
|
||||
return info
|
||||
}
|
||||
|
||||
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
|
||||
// //export or //go:noinline.
|
||||
func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
||||
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
if f.Syntax() == nil {
|
||||
return
|
||||
}
|
||||
@@ -290,10 +295,12 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
||||
info.module = parts[1]
|
||||
case "//go:wasmimport":
|
||||
// Import a WebAssembly function, for example a WASI function.
|
||||
// For details, see: https://github.com/golang/go/issues/38248
|
||||
if len(parts) != 3 || len(f.Blocks) != 0 {
|
||||
// Original proposal: https://github.com/golang/go/issues/38248
|
||||
// Allow globally: https://github.com/golang/go/issues/59149
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
c.checkWasmImport(f, comment.Text)
|
||||
info.exported = true
|
||||
info.module = parts[1]
|
||||
info.importName = parts[2]
|
||||
@@ -354,6 +361,58 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether this function cannot be used in //go:wasmimport. It will add an
|
||||
// error if this is the case.
|
||||
//
|
||||
// The list of allowed types is based on this proposal:
|
||||
// https://github.com/golang/go/issues/59149
|
||||
func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) {
|
||||
if c.pkg.Path() == "runtime" {
|
||||
// The runtime is a special case. Allow all kinds of parameters
|
||||
// (importantly, including pointers).
|
||||
return
|
||||
}
|
||||
if f.Blocks != nil {
|
||||
// Defined functions cannot be exported.
|
||||
c.addError(f.Pos(), fmt.Sprintf("can only use //go:wasmimport on declarations"))
|
||||
return
|
||||
}
|
||||
if f.Signature.Results().Len() > 1 {
|
||||
c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma))
|
||||
} else if f.Signature.Results().Len() == 1 {
|
||||
result := f.Signature.Results().At(0)
|
||||
if !isValidWasmType(result.Type(), true) {
|
||||
c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String()))
|
||||
}
|
||||
}
|
||||
for _, param := range f.Params {
|
||||
// Check whether the type is allowed.
|
||||
// Only a very limited number of types can be mapped to WebAssembly.
|
||||
if !isValidWasmType(param.Type(), false) {
|
||||
c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the type maps directly to a WebAssembly type, according to:
|
||||
// https://github.com/golang/go/issues/59149
|
||||
func isValidWasmType(typ types.Type, isReturn bool) bool {
|
||||
switch typ := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
switch typ.Kind() {
|
||||
case types.Int32, types.Uint32, types.Int64, types.Uint64:
|
||||
return true
|
||||
case types.Float32, types.Float64:
|
||||
return true
|
||||
case types.UnsafePointer:
|
||||
if !isReturn {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getParams returns the function parameters, including the receiver at the
|
||||
// start. This is an alternative to the Params member of *ssa.Function, which is
|
||||
// not yet populated when the package has not yet been built.
|
||||
@@ -417,7 +476,7 @@ func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
|
||||
}
|
||||
}
|
||||
|
||||
// addStandardAttribute adds all attributes added to defined functions.
|
||||
// addStandardAttributes adds all attributes added to defined functions.
|
||||
func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
|
||||
c.addStandardDeclaredAttributes(llvmFn)
|
||||
c.addStandardDefinedAttributes(llvmFn)
|
||||
|
||||
+20
-8
@@ -37,9 +37,16 @@ entry:
|
||||
|
||||
1: ; preds = %entry
|
||||
call void @main.external(ptr undef) #4
|
||||
br label %rundefers.block
|
||||
|
||||
rundefers.after: ; preds = %rundefers.end
|
||||
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
|
||||
ret void
|
||||
|
||||
rundefers.block: ; preds = %1
|
||||
br label %rundefers.loophead
|
||||
|
||||
rundefers.loophead: ; preds = %3, %1
|
||||
rundefers.loophead: ; preds = %3, %rundefers.block
|
||||
%2 = load ptr, ptr %deferPtr, align 4
|
||||
%stackIsNil = icmp eq ptr %2, null
|
||||
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
|
||||
@@ -66,8 +73,7 @@ rundefers.default: ; preds = %rundefers.loop
|
||||
unreachable
|
||||
|
||||
rundefers.end: ; preds = %rundefers.loophead
|
||||
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
|
||||
ret void
|
||||
br label %rundefers.after
|
||||
|
||||
recover: ; preds = %rundefers.end3
|
||||
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
|
||||
@@ -111,6 +117,8 @@ declare ptr @llvm.stacksave() #3
|
||||
|
||||
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #2
|
||||
|
||||
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
@@ -118,8 +126,6 @@ entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2
|
||||
|
||||
declare void @runtime.printint32(i32, ptr) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -146,9 +152,16 @@ entry:
|
||||
|
||||
1: ; preds = %entry
|
||||
call void @main.external(ptr undef) #4
|
||||
br label %rundefers.block
|
||||
|
||||
rundefers.after: ; preds = %rundefers.end
|
||||
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
|
||||
ret void
|
||||
|
||||
rundefers.block: ; preds = %1
|
||||
br label %rundefers.loophead
|
||||
|
||||
rundefers.loophead: ; preds = %4, %3, %1
|
||||
rundefers.loophead: ; preds = %4, %3, %rundefers.block
|
||||
%2 = load ptr, ptr %deferPtr, align 4
|
||||
%stackIsNil = icmp eq ptr %2, null
|
||||
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
|
||||
@@ -185,8 +198,7 @@ rundefers.default: ; preds = %rundefers.loop
|
||||
unreachable
|
||||
|
||||
rundefers.end: ; preds = %rundefers.loophead
|
||||
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
|
||||
ret void
|
||||
br label %rundefers.after
|
||||
|
||||
recover: ; preds = %rundefers.end7
|
||||
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
|
||||
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
//go:wasmimport modulename empty
|
||||
func empty()
|
||||
|
||||
// ERROR: can only use //go:wasmimport on declarations
|
||||
//
|
||||
//go:wasmimport modulename implementation
|
||||
func implementation() {
|
||||
}
|
||||
|
||||
type Uint uint32
|
||||
|
||||
//go:wasmimport modulename validparam
|
||||
func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint)
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type string
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type *int32
|
||||
//
|
||||
//go:wasmimport modulename invalidparam
|
||||
func invalidparam(a int, b string, c []byte, d *int32)
|
||||
|
||||
//go:wasmimport modulename validreturn
|
||||
func validreturn() int32
|
||||
|
||||
// ERROR: //go:wasmimport modulename manyreturns: too many return values
|
||||
//
|
||||
//go:wasmimport modulename manyreturns
|
||||
func manyreturns() (int32, int32)
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidreturn: unsupported result type int
|
||||
//
|
||||
//go:wasmimport modulename invalidreturn
|
||||
func invalidreturn() int
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidUnsafePointerReturn: unsupported result type unsafe.Pointer
|
||||
//
|
||||
//go:wasmimport modulename invalidUnsafePointerReturn
|
||||
func invalidUnsafePointerReturn() unsafe.Pointer
|
||||
Vendored
+2
-2
@@ -21,8 +21,8 @@ target triple = "wasm32-unknown-wasi"
|
||||
@main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 8
|
||||
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " }
|
||||
@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" }
|
||||
@"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 16, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4
|
||||
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4
|
||||
@"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4
|
||||
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4
|
||||
|
||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
|
||||
|
||||
Vendored
+8
-8
@@ -6,15 +6,15 @@ target triple = "wasm32-unknown-wasi"
|
||||
%runtime._interface = type { ptr, ptr }
|
||||
%runtime._string = type { ptr, i32 }
|
||||
|
||||
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
|
||||
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:basic:int" }, align 4
|
||||
@"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4
|
||||
@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 52, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4
|
||||
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4
|
||||
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4
|
||||
@"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4
|
||||
@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4
|
||||
@"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1
|
||||
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
|
||||
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
|
||||
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4
|
||||
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4
|
||||
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
|
||||
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
|
||||
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4
|
||||
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4
|
||||
@"reflect/types.typeid:basic:int" = external constant i8
|
||||
|
||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||
|
||||
Vendored
-4
@@ -62,10 +62,6 @@ func exportedFunctionInSection() {
|
||||
//go:wasmimport modulename import1
|
||||
func declaredImport()
|
||||
|
||||
//go:wasmimport modulename import2
|
||||
func definedImport() {
|
||||
}
|
||||
|
||||
// This function should not: it's only a declaration and not a definition.
|
||||
//
|
||||
//go:section .special_function_section
|
||||
|
||||
Vendored
-6
@@ -62,12 +62,6 @@ entry:
|
||||
|
||||
declare void @main.declaredImport() #7
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.definedImport(ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @main.undefinedFunctionNotInSection(ptr) #1
|
||||
|
||||
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
|
||||
|
||||
@@ -13,6 +13,7 @@ require (
|
||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/mattn/go-tty v0.0.4
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
||||
go.bug.st/serial v1.3.5
|
||||
golang.org/x/sys v0.4.0
|
||||
golang.org/x/tools v0.5.1-0.20230114154351-e035d0c426c8
|
||||
|
||||
@@ -43,6 +43,8 @@ github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3px
|
||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
|
||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE=
|
||||
go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304=
|
||||
|
||||
+20
-57
@@ -346,16 +346,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
dstObj.buffer = dstBuf
|
||||
mem.put(dst.index(), dstObj)
|
||||
}
|
||||
switch inst.llvmInst.Type().IntTypeWidth() {
|
||||
case 16:
|
||||
locals[inst.localIndex] = literalValue{uint16(n)}
|
||||
case 32:
|
||||
locals[inst.localIndex] = literalValue{uint32(n)}
|
||||
case 64:
|
||||
locals[inst.localIndex] = literalValue{uint64(n)}
|
||||
default:
|
||||
panic("unknown integer type width")
|
||||
}
|
||||
locals[inst.localIndex] = makeLiteralInt(n, inst.llvmInst.Type().IntTypeWidth())
|
||||
case strings.HasPrefix(callFn.name, "llvm.memcpy.p0") || strings.HasPrefix(callFn.name, "llvm.memmove.p0"):
|
||||
// Copy a block of memory from one pointer to another.
|
||||
dst, err := operands[1].asPointer(r)
|
||||
@@ -647,16 +638,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
}
|
||||
// GEP on fixed pointer value (for example, memory-mapped I/O).
|
||||
ptrValue := operands[0].Uint() + offset
|
||||
switch operands[0].len(r) {
|
||||
case 8:
|
||||
locals[inst.localIndex] = literalValue{uint64(ptrValue)}
|
||||
case 4:
|
||||
locals[inst.localIndex] = literalValue{uint32(ptrValue)}
|
||||
case 2:
|
||||
locals[inst.localIndex] = literalValue{uint16(ptrValue)}
|
||||
default:
|
||||
panic("pointer operand is not of a known pointer size")
|
||||
}
|
||||
locals[inst.localIndex] = makeLiteralInt(ptrValue, int(operands[0].len(r)*8))
|
||||
continue
|
||||
}
|
||||
ptr = ptr.addOffset(int64(offset))
|
||||
@@ -754,30 +736,33 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
if err == nil {
|
||||
// The lhs is a pointer. This sometimes happens for particular
|
||||
// pointer tricks.
|
||||
switch inst.opcode {
|
||||
case llvm.Add:
|
||||
if inst.opcode == llvm.Add {
|
||||
// This likely means this is part of a
|
||||
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
|
||||
lhsPtr = lhsPtr.addOffset(int64(rhs.Uint()))
|
||||
locals[inst.localIndex] = lhsPtr
|
||||
continue
|
||||
case llvm.Xor:
|
||||
if rhs.Uint() == 0 {
|
||||
// Special workaround for strings.noescape, see
|
||||
// src/strings/builder.go in the Go source tree. This is
|
||||
// the identity operator, so we can return the input.
|
||||
locals[inst.localIndex] = lhs
|
||||
continue
|
||||
}
|
||||
default:
|
||||
} else if inst.opcode == llvm.Xor && rhs.Uint() == 0 {
|
||||
// Special workaround for strings.noescape, see
|
||||
// src/strings/builder.go in the Go source tree. This is
|
||||
// the identity operator, so we can return the input.
|
||||
locals[inst.localIndex] = lhs
|
||||
} else if inst.opcode == llvm.And && rhs.Uint() < 8 {
|
||||
// This is probably part of a pattern to get the lower bits
|
||||
// of a pointer for pointer tagging, like this:
|
||||
// uintptr(unsafe.Pointer(t)) & 0b11
|
||||
// We can actually support this easily by ANDing with the
|
||||
// pointer offset.
|
||||
result := uint64(lhsPtr.offset()) & rhs.Uint()
|
||||
locals[inst.localIndex] = makeLiteralInt(result, int(lhs.len(r)*8))
|
||||
} else {
|
||||
// Catch-all for weird operations that should just be done
|
||||
// at runtime.
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
var result uint64
|
||||
switch inst.opcode {
|
||||
@@ -810,18 +795,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
switch lhs.len(r) {
|
||||
case 8:
|
||||
locals[inst.localIndex] = literalValue{result}
|
||||
case 4:
|
||||
locals[inst.localIndex] = literalValue{uint32(result)}
|
||||
case 2:
|
||||
locals[inst.localIndex] = literalValue{uint16(result)}
|
||||
case 1:
|
||||
locals[inst.localIndex] = literalValue{uint8(result)}
|
||||
default:
|
||||
panic("unknown integer size")
|
||||
}
|
||||
locals[inst.localIndex] = makeLiteralInt(result, int(lhs.len(r)*8))
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", lhs, rhs, "->", result)
|
||||
}
|
||||
@@ -843,18 +817,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
|
||||
}
|
||||
switch bitwidth {
|
||||
case 64:
|
||||
locals[inst.localIndex] = literalValue{value}
|
||||
case 32:
|
||||
locals[inst.localIndex] = literalValue{uint32(value)}
|
||||
case 16:
|
||||
locals[inst.localIndex] = literalValue{uint16(value)}
|
||||
case 8:
|
||||
locals[inst.localIndex] = literalValue{uint8(value)}
|
||||
default:
|
||||
panic("unknown integer size in sext/zext/trunc")
|
||||
}
|
||||
locals[inst.localIndex] = makeLiteralInt(value, int(bitwidth))
|
||||
case llvm.SIToFP, llvm.UIToFP:
|
||||
var value float64
|
||||
switch inst.opcode {
|
||||
|
||||
@@ -373,6 +373,22 @@ type literalValue struct {
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// Make a literalValue given the number of bits.
|
||||
func makeLiteralInt(value uint64, bits int) literalValue {
|
||||
switch bits {
|
||||
case 64:
|
||||
return literalValue{value}
|
||||
case 32:
|
||||
return literalValue{uint32(value)}
|
||||
case 16:
|
||||
return literalValue{uint16(value)}
|
||||
case 8:
|
||||
return literalValue{uint8(value)}
|
||||
default:
|
||||
panic("unknown integer size")
|
||||
}
|
||||
}
|
||||
|
||||
func (v literalValue) len(r *runner) uint32 {
|
||||
switch v.value.(type) {
|
||||
case uint64:
|
||||
|
||||
Vendored
+28
-28
@@ -2,17 +2,17 @@ target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " }
|
||||
@pointerFree12 = global i8* null
|
||||
@pointerFree7 = global i8* null
|
||||
@pointerFree3 = global i8* null
|
||||
@pointerFree0 = global i8* null
|
||||
@layout1 = global i8* null
|
||||
@layout2 = global i8* null
|
||||
@layout3 = global i8* null
|
||||
@layout4 = global i8* null
|
||||
@bigobj1 = global i8* null
|
||||
@pointerFree12 = global ptr null
|
||||
@pointerFree7 = global ptr null
|
||||
@pointerFree3 = global ptr null
|
||||
@pointerFree0 = global ptr null
|
||||
@layout1 = global ptr null
|
||||
@layout2 = global ptr null
|
||||
@layout3 = global ptr null
|
||||
@layout4 = global ptr null
|
||||
@bigobj1 = global ptr null
|
||||
|
||||
declare i8* @runtime.alloc(i32, i8*) unnamed_addr
|
||||
declare ptr @runtime.alloc(i32, ptr) unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
call void @main.init()
|
||||
@@ -21,33 +21,33 @@ define void @runtime.initAll() unnamed_addr {
|
||||
|
||||
define internal void @main.init() unnamed_addr {
|
||||
; Object that's word-aligned.
|
||||
%pointerFree12 = call i8* @runtime.alloc(i32 12, i8* inttoptr (i32 3 to i8*))
|
||||
store i8* %pointerFree12, i8** @pointerFree12
|
||||
%pointerFree12 = call ptr @runtime.alloc(i32 12, ptr inttoptr (i32 3 to ptr))
|
||||
store ptr %pointerFree12, ptr @pointerFree12
|
||||
; Object larger than a word but not word-aligned.
|
||||
%pointerFree7 = call i8* @runtime.alloc(i32 7, i8* inttoptr (i32 3 to i8*))
|
||||
store i8* %pointerFree7, i8** @pointerFree7
|
||||
%pointerFree7 = call ptr @runtime.alloc(i32 7, ptr inttoptr (i32 3 to ptr))
|
||||
store ptr %pointerFree7, ptr @pointerFree7
|
||||
; Object smaller than a word (and of course not word-aligned).
|
||||
%pointerFree3 = call i8* @runtime.alloc(i32 3, i8* inttoptr (i32 3 to i8*))
|
||||
store i8* %pointerFree3, i8** @pointerFree3
|
||||
%pointerFree3 = call ptr @runtime.alloc(i32 3, ptr inttoptr (i32 3 to ptr))
|
||||
store ptr %pointerFree3, ptr @pointerFree3
|
||||
; Zero-sized object.
|
||||
%pointerFree0 = call i8* @runtime.alloc(i32 0, i8* inttoptr (i32 3 to i8*))
|
||||
store i8* %pointerFree0, i8** @pointerFree0
|
||||
%pointerFree0 = call ptr @runtime.alloc(i32 0, ptr inttoptr (i32 3 to ptr))
|
||||
store ptr %pointerFree0, ptr @pointerFree0
|
||||
|
||||
; Object made out of 3 pointers.
|
||||
%layout1 = call i8* @runtime.alloc(i32 12, i8* inttoptr (i32 67 to i8*))
|
||||
store i8* %layout1, i8** @layout1
|
||||
%layout1 = call ptr @runtime.alloc(i32 12, ptr inttoptr (i32 67 to ptr))
|
||||
store ptr %layout1, ptr @layout1
|
||||
; Array (or slice) of 5 slices.
|
||||
%layout2 = call i8* @runtime.alloc(i32 60, i8* inttoptr (i32 71 to i8*))
|
||||
store i8* %layout2, i8** @layout2
|
||||
%layout2 = call ptr @runtime.alloc(i32 60, ptr inttoptr (i32 71 to ptr))
|
||||
store ptr %layout2, ptr @layout2
|
||||
; Oddly shaped object, using all bits in the layout integer.
|
||||
%layout3 = call i8* @runtime.alloc(i32 104, i8* inttoptr (i32 2467830261 to i8*))
|
||||
store i8* %layout3, i8** @layout3
|
||||
%layout3 = call ptr @runtime.alloc(i32 104, ptr inttoptr (i32 2467830261 to ptr))
|
||||
store ptr %layout3, ptr @layout3
|
||||
; ...repeated.
|
||||
%layout4 = call i8* @runtime.alloc(i32 312, i8* inttoptr (i32 2467830261 to i8*))
|
||||
store i8* %layout4, i8** @layout4
|
||||
%layout4 = call ptr @runtime.alloc(i32 312, ptr inttoptr (i32 2467830261 to ptr))
|
||||
store ptr %layout4, ptr @layout4
|
||||
|
||||
; Large object that needs to be stored in a separate global.
|
||||
%bigobj1 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-2000000000000001" to i8*))
|
||||
store i8* %bigobj1, i8** @bigobj1
|
||||
%bigobj1 = call ptr @runtime.alloc(i32 248, ptr @"runtime/gc.layout:62-2000000000000001")
|
||||
store ptr %bigobj1, ptr @bigobj1
|
||||
ret void
|
||||
}
|
||||
|
||||
Vendored
+14
-14
@@ -1,24 +1,24 @@
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
@pointerFree12 = local_unnamed_addr global i8* getelementptr inbounds ([12 x i8], [12 x i8]* @"main$alloc", i32 0, i32 0)
|
||||
@pointerFree7 = local_unnamed_addr global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @"main$alloc.1", i32 0, i32 0)
|
||||
@pointerFree3 = local_unnamed_addr global i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main$alloc.2", i32 0, i32 0)
|
||||
@pointerFree0 = local_unnamed_addr global i8* getelementptr inbounds ([0 x i8], [0 x i8]* @"main$alloc.3", i32 0, i32 0)
|
||||
@layout1 = local_unnamed_addr global i8* bitcast ([3 x i8*]* @"main$alloc.4" to i8*)
|
||||
@layout2 = local_unnamed_addr global i8* bitcast ([5 x { i8*, i32, i32 }]* @"main$alloc.5" to i8*)
|
||||
@layout3 = local_unnamed_addr global i8* bitcast ({ i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }* @"main$alloc.6" to i8*)
|
||||
@layout4 = local_unnamed_addr global i8* bitcast ([3 x { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }]* @"main$alloc.7" to i8*)
|
||||
@bigobj1 = local_unnamed_addr global i8* bitcast ({ i8*, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i8* }* @"main$alloc.8" to i8*)
|
||||
@pointerFree12 = local_unnamed_addr global ptr @"main$alloc"
|
||||
@pointerFree7 = local_unnamed_addr global ptr @"main$alloc.1"
|
||||
@pointerFree3 = local_unnamed_addr global ptr @"main$alloc.2"
|
||||
@pointerFree0 = local_unnamed_addr global ptr @"main$alloc.3"
|
||||
@layout1 = local_unnamed_addr global ptr @"main$alloc.4"
|
||||
@layout2 = local_unnamed_addr global ptr @"main$alloc.5"
|
||||
@layout3 = local_unnamed_addr global ptr @"main$alloc.6"
|
||||
@layout4 = local_unnamed_addr global ptr @"main$alloc.7"
|
||||
@bigobj1 = local_unnamed_addr global ptr @"main$alloc.8"
|
||||
@"main$alloc" = internal global [12 x i8] zeroinitializer, align 4
|
||||
@"main$alloc.1" = internal global [7 x i8] zeroinitializer, align 4
|
||||
@"main$alloc.2" = internal global [3 x i8] zeroinitializer, align 4
|
||||
@"main$alloc.3" = internal global [0 x i8] zeroinitializer, align 4
|
||||
@"main$alloc.4" = internal global [3 x i8*] zeroinitializer, align 4
|
||||
@"main$alloc.5" = internal global [5 x { i8*, i32, i32 }] zeroinitializer, align 4
|
||||
@"main$alloc.6" = internal global { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* } zeroinitializer, align 4
|
||||
@"main$alloc.7" = internal global [3 x { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }] zeroinitializer, align 4
|
||||
@"main$alloc.8" = internal global { i8*, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i8* } zeroinitializer, align 4
|
||||
@"main$alloc.4" = internal global [3 x ptr] zeroinitializer, align 4
|
||||
@"main$alloc.5" = internal global [5 x { ptr, i32, i32 }] zeroinitializer, align 4
|
||||
@"main$alloc.6" = internal global { ptr, ptr, ptr, i32, i32, ptr, ptr, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32, i32, ptr, ptr, i32, i32, ptr, i32, i32, ptr } zeroinitializer, align 4
|
||||
@"main$alloc.7" = internal global [3 x { ptr, ptr, ptr, i32, i32, ptr, ptr, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32, i32, ptr, ptr, i32, i32, ptr, i32, i32, ptr }] zeroinitializer, align 4
|
||||
@"main$alloc.8" = internal global { ptr, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, ptr } zeroinitializer, align 4
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
ret void
|
||||
|
||||
Vendored
+21
-21
@@ -5,7 +5,7 @@ target triple = "x86_64--linux"
|
||||
@main.nonConst1 = global [4 x i64] zeroinitializer
|
||||
@main.nonConst2 = global i64 0
|
||||
@main.someArray = global [8 x {i16, i32}] zeroinitializer
|
||||
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
|
||||
@main.exportedValue = global [1 x ptr] [ptr @main.exposedValue1]
|
||||
@main.exportedConst = constant i64 42
|
||||
@main.exposedValue1 = global i16 0
|
||||
@main.exposedValue2 = global i16 0
|
||||
@@ -24,7 +24,7 @@ entry:
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
%0 = load i64, i64* @main.v1
|
||||
%0 = load i64, ptr @main.v1
|
||||
call void @runtime.printint64(i64 %0)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
@@ -37,43 +37,43 @@ entry:
|
||||
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
store i64 3, i64* @main.v1
|
||||
store i64 3, ptr @main.v1
|
||||
call void @"main.init#1"()
|
||||
|
||||
; test the following pattern:
|
||||
; func someValue() int // extern function
|
||||
; var nonConst1 = [4]int{someValue(), 0, 0, 0}
|
||||
%value1 = call i64 @someValue()
|
||||
%gep1 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
|
||||
store i64 %value1, i64* %gep1
|
||||
%gep1 = getelementptr [4 x i64], ptr @main.nonConst1, i32 0, i32 0
|
||||
store i64 %value1, ptr %gep1
|
||||
|
||||
; Test that the global really is marked dirty:
|
||||
; var nonConst2 = nonConst1[0]
|
||||
%gep2 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
|
||||
%value2 = load i64, i64* %gep2
|
||||
store i64 %value2, i64* @main.nonConst2
|
||||
%gep2 = getelementptr [4 x i64], ptr @main.nonConst1, i32 0, i32 0
|
||||
%value2 = load i64, ptr %gep2
|
||||
store i64 %value2, ptr @main.nonConst2
|
||||
|
||||
; Test that the following GEP works:
|
||||
; var someArray
|
||||
; modifyExternal(&someArray[3].field1)
|
||||
%gep3 = getelementptr [8 x {i16, i32}], [8 x {i16, i32}]* @main.someArray, i32 0, i32 3, i32 1
|
||||
call void @modifyExternal(i32* %gep3)
|
||||
%gep3 = getelementptr [8 x {i16, i32}], ptr @main.someArray, i32 0, i32 3, i32 1
|
||||
call void @modifyExternal(ptr %gep3)
|
||||
|
||||
; Test that marking a value as external also marks all referenced values.
|
||||
call void @modifyExternal(i32* bitcast ([1 x i16*]* @main.exportedValue to i32*))
|
||||
store i16 5, i16* @main.exposedValue1
|
||||
call void @modifyExternal(ptr @main.exportedValue)
|
||||
store i16 5, ptr @main.exposedValue1
|
||||
|
||||
; Test that marking a constant as external still allows loading from it.
|
||||
call void @readExternal(i32* bitcast (i64* @main.exportedConst to i32*))
|
||||
%constLoad = load i64, i64 * @main.exportedConst
|
||||
call void @readExternal(ptr @main.exportedConst)
|
||||
%constLoad = load i64, ptr @main.exportedConst
|
||||
call void @runtime.printint64(i64 %constLoad)
|
||||
|
||||
; Test that this even propagates through functions.
|
||||
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
|
||||
store i16 7, i16* @main.exposedValue2
|
||||
call void @modifyExternal(ptr @willModifyGlobal)
|
||||
store i16 7, ptr @main.exposedValue2
|
||||
|
||||
; Test that inline assembly is ignored.
|
||||
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
|
||||
call void @modifyExternal(ptr @hasInlineAsm)
|
||||
|
||||
; Test switch statement.
|
||||
%switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6
|
||||
@@ -86,7 +86,7 @@ entry:
|
||||
%elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0
|
||||
call void @runtime.printint64(i64 %elt)
|
||||
%agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0
|
||||
store {i8, i32, {float, {i64, i16}}} %agg2, {i8, i32, {float, {i64, i16}}}* @main.insertedValue
|
||||
store {i8, i32, {float, {i64, i16}}} %agg2, ptr @main.insertedValue
|
||||
|
||||
ret void
|
||||
}
|
||||
@@ -100,16 +100,16 @@ entry:
|
||||
|
||||
declare i64 @someValue()
|
||||
|
||||
declare void @modifyExternal(i32*)
|
||||
declare void @modifyExternal(ptr)
|
||||
|
||||
declare void @readExternal(i32*)
|
||||
declare void @readExternal(ptr)
|
||||
|
||||
; This function will modify an external value. By passing this function as a
|
||||
; function pointer to an external function, @main.exposedValue2 should be
|
||||
; marked as external.
|
||||
define void @willModifyGlobal() {
|
||||
entry:
|
||||
store i16 8, i16* @main.exposedValue2
|
||||
store i16 8, ptr @main.exposedValue2
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
Vendored
+15
-15
@@ -4,7 +4,7 @@ target triple = "x86_64--linux"
|
||||
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
|
||||
@main.nonConst2 = local_unnamed_addr global i64 0
|
||||
@main.someArray = global [8 x { i16, i32 }] zeroinitializer
|
||||
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
|
||||
@main.exportedValue = global [1 x ptr] [ptr @main.exposedValue1]
|
||||
@main.exportedConst = constant i64 42
|
||||
@main.exposedValue1 = global i16 0
|
||||
@main.exposedValue2 = local_unnamed_addr global i16 0
|
||||
@@ -19,17 +19,17 @@ entry:
|
||||
call void @runtime.printint64(i64 5)
|
||||
call void @runtime.printnl()
|
||||
%value1 = call i64 @someValue()
|
||||
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0), align 8
|
||||
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0), align 8
|
||||
store i64 %value2, i64* @main.nonConst2, align 8
|
||||
call void @modifyExternal(i32* bitcast (i8* getelementptr inbounds (i8, i8* bitcast ([8 x { i16, i32 }]* @main.someArray to i8*), i32 28) to i32*))
|
||||
call void @modifyExternal(i32* bitcast ([1 x i16*]* @main.exportedValue to i32*))
|
||||
store i16 5, i16* @main.exposedValue1, align 2
|
||||
call void @readExternal(i32* bitcast (i64* @main.exportedConst to i32*))
|
||||
store i64 %value1, ptr @main.nonConst1, align 8
|
||||
%value2 = load i64, ptr @main.nonConst1, align 8
|
||||
store i64 %value2, ptr @main.nonConst2, align 8
|
||||
call void @modifyExternal(ptr getelementptr inbounds (i8, ptr @main.someArray, i32 28))
|
||||
call void @modifyExternal(ptr @main.exportedValue)
|
||||
store i16 5, ptr @main.exposedValue1, align 2
|
||||
call void @readExternal(ptr @main.exportedConst)
|
||||
call void @runtime.printint64(i64 42)
|
||||
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
|
||||
store i16 7, i16* @main.exposedValue2, align 2
|
||||
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
|
||||
call void @modifyExternal(ptr @willModifyGlobal)
|
||||
store i16 7, ptr @main.exposedValue2, align 2
|
||||
call void @modifyExternal(ptr @hasInlineAsm)
|
||||
call void @runtime.printint64(i64 6)
|
||||
call void @runtime.printint64(i64 -1)
|
||||
%agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct()
|
||||
@@ -42,7 +42,7 @@ entry:
|
||||
%agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0
|
||||
%agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1
|
||||
%agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2
|
||||
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, { i8, i32, { float, { i64, i16 } } }* @main.insertedValue, align 8
|
||||
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, ptr @main.insertedValue, align 8
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ entry:
|
||||
|
||||
declare i64 @someValue() local_unnamed_addr
|
||||
|
||||
declare void @modifyExternal(i32*) local_unnamed_addr
|
||||
declare void @modifyExternal(ptr) local_unnamed_addr
|
||||
|
||||
declare void @readExternal(i32*) local_unnamed_addr
|
||||
declare void @readExternal(ptr) local_unnamed_addr
|
||||
|
||||
define void @willModifyGlobal() {
|
||||
entry:
|
||||
store i16 8, i16* @main.exposedValue2, align 2
|
||||
store i16 8, ptr @main.exposedValue2, align 2
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
Vendored
+19
-11
@@ -4,8 +4,9 @@ target triple = "x86_64--linux"
|
||||
@intToPtrResult = global i8 0
|
||||
@ptrToIntResult = global i8 0
|
||||
@icmpResult = global i8 0
|
||||
@pointerTagResult = global i64 0
|
||||
@someArray = internal global {i16, i8, i8} zeroinitializer
|
||||
@someArrayPointer = global i8* zeroinitializer
|
||||
@someArrayPointer = global ptr zeroinitializer
|
||||
|
||||
define void @runtime.initAll() {
|
||||
call void @main.init()
|
||||
@@ -17,49 +18,56 @@ define internal void @main.init() {
|
||||
call void @testPtrToInt()
|
||||
call void @testConstGEP()
|
||||
call void @testICmp()
|
||||
call void @testPointerTag()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @testIntToPtr() {
|
||||
%nil = icmp eq i8* inttoptr (i64 1024 to i8*), null
|
||||
%nil = icmp eq ptr inttoptr (i64 1024 to ptr), null
|
||||
br i1 %nil, label %a, label %b
|
||||
a:
|
||||
; should not be reached
|
||||
store i8 1, i8* @intToPtrResult
|
||||
store i8 1, ptr @intToPtrResult
|
||||
ret void
|
||||
b:
|
||||
; should be reached
|
||||
store i8 2, i8* @intToPtrResult
|
||||
store i8 2, ptr @intToPtrResult
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @testPtrToInt() {
|
||||
%zero = icmp eq i64 ptrtoint (i8* @ptrToIntResult to i64), 0
|
||||
%zero = icmp eq i64 ptrtoint (ptr @ptrToIntResult to i64), 0
|
||||
br i1 %zero, label %a, label %b
|
||||
a:
|
||||
; should not be reached
|
||||
store i8 1, i8* @ptrToIntResult
|
||||
store i8 1, ptr @ptrToIntResult
|
||||
ret void
|
||||
b:
|
||||
; should be reached
|
||||
store i8 2, i8* @ptrToIntResult
|
||||
store i8 2, ptr @ptrToIntResult
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @testConstGEP() {
|
||||
store i8* getelementptr inbounds (i8, i8* bitcast ({i16, i8, i8}* @someArray to i8*), i32 2), i8** @someArrayPointer
|
||||
store ptr getelementptr inbounds (i8, ptr @someArray, i32 2), ptr @someArrayPointer
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @testICmp() {
|
||||
br i1 icmp eq (i64 ptrtoint (i8* @ptrToIntResult to i64), i64 0), label %equal, label %unequal
|
||||
br i1 icmp eq (i64 ptrtoint (ptr @ptrToIntResult to i64), i64 0), label %equal, label %unequal
|
||||
equal:
|
||||
; should not be reached
|
||||
store i8 1, i8* @icmpResult
|
||||
store i8 1, ptr @icmpResult
|
||||
ret void
|
||||
unequal:
|
||||
; should be reached
|
||||
store i8 2, i8* @icmpResult
|
||||
store i8 2, ptr @icmpResult
|
||||
ret void
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @testPointerTag() {
|
||||
%val = and i64 ptrtoint (ptr getelementptr inbounds (i8, ptr @someArray, i32 2) to i64), 3
|
||||
store i64 %val, ptr @pointerTagResult
|
||||
ret void
|
||||
}
|
||||
|
||||
Vendored
+2
-1
@@ -4,8 +4,9 @@ target triple = "x86_64--linux"
|
||||
@intToPtrResult = local_unnamed_addr global i8 2
|
||||
@ptrToIntResult = local_unnamed_addr global i8 2
|
||||
@icmpResult = local_unnamed_addr global i8 2
|
||||
@pointerTagResult = local_unnamed_addr global i64 2
|
||||
@someArray = internal global { i16, i8, i8 } zeroinitializer
|
||||
@someArrayPointer = local_unnamed_addr global i8* getelementptr inbounds ({ i16, i8, i8 }, { i16, i8, i8 }* @someArray, i64 0, i32 1)
|
||||
@someArrayPointer = local_unnamed_addr global ptr getelementptr inbounds ({ i16, i8, i8 }, ptr @someArray, i64 0, i32 1)
|
||||
|
||||
define void @runtime.initAll() local_unnamed_addr {
|
||||
ret void
|
||||
|
||||
Vendored
+9
-9
@@ -3,14 +3,14 @@ target triple = "x86_64--linux"
|
||||
|
||||
@main.v1 = global i1 0
|
||||
@main.v2 = global i1 0
|
||||
@"reflect/types.type:named:main.foo" = private constant { i8, i8*, i8* } { i8 34, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:main.foo", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4
|
||||
@"reflect/types.type:pointer:named:main.foo" = external constant { i8, i8* }
|
||||
@"reflect/types.type:named:main.foo" = private constant { i8, ptr, ptr } { i8 34, ptr @"reflect/types.type:pointer:named:main.foo", ptr @"reflect/types.type:basic:int" }, align 4
|
||||
@"reflect/types.type:pointer:named:main.foo" = external constant { i8, ptr }
|
||||
@"reflect/types.typeid:named:main.foo" = external constant i8
|
||||
@"reflect/types.type:basic:int" = private constant { i8, i8* } { i8 2, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:int", i32 0, i32 0) }, align 4
|
||||
@"reflect/types.type:pointer:basic:int" = external constant { i8, i8* }
|
||||
@"reflect/types.type:basic:int" = private constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
|
||||
@"reflect/types.type:pointer:basic:int" = external constant { i8, ptr }
|
||||
|
||||
|
||||
declare i1 @runtime.typeAssert(i8*, i8*, i8*, i8*)
|
||||
declare i1 @runtime.typeAssert(ptr, ptr, ptr, ptr)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
@@ -21,9 +21,9 @@ entry:
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
; Test type asserts.
|
||||
%typecode = call i1 @runtime.typeAssert(i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:main.foo", i32 0, i32 0), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
|
||||
store i1 %typecode, i1* @main.v1
|
||||
%typecode2 = call i1 @runtime.typeAssert(i8* null, i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
|
||||
store i1 %typecode2, i1* @main.v2
|
||||
%typecode = call i1 @runtime.typeAssert(ptr @"reflect/types.type:named:main.foo", ptr @"reflect/types.typeid:named:main.foo", ptr undef, ptr null)
|
||||
store i1 %typecode, ptr @main.v1
|
||||
%typecode2 = call i1 @runtime.typeAssert(ptr null, ptr @"reflect/types.typeid:named:main.foo", ptr undef, ptr null)
|
||||
store i1 %typecode2, ptr @main.v2
|
||||
ret void
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -25,7 +25,7 @@ for.loop:
|
||||
br i1 %icmp, label %for.done, label %for.loop
|
||||
|
||||
for.done:
|
||||
store i8 %a, i8* @main.phiNodesResultA
|
||||
store i8 %b, i8* @main.phiNodesResultB
|
||||
store i8 %a, ptr @main.phiNodesResultA
|
||||
store i8 %b, ptr @main.phiNodesResultB
|
||||
ret void
|
||||
}
|
||||
|
||||
Vendored
+34
-37
@@ -3,7 +3,7 @@ target triple = "x86_64--linux"
|
||||
|
||||
declare void @externalCall(i64)
|
||||
|
||||
declare i64 @ptrHash(i8* nocapture)
|
||||
declare i64 @ptrHash(ptr nocapture)
|
||||
|
||||
@foo.knownAtRuntime = global i64 0
|
||||
@bar.knownAtRuntime = global i64 0
|
||||
@@ -17,60 +17,60 @@ declare i64 @ptrHash(i8* nocapture)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @baz.init(i8* undef)
|
||||
call void @foo.init(i8* undef)
|
||||
call void @bar.init(i8* undef)
|
||||
call void @main.init(i8* undef)
|
||||
call void @x.init(i8* undef)
|
||||
call void @y.init(i8* undef)
|
||||
call void @z.init(i8* undef)
|
||||
call void @baz.init(ptr undef)
|
||||
call void @foo.init(ptr undef)
|
||||
call void @bar.init(ptr undef)
|
||||
call void @main.init(ptr undef)
|
||||
call void @x.init(ptr undef)
|
||||
call void @y.init(ptr undef)
|
||||
call void @z.init(ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @foo.init(i8* %context) unnamed_addr {
|
||||
store i64 5, i64* @foo.knownAtRuntime
|
||||
define internal void @foo.init(ptr %context) unnamed_addr {
|
||||
store i64 5, ptr @foo.knownAtRuntime
|
||||
unreachable ; this triggers a revert of @foo.init.
|
||||
}
|
||||
|
||||
define internal void @bar.init(i8* %context) unnamed_addr {
|
||||
%val = load i64, i64* @foo.knownAtRuntime
|
||||
store i64 %val, i64* @bar.knownAtRuntime
|
||||
define internal void @bar.init(ptr %context) unnamed_addr {
|
||||
%val = load i64, ptr @foo.knownAtRuntime
|
||||
store i64 %val, ptr @bar.knownAtRuntime
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @baz.init(i8* %context) unnamed_addr {
|
||||
define internal void @baz.init(ptr %context) unnamed_addr {
|
||||
; Test extractvalue/insertvalue with more than one index.
|
||||
%val = load [3 x {i64, i32}], [3 x {i64, i32}]* @baz.someGlobal
|
||||
%val = load [3 x {i64, i32}], ptr @baz.someGlobal
|
||||
%part = extractvalue [3 x {i64, i32}] %val, 0, 1
|
||||
%val2 = insertvalue [3 x {i64, i32}] %val, i32 5, 2, 1
|
||||
unreachable ; trigger revert
|
||||
}
|
||||
|
||||
define internal void @main.init(i8* %context) unnamed_addr {
|
||||
define internal void @main.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
call void @externalCall(i64 3)
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
define internal void @x.init(i8* %context) unnamed_addr {
|
||||
define internal void @x.init(ptr %context) unnamed_addr {
|
||||
; Test atomic and volatile memory accesses.
|
||||
store atomic i32 1, i32* @x.atomicNum seq_cst, align 4
|
||||
%x = load atomic i32, i32* @x.atomicNum seq_cst, align 4
|
||||
store i32 %x, i32* @x.atomicNum
|
||||
%y = load volatile i32, i32* @x.volatileNum
|
||||
store volatile i32 %y, i32* @x.volatileNum
|
||||
store atomic i32 1, ptr @x.atomicNum seq_cst, align 4
|
||||
%x = load atomic i32, ptr @x.atomicNum seq_cst, align 4
|
||||
store i32 %x, ptr @x.atomicNum
|
||||
%y = load volatile i32, ptr @x.volatileNum
|
||||
store volatile i32 %y, ptr @x.volatileNum
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @y.init(i8* %context) unnamed_addr {
|
||||
define internal void @y.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
br label %loop
|
||||
|
||||
loop:
|
||||
; Test a wait-loop.
|
||||
; This function must be reverted.
|
||||
%val = load atomic i32, i32* @y.ready seq_cst, align 4
|
||||
%val = load atomic i32, ptr @y.ready seq_cst, align 4
|
||||
%ready = icmp eq i32 %val, 1
|
||||
br i1 %ready, label %end, label %loop
|
||||
|
||||
@@ -78,27 +78,25 @@ end:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @z.init(i8* %context) unnamed_addr {
|
||||
%bloom = bitcast i64* @z.bloom to i8*
|
||||
|
||||
define internal void @z.init(ptr %context) unnamed_addr {
|
||||
; This can be safely expanded.
|
||||
call void @z.setArr(i8* %bloom, i64 1, i8* %bloom)
|
||||
call void @z.setArr(ptr @z.bloom, i64 1, ptr @z.bloom)
|
||||
|
||||
; This call should be reverted to prevent unrolling.
|
||||
call void @z.setArr(i8* bitcast ([32 x i8]* @z.arr to i8*), i64 32, i8* %bloom)
|
||||
call void @z.setArr(ptr @z.arr, i64 32, ptr @z.bloom)
|
||||
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @z.setArr(i8* %arr, i64 %n, i8* %context) unnamed_addr {
|
||||
define internal void @z.setArr(ptr %arr, i64 %n, ptr %context) unnamed_addr {
|
||||
entry:
|
||||
br label %loop
|
||||
|
||||
loop:
|
||||
%prev = phi i64 [ %n, %entry ], [ %idx, %loop ]
|
||||
%idx = sub i64 %prev, 1
|
||||
%elem = getelementptr i8, i8* %arr, i64 %idx
|
||||
call void @z.set(i8* %elem, i8* %context)
|
||||
%elem = getelementptr i8, ptr %arr, i64 %idx
|
||||
call void @z.set(ptr %elem, ptr %context)
|
||||
%done = icmp eq i64 %idx, 0
|
||||
br i1 %done, label %end, label %loop
|
||||
|
||||
@@ -106,14 +104,13 @@ end:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @z.set(i8* %ptr, i8* %context) unnamed_addr {
|
||||
define internal void @z.set(ptr %ptr, ptr %context) unnamed_addr {
|
||||
; Insert the pointer into the Bloom filter.
|
||||
%hash = call i64 @ptrHash(i8* %ptr)
|
||||
%hash = call i64 @ptrHash(ptr %ptr)
|
||||
%index = lshr i64 %hash, 58
|
||||
%bit = shl i64 1, %index
|
||||
%bloom = bitcast i8* %context to i64*
|
||||
%old = load i64, i64* %bloom
|
||||
%old = load i64, ptr %context
|
||||
%new = or i64 %old, %bit
|
||||
store i64 %new, i64* %bloom
|
||||
store i64 %new, ptr %context
|
||||
ret void
|
||||
}
|
||||
|
||||
Vendored
+27
-28
@@ -13,41 +13,41 @@ target triple = "x86_64--linux"
|
||||
|
||||
declare void @externalCall(i64) local_unnamed_addr
|
||||
|
||||
declare i64 @ptrHash(i8* nocapture) local_unnamed_addr
|
||||
declare i64 @ptrHash(ptr nocapture) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call fastcc void @baz.init(i8* undef)
|
||||
call fastcc void @foo.init(i8* undef)
|
||||
%val = load i64, i64* @foo.knownAtRuntime, align 8
|
||||
store i64 %val, i64* @bar.knownAtRuntime, align 8
|
||||
call fastcc void @baz.init(ptr undef)
|
||||
call fastcc void @foo.init(ptr undef)
|
||||
%val = load i64, ptr @foo.knownAtRuntime, align 8
|
||||
store i64 %val, ptr @bar.knownAtRuntime, align 8
|
||||
call void @externalCall(i64 3)
|
||||
store atomic i32 1, i32* @x.atomicNum seq_cst, align 4
|
||||
%x = load atomic i32, i32* @x.atomicNum seq_cst, align 4
|
||||
store i32 %x, i32* @x.atomicNum, align 4
|
||||
%y = load volatile i32, i32* @x.volatileNum, align 4
|
||||
store volatile i32 %y, i32* @x.volatileNum, align 4
|
||||
call fastcc void @y.init(i8* undef)
|
||||
call fastcc void @z.set(i8* bitcast (i64* @z.bloom to i8*), i8* bitcast (i64* @z.bloom to i8*))
|
||||
call fastcc void @z.setArr(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @z.arr, i32 0, i32 0), i64 32, i8* bitcast (i64* @z.bloom to i8*))
|
||||
store atomic i32 1, ptr @x.atomicNum seq_cst, align 4
|
||||
%x = load atomic i32, ptr @x.atomicNum seq_cst, align 4
|
||||
store i32 %x, ptr @x.atomicNum, align 4
|
||||
%y = load volatile i32, ptr @x.volatileNum, align 4
|
||||
store volatile i32 %y, ptr @x.volatileNum, align 4
|
||||
call fastcc void @y.init(ptr undef)
|
||||
call fastcc void @z.set(ptr @z.bloom, ptr @z.bloom)
|
||||
call fastcc void @z.setArr(ptr @z.arr, i64 32, ptr @z.bloom)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal fastcc void @foo.init(i8* %context) unnamed_addr {
|
||||
store i64 5, i64* @foo.knownAtRuntime, align 8
|
||||
define internal fastcc void @foo.init(ptr %context) unnamed_addr {
|
||||
store i64 5, ptr @foo.knownAtRuntime, align 8
|
||||
unreachable
|
||||
}
|
||||
|
||||
define internal fastcc void @baz.init(i8* %context) unnamed_addr {
|
||||
define internal fastcc void @baz.init(ptr %context) unnamed_addr {
|
||||
unreachable
|
||||
}
|
||||
|
||||
define internal fastcc void @y.init(i8* %context) unnamed_addr {
|
||||
define internal fastcc void @y.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
br label %loop
|
||||
|
||||
loop: ; preds = %loop, %entry
|
||||
%val = load atomic i32, i32* @y.ready seq_cst, align 4
|
||||
%val = load atomic i32, ptr @y.ready seq_cst, align 4
|
||||
%ready = icmp eq i32 %val, 1
|
||||
br i1 %ready, label %end, label %loop
|
||||
|
||||
@@ -55,15 +55,15 @@ end: ; preds = %loop
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal fastcc void @z.setArr(i8* %arr, i64 %n, i8* %context) unnamed_addr {
|
||||
define internal fastcc void @z.setArr(ptr %arr, i64 %n, ptr %context) unnamed_addr {
|
||||
entry:
|
||||
br label %loop
|
||||
|
||||
|
||||
loop: ; preds = %loop, %entry
|
||||
%prev = phi i64 [ %n, %entry ], [ %idx, %loop ]
|
||||
%idx = sub i64 %prev, 1
|
||||
%elem = getelementptr i8, i8* %arr, i64 %idx
|
||||
call fastcc void @z.set(i8* %elem, i8* %context)
|
||||
%elem = getelementptr i8, ptr %arr, i64 %idx
|
||||
call fastcc void @z.set(ptr %elem, ptr %context)
|
||||
%done = icmp eq i64 %idx, 0
|
||||
br i1 %done, label %end, label %loop
|
||||
|
||||
@@ -71,13 +71,12 @@ end: ; preds = %loop
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal fastcc void @z.set(i8* %ptr, i8* %context) unnamed_addr {
|
||||
%hash = call i64 @ptrHash(i8* %ptr)
|
||||
define internal fastcc void @z.set(ptr %ptr, ptr %context) unnamed_addr {
|
||||
%hash = call i64 @ptrHash(ptr %ptr)
|
||||
%index = lshr i64 %hash, 58
|
||||
%bit = shl i64 1, %index
|
||||
%bloom = bitcast i8* %context to i64*
|
||||
%old = load i64, i64* %bloom, align 8
|
||||
%old = load i64, ptr %context, align 8
|
||||
%new = or i64 %old, %bit
|
||||
store i64 %new, i64* %bloom, align 8
|
||||
store i64 %new, ptr %context, align 8
|
||||
ret void
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+32
-35
@@ -2,15 +2,15 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d"
|
||||
@main.uint8SliceSrc = internal unnamed_addr global { i8*, i64, i64 } { i8* getelementptr inbounds ([2 x i8], [2 x i8]* @main.uint8SliceSrc.buf, i32 0, i32 0), i64 2, i64 2 }
|
||||
@main.uint8SliceDst = internal unnamed_addr global { i8*, i64, i64 } zeroinitializer
|
||||
@main.uint8SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.uint8SliceSrc.buf, i64 2, i64 2 }
|
||||
@main.uint8SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer
|
||||
@main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024]
|
||||
@main.int16SliceSrc = internal unnamed_addr global { i16*, i64, i64 } { i16* getelementptr inbounds ([3 x i16], [3 x i16]* @main.int16SliceSrc.buf, i32 0, i32 0), i64 3, i64 3 }
|
||||
@main.int16SliceDst = internal unnamed_addr global { i16*, i64, i64 } zeroinitializer
|
||||
@main.int16SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.int16SliceSrc.buf, i64 3, i64 3 }
|
||||
@main.int16SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer
|
||||
|
||||
declare i64 @runtime.sliceCopy(i8* %dst, i8* %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
|
||||
declare i64 @runtime.sliceCopy(ptr %dst, ptr %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
|
||||
|
||||
declare i8* @runtime.alloc(i64, i8*) unnamed_addr
|
||||
declare ptr @runtime.alloc(i64, ptr) unnamed_addr
|
||||
|
||||
declare void @runtime.printuint8(i8)
|
||||
|
||||
@@ -25,23 +25,23 @@ entry:
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
; print(uintSliceSrc[0])
|
||||
%uint8SliceSrc.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc, i64 0, i32 0)
|
||||
%uint8SliceSrc.val = load i8, i8* %uint8SliceSrc.buf
|
||||
%uint8SliceSrc.buf = load ptr, ptr @main.uint8SliceSrc
|
||||
%uint8SliceSrc.val = load i8, ptr %uint8SliceSrc.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceSrc.val)
|
||||
|
||||
; print(uintSliceDst[0])
|
||||
%uint8SliceDst.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceDst, i64 0, i32 0)
|
||||
%uint8SliceDst.val = load i8, i8* %uint8SliceDst.buf
|
||||
%uint8SliceDst.buf = load ptr, ptr @main.uint8SliceDst
|
||||
%uint8SliceDst.val = load i8, ptr %uint8SliceDst.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceDst.val)
|
||||
|
||||
; print(int16SliceSrc[0])
|
||||
%int16SliceSrc.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc, i64 0, i32 0)
|
||||
%int16SliceSrc.val = load i16, i16* %int16SliceSrc.buf
|
||||
%int16SliceSrc.buf = load ptr, ptr @main.int16SliceSrc
|
||||
%int16SliceSrc.val = load i16, ptr %int16SliceSrc.buf
|
||||
call void @runtime.printint16(i16 %int16SliceSrc.val)
|
||||
|
||||
; print(int16SliceDst[0])
|
||||
%int16SliceDst.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceDst, i64 0, i32 0)
|
||||
%int16SliceDst.val = load i16, i16* %int16SliceDst.buf
|
||||
%int16SliceDst.buf = load ptr, ptr @main.int16SliceDst
|
||||
%int16SliceDst.val = load i16, ptr %int16SliceDst.buf
|
||||
call void @runtime.printint16(i16 %int16SliceDst.val)
|
||||
ret void
|
||||
}
|
||||
@@ -50,37 +50,34 @@ define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
; equivalent of:
|
||||
; uint8SliceDst = make([]uint8, len(uint8SliceSrc))
|
||||
%uint8SliceSrc = load { i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc
|
||||
%uint8SliceSrc.len = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 1
|
||||
%uint8SliceDst.buf = call i8* @runtime.alloc(i64 %uint8SliceSrc.len, i8* null)
|
||||
%0 = insertvalue { i8*, i64, i64 } undef, i8* %uint8SliceDst.buf, 0
|
||||
%1 = insertvalue { i8*, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
|
||||
%2 = insertvalue { i8*, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
|
||||
store { i8*, i64, i64 } %2, { i8*, i64, i64 }* @main.uint8SliceDst
|
||||
%uint8SliceSrc = load { ptr, i64, i64 }, ptr @main.uint8SliceSrc
|
||||
%uint8SliceSrc.len = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 1
|
||||
%uint8SliceDst.buf = call ptr @runtime.alloc(i64 %uint8SliceSrc.len, ptr null)
|
||||
%0 = insertvalue { ptr, i64, i64 } undef, ptr %uint8SliceDst.buf, 0
|
||||
%1 = insertvalue { ptr, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
|
||||
%2 = insertvalue { ptr, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
|
||||
store { ptr, i64, i64 } %2, ptr @main.uint8SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(uint8SliceDst, uint8SliceSrc)
|
||||
%uint8SliceSrc.buf = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 0
|
||||
%copy.n = call i64 @runtime.sliceCopy(i8* %uint8SliceDst.buf, i8* %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
|
||||
%uint8SliceSrc.buf = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 0
|
||||
%copy.n = call i64 @runtime.sliceCopy(ptr %uint8SliceDst.buf, ptr %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
|
||||
|
||||
; equivalent of:
|
||||
; int16SliceDst = make([]int16, len(int16SliceSrc))
|
||||
%int16SliceSrc = load { i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc
|
||||
%int16SliceSrc.len = extractvalue { i16*, i64, i64 } %int16SliceSrc, 1
|
||||
%int16SliceSrc = load { ptr, i64, i64 }, ptr @main.int16SliceSrc
|
||||
%int16SliceSrc.len = extractvalue { ptr, i64, i64 } %int16SliceSrc, 1
|
||||
%int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2
|
||||
%int16SliceDst.buf.raw = call i8* @runtime.alloc(i64 %int16SliceSrc.len.bytes, i8* null)
|
||||
%int16SliceDst.buf = bitcast i8* %int16SliceDst.buf.raw to i16*
|
||||
%3 = insertvalue { i16*, i64, i64 } undef, i16* %int16SliceDst.buf, 0
|
||||
%4 = insertvalue { i16*, i64, i64 } %3, i64 %int16SliceSrc.len, 1
|
||||
%5 = insertvalue { i16*, i64, i64 } %4, i64 %int16SliceSrc.len, 2
|
||||
store { i16*, i64, i64 } %5, { i16*, i64, i64 }* @main.int16SliceDst
|
||||
%int16SliceDst.buf = call ptr @runtime.alloc(i64 %int16SliceSrc.len.bytes, ptr null)
|
||||
%3 = insertvalue { ptr, i64, i64 } undef, ptr %int16SliceDst.buf, 0
|
||||
%4 = insertvalue { ptr, i64, i64 } %3, i64 %int16SliceSrc.len, 1
|
||||
%5 = insertvalue { ptr, i64, i64 } %4, i64 %int16SliceSrc.len, 2
|
||||
store { ptr, i64, i64 } %5, ptr @main.int16SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(int16SliceDst, int16SliceSrc)
|
||||
%int16SliceSrc.buf = extractvalue { i16*, i64, i64 } %int16SliceSrc, 0
|
||||
%int16SliceSrc.buf.i8ptr = bitcast i16* %int16SliceSrc.buf to i8*
|
||||
%int16SliceDst.buf.i8ptr = bitcast i16* %int16SliceDst.buf to i8*
|
||||
%copy.n2 = call i64 @runtime.sliceCopy(i8* %int16SliceDst.buf.i8ptr, i8* %int16SliceSrc.buf.i8ptr, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
|
||||
%int16SliceSrc.buf = extractvalue { ptr, i64, i64 } %int16SliceSrc, 0
|
||||
%copy.n2 = call i64 @runtime.sliceCopy(ptr %int16SliceDst.buf, ptr %int16SliceSrc.buf, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
|
||||
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -236,6 +236,9 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options
|
||||
if testConfig.RunRegexp != "" {
|
||||
flags = append(flags, "-test.run="+testConfig.RunRegexp)
|
||||
}
|
||||
if testConfig.SkipRegexp != "" {
|
||||
flags = append(flags, "-test.skip="+testConfig.SkipRegexp)
|
||||
}
|
||||
if testConfig.BenchRegexp != "" {
|
||||
flags = append(flags, "-test.bench="+testConfig.BenchRegexp)
|
||||
}
|
||||
@@ -248,6 +251,9 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options
|
||||
if testConfig.Count != nil && *testConfig.Count != 1 {
|
||||
flags = append(flags, "-test.count="+strconv.Itoa(*testConfig.Count))
|
||||
}
|
||||
if testConfig.Shuffle != "" {
|
||||
flags = append(flags, "-test.shuffle="+testConfig.Shuffle)
|
||||
}
|
||||
|
||||
logToStdout := testConfig.Verbose || testConfig.BenchRegexp != ""
|
||||
|
||||
@@ -342,7 +348,7 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options
|
||||
fmt.Fprintf(w, "? \t%s\t[no test files]\n", err.ImportPath)
|
||||
// Pretend the test passed - it at least didn't fail.
|
||||
return true, nil
|
||||
} else if passed {
|
||||
} else if passed && !testConfig.CompileOnly {
|
||||
fmt.Fprintf(w, "ok \t%s\t%.3fs\n", importPath, duration.Seconds())
|
||||
} else {
|
||||
fmt.Fprintf(w, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds())
|
||||
@@ -526,7 +532,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
|
||||
return fmt.Errorf("unknown flash method: %s", flashMethod)
|
||||
}
|
||||
if options.Monitor {
|
||||
return Monitor("", options)
|
||||
return Monitor(result.Executable, "", options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -939,28 +945,29 @@ func touchSerialPortAt1200bps(port string) (err error) {
|
||||
return fmt.Errorf("opening port: %s", err)
|
||||
}
|
||||
|
||||
func flashUF2UsingMSD(volume, tmppath string, options *compileopts.Options) error {
|
||||
func flashUF2UsingMSD(volumes []string, tmppath string, options *compileopts.Options) error {
|
||||
// find standard UF2 info path
|
||||
var infoPath string
|
||||
switch runtime.GOOS {
|
||||
case "linux", "freebsd":
|
||||
fi, err := os.Stat("/run/media")
|
||||
if err != nil || !fi.IsDir() {
|
||||
infoPath = "/media/*/" + volume + "/INFO_UF2.TXT"
|
||||
} else {
|
||||
infoPath = "/run/media/*/" + volume + "/INFO_UF2.TXT"
|
||||
infoPaths := make([]string, 0, len(volumes))
|
||||
for _, volume := range volumes {
|
||||
switch runtime.GOOS {
|
||||
case "linux", "freebsd":
|
||||
fi, err := os.Stat("/run/media")
|
||||
if err != nil || !fi.IsDir() {
|
||||
infoPaths = append(infoPaths, "/media/*/"+volume+"/INFO_UF2.TXT")
|
||||
} else {
|
||||
infoPaths = append(infoPaths, "/run/media/*/"+volume+"/INFO_UF2.TXT")
|
||||
}
|
||||
case "darwin":
|
||||
infoPaths = append(infoPaths, "/Volumes/"+volume+"/INFO_UF2.TXT")
|
||||
case "windows":
|
||||
path, err := windowsFindUSBDrive(volume, options)
|
||||
if err == nil {
|
||||
infoPaths = append(infoPaths, path+"/INFO_UF2.TXT")
|
||||
}
|
||||
}
|
||||
case "darwin":
|
||||
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
|
||||
case "windows":
|
||||
path, err := windowsFindUSBDrive(volume, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
infoPath = path + "/INFO_UF2.TXT"
|
||||
}
|
||||
|
||||
d, err := locateDevice(volume, infoPath, options.Timeout)
|
||||
d, err := locateDevice(volumes, infoPaths, options.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -968,28 +975,29 @@ func flashUF2UsingMSD(volume, tmppath string, options *compileopts.Options) erro
|
||||
return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2")
|
||||
}
|
||||
|
||||
func flashHexUsingMSD(volume, tmppath string, options *compileopts.Options) error {
|
||||
func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Options) error {
|
||||
// find expected volume path
|
||||
var destPath string
|
||||
switch runtime.GOOS {
|
||||
case "linux", "freebsd":
|
||||
fi, err := os.Stat("/run/media")
|
||||
if err != nil || !fi.IsDir() {
|
||||
destPath = "/media/*/" + volume
|
||||
} else {
|
||||
destPath = "/run/media/*/" + volume
|
||||
destPaths := make([]string, 0, len(volumes))
|
||||
for _, volume := range volumes {
|
||||
switch runtime.GOOS {
|
||||
case "linux", "freebsd":
|
||||
fi, err := os.Stat("/run/media")
|
||||
if err != nil || !fi.IsDir() {
|
||||
destPaths = append(destPaths, "/media/*/"+volume)
|
||||
} else {
|
||||
destPaths = append(destPaths, "/run/media/*/"+volume)
|
||||
}
|
||||
case "darwin":
|
||||
destPaths = append(destPaths, "/Volumes/"+volume)
|
||||
case "windows":
|
||||
path, err := windowsFindUSBDrive(volume, options)
|
||||
if err == nil {
|
||||
destPaths = append(destPaths, path+"/")
|
||||
}
|
||||
}
|
||||
case "darwin":
|
||||
destPath = "/Volumes/" + volume
|
||||
case "windows":
|
||||
path, err := windowsFindUSBDrive(volume, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destPath = path + "/"
|
||||
}
|
||||
|
||||
d, err := locateDevice(volume, destPath, options.Timeout)
|
||||
d, err := locateDevice(volumes, destPaths, options.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -997,21 +1005,28 @@ func flashHexUsingMSD(volume, tmppath string, options *compileopts.Options) erro
|
||||
return moveFile(tmppath, d+"/flash.hex")
|
||||
}
|
||||
|
||||
func locateDevice(volume, path string, timeout time.Duration) (string, error) {
|
||||
func locateDevice(volumes, paths []string, timeout time.Duration) (string, error) {
|
||||
var d []string
|
||||
var err error
|
||||
for start := time.Now(); time.Since(start) < timeout; {
|
||||
d, err = filepath.Glob(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
for _, path := range paths {
|
||||
d, err = filepath.Glob(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if d != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if d != nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
if d == nil {
|
||||
return "", errors.New("unable to locate device: " + volume)
|
||||
return "", errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]")
|
||||
}
|
||||
return d[0], nil
|
||||
}
|
||||
@@ -1386,9 +1401,6 @@ func main() {
|
||||
serial := flag.String("serial", "", "which serial output to use (none, uart, usb)")
|
||||
work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit")
|
||||
interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout")
|
||||
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
|
||||
var tags buildutil.TagsFlag
|
||||
flag.Var(&tags, "tags", "a space-separated list of extra build tags")
|
||||
target := flag.String("target", "", "chip/board name or JSON target specification file")
|
||||
@@ -1415,6 +1427,17 @@ func main() {
|
||||
monitor := flag.Bool("monitor", false, "enable serial monitor")
|
||||
baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor")
|
||||
|
||||
// Internal flags, that are only intended for TinyGo development.
|
||||
printIR := flag.Bool("internal-printir", false, "print LLVM IR")
|
||||
dumpSSA := flag.Bool("internal-dumpssa", false, "dump internal Go SSA")
|
||||
verifyIR := flag.Bool("internal-verifyir", false, "run extra verification steps on LLVM IR")
|
||||
// Don't generate debug information in the IR, to make IR more readable.
|
||||
// You generally want debug information in IR for various features, like
|
||||
// stack size calculation and features like -size=short, -print-allocs=,
|
||||
// etc. The -no-debug flag is used to strip it at link time. But for TinyGo
|
||||
// development it can be useful to not emit debug information at all.
|
||||
skipDwarf := flag.Bool("internal-nodwarf", false, "internal flag, use -no-debug instead")
|
||||
|
||||
var flagJSON, flagDeps, flagTest bool
|
||||
if command == "help" || command == "list" || command == "info" || command == "build" {
|
||||
flag.BoolVar(&flagJSON, "json", false, "print data in JSON format")
|
||||
@@ -1434,10 +1457,12 @@ func main() {
|
||||
flag.BoolVar(&testConfig.Verbose, "v", false, "verbose: print additional output")
|
||||
flag.BoolVar(&testConfig.Short, "short", false, "short: run smaller test suite to save time")
|
||||
flag.StringVar(&testConfig.RunRegexp, "run", "", "run: regexp of tests to run")
|
||||
flag.StringVar(&testConfig.SkipRegexp, "skip", "", "skip: regexp of tests to skip")
|
||||
testConfig.Count = flag.Int("count", 1, "count: number of times to run tests/benchmarks `count` times")
|
||||
flag.StringVar(&testConfig.BenchRegexp, "bench", "", "run: regexp of benchmarks to run")
|
||||
flag.StringVar(&testConfig.BenchRegexp, "bench", "", "bench: regexp of benchmarks to run")
|
||||
flag.StringVar(&testConfig.BenchTime, "benchtime", "", "run each benchmark for duration `d`")
|
||||
flag.BoolVar(&testConfig.BenchMem, "benchmem", false, "show memory stats for benchmarks")
|
||||
flag.StringVar(&testConfig.Shuffle, "shuffle", "", "shuffle the order the tests and benchmarks run")
|
||||
}
|
||||
|
||||
// Early command processing, before commands are interpreted by the Go flag
|
||||
@@ -1489,6 +1514,7 @@ func main() {
|
||||
PrintIR: *printIR,
|
||||
DumpSSA: *dumpSSA,
|
||||
VerifyIR: *verifyIR,
|
||||
SkipDWARF: *skipDwarf,
|
||||
Semaphore: make(chan struct{}, *parallelism),
|
||||
Debug: !*nodebug,
|
||||
PrintSizes: *printSize,
|
||||
@@ -1530,15 +1556,6 @@ func main() {
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// Limit the number of threads to one.
|
||||
// This is an attempted workaround for the crashes we're seeing in CI on
|
||||
// Windows. If this change helps, it indicates there is a concurrency issue.
|
||||
// If it doesn't, then there is something else going on. Either way, this
|
||||
// should be removed once the test is done.
|
||||
if runtime.GOOS == "windows" {
|
||||
runtime.GOMAXPROCS(1)
|
||||
}
|
||||
|
||||
switch command {
|
||||
case "build":
|
||||
pkgName := "."
|
||||
@@ -1703,7 +1720,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
case "monitor":
|
||||
err := Monitor(*port, options)
|
||||
err := Monitor("", *port, options)
|
||||
handleCompilerError(err)
|
||||
case "targets":
|
||||
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
|
||||
|
||||
+134
-5
@@ -1,9 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/dwarf"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"debug/pe"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-tty"
|
||||
@@ -13,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
// Monitor connects to the given port and reads/writes the serial port.
|
||||
func Monitor(port string, options *compileopts.Options) error {
|
||||
func Monitor(executable, port string, options *compileopts.Options) error {
|
||||
config, err := builder.NewConfig(options)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -74,17 +83,31 @@ func Monitor(port string, options *compileopts.Options) error {
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 100*1024)
|
||||
var line []byte
|
||||
for {
|
||||
n, err := p.Read(buf)
|
||||
if err != nil {
|
||||
errCh <- fmt.Errorf("read error: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
continue
|
||||
start := 0
|
||||
for i, c := range buf[:n] {
|
||||
if c == '\n' {
|
||||
os.Stdout.Write(buf[start : i+1])
|
||||
start = i + 1
|
||||
address := extractPanicAddress(line)
|
||||
if address != 0 {
|
||||
loc, err := addressToLine(executable, address)
|
||||
if err == nil && loc.IsValid() {
|
||||
fmt.Printf("[tinygo: panic at %s]\n", loc.String())
|
||||
}
|
||||
}
|
||||
line = line[:0]
|
||||
} else {
|
||||
line = append(line, c)
|
||||
}
|
||||
}
|
||||
fmt.Printf("%v", string(buf[:n]))
|
||||
os.Stdout.Write(buf[start:n])
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -104,3 +127,109 @@ func Monitor(port string, options *compileopts.Options) error {
|
||||
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
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 {
|
||||
matches := addressMatch.FindSubmatch(line)
|
||||
if matches != nil {
|
||||
address, err := strconv.ParseUint(string(matches[1]), 16, 64)
|
||||
if err == nil {
|
||||
return address
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Convert an address in the binary to a source address location.
|
||||
func addressToLine(executable string, address uint64) (token.Position, error) {
|
||||
data, err := readDWARF(executable)
|
||||
if err != nil {
|
||||
return token.Position{}, err
|
||||
}
|
||||
r := data.Reader()
|
||||
|
||||
for {
|
||||
e, err := r.Next()
|
||||
if err != nil {
|
||||
return token.Position{}, err
|
||||
}
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
switch e.Tag {
|
||||
case dwarf.TagCompileUnit:
|
||||
r.SkipChildren()
|
||||
lr, err := data.LineReader(e)
|
||||
if err != nil {
|
||||
return token.Position{}, err
|
||||
}
|
||||
var lineEntry = dwarf.LineEntry{
|
||||
EndSequence: true,
|
||||
}
|
||||
for {
|
||||
// Read the next .debug_line entry.
|
||||
prevLineEntry := lineEntry
|
||||
err := lr.Next(&lineEntry)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return token.Position{}, err
|
||||
}
|
||||
|
||||
if prevLineEntry.EndSequence && lineEntry.Address == 0 {
|
||||
// Tombstone value. This symbol has been removed, for
|
||||
// example by the --gc-sections linker flag. It is still
|
||||
// here in the debug information because the linker can't
|
||||
// just remove this reference.
|
||||
// Read until the next EndSequence so that this sequence is
|
||||
// skipped.
|
||||
// For more details, see (among others):
|
||||
// https://reviews.llvm.org/D84825
|
||||
for {
|
||||
err := lr.Next(&lineEntry)
|
||||
if err != nil {
|
||||
return token.Position{}, err
|
||||
}
|
||||
if lineEntry.EndSequence {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !prevLineEntry.EndSequence {
|
||||
// The chunk describes the code from prevLineEntry to
|
||||
// lineEntry.
|
||||
if prevLineEntry.Address <= address && lineEntry.Address > address {
|
||||
return token.Position{
|
||||
Filename: prevLineEntry.File.Name,
|
||||
Line: prevLineEntry.Line,
|
||||
Column: prevLineEntry.Column,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return token.Position{}, nil // location not found
|
||||
}
|
||||
|
||||
// Read the DWARF debug information from a given file (in various formats).
|
||||
func readDWARF(executable string) (*dwarf.Data, error) {
|
||||
f, err := os.Open(executable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file, err := elf.NewFile(f); err == nil {
|
||||
return file.DWARF()
|
||||
} else if file, err := macho.NewFile(f); err == nil {
|
||||
return file.DWARF()
|
||||
} else if file, err := pe.NewFile(f); err == nil {
|
||||
return file.DWARF()
|
||||
} else {
|
||||
return nil, errors.New("unknown binary format")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tinygo-org/tinygo/builder"
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
)
|
||||
|
||||
func TestTraceback(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
// We care about testing the ELF format, which is only used on Linux
|
||||
// (not on MacOS or Windows).
|
||||
t.Skip("Test only works on Linux")
|
||||
}
|
||||
|
||||
// Build a small binary that only panics.
|
||||
tmpdir := t.TempDir()
|
||||
config, err := builder.NewConfig(&compileopts.Options{
|
||||
GOOS: runtime.GOOS,
|
||||
GOARCH: runtime.GOARCH,
|
||||
Opt: "z",
|
||||
InterpTimeout: time.Minute,
|
||||
Debug: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result, err := builder.Build("testdata/trivialpanic.go", ".elf", tmpdir, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Run this binary, and capture the output.
|
||||
buf := &bytes.Buffer{}
|
||||
cmd := exec.Command(result.Binary)
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = buf
|
||||
cmd.Run() // this will return an error because of the panic, ignore it
|
||||
|
||||
// Extract the "runtime error at" address.
|
||||
line := bytes.TrimSpace(buf.Bytes())
|
||||
address := extractPanicAddress(line)
|
||||
if address == 0 {
|
||||
t.Fatalf("could not extract panic address from %#v", string(line))
|
||||
}
|
||||
|
||||
// Look up the source location for this address.
|
||||
location, err := addressToLine(result.Executable, address)
|
||||
if err != nil {
|
||||
t.Fatal("could not read source location:", err)
|
||||
}
|
||||
|
||||
// Verify that the source location is as expected.
|
||||
if filepath.Base(location.Filename) != "trivialpanic.go" {
|
||||
t.Errorf("expected path to end with trivialpanic.go, got %#v", location.Filename)
|
||||
}
|
||||
if location.Line != 6 {
|
||||
t.Errorf("expected panic location to be line 6, got line %d", location.Line)
|
||||
}
|
||||
}
|
||||
+374
-21
@@ -51,20 +51,39 @@ var (
|
||||
GRAPHICS = (*GRAPHICS_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x004C)))
|
||||
|
||||
// GBA Sound Channel 1 - Tone & Sweep
|
||||
SOUND1 = (*SOUND_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0060)))
|
||||
SOUND1 = (*SOUND1_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0060)))
|
||||
|
||||
// GBA Sound Channel 2 - Tone
|
||||
SOUND2 = (*SOUND_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0068)))
|
||||
SOUND2 = (*SOUND2_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0068)))
|
||||
|
||||
// TODO: Sound channel 3 and 4
|
||||
// GBA Sound Channel 3 - Wave Output
|
||||
SOUND3 = (*SOUND3_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0070)))
|
||||
|
||||
// GBA Sound Channel 4 - Noise
|
||||
SOUND4 = (*SOUND4_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0078)))
|
||||
|
||||
// GBA Sound Control
|
||||
SOUND = (*SOUND_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0080)))
|
||||
|
||||
DMA0 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00B0)))
|
||||
DMA1 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00BC)))
|
||||
DMA2 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00C8)))
|
||||
DMA3 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00D4)))
|
||||
|
||||
TM0 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0100)))
|
||||
TM1 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0104)))
|
||||
TM2 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0108)))
|
||||
TM3 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x010C)))
|
||||
|
||||
// Communication 1
|
||||
SIODATA32 = (*SIODATA32_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0120)))
|
||||
SIOMULTI = (*SIOMULTI_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0120)))
|
||||
|
||||
KEY = (*KEY_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0130)))
|
||||
|
||||
// Communication 2
|
||||
SIO = (*SIO_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0134)))
|
||||
|
||||
INTERRUPT = (*INTERRUPT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0200)))
|
||||
)
|
||||
|
||||
@@ -119,8 +138,11 @@ const (
|
||||
// Screenblock size
|
||||
SBB_SIZE = 0x00800
|
||||
|
||||
// BG VRAM_MODE_0_2 size
|
||||
VRAM_BG_SIZE_MODE_0_2 = 0x10000
|
||||
|
||||
// BG VRAM size
|
||||
VRAM_BG_SIZE = 0x10000
|
||||
VRAM_BG_SIZE_MODE_3_5 = 0x14000
|
||||
|
||||
// Object VRAM size
|
||||
VRAM_OBJ_SIZE = 0x08000
|
||||
@@ -154,8 +176,11 @@ var (
|
||||
// Back page address
|
||||
MEM_VRAM_BACK = MEM_VRAM + VRAM_PAGE_SIZE
|
||||
|
||||
// Object VRAM address
|
||||
MEM_VRAM_OBJ = MEM_VRAM + VRAM_BG_SIZE
|
||||
// Object VRAM address - BG Mode 0-2
|
||||
MEM_VRAM_OBJ_MODE0_2 = MEM_VRAM + VRAM_BG_SIZE_MODE_0_2
|
||||
|
||||
// Object VRAM address - BG Mode 3-5
|
||||
MEM_VRAM_OBJ_MODE_3_5 = MEM_VRAM + VRAM_BG_SIZE_MODE_3_5
|
||||
)
|
||||
|
||||
// Display registers
|
||||
@@ -221,7 +246,7 @@ type GRAPHICS_Type struct {
|
||||
BLDY volatile.Register16
|
||||
}
|
||||
|
||||
type SOUND_Type struct {
|
||||
type SOUND1_Type struct {
|
||||
// Sweep register
|
||||
CNT_L volatile.Register16
|
||||
|
||||
@@ -232,7 +257,59 @@ type SOUND_Type struct {
|
||||
CNT_X volatile.Register16
|
||||
}
|
||||
|
||||
// TODO: DMA
|
||||
type SOUND2_Type struct {
|
||||
// Duty/Len/Envelope
|
||||
CNT_L volatile.Register16
|
||||
|
||||
// not used
|
||||
_ volatile.Register16
|
||||
|
||||
// Frequency/Control
|
||||
CNT_H volatile.Register16
|
||||
}
|
||||
|
||||
type SOUND3_Type struct {
|
||||
// Stop/Wave RAM select
|
||||
CNT_L volatile.Register16
|
||||
|
||||
// Length/Volume
|
||||
CNT_H volatile.Register16
|
||||
|
||||
// Frequency/Control
|
||||
CNT_X volatile.Register16
|
||||
}
|
||||
|
||||
type SOUND4_Type struct {
|
||||
// Length/Envelope
|
||||
CNT_L volatile.Register16
|
||||
|
||||
// not used
|
||||
_ volatile.Register16
|
||||
|
||||
// Frequency/Control
|
||||
CNT_H volatile.Register16
|
||||
}
|
||||
|
||||
type SOUND_Type struct {
|
||||
// Control Stereo/Volume/Enable
|
||||
CNT_L volatile.Register16
|
||||
|
||||
// Control Mixing/DMA Control
|
||||
CNT_H volatile.Register16
|
||||
|
||||
// Control Sound on/off
|
||||
CNT_X volatile.Register16
|
||||
}
|
||||
|
||||
// DMA
|
||||
type DMA_Type struct {
|
||||
SAD_L volatile.Register16
|
||||
SAD_H volatile.Register16
|
||||
DAD_L volatile.Register16
|
||||
DAD_H volatile.Register16
|
||||
CNT_L volatile.Register16
|
||||
CNT_H volatile.Register16
|
||||
}
|
||||
|
||||
// TIMER
|
||||
type TIMER_Type struct {
|
||||
@@ -240,7 +317,28 @@ type TIMER_Type struct {
|
||||
CNT volatile.Register16
|
||||
}
|
||||
|
||||
// TODO: serial
|
||||
// serial
|
||||
type SIODATA32_Type struct {
|
||||
DATA32_L volatile.Register16
|
||||
DATA32_H volatile.Register16
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
CNT volatile.Register16
|
||||
DATA8 volatile.Register16
|
||||
}
|
||||
|
||||
type SIOMULTI_Type struct {
|
||||
MULTI0 volatile.Register16
|
||||
MULTI1 volatile.Register16
|
||||
MULTI2 volatile.Register16
|
||||
MULTI3 volatile.Register16
|
||||
CNT volatile.Register16
|
||||
MLT_SEND volatile.Register16
|
||||
}
|
||||
|
||||
type SIO_Type struct {
|
||||
RCNT volatile.Register16
|
||||
}
|
||||
|
||||
// Keypad registers
|
||||
type KEY_Type struct {
|
||||
@@ -259,6 +357,34 @@ type INTERRUPT_Type struct {
|
||||
PAUSE volatile.Register16
|
||||
}
|
||||
|
||||
// LCD OBJ Attributes
|
||||
type OAMOBJ_Type struct {
|
||||
ATT0 volatile.Register16
|
||||
ATT1 volatile.Register16
|
||||
ATT2 volatile.Register16
|
||||
_ volatile.Register16
|
||||
}
|
||||
|
||||
// OAM Rotation/Scaling Parameters
|
||||
type OAMROT_Type struct {
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
PA volatile.Register16
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
PB volatile.Register16
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
PC volatile.Register16
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
_ volatile.Register16
|
||||
PD volatile.Register16
|
||||
}
|
||||
|
||||
// Constants for DISP: display
|
||||
const (
|
||||
// BGMODE: background mode.
|
||||
@@ -372,6 +498,40 @@ const (
|
||||
DISPSTAT_VCOUNT_SETTING_Pos = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
BGCNT_PRIORITY_Pos = 0x0
|
||||
BGCNT_PRIORITY_Msk = 0x3
|
||||
|
||||
BGCNT_CHAR_BASE_Pos = 0x2
|
||||
BGCNT_CHAR_BASE_Msk = 0x3
|
||||
|
||||
BGCNT_MOSAIC_Pos = 0x6
|
||||
BGCNT_MOSAIC_DISABLE = 0x0
|
||||
BGCNT_MOSAIC_ENABLE = 0x1
|
||||
|
||||
BGCNT_COLORS_Pos = 0x7
|
||||
BGCNT_COLORS_16 = 0x0
|
||||
BGCNT_COLORS_256 = 0x1
|
||||
|
||||
BGCNT_BASE_Pos = 0x8
|
||||
BGCNT_BASE_Msk = 0x1F
|
||||
|
||||
BGCNT_OVERFLOW_Pos = 0xD
|
||||
BGCNT_OVERFLOW_TRANS = 0x0
|
||||
BGCNT_OVERFLOW_WRAP = 0x1
|
||||
|
||||
BGCNT_SIZE_Pos = 0xE
|
||||
BGCNT_SIZE_Msk = 0x3
|
||||
)
|
||||
|
||||
const (
|
||||
BG_HOFS_Pos = 0x0
|
||||
BG_HOFS_Msk = 0x1FF
|
||||
|
||||
BG_VOFS_Pos = 0x0
|
||||
BG_VOFS_Msk = 0x1FF
|
||||
)
|
||||
|
||||
// Constants for TIMER
|
||||
const (
|
||||
// PRESCALER: Prescaler Selection (0=F/1, 1=F/64, 2=F/256, 3=F/1024)
|
||||
@@ -403,6 +563,135 @@ const (
|
||||
TIMERCNT_TIMER_STOP = 0x0
|
||||
)
|
||||
|
||||
const (
|
||||
// normal mode
|
||||
SIOCNT_NORMAL_SC_Pos = 0x0
|
||||
SIOCNT_NORMAL_SC_INTERNAL = 0x1
|
||||
SIOCNT_NORMAL_SC_EXTERNAL = 0x0
|
||||
|
||||
SIOCNT_NORMAL_SCSPEED_Pos = 0x1
|
||||
SIOCNT_NORMAL_SCSPEED_256K = 0x0
|
||||
SIOCNT_NORMAL_SCSPEED_2M = 0x1
|
||||
|
||||
SIOCNT_NORMAL_SCSTATE_Pos = 0x2
|
||||
SIOCNT_NORMAL_SCSTATE_LOW = 0x0
|
||||
SIOCNT_NORMAL_SCSTATE_HIGH = 0x1
|
||||
|
||||
SIOCNT_NORMAL_SO_INACTIVE_Pos = 0x3
|
||||
SIOCNT_NORMAL_SO_INACTIVE_LOW = 0x0
|
||||
SIOCNT_NORMAL_SO_INACTIVE_HIGH = 0x1
|
||||
|
||||
SIOCNT_NORMAL_START_Pos = 0x7
|
||||
SIOCNT_NORMAL_START_READY = 0x0
|
||||
SIOCNT_NORMAL_START_ACTIVE = 0x1
|
||||
|
||||
SIOCNT_NORMAL_LEN_Pos = 0xC
|
||||
SIOCNT_NORMAL_LEN8 = 0x0
|
||||
SIOCNT_NORMAL_LEN32 = 0x1
|
||||
|
||||
SIOCNT_NORMAL_MODE_Pos = 0xD
|
||||
SIOCNT_NORMAL_MODE = 0x0
|
||||
|
||||
// multiplayer mode
|
||||
SIOCNT_MULTI_BR_Pos = 0x0
|
||||
SIOCNT_MULTI_BR_Msk = 0x3
|
||||
SIOCNT_MULTI_BR_9600 = 0x0
|
||||
SIOCNT_MULTI_BR_38400 = 0x1
|
||||
SIOCNT_MULTI_BR_57600 = 0x2
|
||||
SIOCNT_MULTI_BR_115200 = 0x3
|
||||
|
||||
SIOCNT_MULTI_SI_Pos = 0x2
|
||||
SIOCNT_MULTI_SI_PARENT = 0x0
|
||||
SIOCNT_MULTI_SI_CHILD = 0x1
|
||||
|
||||
SIOCNT_MULTI_SD_Pos = 0x3
|
||||
SIOCNT_MULTI_SD_BAD = 0x0
|
||||
SIOCNT_MULTI_SD_READY = 0x1
|
||||
|
||||
SIOCNT_MULTI_ID_Pos = 0x4
|
||||
SIOCNT_MULTI_ID_Msk = 0x3
|
||||
SIOCNT_MULTI_ID_PARENT = 0x0
|
||||
SIOCNT_MULTI_ID_CHILD1 = 0x1
|
||||
SIOCNT_MULTI_ID_CHILD2 = 0x2
|
||||
SIOCNT_MULTI_ID_CHILD3 = 0x3
|
||||
|
||||
SIOCNT_MULTI_ERR_Pos = 0x6
|
||||
SIOCNT_MULTI_ERR_NORMAL = 0x0
|
||||
SIOCNT_MULTI_ERR_ERROR = 0x1
|
||||
|
||||
SIOCNT_MULTI_STARTBUSY_Pos = 0x7
|
||||
SIOCNT_MULTI_STARTBUSY_INACTIVE = 0x0
|
||||
SIOCNT_MULTI_STARTBUSY_STARTBUSY = 0x1
|
||||
|
||||
SIOCNT_MULTI_MODE_Pos = 0xC
|
||||
SIOCNT_MULTI_MODE_Msk = 0x3
|
||||
SIOCNT_MULTI_MODE = 0x01
|
||||
|
||||
// uart mode
|
||||
SIOCNT_UART_BR_Pos = 0x0
|
||||
SIOCNT_UART_BR_Msk = 0x3
|
||||
SIOCNT_UART_BR_9600 = 0x0
|
||||
SIOCNT_UART_BR_38400 = 0x1
|
||||
SIOCNT_UART_BR_57600 = 0x2
|
||||
SIOCNT_UART_BR_115200 = 0x3
|
||||
|
||||
SIOCNT_UART_CTS_Pos = 0x2
|
||||
SIOCNT_UART_CTS_ALWAYS = 0x0
|
||||
SIOCNT_UART_CTS_SENDLOW = 0x1
|
||||
|
||||
SIOCNT_UART_PARITY_Pos = 0x3
|
||||
SIOCNT_UART_PARITY_EVEN = 0x0
|
||||
SIOCNT_UART_PARITY_ODD = 0x1
|
||||
|
||||
SIOCNT_UART_SEND_Pos = 0x4
|
||||
SIOCNT_UART_SEND_NOTFULL = 0x0
|
||||
SIOCNT_UART_SEND_FULL = 0x1
|
||||
|
||||
SIOCNT_UART_REC_Pos = 0x5
|
||||
SIOCNT_UART_REC_NOTEMPTY = 0x0
|
||||
SIOCNT_UART_REC_EMPTY = 0x1
|
||||
|
||||
SIOCNT_UART_ERR_Pos = 0x6
|
||||
SIOCNT_UART_ERR_NOERROR = 0x0
|
||||
SIOCNT_UART_ERR_ERROR = 0x1
|
||||
|
||||
SIOCNT_UART_DATALEN_Pos = 0x7
|
||||
SIOCNT_UART_DATALEN_7 = 0x0
|
||||
SIOCNT_UART_DATALEN_8 = 0x1
|
||||
|
||||
SIOCNT_UART_FIFO_Pos = 0x8
|
||||
SIOCNT_UART_FIFO_DISABLE = 0x0
|
||||
SIOCNT_UART_FIFO_ENABLE = 0x1
|
||||
|
||||
SIOCNT_UART_PARITY_ENABLE_Pos = 0x9
|
||||
SIOCNT_UART_PARITY_DISABLE = 0x0
|
||||
SIOCNT_UART_PARITY_ENABLE = 0x1
|
||||
|
||||
SIOCNT_UART_SEND_ENABLE_Pos = 0xA
|
||||
SIOCNT_UART_SEND_DISABLE = 0x0
|
||||
SIOCNT_UART_SEND_ENABLE = 0x1
|
||||
|
||||
SIOCNT_UART_REC_ENABLE_Pos = 0xB
|
||||
SIOCNT_UART_REC_DISABLE = 0x0
|
||||
SIOCNT_UART_REC_ENABLE = 0x1
|
||||
|
||||
SIOCNT_UART_MODE_Pos = 0xC
|
||||
SIOCNT_UART_MODE_Msk = 0x3
|
||||
SIOCNT_UART_MODE = 0x11
|
||||
|
||||
// IRQs used by all
|
||||
SIOCNT_IRQ_Pos = 0xE
|
||||
SIOCNT_IRQ_DISABLE = 0x0
|
||||
SIOCNT_IRQ_ENABLE = 0x1
|
||||
)
|
||||
|
||||
const (
|
||||
SIO_RCNT_MODE_Pos = 0xF
|
||||
SIO_RCNT_MODE_NORMAL = 0x0
|
||||
SIO_RCNT_MODE_MULTI = 0x0
|
||||
SIO_RCNT_MODE_UART = 0x0
|
||||
)
|
||||
|
||||
// Constants for KEY
|
||||
const (
|
||||
// KEYINPUT
|
||||
@@ -420,18 +709,24 @@ const (
|
||||
KEYINPUT_BUTTON_L_Pos = 0x9
|
||||
|
||||
// KEYCNT
|
||||
KEYCNT_IGNORE = 0x0
|
||||
KEYCNT_SELECT = 0x1
|
||||
KEYCNT_BUTTON_A_Pos = 0x0
|
||||
KEYCNT_BUTTON_B_Pos = 0x1
|
||||
KEYCNT_BUTTON_SELECT_Pos = 0x2
|
||||
KEYCNT_BUTTON_START_Pos = 0x3
|
||||
KEYCNT_BUTTON_RIGHT_Pos = 0x4
|
||||
KEYCNT_BUTTON_LEFT_Pos = 0x5
|
||||
KEYCNT_BUTTON_UP_Pos = 0x6
|
||||
KEYCNT_BUTTON_DOWN_Pos = 0x7
|
||||
KEYCNT_BUTTON_R_Pos = 0x8
|
||||
KEYCNT_BUTTON_L_Pos = 0x9
|
||||
KEYCNT_IGNORE = 0x0
|
||||
KEYCNT_SELECT = 0x1
|
||||
KEYCNT_BUTTON_A_Pos = 0x0
|
||||
KEYCNT_BUTTON_B_Pos = 0x1
|
||||
KEYCNT_BUTTON_SELECT_Pos = 0x2
|
||||
KEYCNT_BUTTON_START_Pos = 0x3
|
||||
KEYCNT_BUTTON_RIGHT_Pos = 0x4
|
||||
KEYCNT_BUTTON_LEFT_Pos = 0x5
|
||||
KEYCNT_BUTTON_UP_Pos = 0x6
|
||||
KEYCNT_BUTTON_DOWN_Pos = 0x7
|
||||
KEYCNT_BUTTON_R_Pos = 0x8
|
||||
KEYCNT_BUTTON_L_Pos = 0x9
|
||||
KEYCNT_BUTTON_IRQ_DISABLE = 0x0
|
||||
KEYCNT_BUTTON_IRQ_ENABLE = 0x1
|
||||
KEYCNT_BUTTON_IRQ_ENABLE_Pos = 0xE
|
||||
KEYCNT_BUTTON_IRQ_COND_OR = 0x0
|
||||
KEYCNT_BUTTON_IRQ_COND_AND = 0x1
|
||||
KEYCNT_BUTTON_IRQ_COND_Pos = 0xF
|
||||
)
|
||||
|
||||
// Constants for INTERRUPT
|
||||
@@ -472,3 +767,61 @@ const (
|
||||
INTERRUPT_IF_KEYPAD_Pos = 0xC
|
||||
INTERRUPT_IF_GAMPAK_Pos = 0xD
|
||||
)
|
||||
|
||||
const (
|
||||
OAMOBJ_ATT0_Y_Pos = 0x0
|
||||
OAMOBJ_ATT0_Y_Msk = 0xFF
|
||||
|
||||
OAMOBJ_ATT0_OM_Pos = 0x8
|
||||
OAMOBJ_ATT0_OM_Msk = 0x3
|
||||
OAMOBJ_ATT0_OM_REG = 0x0
|
||||
OAMOBJ_ATT0_OM_AFF = 0x1
|
||||
OAMOBJ_ATT0_OM_HIDE = 0x2
|
||||
OAMOBJ_ATT0_OM_DBL = 0x3
|
||||
|
||||
OAMOBJ_ATT0_GM_Pos = 0xA
|
||||
OAMOBJ_ATT0_GM_Msk = 0x3
|
||||
OAMOBJ_ATT0_GM_REG = 0x0
|
||||
OAMOBJ_ATT0_GM_BLEND = 0x1
|
||||
OAMOBJ_ATT0_GM_WIN = 0x2
|
||||
|
||||
OAMOBJ_ATT0_MOSAIC_Pos = 0xC
|
||||
OAMOBJ_ATT0_MOSAIC_DISABLE = 0x0
|
||||
OAMOBJ_ATT0_MOSAIC_ENABLE = 0x1
|
||||
|
||||
OAMOBJ_ATT0_COLOR_Pos = 0xD
|
||||
OAMOBJ_ATT0_COLOR_4BPP = 0x0
|
||||
OAMOBJ_ATT0_COLOR_8BPP = 0x1
|
||||
|
||||
OAMOBJ_ATT0_SH_Pos = 0xE
|
||||
OAMOBJ_ATT0_SH_Msk = 0x3
|
||||
OAMOBJ_ATT0_SH_SQUARE = 0x0
|
||||
OAMOBJ_ATT0_SH_WIDE = 0x1
|
||||
OAMOBJ_ATT0_SH_TALL = 0x2
|
||||
|
||||
OAMOBJ_ATT1_X_Pos = 0x0
|
||||
OAMOBJ_ATT1_X_Msk = 0xFF
|
||||
|
||||
OAMOBJ_ATT1_AID_Pos = 0x9
|
||||
OAMOBJ_ATT1_AID_Msk = 0x1F
|
||||
|
||||
OAMOBJ_ATT1_HF_Pos = 0xC
|
||||
OAMOBJ_ATT1_HF_NOFLIP = 0x0
|
||||
OAMOBJ_ATT1_HF_FLIP = 0x1
|
||||
|
||||
OAMOBJ_ATT1_VF_Pos = 0xD
|
||||
OAMOBJ_ATT1_VF_NOFLIP = 0x0
|
||||
OAMOBJ_ATT1_VF_FLIP = 0x1
|
||||
|
||||
OAMOBJ_ATT1_SZ_Pos = 0xE
|
||||
OAMOBJ_ATT1_SZ_Msk = 0x3
|
||||
|
||||
OAMOBJ_ATT2_TID_Pos = 0x0
|
||||
OAMOBJ_ATT2_TID_Msk = 0xFF
|
||||
|
||||
OAMOBJ_ATT2_PR_Pos = 0xA
|
||||
OAMOBJ_ATT2_PR_Msk = 0x3
|
||||
|
||||
OAMOBJ_ATT2_PB_Pos = 0xC
|
||||
OAMOBJ_ATT2_PB_Msk = 0xF
|
||||
)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
// This is used for smoke tests for chips without an associated board.
|
||||
|
||||
func main() {
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
+38
-11
@@ -7,7 +7,10 @@ import (
|
||||
|
||||
var (
|
||||
err error
|
||||
message = "1234567887654321123456788765432112345678876543211234567887654321"
|
||||
message = "1234567887654321123456788765432112345678876543211234567887654321" +
|
||||
"1234567887654321123456788765432112345678876543211234567887654321" +
|
||||
"1234567887654321123456788765432112345678876543211234567887654321" +
|
||||
"1234567887654321123456788765432112345678876543211234567887654321"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -21,30 +24,54 @@ func main() {
|
||||
println("Flash erase block size:", machine.Flash.EraseBlockSize())
|
||||
println()
|
||||
|
||||
flash := machine.OpenFlashBuffer(machine.Flash, machine.FlashDataStart())
|
||||
original := make([]byte, len(message))
|
||||
saved := make([]byte, len(message))
|
||||
|
||||
// Read flash contents on start (data shall survive power off)
|
||||
print("Reading data from flash: ")
|
||||
_, err = flash.Read(original)
|
||||
println("Reading original data from flash:")
|
||||
_, err = machine.Flash.ReadAt(original, 0)
|
||||
checkError(err)
|
||||
println(string(original))
|
||||
|
||||
// erase flash
|
||||
println("Erasing flash...")
|
||||
needed := int64(len(message)) / machine.Flash.EraseBlockSize()
|
||||
if needed == 0 {
|
||||
// have to erase at least 1 block
|
||||
needed = 1
|
||||
}
|
||||
|
||||
err := machine.Flash.EraseBlocks(0, needed)
|
||||
checkError(err)
|
||||
|
||||
// Write the message to flash
|
||||
print("Writing data to flash: ")
|
||||
flash.Seek(0, 0) // rewind back to beginning
|
||||
_, err = flash.Write([]byte(message))
|
||||
println("Writing new data to flash:")
|
||||
_, err = machine.Flash.WriteAt([]byte(message), 0)
|
||||
checkError(err)
|
||||
println(string(message))
|
||||
|
||||
// Read back flash contents after write (verify data is the same as written)
|
||||
print("Reading data back from flash: ")
|
||||
flash.Seek(0, 0) // rewind back to beginning
|
||||
_, err = flash.Read(saved)
|
||||
println("Reading data back from flash: ")
|
||||
_, err = machine.Flash.ReadAt(saved, 0)
|
||||
checkError(err)
|
||||
if !equal(saved, []byte(message)) {
|
||||
println("data verify error")
|
||||
}
|
||||
|
||||
println(string(saved))
|
||||
println()
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
func equal(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"machine/usb/joystick"
|
||||
"machine/usb/hid/joystick"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Example demonstrating I2C controller / target comms.
|
||||
//
|
||||
// To use this example, physically connect I2C0 and I2C1.
|
||||
// I2C0 will be used as the controller and I2C1 used as
|
||||
// the target.
|
||||
//
|
||||
// In this example, the target implements a simple memory
|
||||
// map, with the controller able to read and write the
|
||||
// memory.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
targetAddress = 0x11
|
||||
maxTxSize = 16
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Delay to enable USB monitor time to attach
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Controller uses default I2C pins and controller
|
||||
// mode is default
|
||||
err := controller.Configure(machine.I2CConfig{})
|
||||
if err != nil {
|
||||
panic("failed to config I2C0 as controller")
|
||||
}
|
||||
|
||||
// Target uses alternate pins and target mode is
|
||||
// explicit
|
||||
err = target.Configure(machine.I2CConfig{
|
||||
Mode: machine.I2CModeTarget,
|
||||
SCL: TARGET_SCL,
|
||||
SDA: TARGET_SDA,
|
||||
})
|
||||
if err != nil {
|
||||
panic("failed to config I2C1 as target")
|
||||
}
|
||||
|
||||
// Put welcome message directly into target memory
|
||||
copy(mem[0:], []byte("Hello World!"))
|
||||
err = target.Listen(targetAddress)
|
||||
if err != nil {
|
||||
panic("failed to listen as I2C target")
|
||||
}
|
||||
|
||||
// Start the target
|
||||
go targetMain()
|
||||
|
||||
// Read welcome message from target over I2C
|
||||
buf := make([]byte, 12)
|
||||
err = controller.Tx(targetAddress, []byte{0}, buf)
|
||||
if err != nil {
|
||||
println("failed to read welcome message over I2C")
|
||||
panic(err)
|
||||
}
|
||||
println("message from target:", string(buf))
|
||||
|
||||
// Write (1,2,3) to the target starting at memory address 3
|
||||
println("writing (1,2,3) @ 0x3")
|
||||
err = controller.Tx(targetAddress, []byte{3, 1, 2, 3}, nil)
|
||||
if err != nil {
|
||||
println("failed to write to I2C target")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Wait for target to process write
|
||||
println("mem:", mem[0], mem[1], mem[2], mem[3], mem[4], mem[5])
|
||||
|
||||
// Read memory address 4 from target, which should be the value 2
|
||||
buf = make([]byte, 1)
|
||||
err = controller.Tx(targetAddress, []byte{4}, buf[:1])
|
||||
if err != nil {
|
||||
println("failed to read from I2C target")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if buf[0] != 2 {
|
||||
panic("read incorrect value from I2C target")
|
||||
}
|
||||
|
||||
println("all done!")
|
||||
for {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// -- target ---
|
||||
|
||||
var (
|
||||
mem [256]byte
|
||||
)
|
||||
|
||||
// targetMain implements the 'main loop' for an I2C target
|
||||
func targetMain() {
|
||||
buf := make([]byte, maxTxSize)
|
||||
var ptr uint8
|
||||
|
||||
for {
|
||||
evt, n, err := target.WaitForEvent(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch evt {
|
||||
case machine.I2CReceive:
|
||||
if n > 0 {
|
||||
ptr = buf[0]
|
||||
}
|
||||
|
||||
for o := 1; o < n; o++ {
|
||||
mem[ptr] = buf[o]
|
||||
ptr++
|
||||
}
|
||||
|
||||
case machine.I2CRequest:
|
||||
target.Reply(mem[ptr:256])
|
||||
|
||||
case machine.I2CFinish:
|
||||
// nothing to do
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build feather_nrf52840
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
const (
|
||||
TARGET_SCL = machine.A5
|
||||
TARGET_SDA = machine.A4
|
||||
)
|
||||
|
||||
var (
|
||||
controller = machine.I2C0
|
||||
target = machine.I2C1
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build rp2040
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
const (
|
||||
TARGET_SCL = machine.GPIO25
|
||||
TARGET_SDA = machine.GPIO24
|
||||
)
|
||||
|
||||
var (
|
||||
controller = machine.I2C1
|
||||
target = machine.I2C0
|
||||
)
|
||||
@@ -83,7 +83,7 @@ func in_ram() uintptr {
|
||||
return device.AsmFull("MOV {}, PC", nil)
|
||||
}
|
||||
|
||||
// go:noinline used here to prevent function being 'inlined' into main()
|
||||
// 'go:noinline' used here to prevent function being 'inlined' into main()
|
||||
// so it appears in objdump output. In normal use, go:inline is not
|
||||
// required for functions running from flash (flash is the default).
|
||||
//
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"machine/usb/midi"
|
||||
"machine/usb/adc/midi"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -15,12 +15,11 @@ func main() {
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
button := machine.BUTTON
|
||||
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
button.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
|
||||
|
||||
m := midi.Port()
|
||||
m.SetHandler(func(b []byte) {
|
||||
led.Set(!led.Get())
|
||||
fmt.Printf("% X\r\n", b)
|
||||
m.Write(b)
|
||||
})
|
||||
|
||||
|
||||
@@ -114,9 +114,9 @@ tinygo_swapTask:
|
||||
pop {r4-r11, pc}
|
||||
#else
|
||||
pop {r4-r7}
|
||||
.cfi_def_cfa_offset 5*9
|
||||
.cfi_def_cfa_offset 5*4
|
||||
pop {r0-r3}
|
||||
.cfi_def_cfa_offset 1*9
|
||||
.cfi_def_cfa_offset 1*4
|
||||
mov r8, r0
|
||||
mov r9, r1
|
||||
mov r10, r2
|
||||
|
||||
@@ -9,4 +9,5 @@ type ADCConfig struct {
|
||||
Reference uint32 // analog reference voltage (AREF) in millivolts
|
||||
Resolution uint32 // number of bits for a single conversion (e.g., 8, 10, 12)
|
||||
Samples uint32 // number of samples for a single conversion (e.g., 4, 8, 16, 32)
|
||||
SampleTime uint32 // sample time, in microseconds (µs)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//go:build bluemicro840
|
||||
|
||||
package machine
|
||||
|
||||
const HasLowFrequencyCrystal = true
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D006 = P0_06
|
||||
D008 = P0_08
|
||||
D015 = P0_15
|
||||
D017 = P0_17
|
||||
D020 = P0_20
|
||||
D013 = P0_13
|
||||
D024 = P0_24
|
||||
D009 = P0_09
|
||||
D010 = P0_10
|
||||
D106 = P1_06
|
||||
|
||||
D031 = P0_31 // AIN7; P0.31 (AIN7) is used to read the voltage of the battery via ADC. It can’t be used for any other function.
|
||||
D012 = P0_12 // VCC 3.3V; P0.12 on VCC shuts off the power to VCC when you set it to high; This saves on battery immensely for LEDs of all kinds that eat power even when off
|
||||
|
||||
D030 = P0_30
|
||||
D026 = P0_26
|
||||
D029 = P0_29
|
||||
D002 = P0_02
|
||||
D113 = P1_13
|
||||
D003 = P0_03
|
||||
D028 = P0_28
|
||||
D111 = P1_11
|
||||
)
|
||||
|
||||
// Analog Pins
|
||||
const (
|
||||
AIN0 = P0_02
|
||||
AIN1 = P0_03
|
||||
AIN2 = P0_04 // Not Connected
|
||||
AIN3 = P0_05 // Not Connected
|
||||
AIN4 = P0_28
|
||||
AIN5 = P0_29
|
||||
AIN6 = P0_30
|
||||
AIN7 = P0_31 // Battery
|
||||
)
|
||||
|
||||
const (
|
||||
LED1 Pin = P1_04 // Red LED
|
||||
LED2 Pin = P1_10 // Blue LED
|
||||
LED Pin = LED1
|
||||
)
|
||||
|
||||
// UART0 pins (logical UART1) - Maps to same location as Pro Micro
|
||||
const (
|
||||
UART_RX_PIN = P0_08
|
||||
UART_TX_PIN = P0_06
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = P0_15 // I2C0 external
|
||||
SCL_PIN = P0_17 // I2C0 external
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN = P1_13 // SCK
|
||||
SPI0_SDI_PIN = P0_03 // SDI
|
||||
SPI0_SDO_PIN = P0_28 // SDO
|
||||
|
||||
)
|
||||
|
||||
// USB CDC identifiers
|
||||
const (
|
||||
usb_STRING_PRODUCT = "bluemicro840"
|
||||
usb_STRING_MANUFACTURER = "BlueMicro"
|
||||
)
|
||||
|
||||
var (
|
||||
usb_VID uint16 = 0x1d50
|
||||
usb_PID uint16 = 0x6161
|
||||
)
|
||||
@@ -0,0 +1,117 @@
|
||||
//go:build m5stick_c
|
||||
|
||||
// This file contains the pin mapping for the M5STACK M5Stick-C device.
|
||||
// doc: https://docs.m5stack.com/en/core/m5stickc
|
||||
|
||||
package machine
|
||||
|
||||
const (
|
||||
// HAT | HY2.0-4P
|
||||
// |
|
||||
// GND | GND
|
||||
// 5V OUT | VOUT
|
||||
// G26 | G32 SDA
|
||||
// G36 | G33 SCL
|
||||
// G0 |
|
||||
// BAT |
|
||||
// 3V3 |
|
||||
// 5V IN |
|
||||
|
||||
IO0 = GPIO0 // CLK
|
||||
IO1 = GPIO1 // U0TXD
|
||||
IO2 = GPIO2
|
||||
IO3 = GPIO3 // U0RXD
|
||||
IO4 = GPIO4
|
||||
IO5 = GPIO5 // TFT_CS LCD_SS SPI0_SS
|
||||
IO6 = GPIO6
|
||||
IO7 = GPIO7
|
||||
IO8 = GPIO8
|
||||
IO9 = GPIO9 // IR
|
||||
IO10 = GPIO10 // LED
|
||||
IO11 = GPIO11
|
||||
IO12 = GPIO12
|
||||
IO13 = GPIO13 // TFT_CLK LCD_SCK SPI0_SCK
|
||||
IO14 = GPIO14
|
||||
IO15 = GPIO15 // TFT_MOSI LCD_MOSI SPI0_MOSI
|
||||
IO16 = GPIO16
|
||||
IO17 = GPIO17
|
||||
IO18 = GPIO18 // TFT_RST LCD_RST
|
||||
IO19 = GPIO19
|
||||
IO21 = GPIO21 // SDA0
|
||||
IO22 = GPIO22 // SCL0
|
||||
IO23 = GPIO23 // TFT_DC LCD_DC
|
||||
IO25 = GPIO25 // - DAC1
|
||||
IO26 = GPIO26 // HAT DAC2
|
||||
IO27 = GPIO27
|
||||
IO32 = GPIO32 // SDA1 / PIN 32 / RXD2
|
||||
IO33 = GPIO33 // SCL1 / PIN 33 / TXD2
|
||||
IO34 = GPIO34 // MIC_DATA
|
||||
IO35 = GPIO35 // IRQ0 ADC1
|
||||
IO36 = GPIO36 // HAT ADC2 LCD_MISO SPI0_MISO
|
||||
IO37 = GPIO37 // BUTTON_A, BUTTON_HOME, BUTTON
|
||||
IO38 = GPIO38
|
||||
IO39 = GPIO39 // BUTTON_B, BUTTON_RST
|
||||
)
|
||||
|
||||
const (
|
||||
// Buttons
|
||||
BUTTON_A = IO37
|
||||
BUTTON_B = IO39
|
||||
BUTTON_HOME = BUTTON_A
|
||||
BUTTON_RST = BUTTON_B
|
||||
BUTTON = BUTTON_A
|
||||
|
||||
// LED
|
||||
IR = IO9
|
||||
LED = IO10
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN = IO13
|
||||
SPI0_SDO_PIN = IO15
|
||||
SPI0_CS0_PIN = IO5
|
||||
|
||||
// LCD ()
|
||||
LCD_SCK_PIN = SPI0_SCK_PIN
|
||||
LCD_SDO_PIN = SPI0_SDO_PIN
|
||||
LCD_SS_PIN = SPI0_CS0_PIN
|
||||
LCD_DC_PIN = IO23
|
||||
LCD_RST_PIN = IO18
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
// Internal I2C (AXP192 / BM8563 / MPU6886)
|
||||
SDA0_PIN = IO21
|
||||
SCL0_PIN = IO22
|
||||
|
||||
// External I2C (GROOVE PORT)
|
||||
SDA1_PIN = IO32
|
||||
SCL1_PIN = IO33
|
||||
|
||||
SDA_PIN = SDA1_PIN
|
||||
SCL_PIN = SCL1_PIN
|
||||
)
|
||||
|
||||
// ADC pins
|
||||
const (
|
||||
ADC1 Pin = IO35
|
||||
ADC2 Pin = IO36
|
||||
)
|
||||
|
||||
// DAC pins
|
||||
const (
|
||||
DAC1 Pin = IO25
|
||||
DAC2 Pin = IO26
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
// UART0 (CP2104)
|
||||
UART0_TX_PIN = IO1
|
||||
UART0_RX_PIN = IO3
|
||||
|
||||
UART_TX_PIN = UART0_TX_PIN
|
||||
UART_RX_PIN = UART0_RX_PIN
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build pinetime_devkit0
|
||||
//go:build pinetime
|
||||
|
||||
package machine
|
||||
|
||||
@@ -17,13 +17,10 @@ const (
|
||||
LED = LED1
|
||||
)
|
||||
|
||||
var DefaultUART = UART0
|
||||
|
||||
// UART pins for PineTime. Note that RX is set to NoPin as RXD is not listed in
|
||||
// the PineTime schematic 1.0:
|
||||
// http://files.pine64.org/doc/PineTime/PineTime%20Port%20Assignment%20rev1.0.pdf
|
||||
// The PineTime doesn't have a UART output.
|
||||
// Additionally, leaving the UART on results in a pretty big current drain.
|
||||
const (
|
||||
UART_TX_PIN Pin = 11 // TP29 (TXD)
|
||||
UART_TX_PIN Pin = NoPin
|
||||
UART_RX_PIN Pin = NoPin
|
||||
)
|
||||
|
||||
@@ -96,3 +96,7 @@ var (
|
||||
usb_VID uint16 = 0x2886
|
||||
usb_PID uint16 = 0x802F
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultUART = UART1
|
||||
)
|
||||
|
||||
@@ -64,82 +64,3 @@ type BlockDevice interface {
|
||||
// EraseBlockSize to map addresses to blocks.
|
||||
EraseBlocks(start, len int64) error
|
||||
}
|
||||
|
||||
// FlashBuffer implements the ReadWriteCloser interface using the BlockDevice interface.
|
||||
type FlashBuffer struct {
|
||||
b BlockDevice
|
||||
|
||||
// start is actual address
|
||||
start uintptr
|
||||
|
||||
// offset is relative to start
|
||||
offset uintptr
|
||||
}
|
||||
|
||||
// OpenFlashBuffer opens a FlashBuffer.
|
||||
func OpenFlashBuffer(b BlockDevice, address uintptr) *FlashBuffer {
|
||||
return &FlashBuffer{b: b, start: address}
|
||||
}
|
||||
|
||||
// Read data from a FlashBuffer.
|
||||
func (fl *FlashBuffer) Read(p []byte) (n int, err error) {
|
||||
n, err = fl.b.ReadAt(p, int64(fl.offset))
|
||||
fl.offset += uintptr(n)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Write data to a FlashBuffer.
|
||||
func (fl *FlashBuffer) Write(p []byte) (n int, err error) {
|
||||
// any new pages needed?
|
||||
// NOTE probably will not work as expected if you try to write over page boundary
|
||||
// of pages with different sizes.
|
||||
pagesize := uintptr(fl.b.EraseBlockSize())
|
||||
|
||||
// calculate currentPageBlock relative to fl.start, meaning that
|
||||
// block 0 -> fl.start
|
||||
// block 1 -> fl.start + pagesize
|
||||
// block 2 -> fl.start + pagesize*2
|
||||
// ...
|
||||
currentPageBlock := (fl.start + fl.offset - FlashDataStart()) + (pagesize-1)/pagesize
|
||||
lastPageBlockNeeded := (fl.start + fl.offset + uintptr(len(p)) - FlashDataStart()) + (pagesize-1)/pagesize
|
||||
|
||||
// erase enough blocks to hold the data
|
||||
if err := fl.b.EraseBlocks(int64(currentPageBlock), int64(lastPageBlockNeeded-currentPageBlock)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// write the data
|
||||
for i := 0; i < len(p); i += int(pagesize) {
|
||||
var last int = i + int(pagesize)
|
||||
if i+int(pagesize) > len(p) {
|
||||
last = len(p)
|
||||
}
|
||||
|
||||
_, err := fl.b.WriteAt(p[i:last], int64(fl.offset))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
fl.offset += uintptr(len(p[i:last]))
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Close the FlashBuffer.
|
||||
func (fl *FlashBuffer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Seek implements io.Seeker interface, but with limitations.
|
||||
// You can only seek relative to the start.
|
||||
// Also, you cannot use seek before write operations, only read.
|
||||
func (fl *FlashBuffer) Seek(offset int64, whence int) (int64, error) {
|
||||
if whence != io.SeekStart {
|
||||
panic("you can only Seek relative to Start")
|
||||
}
|
||||
|
||||
fl.offset = uintptr(offset)
|
||||
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
@@ -23,6 +23,37 @@ var (
|
||||
errI2CSignalStopTimeout = errors.New("I2C timeout on signal stop")
|
||||
errI2CAckExpected = errors.New("I2C error: expected ACK not NACK")
|
||||
errI2CBusError = errors.New("I2C bus error")
|
||||
errI2COverflow = errors.New("I2C receive buffer overflow")
|
||||
errI2COverread = errors.New("I2C transmit buffer overflow")
|
||||
)
|
||||
|
||||
// I2CTargetEvent reflects events on the I2C bus
|
||||
type I2CTargetEvent uint8
|
||||
|
||||
const (
|
||||
// I2CReceive indicates target has received a message from the controller.
|
||||
I2CReceive I2CTargetEvent = iota
|
||||
|
||||
// I2CRequest indicates the controller is expecting a message from the target.
|
||||
I2CRequest
|
||||
|
||||
// I2CFinish indicates the controller has ended the transaction.
|
||||
//
|
||||
// I2C controllers can chain multiple receive/request messages without
|
||||
// relinquishing the bus by doing 'restarts'. I2CFinish indicates the
|
||||
// bus has been relinquished by an I2C 'stop'.
|
||||
I2CFinish
|
||||
)
|
||||
|
||||
// I2CMode determines if an I2C peripheral is in Controller or Target mode.
|
||||
type I2CMode int
|
||||
|
||||
const (
|
||||
// I2CModeController represents an I2C peripheral in controller mode.
|
||||
I2CModeController I2CMode = iota
|
||||
|
||||
// I2CModeTarget represents an I2C peripheral in target mode.
|
||||
I2CModeTarget
|
||||
)
|
||||
|
||||
// WriteRegister transmits first the register and then the data to the
|
||||
|
||||
@@ -1910,12 +1910,12 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error {
|
||||
|
||||
// pad data if needed so it is long enough for correct byte alignment on writes.
|
||||
func (f flashBlockDevice) pad(p []byte) []byte {
|
||||
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
|
||||
if paddingNeeded == 0 {
|
||||
overflow := int64(len(p)) % f.WriteBlockSize()
|
||||
if overflow == 0 {
|
||||
return p
|
||||
}
|
||||
|
||||
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
|
||||
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
|
||||
return append(p, padding...)
|
||||
}
|
||||
|
||||
|
||||
@@ -139,8 +139,9 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||
setup := usb.NewSetup(udd_ep_out_cache_buffer[0][:])
|
||||
|
||||
// Clear the Bank 0 ready flag on Control OUT
|
||||
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
|
||||
usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0]))))
|
||||
usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
|
||||
|
||||
ok := false
|
||||
if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD {
|
||||
@@ -344,15 +345,6 @@ func sendUSBPacket(ep uint32, data []byte, maxsize uint16) {
|
||||
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
|
||||
var b [cdcLineInfoSize]byte
|
||||
|
||||
// address
|
||||
usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0]))))
|
||||
|
||||
// set byte count to zero
|
||||
usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
|
||||
// set ready for next data
|
||||
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
|
||||
|
||||
// Wait until OUT transfer is ready.
|
||||
timeout := 300000
|
||||
for (getEPSTATUS(0) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
|
||||
|
||||
@@ -46,10 +46,14 @@ func init() {
|
||||
// Return the register and mask to enable a given GPIO pin. This can be used to
|
||||
// implement bit-banged drivers.
|
||||
func (p Pin) PortMaskSet() (*uint32, uint32) {
|
||||
// Note: using PORT_IOBUS for faster pin accesses.
|
||||
// The regular PORT registers appear to take around 4 clock cycles to store,
|
||||
// which is longer than the ws2812 driver expects. The IOBUS is is fast
|
||||
// enough to avoid this issue.
|
||||
if p < 32 {
|
||||
return &sam.PORT.OUTSET0.Reg, 1 << uint8(p)
|
||||
return &sam.PORT_IOBUS.OUTSET0.Reg, 1 << uint8(p)
|
||||
} else {
|
||||
return &sam.PORT.OUTSET1.Reg, 1 << uint8(p-32)
|
||||
return &sam.PORT_IOBUS.OUTSET1.Reg, 1 << uint8(p-32)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +61,9 @@ func (p Pin) PortMaskSet() (*uint32, uint32) {
|
||||
// implement bit-banged drivers.
|
||||
func (p Pin) PortMaskClear() (*uint32, uint32) {
|
||||
if p < 32 {
|
||||
return &sam.PORT.OUTCLR0.Reg, 1 << uint8(p)
|
||||
return &sam.PORT_IOBUS.OUTCLR0.Reg, 1 << uint8(p)
|
||||
} else {
|
||||
return &sam.PORT.OUTCLR1.Reg, 1 << uint8(p-32)
|
||||
return &sam.PORT_IOBUS.OUTCLR1.Reg, 1 << uint8(p-32)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -749,36 +749,10 @@ func (a ADC) Configure(config ADCConfig) {
|
||||
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_CTRLB) {
|
||||
} // wait for sync
|
||||
|
||||
adc.CTRLA.SetBits(sam.ADC_CTRLA_PRESCALER_DIV32 << sam.ADC_CTRLA_PRESCALER_Pos)
|
||||
var resolution uint32
|
||||
switch config.Resolution {
|
||||
case 8:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_8BIT
|
||||
case 10:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_10BIT
|
||||
case 12:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_12BIT
|
||||
case 16:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_16BIT
|
||||
default:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_12BIT
|
||||
}
|
||||
adc.CTRLB.SetBits(uint16(resolution << sam.ADC_CTRLB_RESSEL_Pos))
|
||||
adc.SAMPCTRL.Set(5) // sampling Time Length
|
||||
|
||||
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_SAMPCTRL) {
|
||||
} // wait for sync
|
||||
|
||||
// No Negative input (Internal Ground)
|
||||
adc.INPUTCTRL.Set(sam.ADC_INPUTCTRL_MUXNEG_GND << sam.ADC_INPUTCTRL_MUXNEG_Pos)
|
||||
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_INPUTCTRL) {
|
||||
} // wait for sync
|
||||
|
||||
// Averaging (see datasheet table in AVGCTRL register description)
|
||||
var resolution uint32 = sam.ADC_CTRLB_RESSEL_16BIT
|
||||
var samples uint32
|
||||
switch config.Samples {
|
||||
case 1:
|
||||
samples = sam.ADC_AVGCTRL_SAMPLENUM_1
|
||||
case 2:
|
||||
samples = sam.ADC_AVGCTRL_SAMPLENUM_2
|
||||
case 4:
|
||||
@@ -800,11 +774,39 @@ func (a ADC) Configure(config ADCConfig) {
|
||||
case 1024:
|
||||
samples = sam.ADC_AVGCTRL_SAMPLENUM_1024
|
||||
default: // 1 sample only (no oversampling nor averaging), adjusting result by 0
|
||||
// Resolutions less than 16 bits only make sense when sampling only
|
||||
// once. Resulting ADC values become erratic when using both
|
||||
// multi-sampling and less than 16 bits of resolution.
|
||||
samples = sam.ADC_AVGCTRL_SAMPLENUM_1
|
||||
switch config.Resolution {
|
||||
case 8:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_8BIT
|
||||
case 10:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_10BIT
|
||||
case 12:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_12BIT
|
||||
case 16:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_16BIT
|
||||
default:
|
||||
resolution = sam.ADC_CTRLB_RESSEL_12BIT
|
||||
}
|
||||
}
|
||||
|
||||
adc.AVGCTRL.Set(uint8(samples<<sam.ADC_AVGCTRL_SAMPLENUM_Pos) |
|
||||
(0 << sam.ADC_AVGCTRL_ADJRES_Pos))
|
||||
|
||||
adc.CTRLA.SetBits(sam.ADC_CTRLA_PRESCALER_DIV32 << sam.ADC_CTRLA_PRESCALER_Pos)
|
||||
adc.CTRLB.SetBits(uint16(resolution << sam.ADC_CTRLB_RESSEL_Pos))
|
||||
adc.SAMPCTRL.Set(5) // sampling Time Length
|
||||
|
||||
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_SAMPCTRL) {
|
||||
} // wait for sync
|
||||
|
||||
// No Negative input (Internal Ground)
|
||||
adc.INPUTCTRL.Set(sam.ADC_INPUTCTRL_MUXNEG_GND << sam.ADC_INPUTCTRL_MUXNEG_Pos)
|
||||
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_INPUTCTRL) {
|
||||
} // wait for sync
|
||||
|
||||
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_AVGCTRL) {
|
||||
} // wait for sync
|
||||
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_REFCTRL) {
|
||||
@@ -871,10 +873,24 @@ func (a ADC) Get() uint16 {
|
||||
val = val << 8
|
||||
case sam.ADC_CTRLB_RESSEL_10BIT:
|
||||
val = val << 6
|
||||
case sam.ADC_CTRLB_RESSEL_16BIT:
|
||||
val = val << 4
|
||||
case sam.ADC_CTRLB_RESSEL_12BIT:
|
||||
val = val << 4
|
||||
case sam.ADC_CTRLB_RESSEL_16BIT:
|
||||
// Adjust for multiple samples. This is only configured when the
|
||||
// resolution is 16 bits.
|
||||
switch (bus.AVGCTRL.Get() & sam.ADC_AVGCTRL_SAMPLENUM_Msk) >> sam.ADC_AVGCTRL_SAMPLENUM_Pos {
|
||||
case sam.ADC_AVGCTRL_SAMPLENUM_1:
|
||||
val <<= 4
|
||||
case sam.ADC_AVGCTRL_SAMPLENUM_2:
|
||||
val <<= 3
|
||||
case sam.ADC_AVGCTRL_SAMPLENUM_4:
|
||||
val <<= 2
|
||||
case sam.ADC_AVGCTRL_SAMPLENUM_8:
|
||||
val <<= 1
|
||||
default:
|
||||
// These values are all shifted by the hardware so they fit exactly
|
||||
// in a 16-bit integer, so they don't need to be shifted here.
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
@@ -1490,22 +1506,39 @@ func (spi SPI) Configure(config SPIConfig) error {
|
||||
spi.Bus.CTRLA.ClearBits(sam.SERCOM_SPIM_CTRLA_CPOL)
|
||||
}
|
||||
|
||||
// set clock
|
||||
freqRef := uint32(0)
|
||||
if config.Frequency > SERCOM_FREQ_REF/2 {
|
||||
setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK0)
|
||||
freqRef = uint32(SERCOM_FREQ_REF_GCLK0)
|
||||
} else {
|
||||
setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1)
|
||||
freqRef = uint32(SERCOM_FREQ_REF)
|
||||
}
|
||||
// Set the clock frequency.
|
||||
// There are two clocks we can use GCLK0 (120MHz) and GCLK1 (48MHz).
|
||||
// We can use any even divisor for these clock, which means:
|
||||
// - for GCLK0 we can make 60MHz, 30MHz, 20MHz, 15MHz, 12MHz, 10MHz, etc
|
||||
// - for GCLK1 we can make 24MHz, 12MHz, 8MHz, 6MHz, 4.8MHz, 4MHz, etc
|
||||
// This means that by trying both clocks, we can have a wider selection of
|
||||
// available SPI clock frequencies.
|
||||
|
||||
// Set synch speed for SPI
|
||||
baudRate := freqRef / (2 * config.Frequency)
|
||||
if baudRate > 0 {
|
||||
baudRate--
|
||||
// Calculate the baudrate if we would use GCLK1 (48MHz), and the resulting
|
||||
// frequency. The baud rate is rounded up, so that the resulting frequency
|
||||
// is rounded down from the maximum value (meaning it will always be smaller
|
||||
// than or equal to config.Frequency).
|
||||
baudRateGCLK1 := (SERCOM_FREQ_REF/2 + config.Frequency - 1) / config.Frequency
|
||||
freqGCLK1 := SERCOM_FREQ_REF / 2 / baudRateGCLK1
|
||||
|
||||
// Same for GCLK0 (120MHz).
|
||||
baudRateGCLK0 := (SERCOM_FREQ_REF_GCLK0/2 + config.Frequency - 1) / config.Frequency
|
||||
freqGCLK0 := SERCOM_FREQ_REF_GCLK0 / 2 / baudRateGCLK0
|
||||
|
||||
// Pick the clock source that is the closest to the maximum baud rate.
|
||||
// Note: there may be reasons to prefer the lower frequency clock (like
|
||||
// power consumption). If that's the case, we might want to always use the
|
||||
// 48MHz clock at low frequencies (below 4MHz or so).
|
||||
if freqGCLK0 > freqGCLK1 && uint32(uint8(baudRateGCLK0-1))+1 == baudRateGCLK0 {
|
||||
// Pick this 120MHz clock if it results in a better frequency after
|
||||
// division, and the baudRate value fits in the BAUD register.
|
||||
setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK0)
|
||||
spi.Bus.BAUD.Set(uint8(baudRateGCLK0 - 1))
|
||||
} else {
|
||||
// Use the 48MHz clock in other cases.
|
||||
setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1)
|
||||
spi.Bus.BAUD.Set(uint8(baudRateGCLK1 - 1))
|
||||
}
|
||||
spi.Bus.BAUD.Set(uint8(baudRate))
|
||||
|
||||
// Enable SPI port.
|
||||
spi.Bus.CTRLA.SetBits(sam.SERCOM_SPIM_CTRLA_ENABLE)
|
||||
@@ -2206,12 +2239,12 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error {
|
||||
|
||||
// pad data if needed so it is long enough for correct byte alignment on writes.
|
||||
func (f flashBlockDevice) pad(p []byte) []byte {
|
||||
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
|
||||
if paddingNeeded == 0 {
|
||||
overflow := int64(len(p)) % f.WriteBlockSize()
|
||||
if overflow == 0 {
|
||||
return p
|
||||
}
|
||||
|
||||
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
|
||||
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
|
||||
return append(p, padding...)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
//go:build attiny1616
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/avr"
|
||||
)
|
||||
|
||||
const (
|
||||
portA Pin = iota * 8
|
||||
portB
|
||||
portC
|
||||
)
|
||||
|
||||
const (
|
||||
PA0 = portA + 0
|
||||
PA1 = portA + 1
|
||||
PA2 = portA + 2
|
||||
PA3 = portA + 3
|
||||
PA4 = portA + 4
|
||||
PA5 = portA + 5
|
||||
PA6 = portA + 6
|
||||
PA7 = portA + 7
|
||||
PB0 = portB + 0
|
||||
PB1 = portB + 1
|
||||
PB2 = portB + 2
|
||||
PB3 = portB + 3
|
||||
PB4 = portB + 4
|
||||
PB5 = portB + 5
|
||||
PB6 = portB + 6
|
||||
PB7 = portB + 7
|
||||
PC0 = portC + 0
|
||||
PC1 = portC + 1
|
||||
PC2 = portC + 2
|
||||
PC3 = portC + 3
|
||||
PC4 = portC + 4
|
||||
PC5 = portC + 5
|
||||
PC6 = portC + 6
|
||||
PC7 = portC + 7
|
||||
)
|
||||
|
||||
// getPortMask returns the PORT peripheral and mask for the pin.
|
||||
func (p Pin) getPortMask() (*avr.PORT_Type, uint8) {
|
||||
switch {
|
||||
case p >= PA0 && p <= PA7: // port A
|
||||
return avr.PORTA, 1 << uint8(p-portA)
|
||||
case p >= PB0 && p <= PB7: // port B
|
||||
return avr.PORTB, 1 << uint8(p-portB)
|
||||
default: // port C
|
||||
return avr.PORTC, 1 << uint8(p-portC)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build avr
|
||||
//go:build avr && !avrtiny
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//go:build avrtiny
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/avr"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const deviceName = avr.DEVICE
|
||||
|
||||
const (
|
||||
PinInput PinMode = iota
|
||||
PinInputPullup
|
||||
PinOutput
|
||||
)
|
||||
|
||||
// Configure sets the pin to input or output.
|
||||
func (p Pin) Configure(config PinConfig) {
|
||||
port, mask := p.getPortMask()
|
||||
|
||||
if config.Mode == PinOutput {
|
||||
// set output bit
|
||||
port.DIRSET.Set(mask)
|
||||
|
||||
// Note: the output state (high or low) is as it was before.
|
||||
} else {
|
||||
// Configure the pin as an input.
|
||||
// First set up the configuration that will be used when it is an input.
|
||||
pinctrl := uint8(0)
|
||||
if config.Mode == PinInputPullup {
|
||||
pinctrl |= avr.PORT_PIN0CTRL_PULLUPEN
|
||||
}
|
||||
// Find the PINxCTRL register for this pin.
|
||||
ctrlAddress := (*volatile.Register8)(unsafe.Add(unsafe.Pointer(&port.PIN0CTRL), p%8))
|
||||
ctrlAddress.Set(pinctrl)
|
||||
|
||||
// Configure the pin as input (if it wasn't an input pin before).
|
||||
port.DIRCLR.Set(mask)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin when the pin is configured as an
|
||||
// input or as an output.
|
||||
func (p Pin) Get() bool {
|
||||
port, mask := p.getPortMask()
|
||||
// As noted above, the PINx register is always two registers below the PORTx
|
||||
// register, so we can find it simply by subtracting two from the PORTx
|
||||
// register address.
|
||||
return (port.IN.Get() & mask) > 0
|
||||
}
|
||||
|
||||
// Set changes the value of the GPIO pin. The pin must be configured as output.
|
||||
func (p Pin) Set(high bool) {
|
||||
port, mask := p.getPortMask()
|
||||
if high {
|
||||
port.OUTSET.Set(mask)
|
||||
} else {
|
||||
port.OUTCLR.Set(mask)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//go:build cortexm
|
||||
|
||||
package machine
|
||||
|
||||
import "device/arm"
|
||||
|
||||
// CPUReset performs a hard system reset.
|
||||
func CPUReset() {
|
||||
arm.SystemReset()
|
||||
}
|
||||
@@ -38,27 +38,27 @@ func (p Pin) Set(value bool) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
var Display = FramebufDisplay{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))}
|
||||
var Display = DisplayMode3{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))}
|
||||
|
||||
type FramebufDisplay struct {
|
||||
type DisplayMode3 struct {
|
||||
port *[160][240]volatile.Register16
|
||||
}
|
||||
|
||||
func (d FramebufDisplay) Configure() {
|
||||
func (d *DisplayMode3) Configure() {
|
||||
// Use video mode 3 (in BG2, a 16bpp bitmap in VRAM) and Enable BG2
|
||||
gba.DISP.DISPCNT.Set(gba.DISPCNT_BGMODE_3<<gba.DISPCNT_BGMODE_Pos |
|
||||
gba.DISPCNT_SCREENDISPLAY_BG2_ENABLE<<gba.DISPCNT_SCREENDISPLAY_BG2_Pos)
|
||||
}
|
||||
|
||||
func (d FramebufDisplay) Size() (x, y int16) {
|
||||
func (d *DisplayMode3) Size() (x, y int16) {
|
||||
return 240, 160
|
||||
}
|
||||
|
||||
func (d FramebufDisplay) SetPixel(x, y int16, c color.RGBA) {
|
||||
func (d *DisplayMode3) SetPixel(x, y int16, c color.RGBA) {
|
||||
d.port[y][x].Set((uint16(c.R) >> 3) | ((uint16(c.G) >> 3) << 5) | ((uint16(c.B) >> 3) << 10))
|
||||
}
|
||||
|
||||
func (d FramebufDisplay) Display() error {
|
||||
func (d *DisplayMode3) Display() error {
|
||||
// Nothing to do here.
|
||||
return nil
|
||||
}
|
||||
|
||||
+18
-99
@@ -201,28 +201,18 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
|
||||
}
|
||||
}
|
||||
|
||||
// I2C on the NRF.
|
||||
type I2C struct {
|
||||
Bus nrf.TWI_Type
|
||||
}
|
||||
|
||||
// There are 2 I2C interfaces on the NRF.
|
||||
var (
|
||||
I2C0 = (*I2C)(unsafe.Pointer(nrf.TWI0))
|
||||
I2C1 = (*I2C)(unsafe.Pointer(nrf.TWI1))
|
||||
)
|
||||
|
||||
// I2CConfig is used to store config info for I2C.
|
||||
type I2CConfig struct {
|
||||
Frequency uint32
|
||||
SCL Pin
|
||||
SDA Pin
|
||||
Mode I2CMode
|
||||
}
|
||||
|
||||
// Configure is intended to setup the I2C interface.
|
||||
func (i2c *I2C) Configure(config I2CConfig) error {
|
||||
|
||||
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Disabled)
|
||||
i2c.disable()
|
||||
|
||||
// Default I2C bus speed is 100 kHz.
|
||||
if config.Frequency == 0 {
|
||||
@@ -249,71 +239,25 @@ func (i2c *I2C) Configure(config I2CConfig) error {
|
||||
(nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) |
|
||||
(nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos))
|
||||
|
||||
if config.Frequency >= 400*KHz {
|
||||
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K400)
|
||||
} else {
|
||||
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100)
|
||||
}
|
||||
|
||||
i2c.setPins(config.SCL, config.SDA)
|
||||
|
||||
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled)
|
||||
i2c.mode = config.Mode
|
||||
|
||||
if i2c.mode == I2CModeController {
|
||||
if config.Frequency >= 400*KHz {
|
||||
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K400)
|
||||
} else {
|
||||
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100)
|
||||
}
|
||||
|
||||
i2c.enableAsController()
|
||||
} else {
|
||||
i2c.enableAsTarget()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tx does a single I2C transaction at the specified address.
|
||||
// It clocks out the given address, writes the bytes in w, reads back len(r)
|
||||
// bytes and stores them in r, and generates a stop condition on the bus.
|
||||
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
|
||||
|
||||
// Tricky stop condition.
|
||||
// After reads, the stop condition is generated implicitly with a shortcut.
|
||||
// After writes not followed by reads and in the case of errors, stop must be generated explicitly.
|
||||
|
||||
i2c.Bus.ADDRESS.Set(uint32(addr))
|
||||
|
||||
if len(w) != 0 {
|
||||
i2c.Bus.TASKS_STARTTX.Set(1) // start transmission for writing
|
||||
for _, b := range w {
|
||||
if err = i2c.writeByte(b); err != nil {
|
||||
i2c.signalStop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r) != 0 {
|
||||
// To trigger suspend task when a byte is received
|
||||
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND)
|
||||
i2c.Bus.TASKS_STARTRX.Set(1) // re-start transmission for reading
|
||||
for i := range r { // read each char
|
||||
if i+1 == len(r) {
|
||||
// To trigger stop task when last byte is received, set before resume task.
|
||||
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_STOP)
|
||||
}
|
||||
if i > 0 {
|
||||
i2c.Bus.TASKS_RESUME.Set(1) // re-start transmission for reading
|
||||
}
|
||||
if r[i], err = i2c.readByte(); err != nil {
|
||||
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
|
||||
i2c.signalStop()
|
||||
return
|
||||
}
|
||||
}
|
||||
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
|
||||
}
|
||||
|
||||
// Stop explicitly when no reads were executed, stoping unconditionally would be a mistake.
|
||||
// It may execute after I2C peripheral has already been stopped by the shortcut in the read block,
|
||||
// so stop task will trigger first thing in a subsequent transaction, hanging it.
|
||||
if len(r) == 0 {
|
||||
i2c.signalStop()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// signalStop sends a stop signal to the I2C peripheral and waits for confirmation.
|
||||
func (i2c *I2C) signalStop() {
|
||||
i2c.Bus.TASKS_STOP.Set(1)
|
||||
@@ -322,31 +266,6 @@ func (i2c *I2C) signalStop() {
|
||||
i2c.Bus.EVENTS_STOPPED.Set(0)
|
||||
}
|
||||
|
||||
// writeByte writes a single byte to the I2C bus and waits for confirmation.
|
||||
func (i2c *I2C) writeByte(data byte) error {
|
||||
i2c.Bus.TXD.Set(uint32(data))
|
||||
for i2c.Bus.EVENTS_TXDSENT.Get() == 0 {
|
||||
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
|
||||
i2c.Bus.EVENTS_ERROR.Set(0)
|
||||
return errI2CBusError
|
||||
}
|
||||
}
|
||||
i2c.Bus.EVENTS_TXDSENT.Set(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// readByte reads a single byte from the I2C bus when it is ready.
|
||||
func (i2c *I2C) readByte() (byte, error) {
|
||||
for i2c.Bus.EVENTS_RXDREADY.Get() == 0 {
|
||||
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
|
||||
i2c.Bus.EVENTS_ERROR.Set(0)
|
||||
return 0, errI2CBusError
|
||||
}
|
||||
}
|
||||
i2c.Bus.EVENTS_RXDREADY.Set(0)
|
||||
return byte(i2c.Bus.RXD.Get()), nil
|
||||
}
|
||||
|
||||
var rngStarted = false
|
||||
|
||||
// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise.
|
||||
@@ -477,12 +396,12 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error {
|
||||
|
||||
// pad data if needed so it is long enough for correct byte alignment on writes.
|
||||
func (f flashBlockDevice) pad(p []byte) []byte {
|
||||
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
|
||||
if paddingNeeded == 0 {
|
||||
overflow := int64(len(p)) % f.WriteBlockSize()
|
||||
if overflow == 0 {
|
||||
return p
|
||||
}
|
||||
|
||||
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
|
||||
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
|
||||
return append(p, padding...)
|
||||
}
|
||||
|
||||
|
||||
+172
-419
@@ -1,463 +1,216 @@
|
||||
//go:build nrf52 || nrf52840 || nrf52833
|
||||
//go:build nrf52840 || nrf52833
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/nrf"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func CPUFrequency() uint32 {
|
||||
return 64000000
|
||||
// I2C on the NRF528xx.
|
||||
type I2C struct {
|
||||
Bus *nrf.TWIM_Type // Called Bus to align with Bus field in nrf51
|
||||
BusT *nrf.TWIS_Type
|
||||
mode I2CMode
|
||||
}
|
||||
|
||||
// InitADC initializes the registers needed for ADC.
|
||||
func InitADC() {
|
||||
return // no specific setup on nrf52 machine.
|
||||
}
|
||||
|
||||
// Configure configures an ADC pin to be able to read analog data.
|
||||
func (a ADC) Configure(ADCConfig) {
|
||||
return // no pin specific setup on nrf52 machine.
|
||||
}
|
||||
|
||||
// Get returns the current value of a ADC pin in the range 0..0xffff.
|
||||
func (a ADC) Get() uint16 {
|
||||
var pwmPin uint32
|
||||
var rawValue volatile.Register16
|
||||
|
||||
switch a.Pin {
|
||||
case 2:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0
|
||||
|
||||
case 3:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1
|
||||
|
||||
case 4:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2
|
||||
|
||||
case 5:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3
|
||||
|
||||
case 28:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4
|
||||
|
||||
case 29:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5
|
||||
|
||||
case 30:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6
|
||||
|
||||
case 31:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7
|
||||
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
nrf.SAADC.RESOLUTION.Set(nrf.SAADC_RESOLUTION_VAL_12bit)
|
||||
|
||||
// Enable ADC.
|
||||
nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos)
|
||||
for i := 0; i < 8; i++ {
|
||||
nrf.SAADC.CH[i].PSELN.Set(nrf.SAADC_CH_PSELP_PSELP_NC)
|
||||
nrf.SAADC.CH[i].PSELP.Set(nrf.SAADC_CH_PSELP_PSELP_NC)
|
||||
}
|
||||
|
||||
// Configure ADC.
|
||||
nrf.SAADC.CH[0].CONFIG.Set(((nrf.SAADC_CH_CONFIG_RESP_Bypass << nrf.SAADC_CH_CONFIG_RESP_Pos) & nrf.SAADC_CH_CONFIG_RESP_Msk) |
|
||||
((nrf.SAADC_CH_CONFIG_RESP_Bypass << nrf.SAADC_CH_CONFIG_RESN_Pos) & nrf.SAADC_CH_CONFIG_RESN_Msk) |
|
||||
((nrf.SAADC_CH_CONFIG_GAIN_Gain1_5 << nrf.SAADC_CH_CONFIG_GAIN_Pos) & nrf.SAADC_CH_CONFIG_GAIN_Msk) |
|
||||
((nrf.SAADC_CH_CONFIG_REFSEL_Internal << nrf.SAADC_CH_CONFIG_REFSEL_Pos) & nrf.SAADC_CH_CONFIG_REFSEL_Msk) |
|
||||
((nrf.SAADC_CH_CONFIG_TACQ_3us << nrf.SAADC_CH_CONFIG_TACQ_Pos) & nrf.SAADC_CH_CONFIG_TACQ_Msk) |
|
||||
((nrf.SAADC_CH_CONFIG_MODE_SE << nrf.SAADC_CH_CONFIG_MODE_Pos) & nrf.SAADC_CH_CONFIG_MODE_Msk))
|
||||
|
||||
// Set pin to read.
|
||||
nrf.SAADC.CH[0].PSELN.Set(pwmPin)
|
||||
nrf.SAADC.CH[0].PSELP.Set(pwmPin)
|
||||
|
||||
// Destination for sample result.
|
||||
nrf.SAADC.RESULT.PTR.Set(uint32(uintptr(unsafe.Pointer(&rawValue))))
|
||||
nrf.SAADC.RESULT.MAXCNT.Set(1) // One sample
|
||||
|
||||
// Start tasks.
|
||||
nrf.SAADC.TASKS_START.Set(1)
|
||||
for nrf.SAADC.EVENTS_STARTED.Get() == 0 {
|
||||
}
|
||||
nrf.SAADC.EVENTS_STARTED.Set(0x00)
|
||||
|
||||
// Start the sample task.
|
||||
nrf.SAADC.TASKS_SAMPLE.Set(1)
|
||||
|
||||
// Wait until the sample task is done.
|
||||
for nrf.SAADC.EVENTS_END.Get() == 0 {
|
||||
}
|
||||
nrf.SAADC.EVENTS_END.Set(0x00)
|
||||
|
||||
// Stop the ADC
|
||||
nrf.SAADC.TASKS_STOP.Set(1)
|
||||
for nrf.SAADC.EVENTS_STOPPED.Get() == 0 {
|
||||
}
|
||||
nrf.SAADC.EVENTS_STOPPED.Set(0)
|
||||
|
||||
// Disable the ADC.
|
||||
nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Disabled << nrf.SAADC_ENABLE_ENABLE_Pos)
|
||||
|
||||
value := int16(rawValue.Get())
|
||||
if value < 0 {
|
||||
value = 0
|
||||
}
|
||||
|
||||
// Return 16-bit result from 12-bit value.
|
||||
return uint16(value << 4)
|
||||
}
|
||||
|
||||
// SPI on the NRF.
|
||||
type SPI struct {
|
||||
Bus *nrf.SPIM_Type
|
||||
buf *[1]byte // 1-byte buffer for the Transfer method
|
||||
}
|
||||
|
||||
// There are 3 SPI interfaces on the NRF528xx.
|
||||
// There are 2 I2C interfaces on the NRF.
|
||||
var (
|
||||
SPI0 = SPI{Bus: nrf.SPIM0, buf: new([1]byte)}
|
||||
SPI1 = SPI{Bus: nrf.SPIM1, buf: new([1]byte)}
|
||||
SPI2 = SPI{Bus: nrf.SPIM2, buf: new([1]byte)}
|
||||
I2C0 = &I2C{Bus: nrf.TWIM0, BusT: nrf.TWIS0}
|
||||
I2C1 = &I2C{Bus: nrf.TWIM1, BusT: nrf.TWIS1}
|
||||
)
|
||||
|
||||
// SPIConfig is used to store config info for SPI.
|
||||
type SPIConfig struct {
|
||||
Frequency uint32
|
||||
SCK Pin
|
||||
SDO Pin
|
||||
SDI Pin
|
||||
LSBFirst bool
|
||||
Mode uint8
|
||||
func (i2c *I2C) enableAsController() {
|
||||
i2c.Bus.ENABLE.Set(nrf.TWIM_ENABLE_ENABLE_Enabled)
|
||||
}
|
||||
|
||||
// Configure is intended to setup the SPI interface.
|
||||
func (spi SPI) Configure(config SPIConfig) {
|
||||
// Disable bus to configure it
|
||||
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled)
|
||||
|
||||
// Pick a default frequency.
|
||||
if config.Frequency == 0 {
|
||||
config.Frequency = 4000000 // 4MHz
|
||||
}
|
||||
|
||||
// set frequency
|
||||
var freq uint32
|
||||
switch {
|
||||
case config.Frequency >= 8000000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M8
|
||||
case config.Frequency >= 4000000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M4
|
||||
case config.Frequency >= 2000000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M2
|
||||
case config.Frequency >= 1000000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M1
|
||||
case config.Frequency >= 500000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K500
|
||||
case config.Frequency >= 250000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K250
|
||||
default: // below 250kHz, default to the lowest speed available
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K125
|
||||
}
|
||||
spi.Bus.FREQUENCY.Set(freq)
|
||||
|
||||
var conf uint32
|
||||
|
||||
// set bit transfer order
|
||||
if config.LSBFirst {
|
||||
conf = (nrf.SPIM_CONFIG_ORDER_LsbFirst << nrf.SPIM_CONFIG_ORDER_Pos)
|
||||
}
|
||||
|
||||
// set mode
|
||||
switch config.Mode {
|
||||
case 0:
|
||||
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
case 1:
|
||||
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
case 2:
|
||||
conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
case 3:
|
||||
conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
default: // to mode
|
||||
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
}
|
||||
spi.Bus.CONFIG.Set(conf)
|
||||
|
||||
// set pins
|
||||
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
|
||||
config.SCK = SPI0_SCK_PIN
|
||||
config.SDO = SPI0_SDO_PIN
|
||||
config.SDI = SPI0_SDI_PIN
|
||||
}
|
||||
spi.Bus.PSEL.SCK.Set(uint32(config.SCK))
|
||||
spi.Bus.PSEL.MOSI.Set(uint32(config.SDO))
|
||||
spi.Bus.PSEL.MISO.Set(uint32(config.SDI))
|
||||
|
||||
// Re-enable bus now that it is configured.
|
||||
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled)
|
||||
func (i2c *I2C) enableAsTarget() {
|
||||
i2c.BusT.ENABLE.Set(nrf.TWIS_ENABLE_ENABLE_Enabled)
|
||||
}
|
||||
|
||||
// Transfer writes/reads a single byte using the SPI interface.
|
||||
func (spi SPI) Transfer(w byte) (byte, error) {
|
||||
buf := spi.buf[:]
|
||||
buf[0] = w
|
||||
err := spi.Tx(buf[:], buf[:])
|
||||
return buf[0], err
|
||||
func (i2c *I2C) disable() {
|
||||
i2c.Bus.ENABLE.Set(0)
|
||||
}
|
||||
|
||||
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous
|
||||
// write/read interface, there must always be the same number of bytes written
|
||||
// as bytes read. Therefore, if the number of bytes don't match it will be
|
||||
// padded until they fit: if len(w) > len(r) the extra bytes received will be
|
||||
// dropped and if len(w) < len(r) extra 0 bytes will be sent.
|
||||
func (spi SPI) Tx(w, r []byte) error {
|
||||
// Unfortunately the hardware (on the nrf52832) only supports up to 255
|
||||
// bytes in the buffers, so if either w or r is longer than that the
|
||||
// transfer needs to be broken up in pieces.
|
||||
// The nrf52840 supports far larger buffers however, which isn't yet
|
||||
// supported.
|
||||
for len(r) != 0 || len(w) != 0 {
|
||||
// Prepare the SPI transfer: set the DMA pointers and lengths.
|
||||
if len(r) != 0 {
|
||||
spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0]))))
|
||||
n := uint32(len(r))
|
||||
if n > 255 {
|
||||
n = 255
|
||||
// Tx does a single I2C transaction at the specified address (when in controller mode).
|
||||
//
|
||||
// It clocks out the given address, writes the bytes in w, reads back len(r)
|
||||
// bytes and stores them in r, and generates a stop condition on the bus.
|
||||
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
|
||||
i2c.Bus.ADDRESS.Set(uint32(addr))
|
||||
|
||||
i2c.Bus.EVENTS_STOPPED.Set(0)
|
||||
i2c.Bus.EVENTS_ERROR.Set(0)
|
||||
i2c.Bus.EVENTS_RXSTARTED.Set(0)
|
||||
i2c.Bus.EVENTS_TXSTARTED.Set(0)
|
||||
i2c.Bus.EVENTS_LASTRX.Set(0)
|
||||
i2c.Bus.EVENTS_LASTTX.Set(0)
|
||||
i2c.Bus.EVENTS_SUSPENDED.Set(0)
|
||||
|
||||
// Configure for a single shot to perform both write and read (as applicable)
|
||||
if len(w) != 0 {
|
||||
i2c.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0]))))
|
||||
i2c.Bus.TXD.MAXCNT.Set(uint32(len(w)))
|
||||
|
||||
// If no read, immediately signal stop after TX
|
||||
if len(r) == 0 {
|
||||
i2c.Bus.SHORTS.Set(nrf.TWIM_SHORTS_LASTTX_STOP)
|
||||
}
|
||||
}
|
||||
if len(r) != 0 {
|
||||
i2c.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0]))))
|
||||
i2c.Bus.RXD.MAXCNT.Set(uint32(len(r)))
|
||||
|
||||
// Auto-start Rx after Tx and Stop after Rx
|
||||
i2c.Bus.SHORTS.Set(nrf.TWIM_SHORTS_LASTTX_STARTRX | nrf.TWIM_SHORTS_LASTRX_STOP)
|
||||
}
|
||||
|
||||
// Fire the transaction
|
||||
i2c.Bus.TASKS_RESUME.Set(1)
|
||||
if len(w) != 0 {
|
||||
i2c.Bus.TASKS_STARTTX.Set(1)
|
||||
} else if len(r) != 0 {
|
||||
i2c.Bus.TASKS_STARTRX.Set(1)
|
||||
}
|
||||
|
||||
// Wait until transaction stopped to ensure buffers fully processed
|
||||
for i2c.Bus.EVENTS_STOPPED.Get() == 0 {
|
||||
// Allow scheduler to run
|
||||
gosched()
|
||||
|
||||
// Handle errors by ensuring STOP sent on bus
|
||||
if i2c.Bus.EVENTS_ERROR.Get() != 0 {
|
||||
if i2c.Bus.EVENTS_STOPPED.Get() == 0 {
|
||||
// STOP cannot be sent during SUSPEND
|
||||
i2c.Bus.TASKS_RESUME.Set(1)
|
||||
i2c.Bus.TASKS_STOP.Set(1)
|
||||
}
|
||||
spi.Bus.RXD.MAXCNT.Set(n)
|
||||
r = r[n:]
|
||||
err = twiCError(i2c.Bus.ERRORSRC.Get())
|
||||
}
|
||||
if len(w) != 0 {
|
||||
spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0]))))
|
||||
n := uint32(len(w))
|
||||
if n > 255 {
|
||||
n = 255
|
||||
}
|
||||
spi.Bus.TXD.MAXCNT.Set(n)
|
||||
w = w[n:]
|
||||
}
|
||||
|
||||
// Do the transfer.
|
||||
// Note: this can be improved by not waiting until the transfer is
|
||||
// finished if the transfer is send-only (a common case).
|
||||
spi.Bus.TASKS_START.Set(1)
|
||||
for spi.Bus.EVENTS_END.Get() == 0 {
|
||||
}
|
||||
spi.Bus.EVENTS_END.Set(0)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Listen starts listening for I2C requests sent to specified address
|
||||
//
|
||||
// addr is the address to listen to
|
||||
func (i2c *I2C) Listen(addr uint8) error {
|
||||
i2c.BusT.ADDRESS[0].Set(uint32(addr))
|
||||
i2c.BusT.CONFIG.Set(nrf.TWIS_CONFIG_ADDRESS0_Enabled)
|
||||
|
||||
i2c.BusT.EVENTS_STOPPED.Set(0)
|
||||
i2c.BusT.EVENTS_ERROR.Set(0)
|
||||
i2c.BusT.EVENTS_RXSTARTED.Set(0)
|
||||
i2c.BusT.EVENTS_TXSTARTED.Set(0)
|
||||
i2c.BusT.EVENTS_WRITE.Set(0)
|
||||
i2c.BusT.EVENTS_READ.Set(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PWM is one PWM peripheral, which consists of a counter and multiple output
|
||||
// channels (that can be connected to actual pins). You can set the frequency
|
||||
// using SetPeriod, but only for all the channels in this PWM peripheral at
|
||||
// once.
|
||||
type PWM struct {
|
||||
PWM *nrf.PWM_Type
|
||||
// WaitForEvent blocks the current go-routine until an I2C event is received (when in Target mode).
|
||||
//
|
||||
// The passed buffer will be populated for receive events, with the number of bytes
|
||||
// received returned in count. For other event types, buf is not modified and a count
|
||||
// of zero is returned.
|
||||
//
|
||||
// For request events, the caller MUST call `Reply` to avoid hanging the i2c bus indefinitely.
|
||||
func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) {
|
||||
i2c.BusT.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0]))))
|
||||
i2c.BusT.RXD.MAXCNT.Set(uint32(len(buf)))
|
||||
|
||||
channelValues [4]volatile.Register16
|
||||
}
|
||||
i2c.BusT.TASKS_PREPARERX.Set(nrf.TWIS_TASKS_PREPARERX_TASKS_PREPARERX_Trigger)
|
||||
|
||||
// Configure enables and configures this PWM.
|
||||
// On the nRF52 series, the maximum period is around 0.26s.
|
||||
func (pwm *PWM) Configure(config PWMConfig) error {
|
||||
// Enable the peripheral.
|
||||
pwm.PWM.ENABLE.Set(nrf.PWM_ENABLE_ENABLE_Enabled << nrf.PWM_ENABLE_ENABLE_Pos)
|
||||
i2c.Bus.TASKS_RESUME.Set(1)
|
||||
|
||||
// Use up counting only. TODO: allow configuring as up-and-down.
|
||||
pwm.PWM.MODE.Set(nrf.PWM_MODE_UPDOWN_Up << nrf.PWM_MODE_UPDOWN_Pos)
|
||||
for i2c.BusT.EVENTS_STOPPED.Get() == 0 &&
|
||||
i2c.BusT.EVENTS_READ.Get() == 0 {
|
||||
gosched()
|
||||
|
||||
// Indicate there are four channels that each have a different value.
|
||||
pwm.PWM.DECODER.Set(nrf.PWM_DECODER_LOAD_Individual<<nrf.PWM_DECODER_LOAD_Pos | nrf.PWM_DECODER_MODE_RefreshCount<<nrf.PWM_DECODER_MODE_Pos)
|
||||
|
||||
err := pwm.setPeriod(config.Period, true)
|
||||
if err != nil {
|
||||
return err
|
||||
if i2c.BusT.EVENTS_ERROR.Get() != 0 {
|
||||
i2c.BusT.EVENTS_ERROR.Set(0)
|
||||
return I2CReceive, 0, twisError(i2c.BusT.ERRORSRC.Get())
|
||||
}
|
||||
}
|
||||
|
||||
// Set the EasyDMA buffer, which has 4 values (one for each channel).
|
||||
pwm.PWM.SEQ[0].PTR.Set(uint32(uintptr(unsafe.Pointer(&pwm.channelValues[0]))))
|
||||
pwm.PWM.SEQ[0].CNT.Set(4)
|
||||
count = 0
|
||||
evt = I2CFinish
|
||||
err = nil
|
||||
|
||||
// SEQ[0] is not yet started, it will be started on the first
|
||||
// PWMChannel.Set() call.
|
||||
if i2c.BusT.EVENTS_WRITE.Get() != 0 {
|
||||
i2c.BusT.EVENTS_WRITE.Set(0)
|
||||
|
||||
// Data was sent to this target. We've waited for
|
||||
// READ or STOPPED event, so transmission should be
|
||||
// complete.
|
||||
count = int(i2c.BusT.RXD.AMOUNT.Get())
|
||||
evt = I2CReceive
|
||||
} else if i2c.BusT.EVENTS_READ.Get() != 0 {
|
||||
i2c.BusT.EVENTS_READ.Set(0)
|
||||
|
||||
// Data is requested from this target, hw will stretch
|
||||
// the controller's clock until there is a reply to
|
||||
// send
|
||||
evt = I2CRequest
|
||||
} else if i2c.BusT.EVENTS_STOPPED.Get() != 0 {
|
||||
i2c.BusT.EVENTS_STOPPED.Set(0)
|
||||
evt = I2CFinish
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Reply supplies the response data the controller.
|
||||
func (i2c *I2C) Reply(buf []byte) error {
|
||||
i2c.BusT.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0]))))
|
||||
i2c.BusT.TXD.MAXCNT.Set(uint32(len(buf)))
|
||||
|
||||
i2c.BusT.EVENTS_STOPPED.Set(0)
|
||||
|
||||
// Trigger Tx
|
||||
i2c.BusT.TASKS_PREPARETX.Set(nrf.TWIS_TASKS_PREPARETX_TASKS_PREPARETX_Trigger)
|
||||
|
||||
// Block, waiting for Tx to complete
|
||||
for i2c.BusT.EVENTS_STOPPED.Get() == 0 {
|
||||
gosched()
|
||||
|
||||
if i2c.BusT.EVENTS_ERROR.Get() != 0 {
|
||||
return twisError(i2c.BusT.ERRORSRC.Get())
|
||||
}
|
||||
}
|
||||
|
||||
i2c.BusT.EVENTS_STOPPED.Set(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPeriod updates the period of this PWM peripheral.
|
||||
// To set a particular frequency, use the following formula:
|
||||
//
|
||||
// period = 1e9 / frequency
|
||||
//
|
||||
// If you use a period of 0, a period that works well for LEDs will be picked.
|
||||
//
|
||||
// SetPeriod will not change the prescaler, but also won't change the current
|
||||
// value in any of the channels. This means that you may need to update the
|
||||
// value for the particular channel.
|
||||
//
|
||||
// Note that you cannot pick any arbitrary period after the PWM peripheral has
|
||||
// been configured. If you want to switch between frequencies, pick the lowest
|
||||
// frequency (longest period) once when calling Configure and adjust the
|
||||
// frequency here as needed.
|
||||
func (pwm *PWM) SetPeriod(period uint64) error {
|
||||
return pwm.setPeriod(period, false)
|
||||
}
|
||||
|
||||
func (pwm *PWM) setPeriod(period uint64, updatePrescaler bool) error {
|
||||
const maxTop = 0x7fff // 15 bits counter
|
||||
|
||||
// The top value is the number of PWM ticks a PWM period takes. It is
|
||||
// initially picked assuming an unlimited COUNTERTOP and no PWM prescaler.
|
||||
var top uint64
|
||||
if period == 0 {
|
||||
// The period is 0, which means "pick something reasonable for LEDs".
|
||||
top = maxTop
|
||||
} else {
|
||||
// The formula below calculates the following formula, optimized:
|
||||
// period * (16e6 / 1e9)
|
||||
// The max frequency (16e6 or 16MHz) is set by the hardware.
|
||||
top = period * 2 / 125
|
||||
// twiCError converts an I2C controller error to Go
|
||||
func twiCError(val uint32) error {
|
||||
if val == 0 {
|
||||
return nil
|
||||
} else if val&nrf.TWIM_ERRORSRC_OVERRUN_Msk == nrf.TWIM_ERRORSRC_OVERRUN {
|
||||
return errI2CBusError
|
||||
} else if val&nrf.TWIM_ERRORSRC_ANACK_Msk == nrf.TWIM_ERRORSRC_ANACK {
|
||||
return errI2CAckExpected
|
||||
} else if val&nrf.TWIM_ERRORSRC_DNACK_Msk == nrf.TWIM_ERRORSRC_DNACK {
|
||||
return errI2CAckExpected
|
||||
}
|
||||
|
||||
// The ideal PWM period may be larger than would fit in the PWM counter,
|
||||
// which is only 15 bits (see maxTop). Therefore, try to make the PWM clock
|
||||
// speed lower with a prescaler to make the top value fit the COUNTERTOP.
|
||||
if updatePrescaler {
|
||||
// This function was called during Configure().
|
||||
switch {
|
||||
case top <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_1)
|
||||
case top/2 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_2)
|
||||
top /= 2
|
||||
case top/4 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_4)
|
||||
top /= 4
|
||||
case top/8 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_8)
|
||||
top /= 8
|
||||
case top/16 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_16)
|
||||
top /= 16
|
||||
case top/32 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_32)
|
||||
top /= 32
|
||||
case top/64 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_64)
|
||||
top /= 64
|
||||
case top/128 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_128)
|
||||
top /= 128
|
||||
default:
|
||||
return ErrPWMPeriodTooLong
|
||||
}
|
||||
} else {
|
||||
// Do not update the prescaler, but use the already-configured
|
||||
// prescaler. This is the normal SetPeriod case, where the prescaler
|
||||
// must not be changed.
|
||||
prescaler := pwm.PWM.PRESCALER.Get()
|
||||
switch prescaler {
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_1:
|
||||
top /= 1
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_2:
|
||||
top /= 2
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_4:
|
||||
top /= 4
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_8:
|
||||
top /= 8
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_16:
|
||||
top /= 16
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_32:
|
||||
top /= 32
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_64:
|
||||
top /= 64
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_128:
|
||||
top /= 128
|
||||
}
|
||||
if top > maxTop {
|
||||
return ErrPWMPeriodTooLong
|
||||
}
|
||||
}
|
||||
pwm.PWM.COUNTERTOP.Set(uint32(top))
|
||||
|
||||
// Apparently this is needed to apply the new COUNTERTOP.
|
||||
pwm.PWM.TASKS_SEQSTART[0].Set(1)
|
||||
|
||||
return nil
|
||||
return errI2CBusError
|
||||
}
|
||||
|
||||
// Top returns the current counter top, for use in duty cycle calculation. It
|
||||
// will only change with a call to Configure or SetPeriod, otherwise it is
|
||||
// constant.
|
||||
//
|
||||
// The value returned here is hardware dependent. In general, it's best to treat
|
||||
// it as an opaque value that can be divided by some number and passed to
|
||||
// pwm.Set (see pwm.Set for more information).
|
||||
func (pwm *PWM) Top() uint32 {
|
||||
return pwm.PWM.COUNTERTOP.Get()
|
||||
}
|
||||
|
||||
// Channel returns a PWM channel for the given pin.
|
||||
func (pwm *PWM) Channel(pin Pin) (uint8, error) {
|
||||
config := uint32(pin)
|
||||
for ch := uint8(0); ch < 4; ch++ {
|
||||
channelConfig := pwm.PWM.PSEL.OUT[ch].Get()
|
||||
if channelConfig == 0xffffffff {
|
||||
// Unused channel. Configure it.
|
||||
pwm.PWM.PSEL.OUT[ch].Set(config)
|
||||
// Configure the pin (required by the reference manual).
|
||||
pin.Configure(PinConfig{Mode: PinOutput})
|
||||
// Set channel to zero and non-inverting.
|
||||
pwm.channelValues[ch].Set(0x8000)
|
||||
return ch, nil
|
||||
} else if channelConfig == config {
|
||||
// This channel is already configured for this pin.
|
||||
return ch, nil
|
||||
}
|
||||
// twisError converts an I2C target error to Go
|
||||
func twisError(val uint32) error {
|
||||
if val == 0 {
|
||||
return nil
|
||||
} else if val&nrf.TWIS_ERRORSRC_OVERFLOW_Msk == nrf.TWIS_ERRORSRC_OVERFLOW {
|
||||
return errI2COverflow
|
||||
} else if val&nrf.TWIS_ERRORSRC_DNACK_Msk == nrf.TWIS_ERRORSRC_DNACK {
|
||||
return errI2CAckExpected
|
||||
} else if val&nrf.TWIS_ERRORSRC_OVERREAD_Msk == nrf.TWIS_ERRORSRC_OVERREAD {
|
||||
return errI2COverread
|
||||
}
|
||||
|
||||
// All four pins are already in use with other pins.
|
||||
return 0, ErrInvalidOutputPin
|
||||
}
|
||||
|
||||
// SetInverting sets whether to invert the output of this channel.
|
||||
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
|
||||
// the time and low for the rest. Inverting flips the output as if a NOT gate
|
||||
// was placed at the output, meaning that the output would be 25% low and 75%
|
||||
// high with a duty cycle of 25%.
|
||||
func (pwm *PWM) SetInverting(channel uint8, inverting bool) {
|
||||
ptr := &pwm.channelValues[channel]
|
||||
if inverting {
|
||||
ptr.Set(ptr.Get() &^ 0x8000)
|
||||
} else {
|
||||
ptr.Set(ptr.Get() | 0x8000)
|
||||
}
|
||||
}
|
||||
|
||||
// Set updates the channel value. This is used to control the channel duty
|
||||
// cycle. For example, to set it to a 25% duty cycle, use:
|
||||
//
|
||||
// ch.Set(ch.Top() / 4)
|
||||
//
|
||||
// ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output
|
||||
// to high, assuming the output isn't inverted.
|
||||
func (pwm *PWM) Set(channel uint8, value uint32) {
|
||||
// Update the channel value while retaining the polarity bit.
|
||||
ptr := &pwm.channelValues[channel]
|
||||
ptr.Set(ptr.Get()&0x8000 | uint16(value)&0x7fff)
|
||||
|
||||
// Start the PWM, if it isn't already running.
|
||||
pwm.PWM.TASKS_SEQSTART[0].Set(1)
|
||||
return errI2CBusError
|
||||
}
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
//go:build nrf52 || nrf52840 || nrf52833
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/nrf"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func CPUFrequency() uint32 {
|
||||
return 64000000
|
||||
}
|
||||
|
||||
// InitADC initializes the registers needed for ADC.
|
||||
func InitADC() {
|
||||
return // no specific setup on nrf52 machine.
|
||||
}
|
||||
|
||||
// Configure configures an ADC pin to be able to read analog data.
|
||||
func (a ADC) Configure(config ADCConfig) {
|
||||
// Enable ADC.
|
||||
// The ADC does not consume a noticeable amount of current simply by being
|
||||
// enabled.
|
||||
nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos)
|
||||
|
||||
// Use fixed resolution of 12 bits.
|
||||
// TODO: is it useful for users to change this?
|
||||
nrf.SAADC.RESOLUTION.Set(nrf.SAADC_RESOLUTION_VAL_12bit)
|
||||
|
||||
var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESP_Pos |
|
||||
nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESN_Pos |
|
||||
nrf.SAADC_CH_CONFIG_REFSEL_Internal<<nrf.SAADC_CH_CONFIG_REFSEL_Pos |
|
||||
nrf.SAADC_CH_CONFIG_MODE_SE<<nrf.SAADC_CH_CONFIG_MODE_Pos
|
||||
|
||||
switch config.Reference {
|
||||
case 150: // 0.15V
|
||||
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain4 << nrf.SAADC_CH_CONFIG_GAIN_Pos
|
||||
case 300: // 0.3V
|
||||
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain2 << nrf.SAADC_CH_CONFIG_GAIN_Pos
|
||||
case 600: // 0.6V
|
||||
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1 << nrf.SAADC_CH_CONFIG_GAIN_Pos
|
||||
case 1200: // 1.2V
|
||||
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_2 << nrf.SAADC_CH_CONFIG_GAIN_Pos
|
||||
case 1800: // 1.8V
|
||||
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_3 << nrf.SAADC_CH_CONFIG_GAIN_Pos
|
||||
case 2400: // 2.4V
|
||||
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_4 << nrf.SAADC_CH_CONFIG_GAIN_Pos
|
||||
case 3000, 0: // 3.0V (default)
|
||||
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_5 << nrf.SAADC_CH_CONFIG_GAIN_Pos
|
||||
case 3600: // 3.6V
|
||||
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_6 << nrf.SAADC_CH_CONFIG_GAIN_Pos
|
||||
default:
|
||||
// TODO: return an error
|
||||
}
|
||||
|
||||
// Source resistance, according to table 89 on page 364 of the nrf52832 datasheet.
|
||||
// https://infocenter.nordicsemi.com/pdf/nRF52832_PS_v1.4.pdf
|
||||
if config.SampleTime <= 3 { // <= 10kΩ
|
||||
configVal |= nrf.SAADC_CH_CONFIG_TACQ_3us << nrf.SAADC_CH_CONFIG_TACQ_Pos
|
||||
} else if config.SampleTime <= 5 { // <= 40kΩ
|
||||
configVal |= nrf.SAADC_CH_CONFIG_TACQ_5us << nrf.SAADC_CH_CONFIG_TACQ_Pos
|
||||
} else if config.SampleTime <= 10 { // <= 100kΩ
|
||||
configVal |= nrf.SAADC_CH_CONFIG_TACQ_10us << nrf.SAADC_CH_CONFIG_TACQ_Pos
|
||||
} else if config.SampleTime <= 15 { // <= 200kΩ
|
||||
configVal |= nrf.SAADC_CH_CONFIG_TACQ_15us << nrf.SAADC_CH_CONFIG_TACQ_Pos
|
||||
} else if config.SampleTime <= 20 { // <= 400kΩ
|
||||
configVal |= nrf.SAADC_CH_CONFIG_TACQ_20us << nrf.SAADC_CH_CONFIG_TACQ_Pos
|
||||
} else { // <= 800kΩ
|
||||
configVal |= nrf.SAADC_CH_CONFIG_TACQ_40us << nrf.SAADC_CH_CONFIG_TACQ_Pos
|
||||
}
|
||||
|
||||
// Oversampling configuration.
|
||||
burst := true
|
||||
switch config.Samples {
|
||||
default: // no oversampling
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Bypass)
|
||||
burst = false
|
||||
case 2:
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over2x)
|
||||
case 4:
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over4x)
|
||||
case 8:
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over8x)
|
||||
case 16:
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over16x)
|
||||
case 32:
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over32x)
|
||||
case 64:
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over64x)
|
||||
case 128:
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over128x)
|
||||
case 256:
|
||||
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over256x)
|
||||
}
|
||||
if burst {
|
||||
// BURST=1 is needed when oversampling
|
||||
configVal |= nrf.SAADC_CH_CONFIG_BURST
|
||||
}
|
||||
|
||||
// Configure channel 0, which is the only channel we use.
|
||||
nrf.SAADC.CH[0].CONFIG.Set(configVal)
|
||||
}
|
||||
|
||||
// Get returns the current value of a ADC pin in the range 0..0xffff.
|
||||
func (a ADC) Get() uint16 {
|
||||
var pwmPin uint32
|
||||
var rawValue volatile.Register16
|
||||
|
||||
switch a.Pin {
|
||||
case 2:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0
|
||||
|
||||
case 3:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1
|
||||
|
||||
case 4:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2
|
||||
|
||||
case 5:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3
|
||||
|
||||
case 28:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4
|
||||
|
||||
case 29:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5
|
||||
|
||||
case 30:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6
|
||||
|
||||
case 31:
|
||||
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7
|
||||
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
// Set pin to read.
|
||||
nrf.SAADC.CH[0].PSELN.Set(pwmPin)
|
||||
nrf.SAADC.CH[0].PSELP.Set(pwmPin)
|
||||
|
||||
// Destination for sample result.
|
||||
nrf.SAADC.RESULT.PTR.Set(uint32(uintptr(unsafe.Pointer(&rawValue))))
|
||||
nrf.SAADC.RESULT.MAXCNT.Set(1) // One sample
|
||||
|
||||
// Start tasks.
|
||||
nrf.SAADC.TASKS_START.Set(1)
|
||||
for nrf.SAADC.EVENTS_STARTED.Get() == 0 {
|
||||
}
|
||||
nrf.SAADC.EVENTS_STARTED.Set(0x00)
|
||||
|
||||
// Start the sample task.
|
||||
nrf.SAADC.TASKS_SAMPLE.Set(1)
|
||||
|
||||
// Wait until the sample task is done.
|
||||
for nrf.SAADC.EVENTS_END.Get() == 0 {
|
||||
}
|
||||
nrf.SAADC.EVENTS_END.Set(0x00)
|
||||
|
||||
// Stop the ADC
|
||||
nrf.SAADC.TASKS_STOP.Set(1)
|
||||
for nrf.SAADC.EVENTS_STOPPED.Get() == 0 {
|
||||
}
|
||||
nrf.SAADC.EVENTS_STOPPED.Set(0)
|
||||
|
||||
value := int16(rawValue.Get())
|
||||
if value < 0 {
|
||||
value = 0
|
||||
}
|
||||
|
||||
// Return 16-bit result from 12-bit value.
|
||||
return uint16(value << 4)
|
||||
}
|
||||
|
||||
// SPI on the NRF.
|
||||
type SPI struct {
|
||||
Bus *nrf.SPIM_Type
|
||||
buf *[1]byte // 1-byte buffer for the Transfer method
|
||||
}
|
||||
|
||||
// There are 3 SPI interfaces on the NRF528xx.
|
||||
var (
|
||||
SPI0 = SPI{Bus: nrf.SPIM0, buf: new([1]byte)}
|
||||
SPI1 = SPI{Bus: nrf.SPIM1, buf: new([1]byte)}
|
||||
SPI2 = SPI{Bus: nrf.SPIM2, buf: new([1]byte)}
|
||||
)
|
||||
|
||||
// SPIConfig is used to store config info for SPI.
|
||||
type SPIConfig struct {
|
||||
Frequency uint32
|
||||
SCK Pin
|
||||
SDO Pin
|
||||
SDI Pin
|
||||
LSBFirst bool
|
||||
Mode uint8
|
||||
}
|
||||
|
||||
// Configure is intended to setup the SPI interface.
|
||||
func (spi SPI) Configure(config SPIConfig) {
|
||||
// Disable bus to configure it
|
||||
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled)
|
||||
|
||||
// Pick a default frequency.
|
||||
if config.Frequency == 0 {
|
||||
config.Frequency = 4000000 // 4MHz
|
||||
}
|
||||
|
||||
// set frequency
|
||||
var freq uint32
|
||||
switch {
|
||||
case config.Frequency >= 8000000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M8
|
||||
case config.Frequency >= 4000000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M4
|
||||
case config.Frequency >= 2000000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M2
|
||||
case config.Frequency >= 1000000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M1
|
||||
case config.Frequency >= 500000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K500
|
||||
case config.Frequency >= 250000:
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K250
|
||||
default: // below 250kHz, default to the lowest speed available
|
||||
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K125
|
||||
}
|
||||
spi.Bus.FREQUENCY.Set(freq)
|
||||
|
||||
var conf uint32
|
||||
|
||||
// set bit transfer order
|
||||
if config.LSBFirst {
|
||||
conf = (nrf.SPIM_CONFIG_ORDER_LsbFirst << nrf.SPIM_CONFIG_ORDER_Pos)
|
||||
}
|
||||
|
||||
// set mode
|
||||
switch config.Mode {
|
||||
case 0:
|
||||
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
case 1:
|
||||
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
case 2:
|
||||
conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
case 3:
|
||||
conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
default: // to mode
|
||||
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
|
||||
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
|
||||
}
|
||||
spi.Bus.CONFIG.Set(conf)
|
||||
|
||||
// set pins
|
||||
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
|
||||
config.SCK = SPI0_SCK_PIN
|
||||
config.SDO = SPI0_SDO_PIN
|
||||
config.SDI = SPI0_SDI_PIN
|
||||
}
|
||||
spi.Bus.PSEL.SCK.Set(uint32(config.SCK))
|
||||
spi.Bus.PSEL.MOSI.Set(uint32(config.SDO))
|
||||
spi.Bus.PSEL.MISO.Set(uint32(config.SDI))
|
||||
|
||||
// Re-enable bus now that it is configured.
|
||||
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled)
|
||||
}
|
||||
|
||||
// Transfer writes/reads a single byte using the SPI interface.
|
||||
func (spi SPI) Transfer(w byte) (byte, error) {
|
||||
buf := spi.buf[:]
|
||||
buf[0] = w
|
||||
err := spi.Tx(buf[:], buf[:])
|
||||
return buf[0], err
|
||||
}
|
||||
|
||||
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous
|
||||
// write/read interface, there must always be the same number of bytes written
|
||||
// as bytes read. Therefore, if the number of bytes don't match it will be
|
||||
// padded until they fit: if len(w) > len(r) the extra bytes received will be
|
||||
// dropped and if len(w) < len(r) extra 0 bytes will be sent.
|
||||
func (spi SPI) Tx(w, r []byte) error {
|
||||
// Unfortunately the hardware (on the nrf52832) only supports up to 255
|
||||
// bytes in the buffers, so if either w or r is longer than that the
|
||||
// transfer needs to be broken up in pieces.
|
||||
// The nrf52840 supports far larger buffers however, which isn't yet
|
||||
// supported.
|
||||
for len(r) != 0 || len(w) != 0 {
|
||||
// Prepare the SPI transfer: set the DMA pointers and lengths.
|
||||
if len(r) != 0 {
|
||||
spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0]))))
|
||||
n := uint32(len(r))
|
||||
if n > 255 {
|
||||
n = 255
|
||||
}
|
||||
spi.Bus.RXD.MAXCNT.Set(n)
|
||||
r = r[n:]
|
||||
}
|
||||
if len(w) != 0 {
|
||||
spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0]))))
|
||||
n := uint32(len(w))
|
||||
if n > 255 {
|
||||
n = 255
|
||||
}
|
||||
spi.Bus.TXD.MAXCNT.Set(n)
|
||||
w = w[n:]
|
||||
}
|
||||
|
||||
// Do the transfer.
|
||||
// Note: this can be improved by not waiting until the transfer is
|
||||
// finished if the transfer is send-only (a common case).
|
||||
spi.Bus.TASKS_START.Set(1)
|
||||
for spi.Bus.EVENTS_END.Get() == 0 {
|
||||
}
|
||||
spi.Bus.EVENTS_END.Set(0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PWM is one PWM peripheral, which consists of a counter and multiple output
|
||||
// channels (that can be connected to actual pins). You can set the frequency
|
||||
// using SetPeriod, but only for all the channels in this PWM peripheral at
|
||||
// once.
|
||||
type PWM struct {
|
||||
PWM *nrf.PWM_Type
|
||||
|
||||
channelValues [4]volatile.Register16
|
||||
}
|
||||
|
||||
// Configure enables and configures this PWM.
|
||||
// On the nRF52 series, the maximum period is around 0.26s.
|
||||
func (pwm *PWM) Configure(config PWMConfig) error {
|
||||
// Enable the peripheral.
|
||||
pwm.PWM.ENABLE.Set(nrf.PWM_ENABLE_ENABLE_Enabled << nrf.PWM_ENABLE_ENABLE_Pos)
|
||||
|
||||
// Use up counting only. TODO: allow configuring as up-and-down.
|
||||
pwm.PWM.MODE.Set(nrf.PWM_MODE_UPDOWN_Up << nrf.PWM_MODE_UPDOWN_Pos)
|
||||
|
||||
// Indicate there are four channels that each have a different value.
|
||||
pwm.PWM.DECODER.Set(nrf.PWM_DECODER_LOAD_Individual<<nrf.PWM_DECODER_LOAD_Pos | nrf.PWM_DECODER_MODE_RefreshCount<<nrf.PWM_DECODER_MODE_Pos)
|
||||
|
||||
err := pwm.setPeriod(config.Period, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the EasyDMA buffer, which has 4 values (one for each channel).
|
||||
pwm.PWM.SEQ[0].PTR.Set(uint32(uintptr(unsafe.Pointer(&pwm.channelValues[0]))))
|
||||
pwm.PWM.SEQ[0].CNT.Set(4)
|
||||
|
||||
// SEQ[0] is not yet started, it will be started on the first
|
||||
// PWMChannel.Set() call.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPeriod updates the period of this PWM peripheral.
|
||||
// To set a particular frequency, use the following formula:
|
||||
//
|
||||
// period = 1e9 / frequency
|
||||
//
|
||||
// If you use a period of 0, a period that works well for LEDs will be picked.
|
||||
//
|
||||
// SetPeriod will not change the prescaler, but also won't change the current
|
||||
// value in any of the channels. This means that you may need to update the
|
||||
// value for the particular channel.
|
||||
//
|
||||
// Note that you cannot pick any arbitrary period after the PWM peripheral has
|
||||
// been configured. If you want to switch between frequencies, pick the lowest
|
||||
// frequency (longest period) once when calling Configure and adjust the
|
||||
// frequency here as needed.
|
||||
func (pwm *PWM) SetPeriod(period uint64) error {
|
||||
return pwm.setPeriod(period, false)
|
||||
}
|
||||
|
||||
func (pwm *PWM) setPeriod(period uint64, updatePrescaler bool) error {
|
||||
const maxTop = 0x7fff // 15 bits counter
|
||||
|
||||
// The top value is the number of PWM ticks a PWM period takes. It is
|
||||
// initially picked assuming an unlimited COUNTERTOP and no PWM prescaler.
|
||||
var top uint64
|
||||
if period == 0 {
|
||||
// The period is 0, which means "pick something reasonable for LEDs".
|
||||
top = maxTop
|
||||
} else {
|
||||
// The formula below calculates the following formula, optimized:
|
||||
// period * (16e6 / 1e9)
|
||||
// The max frequency (16e6 or 16MHz) is set by the hardware.
|
||||
top = period * 2 / 125
|
||||
}
|
||||
|
||||
// The ideal PWM period may be larger than would fit in the PWM counter,
|
||||
// which is only 15 bits (see maxTop). Therefore, try to make the PWM clock
|
||||
// speed lower with a prescaler to make the top value fit the COUNTERTOP.
|
||||
if updatePrescaler {
|
||||
// This function was called during Configure().
|
||||
switch {
|
||||
case top <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_1)
|
||||
case top/2 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_2)
|
||||
top /= 2
|
||||
case top/4 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_4)
|
||||
top /= 4
|
||||
case top/8 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_8)
|
||||
top /= 8
|
||||
case top/16 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_16)
|
||||
top /= 16
|
||||
case top/32 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_32)
|
||||
top /= 32
|
||||
case top/64 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_64)
|
||||
top /= 64
|
||||
case top/128 <= maxTop:
|
||||
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_128)
|
||||
top /= 128
|
||||
default:
|
||||
return ErrPWMPeriodTooLong
|
||||
}
|
||||
} else {
|
||||
// Do not update the prescaler, but use the already-configured
|
||||
// prescaler. This is the normal SetPeriod case, where the prescaler
|
||||
// must not be changed.
|
||||
prescaler := pwm.PWM.PRESCALER.Get()
|
||||
switch prescaler {
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_1:
|
||||
top /= 1
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_2:
|
||||
top /= 2
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_4:
|
||||
top /= 4
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_8:
|
||||
top /= 8
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_16:
|
||||
top /= 16
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_32:
|
||||
top /= 32
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_64:
|
||||
top /= 64
|
||||
case nrf.PWM_PRESCALER_PRESCALER_DIV_128:
|
||||
top /= 128
|
||||
}
|
||||
if top > maxTop {
|
||||
return ErrPWMPeriodTooLong
|
||||
}
|
||||
}
|
||||
pwm.PWM.COUNTERTOP.Set(uint32(top))
|
||||
|
||||
// Apparently this is needed to apply the new COUNTERTOP.
|
||||
pwm.PWM.TASKS_SEQSTART[0].Set(1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Top returns the current counter top, for use in duty cycle calculation. It
|
||||
// will only change with a call to Configure or SetPeriod, otherwise it is
|
||||
// constant.
|
||||
//
|
||||
// The value returned here is hardware dependent. In general, it's best to treat
|
||||
// it as an opaque value that can be divided by some number and passed to
|
||||
// pwm.Set (see pwm.Set for more information).
|
||||
func (pwm *PWM) Top() uint32 {
|
||||
return pwm.PWM.COUNTERTOP.Get()
|
||||
}
|
||||
|
||||
// Channel returns a PWM channel for the given pin.
|
||||
func (pwm *PWM) Channel(pin Pin) (uint8, error) {
|
||||
config := uint32(pin)
|
||||
for ch := uint8(0); ch < 4; ch++ {
|
||||
channelConfig := pwm.PWM.PSEL.OUT[ch].Get()
|
||||
if channelConfig == 0xffffffff {
|
||||
// Unused channel. Configure it.
|
||||
pwm.PWM.PSEL.OUT[ch].Set(config)
|
||||
// Configure the pin (required by the reference manual).
|
||||
pin.Configure(PinConfig{Mode: PinOutput})
|
||||
// Set channel to zero and non-inverting.
|
||||
pwm.channelValues[ch].Set(0x8000)
|
||||
return ch, nil
|
||||
} else if channelConfig == config {
|
||||
// This channel is already configured for this pin.
|
||||
return ch, nil
|
||||
}
|
||||
}
|
||||
|
||||
// All four pins are already in use with other pins.
|
||||
return 0, ErrInvalidOutputPin
|
||||
}
|
||||
|
||||
// SetInverting sets whether to invert the output of this channel.
|
||||
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
|
||||
// the time and low for the rest. Inverting flips the output as if a NOT gate
|
||||
// was placed at the output, meaning that the output would be 25% low and 75%
|
||||
// high with a duty cycle of 25%.
|
||||
func (pwm *PWM) SetInverting(channel uint8, inverting bool) {
|
||||
ptr := &pwm.channelValues[channel]
|
||||
if inverting {
|
||||
ptr.Set(ptr.Get() &^ 0x8000)
|
||||
} else {
|
||||
ptr.Set(ptr.Get() | 0x8000)
|
||||
}
|
||||
}
|
||||
|
||||
// Set updates the channel value. This is used to control the channel duty
|
||||
// cycle. For example, to set it to a 25% duty cycle, use:
|
||||
//
|
||||
// ch.Set(ch.Top() / 4)
|
||||
//
|
||||
// ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output
|
||||
// to high, assuming the output isn't inverted.
|
||||
func (pwm *PWM) Set(channel uint8, value uint32) {
|
||||
// Update the channel value while retaining the polarity bit.
|
||||
ptr := &pwm.channelValues[channel]
|
||||
ptr.Set(ptr.Get()&0x8000 | uint16(value)&0x7fff)
|
||||
|
||||
// Start the PWM, if it isn't already running.
|
||||
pwm.PWM.TASKS_SEQSTART[0].Set(1)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//go:build nrf51 || nrf52
|
||||
|
||||
package machine
|
||||
|
||||
import "device/nrf"
|
||||
|
||||
// I2C on the NRF51 and NRF52.
|
||||
type I2C struct {
|
||||
Bus *nrf.TWI_Type
|
||||
mode I2CMode
|
||||
}
|
||||
|
||||
// There are 2 I2C interfaces on the NRF.
|
||||
var (
|
||||
I2C0 = &I2C{Bus: nrf.TWI0}
|
||||
I2C1 = &I2C{Bus: nrf.TWI1}
|
||||
)
|
||||
|
||||
func (i2c *I2C) enableAsController() {
|
||||
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled)
|
||||
}
|
||||
|
||||
func (i2c *I2C) enableAsTarget() {
|
||||
// Not supported on this hardware
|
||||
}
|
||||
|
||||
func (i2c *I2C) disable() {
|
||||
i2c.Bus.ENABLE.Set(0)
|
||||
}
|
||||
|
||||
// Tx does a single I2C transaction at the specified address.
|
||||
// It clocks out the given address, writes the bytes in w, reads back len(r)
|
||||
// bytes and stores them in r, and generates a stop condition on the bus.
|
||||
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
|
||||
|
||||
// Tricky stop condition.
|
||||
// After reads, the stop condition is generated implicitly with a shortcut.
|
||||
// After writes not followed by reads and in the case of errors, stop must be generated explicitly.
|
||||
|
||||
i2c.Bus.ADDRESS.Set(uint32(addr))
|
||||
|
||||
if len(w) != 0 {
|
||||
i2c.Bus.TASKS_STARTTX.Set(1) // start transmission for writing
|
||||
for _, b := range w {
|
||||
if err = i2c.writeByte(b); err != nil {
|
||||
i2c.signalStop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r) != 0 {
|
||||
// To trigger suspend task when a byte is received
|
||||
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND)
|
||||
i2c.Bus.TASKS_STARTRX.Set(1) // re-start transmission for reading
|
||||
for i := range r { // read each char
|
||||
if i+1 == len(r) {
|
||||
// To trigger stop task when last byte is received, set before resume task.
|
||||
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_STOP)
|
||||
}
|
||||
if i > 0 {
|
||||
i2c.Bus.TASKS_RESUME.Set(1) // re-start transmission for reading
|
||||
}
|
||||
if r[i], err = i2c.readByte(); err != nil {
|
||||
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
|
||||
i2c.signalStop()
|
||||
return
|
||||
}
|
||||
}
|
||||
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
|
||||
}
|
||||
|
||||
// Stop explicitly when no reads were executed, stoping unconditionally would be a mistake.
|
||||
// It may execute after I2C peripheral has already been stopped by the shortcut in the read block,
|
||||
// so stop task will trigger first thing in a subsequent transaction, hanging it.
|
||||
if len(r) == 0 {
|
||||
i2c.signalStop()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// writeByte writes a single byte to the I2C bus and waits for confirmation.
|
||||
func (i2c *I2C) writeByte(data byte) error {
|
||||
i2c.Bus.TXD.Set(uint32(data))
|
||||
for i2c.Bus.EVENTS_TXDSENT.Get() == 0 {
|
||||
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
|
||||
i2c.Bus.EVENTS_ERROR.Set(0)
|
||||
return errI2CBusError
|
||||
}
|
||||
}
|
||||
i2c.Bus.EVENTS_TXDSENT.Set(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// readByte reads a single byte from the I2C bus when it is ready.
|
||||
func (i2c *I2C) readByte() (byte, error) {
|
||||
for i2c.Bus.EVENTS_RXDREADY.Get() == 0 {
|
||||
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
|
||||
i2c.Bus.EVENTS_ERROR.Set(0)
|
||||
return 0, errI2CBusError
|
||||
}
|
||||
}
|
||||
i2c.Bus.EVENTS_RXDREADY.Set(0)
|
||||
return byte(i2c.Bus.RXD.Get()), nil
|
||||
}
|
||||
@@ -59,9 +59,6 @@ var (
|
||||
ErrNotConfigured = errors.New("device has not been configured")
|
||||
)
|
||||
|
||||
//go:linkname gosched runtime.Gosched
|
||||
func gosched()
|
||||
|
||||
// PutcharUART writes a byte to the UART synchronously, without using interrupts
|
||||
// or calling the scheduler
|
||||
func PutcharUART(u *UART, c byte) {
|
||||
|
||||
@@ -4,6 +4,7 @@ package machine
|
||||
|
||||
import (
|
||||
"device/rp"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -114,3 +115,23 @@ func ChipVersion() uint8 {
|
||||
version := (chipID & SYSINFO_CHIP_ID_REVISION_BITS) >> SYSINFO_CHIP_ID_REVISION_LSB
|
||||
return uint8(version)
|
||||
}
|
||||
|
||||
// Single DMA channel. See rp.DMA_Type.
|
||||
type dmaChannel struct {
|
||||
READ_ADDR volatile.Register32
|
||||
WRITE_ADDR volatile.Register32
|
||||
TRANS_COUNT volatile.Register32
|
||||
CTRL_TRIG volatile.Register32
|
||||
_ [12]volatile.Register32 // aliases
|
||||
}
|
||||
|
||||
// Static assignment of DMA channels to peripherals.
|
||||
// Allocating them statically is good enough for now. If lots of peripherals use
|
||||
// DMA, these might need to be assigned at runtime.
|
||||
const (
|
||||
spi0DMAChannel = iota
|
||||
spi1DMAChannel
|
||||
)
|
||||
|
||||
// DMA channels usable on the RP2040.
|
||||
var dmaChannels = (*[12]dmaChannel)(unsafe.Pointer(rp.DMA))
|
||||
|
||||
@@ -20,10 +20,13 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// The I2C target implementation is based on the C implementation from
|
||||
// here: https://github.com/vmilea/pico_i2c_slave
|
||||
|
||||
// Features: Taken from datasheet.
|
||||
// Default master mode, with slave mode available (not simulataneously).
|
||||
// Default slave address of RP2040: 0x055
|
||||
// Supports 10-bit addressing in Master mode
|
||||
// Default controller mode, with target mode available (not simulataneously).
|
||||
// Default target address of RP2040: 0x055
|
||||
// Supports 10-bit addressing in controller mode
|
||||
// 16-element transmit buffer
|
||||
// 16-element receive buffer
|
||||
// Can be driven from DMA
|
||||
@@ -45,20 +48,25 @@ type I2CConfig struct {
|
||||
// SDA/SCL Serial Data and clock pins. Refer to datasheet to see
|
||||
// which pins match the desired bus.
|
||||
SDA, SCL Pin
|
||||
Mode I2CMode
|
||||
}
|
||||
|
||||
type I2C struct {
|
||||
Bus *rp.I2C0_Type
|
||||
restartOnNext bool
|
||||
Bus *rp.I2C0_Type
|
||||
mode I2CMode
|
||||
txInProgress bool
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidI2CBaudrate = errors.New("invalid i2c baudrate")
|
||||
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
|
||||
ErrI2CGeneric = errors.New("i2c error")
|
||||
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
|
||||
errInvalidI2CSDA = errors.New("invalid I2C SDA pin")
|
||||
errInvalidI2CSCL = errors.New("invalid I2C SCL pin")
|
||||
ErrInvalidI2CBaudrate = errors.New("invalid i2c baudrate")
|
||||
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
|
||||
ErrI2CGeneric = errors.New("i2c error")
|
||||
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
|
||||
errInvalidI2CSDA = errors.New("invalid I2C SDA pin")
|
||||
errInvalidI2CSCL = errors.New("invalid I2C SCL pin")
|
||||
ErrI2CAlreadyListening = errors.New("i2c already listening")
|
||||
ErrI2CWrongMode = errors.New("i2c wrong mode")
|
||||
ErrI2CUnderflow = errors.New("i2c underflow")
|
||||
)
|
||||
|
||||
// Tx performs a write and then a read transfer placing the result in
|
||||
@@ -75,11 +83,26 @@ var (
|
||||
//
|
||||
// Performs only a write transfer.
|
||||
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
|
||||
if i2c.mode != I2CModeController {
|
||||
return ErrI2CWrongMode
|
||||
}
|
||||
|
||||
// timeout in microseconds.
|
||||
const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system.
|
||||
return i2c.tx(uint8(addr), w, r, timeout)
|
||||
}
|
||||
|
||||
// Listen starts listening for I2C requests sent to specified address
|
||||
//
|
||||
// addr is the address to listen to
|
||||
func (i2c *I2C) Listen(addr uint16) error {
|
||||
if i2c.mode != I2CModeTarget {
|
||||
return ErrI2CWrongMode
|
||||
}
|
||||
|
||||
return i2c.listen(uint8(addr))
|
||||
}
|
||||
|
||||
// Configure initializes i2c peripheral and configures I2C config's pins passed.
|
||||
// Here's a list of valid SDA and SCL GPIO pins on bus I2C0 of the rp2040:
|
||||
//
|
||||
@@ -212,15 +235,22 @@ func (i2c *I2C) init(config I2CConfig) error {
|
||||
if err := i2c.disable(); err != nil {
|
||||
return err
|
||||
}
|
||||
i2c.restartOnNext = false
|
||||
// Configure as a fast-mode master with RepStart support, 7-bit addresses
|
||||
i2c.Bus.IC_CON.Set((rp.I2C0_IC_CON_SPEED_FAST << rp.I2C0_IC_CON_SPEED_Pos) |
|
||||
rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE |
|
||||
rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL) // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status
|
||||
|
||||
i2c.mode = config.Mode
|
||||
|
||||
// Configure as fast-mode with RepStart support, 7-bit addresses
|
||||
mode := uint32(rp.I2C0_IC_CON_SPEED_FAST<<rp.I2C0_IC_CON_SPEED_Pos) |
|
||||
rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status
|
||||
if config.Mode == I2CModeController {
|
||||
mode |= rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE
|
||||
}
|
||||
i2c.Bus.IC_CON.Set(mode)
|
||||
|
||||
// Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0.
|
||||
i2c.Bus.IC_TX_TL.Set(0)
|
||||
i2c.Bus.IC_RX_TL.Set(0)
|
||||
if config.Mode == I2CModeController {
|
||||
i2c.Bus.IC_TX_TL.Set(0)
|
||||
i2c.Bus.IC_RX_TL.Set(0)
|
||||
}
|
||||
|
||||
// Always enable the DREQ signalling -- harmless if DMA isn't listening
|
||||
i2c.Bus.IC_DMA_CR.Set(rp.I2C0_IC_DMA_CR_TDMAE | rp.I2C0_IC_DMA_CR_RDMAE)
|
||||
@@ -274,7 +304,8 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
i2c.Bus.IC_TAR.Set(uint32(addr))
|
||||
i2c.enable()
|
||||
abort := false
|
||||
var abortReason uint32
|
||||
var abortReason i2cAbortError
|
||||
txStop := rxlen == 0
|
||||
for txCtr := 0; txCtr < txlen; txCtr++ {
|
||||
if abort {
|
||||
break
|
||||
@@ -282,8 +313,8 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
first := txCtr == 0
|
||||
last := txCtr == txlen-1 && rxlen == 0
|
||||
i2c.Bus.IC_DATA_CMD.Set(
|
||||
(boolToBit(first && i2c.restartOnNext) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) |
|
||||
(boolToBit(last) << rp.I2C0_IC_DATA_CMD_STOP_Pos) |
|
||||
(boolToBit(first) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) |
|
||||
(boolToBit(last && txStop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) |
|
||||
uint32(tx[txCtr]))
|
||||
|
||||
// Wait until the transmission of the address/data from the internal
|
||||
@@ -300,12 +331,14 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
// IC_ENABLE[0] is set to 0, the TX FIFO is flushed and held
|
||||
// in reset. There the TX FIFO looks like it has no data within
|
||||
// it, so this bit is set to 1, provided there is activity in the
|
||||
// master or slave state machines. When there is no longer
|
||||
// controller or target state machines. When there is no longer
|
||||
// any activity, then with ic_en=0, this bit is set to 0.
|
||||
for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) {
|
||||
if ticks() > deadline {
|
||||
return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
|
||||
abortReason = i2c.getAbortReason()
|
||||
@@ -322,33 +355,51 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
// to take care of the abort.
|
||||
for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) {
|
||||
if ticks() > deadline {
|
||||
if abort {
|
||||
return abortReason
|
||||
}
|
||||
return errI2CWriteTimeout
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
i2c.Bus.IC_CLR_STOP_DET.Get()
|
||||
}
|
||||
}
|
||||
|
||||
// Midway check for abort. Related issue https://github.com/tinygo-org/tinygo/issues/3671.
|
||||
// The root cause for an abort after writing registers was "tx data no ack" (abort code=8).
|
||||
// If the abort code was not registered then the whole peripheral would remain in disabled state forever.
|
||||
abortReason = i2c.getAbortReason()
|
||||
if abortReason != 0 {
|
||||
i2c.clearAbortReason()
|
||||
abort = true
|
||||
}
|
||||
|
||||
rxStart := txlen == 0
|
||||
if rxlen > 0 && !abort {
|
||||
for rxCtr := 0; rxCtr < rxlen; rxCtr++ {
|
||||
first := rxCtr == 0
|
||||
last := rxCtr == rxlen-1
|
||||
for i2c.writeAvailable() == 0 {
|
||||
gosched()
|
||||
}
|
||||
i2c.Bus.IC_DATA_CMD.Set(
|
||||
boolToBit(first && i2c.restartOnNext)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
|
||||
boolToBit(first && rxStart)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
|
||||
boolToBit(last)<<rp.I2C0_IC_DATA_CMD_STOP_Pos |
|
||||
rp.I2C0_IC_DATA_CMD_CMD) // -> 1 for read
|
||||
|
||||
for !abort && i2c.readAvailable() == 0 {
|
||||
abortReason = i2c.getAbortReason()
|
||||
i2c.clearAbortReason()
|
||||
if abortReason != 0 {
|
||||
i2c.clearAbortReason()
|
||||
abort = true
|
||||
}
|
||||
if ticks() > deadline {
|
||||
return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
if abort {
|
||||
break
|
||||
@@ -368,12 +419,106 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
// Address acknowledged, some data not acknowledged
|
||||
fallthrough
|
||||
default:
|
||||
err = makeI2CAbortError(abortReason)
|
||||
err = abortReason
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// listen sets up for async handling of requests on the I2C bus.
|
||||
func (i2c *I2C) listen(addr uint8) error {
|
||||
if addr >= 0x80 || isReservedI2CAddr(addr) {
|
||||
return ErrInvalidTgtAddr
|
||||
}
|
||||
|
||||
err := i2c.disable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i2c.Bus.IC_SAR.Set(uint32(addr))
|
||||
|
||||
i2c.enable()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) {
|
||||
rxPtr := 0
|
||||
for {
|
||||
stat := i2c.Bus.IC_RAW_INTR_STAT.Get()
|
||||
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_RX_FULL != 0 {
|
||||
b := uint8(i2c.Bus.IC_DATA_CMD.Get())
|
||||
if rxPtr < len(buf) {
|
||||
buf[rxPtr] = b
|
||||
rxPtr++
|
||||
}
|
||||
}
|
||||
|
||||
// Stop
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_STOP_DET != 0 {
|
||||
if rxPtr > 0 {
|
||||
return I2CReceive, rxPtr, nil
|
||||
}
|
||||
|
||||
i2c.Bus.IC_CLR_STOP_DET.Get() // clear
|
||||
return I2CFinish, 0, nil
|
||||
}
|
||||
|
||||
// Start or restart - ignore start, return on restart
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_START_DET != 0 {
|
||||
i2c.Bus.IC_CLR_START_DET.Get() // clear restart
|
||||
|
||||
// Restart
|
||||
if rxPtr > 0 {
|
||||
return I2CReceive, rxPtr, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Read request - leave flag set until we start to reply.
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ != 0 {
|
||||
return I2CRequest, 0, nil
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (i2c *I2C) Reply(buf []byte) error {
|
||||
txPtr := 0
|
||||
|
||||
stat := i2c.Bus.IC_RAW_INTR_STAT.Get()
|
||||
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ == 0 {
|
||||
return ErrI2CWrongMode
|
||||
}
|
||||
i2c.Bus.IC_CLR_RD_REQ.Get() // clear restart
|
||||
|
||||
// Clear any dangling TX abort
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 {
|
||||
i2c.Bus.IC_CLR_TX_ABRT.Get()
|
||||
}
|
||||
|
||||
for txPtr < len(buf) {
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_TX_EMPTY != 0 {
|
||||
i2c.Bus.IC_DATA_CMD.Set(uint32(buf[txPtr]))
|
||||
txPtr++
|
||||
}
|
||||
|
||||
// This Tx abort is a normal case - we're sending more
|
||||
// data than controller wants to receive
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 {
|
||||
i2c.Bus.IC_CLR_TX_ABRT.Get()
|
||||
return nil
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAvailable determines non-blocking write space available
|
||||
//
|
||||
//go:inline
|
||||
@@ -401,8 +546,8 @@ func (i2c *I2C) clearAbortReason() {
|
||||
// getAbortReason reads IC_TX_ABRT_SOURCE register.
|
||||
//
|
||||
//go:inline
|
||||
func (i2c *I2C) getAbortReason() uint32 {
|
||||
return i2c.Bus.IC_TX_ABRT_SOURCE.Get()
|
||||
func (i2c *I2C) getAbortReason() i2cAbortError {
|
||||
return i2cAbortError(i2c.Bus.IC_TX_ABRT_SOURCE.Get())
|
||||
}
|
||||
|
||||
// returns true if RAW_INTR_STAT bits in mask are all set. performs:
|
||||
@@ -421,9 +566,62 @@ func (b i2cAbortError) Error() string {
|
||||
return "i2c abort, reason " + itoa.Uitoa(uint(b))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func makeI2CAbortError(reason uint32) error {
|
||||
return i2cAbortError(reason)
|
||||
func (b i2cAbortError) Reasons() (reasons []string) {
|
||||
if b == 0 {
|
||||
return nil
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0 {
|
||||
reasons = append(reasons, "7-bit address no ack")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR1_NOACK != 0 {
|
||||
reasons = append(reasons, "10-bit address first byte no ack")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR2_NOACK != 0 {
|
||||
reasons = append(reasons, "10-bit address second byte no ack")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0 {
|
||||
reasons = append(reasons, "tx data no ack")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_NOACK != 0 {
|
||||
reasons = append(reasons, "general call no ack")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_READ != 0 {
|
||||
reasons = append(reasons, "general call read")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_ACKDET != 0 {
|
||||
reasons = append(reasons, "high speed ack detect")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_ACKDET != 0 {
|
||||
reasons = append(reasons, "start byte ack detect")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_NORSTRT != 0 {
|
||||
reasons = append(reasons, "high speed no restart")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_NORSTRT != 0 {
|
||||
reasons = append(reasons, "start byte no restart")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10B_RD_NORSTRT != 0 {
|
||||
reasons = append(reasons, "10-bit read no restart")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_MASTER_DIS != 0 {
|
||||
reasons = append(reasons, "master disabled")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ARB_LOST != 0 {
|
||||
reasons = append(reasons, "arbitration lost")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVFLUSH_TXFIFO != 0 {
|
||||
reasons = append(reasons, "slave flush tx fifo")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLV_ARBLOST != 0 {
|
||||
reasons = append(reasons, "slave arbitration lost")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVRD_INTX != 0 {
|
||||
reasons = append(reasons, "slave read while inactive")
|
||||
}
|
||||
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_USER_ABRT != 0 {
|
||||
reasons = append(reasons, "user abort")
|
||||
}
|
||||
return reasons
|
||||
}
|
||||
|
||||
//go:inline
|
||||
|
||||
@@ -260,7 +260,9 @@ func (pwm *pwmGroup) setPeriod(period uint64) error {
|
||||
maxTop = math.MaxUint16
|
||||
// start algorithm at 95% Top. This allows us to undershoot period with prescale.
|
||||
topStart = 95 * maxTop / 100
|
||||
milliseconds = 1_000_000_000
|
||||
nanosecond = 1 // 1e-9 [s]
|
||||
microsecond = 1000 * nanosecond // 1e-6 [s]
|
||||
milliseconds = 1000 * microsecond // 1e-3 [s]
|
||||
// Maximum Period is 268369920ns on rp2040, given by (16*255+15)*8*(1+0xffff)*(1+1)/16
|
||||
// With no phase shift max period is half of this value.
|
||||
maxPeriod = 268 * milliseconds
|
||||
|
||||
@@ -195,7 +195,7 @@ func (f flashBlockDevice) Size() int64 {
|
||||
return int64(FlashDataEnd() - FlashDataStart())
|
||||
}
|
||||
|
||||
const writeBlockSize = 4
|
||||
const writeBlockSize = 1 << 8
|
||||
|
||||
// WriteBlockSize returns the block size in which data can be written to
|
||||
// memory. It can be used by a client to optimize writes, non-aligned writes
|
||||
@@ -229,19 +229,19 @@ func (f flashBlockDevice) EraseBlocks(start, length int64) error {
|
||||
state := interrupt.Disable()
|
||||
defer interrupt.Restore(state)
|
||||
|
||||
C.flash_erase_blocks(C.uint32_t(address), C.ulong(length))
|
||||
C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// pad data if needed so it is long enough for correct byte alignment on writes.
|
||||
func (f flashBlockDevice) pad(p []byte) []byte {
|
||||
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
|
||||
if paddingNeeded == 0 {
|
||||
overflow := int64(len(p)) % f.WriteBlockSize()
|
||||
if overflow == 0 {
|
||||
return p
|
||||
}
|
||||
|
||||
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
|
||||
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
|
||||
return append(p, padding...)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package machine
|
||||
import (
|
||||
"device/rp"
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// SPI on the RP2040
|
||||
@@ -47,9 +48,6 @@ type SPI struct {
|
||||
Bus *rp.SPI0_Type
|
||||
}
|
||||
|
||||
// time to wait on a transaction before dropping. Unit in Microseconds for compatibility with ticks().
|
||||
const _SPITimeout = 10 * 1000 // 10 ms
|
||||
|
||||
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
|
||||
// interface, there must always be the same number of bytes written as bytes read.
|
||||
// The Tx method knows about this, and offers a few different ways of calling it.
|
||||
@@ -95,26 +93,19 @@ func (spi SPI) Tx(w, r []byte) (err error) {
|
||||
|
||||
// Write a single byte and read a single byte from TX/RX FIFO.
|
||||
func (spi SPI) Transfer(w byte) (byte, error) {
|
||||
var deadline = ticks() + _SPITimeout
|
||||
for !spi.isWritable() {
|
||||
if ticks() > deadline {
|
||||
return 0, ErrSPITimeout
|
||||
}
|
||||
}
|
||||
|
||||
spi.Bus.SSPDR.Set(uint32(w))
|
||||
|
||||
for !spi.isReadable() {
|
||||
if ticks() > deadline {
|
||||
return 0, ErrSPITimeout
|
||||
}
|
||||
}
|
||||
return uint8(spi.Bus.SSPDR.Get()), nil
|
||||
}
|
||||
|
||||
func (spi SPI) SetBaudRate(br uint32) error {
|
||||
const freqin uint32 = 125 * MHz
|
||||
const maxBaud uint32 = 62.5 * MHz // max output frequency is 62.5MHz on rp2040, see page 507
|
||||
const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527.
|
||||
// Find smallest prescale value which puts output frequency in range of
|
||||
// post-divide. Prescale is an even number from 2 to 254 inclusive.
|
||||
var prescale, postdiv uint32
|
||||
@@ -146,7 +137,7 @@ func (spi SPI) GetBaudRate() uint32 {
|
||||
}
|
||||
|
||||
// Configure is intended to setup/initialize the SPI interface.
|
||||
// Default baudrate of 115200 is used if Frequency == 0. Default
|
||||
// Default baudrate of 4MHz is used if Frequency == 0. Default
|
||||
// word length (data bits) is 8.
|
||||
// Below is a list of GPIO pins corresponding to SPI0 bus on the rp2040:
|
||||
//
|
||||
@@ -162,7 +153,7 @@ func (spi SPI) GetBaudRate() uint32 {
|
||||
//
|
||||
// No pin configuration is needed of SCK, SDO and SDI needed after calling Configure.
|
||||
func (spi SPI) Configure(config SPIConfig) error {
|
||||
const defaultBaud uint32 = 115200
|
||||
const defaultBaud uint32 = 4 * MHz
|
||||
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
|
||||
// set default pins if config zero valued or invalid clock pin supplied.
|
||||
switch spi.Bus {
|
||||
@@ -298,27 +289,57 @@ func (spi SPI) isBusy() bool {
|
||||
|
||||
// tx writes buffer to SPI ignoring Rx.
|
||||
func (spi SPI) tx(tx []byte) error {
|
||||
var deadline = ticks() + _SPITimeout
|
||||
// Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX
|
||||
// is full, PL022 inhibits RX pushes, and sets a sticky flag on
|
||||
// push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set.
|
||||
for i := range tx {
|
||||
for !spi.isWritable() {
|
||||
if ticks() > deadline {
|
||||
return ErrSPITimeout
|
||||
}
|
||||
}
|
||||
spi.Bus.SSPDR.Set(uint32(tx[i]))
|
||||
if len(tx) == 0 {
|
||||
// We don't have to do anything.
|
||||
// This avoids a panic in &tx[0] when len(tx) == 0.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pick the DMA channel reserved for this SPI peripheral.
|
||||
var ch *dmaChannel
|
||||
var dreq uint32
|
||||
if spi.Bus == rp.SPI0 {
|
||||
ch = &dmaChannels[spi0DMAChannel]
|
||||
dreq = 16 // DREQ_SPI0_TX
|
||||
} else { // SPI1
|
||||
ch = &dmaChannels[spi1DMAChannel]
|
||||
dreq = 18 // DREQ_SPI1_TX
|
||||
}
|
||||
|
||||
// Configure the DMA peripheral as follows:
|
||||
// - set read address, write address, and number of transfer units (bytes)
|
||||
// - increment read address (in memory), don't increment write address (SSPDR)
|
||||
// - set data size to single bytes
|
||||
// - set the DREQ so that the DMA will fill the SPI FIFO as needed
|
||||
// - start the transfer
|
||||
ch.READ_ADDR.Set(uint32(uintptr(unsafe.Pointer(&tx[0]))))
|
||||
ch.WRITE_ADDR.Set(uint32(uintptr(unsafe.Pointer(&spi.Bus.SSPDR))))
|
||||
ch.TRANS_COUNT.Set(uint32(len(tx)))
|
||||
ch.CTRL_TRIG.Set(rp.DMA_CH0_CTRL_TRIG_INCR_READ |
|
||||
rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_SIZE_BYTE<<rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos |
|
||||
dreq<<rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos |
|
||||
rp.DMA_CH0_CTRL_TRIG_EN)
|
||||
|
||||
// Wait until the transfer is complete.
|
||||
// TODO: do this more efficiently:
|
||||
// - Add a new API to start the transfer, without waiting for it to
|
||||
// complete. This way, the CPU can do something useful while the
|
||||
// transfer is in progress.
|
||||
// - If we have to wait, do so by waiting for an interrupt and blocking
|
||||
// this goroutine until finished (so that other goroutines can run or
|
||||
// the CPU can go to sleep).
|
||||
for ch.CTRL_TRIG.Get()&rp.DMA_CH0_CTRL_TRIG_BUSY != 0 {
|
||||
}
|
||||
|
||||
// We didn't read any result values, which means the RX FIFO has likely
|
||||
// overflown. We have to clean up this mess now.
|
||||
|
||||
// Drain RX FIFO, then wait for shifting to finish (which may be *after*
|
||||
// TX FIFO drains), then drain RX FIFO again
|
||||
for spi.isReadable() {
|
||||
spi.Bus.SSPDR.Get()
|
||||
}
|
||||
for spi.isBusy() {
|
||||
if ticks() > deadline {
|
||||
return ErrSPITimeout
|
||||
}
|
||||
}
|
||||
for spi.isReadable() {
|
||||
spi.Bus.SSPDR.Get()
|
||||
@@ -333,7 +354,6 @@ func (spi SPI) tx(tx []byte) error {
|
||||
// Generally this can be 0, but some devices require a specific value here,
|
||||
// e.g. SD cards expect 0xff
|
||||
func (spi SPI) rx(rx []byte, txrepeat byte) error {
|
||||
var deadline = ticks() + _SPITimeout
|
||||
plen := len(rx)
|
||||
const fifoDepth = 8 // see txrx
|
||||
var rxleft, txleft = plen, plen
|
||||
@@ -345,10 +365,7 @@ func (spi SPI) rx(rx []byte, txrepeat byte) error {
|
||||
if rxleft != 0 && spi.isReadable() {
|
||||
rx[plen-rxleft] = uint8(spi.Bus.SSPDR.Get())
|
||||
rxleft--
|
||||
continue // if reading succesfully in rx there is no need to check deadline.
|
||||
}
|
||||
if ticks() > deadline {
|
||||
return ErrSPITimeout
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -357,7 +374,6 @@ func (spi SPI) rx(rx []byte, txrepeat byte) error {
|
||||
// Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst.
|
||||
// Note this function is guaranteed to exit in a known amount of time (bits sent * time per bit)
|
||||
func (spi SPI) txrx(tx, rx []byte) error {
|
||||
var deadline = ticks() + _SPITimeout
|
||||
plen := len(tx)
|
||||
if plen != len(rx) {
|
||||
return ErrTxInvalidSliceSize
|
||||
@@ -366,7 +382,7 @@ func (spi SPI) txrx(tx, rx []byte) error {
|
||||
// else FIFO will overflow if this code is heavily interrupted.
|
||||
const fifoDepth = 8
|
||||
var rxleft, txleft = plen, plen
|
||||
for (txleft != 0 || rxleft != 0) && ticks() <= deadline {
|
||||
for txleft != 0 || rxleft != 0 {
|
||||
if txleft != 0 && spi.isWritable() && rxleft < txleft+fifoDepth {
|
||||
spi.Bus.SSPDR.Set(uint32(tx[plen-txleft]))
|
||||
txleft--
|
||||
|
||||
@@ -92,13 +92,13 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error {
|
||||
|
||||
// pad data if needed so it is long enough for correct byte alignment on writes.
|
||||
func (f flashBlockDevice) pad(p []byte) []byte {
|
||||
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
|
||||
if paddingNeeded == 0 {
|
||||
overflow := int64(len(p)) % f.WriteBlockSize()
|
||||
if overflow == 0 {
|
||||
return p
|
||||
}
|
||||
|
||||
padded := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
|
||||
return append(p, padded...)
|
||||
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
|
||||
return append(p, padding...)
|
||||
}
|
||||
|
||||
const memoryStart = 0x08000000
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//
|
||||
// This file provides access to runtime package that would not otherwise
|
||||
// be permitted due to linker dependencies.
|
||||
//
|
||||
|
||||
//go:linkname gosched runtime.Gosched
|
||||
func gosched()
|
||||
+24
-20
@@ -3,10 +3,10 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"machine/usb"
|
||||
"machine/usb/descriptor"
|
||||
|
||||
"errors"
|
||||
)
|
||||
|
||||
type USBDevice struct {
|
||||
@@ -29,7 +29,7 @@ type Serialer interface {
|
||||
RTS() bool
|
||||
}
|
||||
|
||||
var usbDescriptor = usb.DescriptorCDC
|
||||
var usbDescriptor = descriptor.CDC
|
||||
|
||||
var usbDescriptorConfig uint8 = usb.DescriptorConfigCDC
|
||||
|
||||
@@ -72,7 +72,7 @@ func usbProduct() string {
|
||||
// binary.
|
||||
func strToUTF16LEDescriptor(in string, out []byte) {
|
||||
out[0] = byte(len(out))
|
||||
out[1] = usb.STRING_DESCRIPTOR_TYPE
|
||||
out[1] = descriptor.TypeString
|
||||
for i, rune := range in {
|
||||
out[(i<<1)+2] = byte(rune)
|
||||
out[(i<<1)+3] = 0
|
||||
@@ -88,7 +88,7 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
usbEndpointDescriptors [usb.NumberOfEndpoints]usb.DeviceDescriptor
|
||||
usbEndpointDescriptors [usb.NumberOfEndpoints]descriptor.Device
|
||||
|
||||
isEndpointHalt = false
|
||||
isRemoteWakeUpEnabled = false
|
||||
@@ -134,27 +134,27 @@ var (
|
||||
// can be requested by the host.
|
||||
func sendDescriptor(setup usb.Setup) {
|
||||
switch setup.WValueH {
|
||||
case usb.CONFIGURATION_DESCRIPTOR_TYPE:
|
||||
case descriptor.TypeConfiguration:
|
||||
sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength)
|
||||
return
|
||||
case usb.DEVICE_DESCRIPTOR_TYPE:
|
||||
case descriptor.TypeDevice:
|
||||
// composite descriptor
|
||||
switch {
|
||||
case (usbDescriptorConfig & usb.DescriptorConfigHID) > 0:
|
||||
usbDescriptor = usb.DescriptorCDCHID
|
||||
usbDescriptor = descriptor.CDCHID
|
||||
case (usbDescriptorConfig & usb.DescriptorConfigMIDI) > 0:
|
||||
usbDescriptor = usb.DescriptorCDCMIDI
|
||||
usbDescriptor = descriptor.CDCMIDI
|
||||
case (usbDescriptorConfig & usb.DescriptorConfigJoystick) > 0:
|
||||
usbDescriptor = usb.DescriptorCDCJoystick
|
||||
usbDescriptor = descriptor.CDCJoystick
|
||||
default:
|
||||
usbDescriptor = usb.DescriptorCDC
|
||||
usbDescriptor = descriptor.CDC
|
||||
}
|
||||
|
||||
usbDescriptor.Configure(usbVendorID(), usbProductID())
|
||||
sendUSBPacket(0, usbDescriptor.Device, setup.WLength)
|
||||
return
|
||||
|
||||
case usb.STRING_DESCRIPTOR_TYPE:
|
||||
case descriptor.TypeString:
|
||||
switch setup.WValueL {
|
||||
case 0:
|
||||
usb_trans_buffer[0] = 0x04
|
||||
@@ -178,12 +178,12 @@ func sendDescriptor(setup usb.Setup) {
|
||||
SendZlp()
|
||||
}
|
||||
return
|
||||
case usb.HID_REPORT_TYPE:
|
||||
case descriptor.TypeHIDReport:
|
||||
if h, ok := usbDescriptor.HID[setup.WIndex]; ok {
|
||||
sendUSBPacket(0, h, setup.WLength)
|
||||
return
|
||||
}
|
||||
case usb.DEVICE_QUALIFIER:
|
||||
case descriptor.TypeDeviceQualifier:
|
||||
// skip
|
||||
default:
|
||||
}
|
||||
@@ -302,11 +302,15 @@ func EnableMIDI(txHandler func(), rxHandler func([]byte), setupHandler func(usb.
|
||||
|
||||
// EnableJoystick enables HID. This function must be executed from the init().
|
||||
func EnableJoystick(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool, hidDesc []byte) {
|
||||
idx := bytes.Index(usb.DescriptorCDCJoystick.Configuration, []byte{
|
||||
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22,
|
||||
})
|
||||
binary.LittleEndian.PutUint16(usb.DescriptorCDCJoystick.Configuration[idx+7:idx+9], uint16(len(hidDesc)))
|
||||
usb.DescriptorCDCJoystick.HID[2] = hidDesc
|
||||
class, err := descriptor.FindClassHIDType(descriptor.CDCJoystick.Configuration, descriptor.ClassHIDJoystick.Bytes())
|
||||
if err != nil {
|
||||
// TODO: some way to notify about error
|
||||
return
|
||||
}
|
||||
|
||||
class.ClassLength(uint16(len(hidDesc)))
|
||||
descriptor.CDCJoystick.HID[2] = hidDesc
|
||||
|
||||
usbDescriptorConfig |= usb.DescriptorConfigJoystick
|
||||
endPoints[usb.HID_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut)
|
||||
usbRxHandler[usb.HID_ENDPOINT_OUT] = rxHandler
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// package adc is for USB Audio Device Class devices.
|
||||
package adc
|
||||
@@ -0,0 +1,2 @@
|
||||
// package cdc is for USB Communication Device Class devices.
|
||||
package cdc
|
||||
@@ -1,179 +0,0 @@
|
||||
package usb
|
||||
|
||||
import "runtime/volatile"
|
||||
|
||||
// DeviceDescBank is the USB device endpoint descriptor.
|
||||
type DeviceDescBank struct {
|
||||
ADDR volatile.Register32
|
||||
PCKSIZE volatile.Register32
|
||||
EXTREG volatile.Register16
|
||||
STATUS_BK volatile.Register8
|
||||
_reserved [5]volatile.Register8
|
||||
}
|
||||
|
||||
type DeviceDescriptor struct {
|
||||
DeviceDescBank [2]DeviceDescBank
|
||||
}
|
||||
|
||||
type Descriptor struct {
|
||||
Device []byte
|
||||
Configuration []byte
|
||||
HID map[uint16][]byte
|
||||
}
|
||||
|
||||
func (d *Descriptor) Configure(idVendor, idProduct uint16) {
|
||||
d.Device[8] = byte(idVendor)
|
||||
d.Device[9] = byte(idVendor >> 8)
|
||||
d.Device[10] = byte(idProduct)
|
||||
d.Device[11] = byte(idProduct >> 8)
|
||||
|
||||
d.Configuration[2] = byte(len(d.Configuration))
|
||||
d.Configuration[3] = byte(len(d.Configuration) >> 8)
|
||||
}
|
||||
|
||||
var DescriptorCDC = Descriptor{
|
||||
Device: []byte{
|
||||
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x86, 0x28, 0x2d, 0x80, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01,
|
||||
},
|
||||
Configuration: []byte{
|
||||
0x09, 0x02, 0x4b, 0x00, 0x02, 0x01, 0x00, 0xa0, 0x32,
|
||||
0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,
|
||||
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
|
||||
0x05, 0x24, 0x00, 0x10, 0x01,
|
||||
0x04, 0x24, 0x02, 0x06,
|
||||
0x05, 0x24, 0x06, 0x00, 0x01,
|
||||
0x05, 0x24, 0x01, 0x01, 0x01,
|
||||
0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x10,
|
||||
0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
|
||||
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
var DescriptorCDCHID = Descriptor{
|
||||
Device: []byte{
|
||||
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x86, 0x28, 0x2d, 0x80, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01,
|
||||
},
|
||||
Configuration: []byte{
|
||||
0x09, 0x02, 0x64, 0x00, 0x03, 0x01, 0x00, 0xa0, 0x32,
|
||||
0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,
|
||||
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
|
||||
0x05, 0x24, 0x00, 0x10, 0x01,
|
||||
0x04, 0x24, 0x02, 0x06,
|
||||
0x05, 0x24, 0x06, 0x00, 0x01,
|
||||
0x05, 0x24, 0x01, 0x01, 0x01,
|
||||
0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x10,
|
||||
0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
|
||||
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
|
||||
0x09, 0x04, 0x02, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00,
|
||||
0x09, 0x21, 0x01, 0x01, 0x00, 0x01, 0x22, 0x7E, 0x00,
|
||||
0x07, 0x05, 0x84, 0x03, 0x40, 0x00, 0x01,
|
||||
},
|
||||
HID: map[uint16][]byte{
|
||||
2: []byte{
|
||||
// keyboard and mouse
|
||||
0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x02, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00,
|
||||
0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x03, 0x95, 0x06,
|
||||
0x75, 0x08, 0x15, 0x00, 0x25, 0xFF, 0x05, 0x07, 0x19, 0x00, 0x29, 0xFF, 0x81, 0x00, 0xc0, 0x05,
|
||||
0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x85, 0x01, 0x05, 0x09, 0x19, 0x01, 0x29,
|
||||
0x03, 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, 0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81,
|
||||
0x03, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95,
|
||||
0x03, 0x81, 0x06, 0xc0, 0xc0,
|
||||
|
||||
0x05, 0x0C, // Usage Page (Consumer)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, 0x03, // Report ID (3)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x1F, // Logical Maximum (8191)
|
||||
0x19, 0x00, // Usage Minimum (Unassigned)
|
||||
0x2A, 0xFF, 0x1F, // Usage Maximum (0x1FFF)
|
||||
0x75, 0x10, // Report Size (16)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, // End Collection
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var DescriptorCDCMIDI = Descriptor{
|
||||
Device: []byte{
|
||||
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x86, 0x28, 0x2d, 0x80, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01,
|
||||
},
|
||||
Configuration: []byte{
|
||||
0x09, 0x02, 0xaf, 0x00, 0x04, 0x01, 0x00, 0xa0, 0x32,
|
||||
0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,
|
||||
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
|
||||
0x05, 0x24, 0x00, 0x10, 0x01,
|
||||
0x04, 0x24, 0x02, 0x06,
|
||||
0x05, 0x24, 0x06, 0x00, 0x01,
|
||||
0x05, 0x24, 0x01, 0x01, 0x01,
|
||||
0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x10,
|
||||
0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
|
||||
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
|
||||
0x08, 0x0b, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00,
|
||||
0x09, 0x04, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
|
||||
0x09, 0x24, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, 0x03,
|
||||
0x09, 0x04, 0x03, 0x00, 0x02, 0x01, 0x03, 0x00, 0x00,
|
||||
0x07, 0x24, 0x01, 0x00, 0x01, 0x41, 0x00,
|
||||
0x06, 0x24, 0x02, 0x01, 0x01, 0x00,
|
||||
0x06, 0x24, 0x02, 0x02, 0x02, 0x00,
|
||||
0x09, 0x24, 0x03, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00,
|
||||
0x09, 0x24, 0x03, 0x02, 0x04, 0x01, 0x01, 0x01, 0x00,
|
||||
0x09, 0x05, 0x07, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
0x05, 0x25, 0x01, 0x01, 0x01,
|
||||
0x09, 0x05, 0x86, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
0x05, 0x25, 0x01, 0x01, 0x03,
|
||||
},
|
||||
}
|
||||
|
||||
var DescriptorCDCJoystick = Descriptor{
|
||||
Device: []byte{
|
||||
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40,
|
||||
0x41, 0x23, 0x36, 0x80, 0x00, 0x01, 0x01, 0x02,
|
||||
0x03, 0x01,
|
||||
},
|
||||
Configuration: []byte{
|
||||
// Configuration Descriptor Header
|
||||
0x09, 0x02,
|
||||
0x6b, 0x00, // Total Length: 0x006b(107)
|
||||
0x03, 0x01, 0x00, 0xa0, 0xfa,
|
||||
// InterfaceAssociation Descriptoy
|
||||
0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,
|
||||
// InterfaceSescriptor(CDC-Ctrl)
|
||||
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
|
||||
// Communication Descriptor
|
||||
0x05, 0x24, 0x00, 0x10, 0x01,
|
||||
// Communication Descriptor
|
||||
0x05, 0x24, 0x01, 0x01, 0x01,
|
||||
// Communication Descriptor
|
||||
0x04, 0x24, 0x02, 0x06,
|
||||
// Communication Descriptor
|
||||
0x05, 0x24, 0x06, 0x00, 0x01,
|
||||
// ENDPOINT Descriptor
|
||||
0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x40,
|
||||
// InterfaceSescriptor(CDC-Data)
|
||||
0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00,
|
||||
// ENDPOINT Descriptor
|
||||
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
|
||||
// ENDPOINT Descriptor
|
||||
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
|
||||
// InterfaceSescriptor(HID)
|
||||
0x09, 0x04, 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00,
|
||||
// HID Descriptor
|
||||
0x09, // bLength: 9
|
||||
0x21, // bDescriptorType: 0x21(HID)
|
||||
0x11, 0x01, // bcdHID: 0x111
|
||||
0x00, // bCountryCode: Not Supported
|
||||
0x01, // bNumDescriptors: 1
|
||||
0x22, // Type: HID Report
|
||||
0x00, 0x00, // Length
|
||||
// ENDPOINT Descriptor
|
||||
0x07, 0x05, 0x84, 0x03, 0x40, 0x00, 0x01,
|
||||
// ENDPOINT Descriptor
|
||||
0x07, 0x05, 0x05, 0x03, 0x40, 0x00, 0x01,
|
||||
},
|
||||
HID: map[uint16][]byte{},
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package descriptor
|
||||
|
||||
const (
|
||||
cdcFunctionalHeader = 0
|
||||
cdcFunctionalCallManagement = 0x1
|
||||
cdcFunctionalACM = 0x2
|
||||
cdcFunctionalDirect = 0x3
|
||||
cdcFunctionalRinger = 0x4
|
||||
cdcFunctionalCall = 0x5
|
||||
cdcFunctionalUnion = 0x6
|
||||
cdcFunctionalCountry = 0x7
|
||||
cdcFunctionalOperational = 0x8
|
||||
cdcFunctionalUSB = 0x9
|
||||
cdcFunctionalNetwork = 0xa
|
||||
cdcFunctionalProtocol = 0xb
|
||||
cdcFunctionalExtension = 0xc
|
||||
cdcFunctionalMulti = 0xd
|
||||
cdcFunctionalCAPI = 0xe
|
||||
cdcFunctionalEthernet = 0xf
|
||||
cdcFunctionalATM = 0x10
|
||||
)
|
||||
|
||||
var classSpecificCDCHeader = [classSpecificTypeLen]byte{
|
||||
classSpecificTypeLen,
|
||||
TypeClassSpecific,
|
||||
cdcFunctionalHeader,
|
||||
0x10, //
|
||||
0x1, //
|
||||
}
|
||||
|
||||
var ClassSpecificCDCHeader = ClassSpecificType{
|
||||
data: classSpecificCDCHeader[:],
|
||||
}
|
||||
|
||||
var classSpecificCDCCallManagement = [classSpecificTypeLen]byte{
|
||||
classSpecificTypeLen,
|
||||
TypeClassSpecific,
|
||||
cdcFunctionalCallManagement,
|
||||
0x0, //
|
||||
0x1, //
|
||||
}
|
||||
|
||||
var ClassSpecificCDCCallManagement = ClassSpecificType{
|
||||
data: classSpecificCDCCallManagement[:],
|
||||
}
|
||||
|
||||
var classSpecificCDCACM = [classSpecificTypeLen]byte{
|
||||
4,
|
||||
TypeClassSpecific,
|
||||
cdcFunctionalACM,
|
||||
0x2, //
|
||||
}
|
||||
|
||||
var ClassSpecificCDCACM = ClassSpecificType{
|
||||
data: classSpecificCDCACM[:],
|
||||
}
|
||||
|
||||
var classSpecificCDCUnion = [classSpecificTypeLen]byte{
|
||||
classSpecificTypeLen,
|
||||
TypeClassSpecific,
|
||||
cdcFunctionalUnion,
|
||||
0x0, //
|
||||
0x1, //
|
||||
}
|
||||
|
||||
var ClassSpecificCDCUnion = ClassSpecificType{
|
||||
data: classSpecificCDCUnion[:],
|
||||
}
|
||||
|
||||
var interfaceAssociationCDC = [interfaceAssociationTypeLen]byte{
|
||||
interfaceAssociationTypeLen,
|
||||
TypeInterfaceAssociation,
|
||||
0x00, // FirstInterface
|
||||
0x02, // InterfaceCount
|
||||
0x02, // FunctionClass
|
||||
0x02, // FunctionSubClass
|
||||
0x01, // FunctionProtocol
|
||||
0x00, // Function
|
||||
}
|
||||
|
||||
var InterfaceAssociationCDC = InterfaceAssociationType{
|
||||
data: interfaceAssociationCDC[:],
|
||||
}
|
||||
|
||||
var deviceCDC = [deviceTypeLen]byte{
|
||||
deviceTypeLen,
|
||||
TypeDevice,
|
||||
0x00, 0x02, // USB version
|
||||
0xef, // device class
|
||||
0x02, // device subclass
|
||||
0x01, // protocol
|
||||
0x40, // maxpacketsize
|
||||
0x86, 0x28, // vendor id
|
||||
0x2d, 0x80, // product id
|
||||
0x00, 0x01, // device
|
||||
0x01, // manufacturer
|
||||
0x02, // product
|
||||
0x03, // SerialNumber
|
||||
0x01, // NumConfigurations
|
||||
}
|
||||
|
||||
var DeviceCDC = DeviceType{
|
||||
data: deviceCDC[:],
|
||||
}
|
||||
|
||||
var configurationCDC = [configurationTypeLen]byte{
|
||||
configurationTypeLen,
|
||||
TypeConfiguration,
|
||||
0x4b, 0x00, // adjust length as needed
|
||||
0x02, // number of interfaces
|
||||
0x01, // configuration value
|
||||
0x00, // index to string description
|
||||
0xa0, // attributes
|
||||
0x32, // maxpower
|
||||
}
|
||||
|
||||
var ConfigurationCDC = ConfigurationType{
|
||||
data: configurationCDC[:],
|
||||
}
|
||||
|
||||
var interfaceCDCControl = [interfaceTypeLen]byte{
|
||||
interfaceTypeLen,
|
||||
TypeInterface,
|
||||
0x00, // InterfaceNumber
|
||||
0x00, // AlternateSetting
|
||||
0x01, // NumEndpoints
|
||||
0x02, // InterfaceClass
|
||||
0x02, // InterfaceSubClass
|
||||
0x01, // InterfaceProtocol
|
||||
0x00, // Interface
|
||||
}
|
||||
|
||||
var InterfaceCDCControl = InterfaceType{
|
||||
data: interfaceCDCControl[:],
|
||||
}
|
||||
|
||||
var interfaceCDCData = [interfaceTypeLen]byte{
|
||||
interfaceTypeLen,
|
||||
TypeInterface,
|
||||
0x01, // InterfaceNumber
|
||||
0x00, // AlternateSetting
|
||||
0x02, // NumEndpoints
|
||||
0x0a, // InterfaceClass
|
||||
0x00, // InterfaceSubClass
|
||||
0x00, // InterfaceProtocol
|
||||
0x00, // Interface
|
||||
}
|
||||
|
||||
var InterfaceCDCData = InterfaceType{
|
||||
data: interfaceCDCData[:],
|
||||
}
|
||||
|
||||
var CDC = Descriptor{
|
||||
Device: DeviceCDC.Bytes(),
|
||||
Configuration: Append([][]byte{
|
||||
ConfigurationCDC.Bytes(),
|
||||
InterfaceAssociationCDC.Bytes(),
|
||||
InterfaceCDCControl.Bytes(),
|
||||
ClassSpecificCDCHeader.Bytes(),
|
||||
ClassSpecificCDCCallManagement.Bytes(),
|
||||
ClassSpecificCDCACM.Bytes(),
|
||||
ClassSpecificCDCUnion.Bytes(),
|
||||
EndpointEP1IN.Bytes(),
|
||||
InterfaceCDCData.Bytes(),
|
||||
EndpointEP2OUT.Bytes(),
|
||||
EndpointEP3IN.Bytes(),
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package descriptor
|
||||
|
||||
const (
|
||||
classSpecificTypeLen = 5
|
||||
)
|
||||
|
||||
type ClassSpecificType struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (d ClassSpecificType) Bytes() []byte {
|
||||
return d.data[:d.data[0]]
|
||||
}
|
||||
|
||||
func (d ClassSpecificType) Length(v uint8) {
|
||||
d.data[0] = byte(v)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package descriptor
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
configurationTypeLen = 9
|
||||
)
|
||||
|
||||
type ConfigurationType struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (d ConfigurationType) Bytes() []byte {
|
||||
return d.data
|
||||
}
|
||||
|
||||
func (d ConfigurationType) Length(v uint8) {
|
||||
d.data[0] = byte(v)
|
||||
}
|
||||
|
||||
func (d ConfigurationType) Type(v uint8) {
|
||||
d.data[1] = byte(v)
|
||||
}
|
||||
|
||||
func (d ConfigurationType) TotalLength(v uint16) {
|
||||
binary.LittleEndian.PutUint16(d.data[2:4], v)
|
||||
}
|
||||
|
||||
func (d ConfigurationType) NumInterfaces(v uint8) {
|
||||
d.data[4] = byte(v)
|
||||
}
|
||||
|
||||
func (d ConfigurationType) ConfigurationValue(v uint8) {
|
||||
d.data[5] = byte(v)
|
||||
}
|
||||
|
||||
func (d ConfigurationType) Configuration(v uint8) {
|
||||
d.data[6] = byte(v)
|
||||
}
|
||||
|
||||
func (d ConfigurationType) Attributes(v uint8) {
|
||||
d.data[7] = byte(v)
|
||||
}
|
||||
|
||||
func (d ConfigurationType) MaxPower(v uint8) {
|
||||
d.data[8] = byte(v)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package descriptor
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeDevice = 0x1
|
||||
TypeConfiguration = 0x2
|
||||
TypeString = 0x3
|
||||
TypeInterface = 0x4
|
||||
TypeEndpoint = 0x5
|
||||
TypeDeviceQualifier = 0x6
|
||||
TypeInterfaceAssociation = 0xb
|
||||
TypeClassHID = 0x21
|
||||
TypeHIDReport = 0x22
|
||||
TypeClassSpecific = 0x24
|
||||
TypeClassSpecificEndpoint = 0x25
|
||||
)
|
||||
|
||||
// DeviceDescBank is the USB device endpoint .
|
||||
type DeviceDescBank struct {
|
||||
ADDR volatile.Register32
|
||||
PCKSIZE volatile.Register32
|
||||
EXTREG volatile.Register16
|
||||
STATUS_BK volatile.Register8
|
||||
_reserved [5]volatile.Register8
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
DeviceDescBank [2]DeviceDescBank
|
||||
}
|
||||
|
||||
type Descriptor struct {
|
||||
Device []byte
|
||||
Configuration []byte
|
||||
HID map[uint16][]byte
|
||||
}
|
||||
|
||||
func (d *Descriptor) Configure(idVendor, idProduct uint16) {
|
||||
dev := DeviceType{d.Device}
|
||||
dev.VendorID(idVendor)
|
||||
dev.ProductID(idProduct)
|
||||
|
||||
conf := ConfigurationType{d.Configuration}
|
||||
conf.TotalLength(uint16(len(d.Configuration)))
|
||||
}
|
||||
|
||||
func Append[T any](slices [][]T) []T {
|
||||
var size, pos int
|
||||
|
||||
for _, s := range slices {
|
||||
size += len(s)
|
||||
}
|
||||
|
||||
result := make([]T, size)
|
||||
|
||||
for _, s := range slices {
|
||||
pos += copy(result[pos:], s)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package descriptor
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
deviceTypeLen = 18
|
||||
)
|
||||
|
||||
type DeviceType struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (d DeviceType) Bytes() []byte {
|
||||
return d.data
|
||||
}
|
||||
|
||||
func (d DeviceType) Length(v uint8) {
|
||||
d.data[0] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) Type(v uint8) {
|
||||
d.data[1] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) USB(v uint16) {
|
||||
binary.LittleEndian.PutUint16(d.data[2:4], v)
|
||||
}
|
||||
|
||||
func (d DeviceType) DeviceClass(v uint8) {
|
||||
d.data[4] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) DeviceSubClass(v uint8) {
|
||||
d.data[5] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) DeviceProtocol(v uint8) {
|
||||
d.data[6] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) MaxPacketSize0(v uint8) {
|
||||
d.data[7] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) VendorID(v uint16) {
|
||||
binary.LittleEndian.PutUint16(d.data[8:10], v)
|
||||
}
|
||||
|
||||
func (d DeviceType) ProductID(v uint16) {
|
||||
binary.LittleEndian.PutUint16(d.data[10:12], v)
|
||||
}
|
||||
|
||||
func (d DeviceType) Device(v uint16) {
|
||||
binary.LittleEndian.PutUint16(d.data[12:14], v)
|
||||
}
|
||||
|
||||
func (d DeviceType) Manufacturer(v uint8) {
|
||||
d.data[14] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) Product(v uint8) {
|
||||
d.data[15] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) SerialNumber(v uint8) {
|
||||
d.data[16] = byte(v)
|
||||
}
|
||||
|
||||
func (d DeviceType) NumConfigurations(v uint8) {
|
||||
d.data[17] = byte(v)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// package descriptor is for the USB descriptor definitions.
|
||||
// For the actual implementations, see the individual packages.
|
||||
package descriptor
|
||||
@@ -0,0 +1,111 @@
|
||||
package descriptor
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
var endpointEP1IN = [endpointTypeLen]byte{
|
||||
endpointTypeLen,
|
||||
TypeEndpoint,
|
||||
0x81, // EndpointAddress
|
||||
0x03, // Attributes
|
||||
0x10, // MaxPacketSizeL
|
||||
0x00, // MaxPacketSizeH
|
||||
0x10, // Interval
|
||||
}
|
||||
|
||||
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[:],
|
||||
}
|
||||
|
||||
const (
|
||||
endpointTypeLen = 7
|
||||
)
|
||||
|
||||
type EndpointType struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (d EndpointType) Bytes() []byte {
|
||||
return d.data
|
||||
}
|
||||
|
||||
func (d EndpointType) Length(v uint8) {
|
||||
d.data[0] = byte(v)
|
||||
}
|
||||
|
||||
func (d EndpointType) Type(v uint8) {
|
||||
d.data[1] = byte(v)
|
||||
}
|
||||
|
||||
func (d EndpointType) EndpointAddress(v uint8) {
|
||||
d.data[2] = byte(v)
|
||||
}
|
||||
|
||||
func (d EndpointType) Attributes(v uint8) {
|
||||
d.data[3] = byte(v)
|
||||
}
|
||||
|
||||
func (d EndpointType) MaxPacketSize(v uint16) {
|
||||
binary.LittleEndian.PutUint16(d.data[4:6], v)
|
||||
}
|
||||
|
||||
func (d EndpointType) Interval(v uint8) {
|
||||
d.data[6] = byte(v)
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package descriptor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var configurationCDCHID = [configurationTypeLen]byte{
|
||||
configurationTypeLen,
|
||||
TypeConfiguration,
|
||||
0x64, 0x00, // adjust length as needed
|
||||
0x03, // number of interfaces
|
||||
0x01, // configuration value
|
||||
0x00, // index to string description
|
||||
0xa0, // attributes
|
||||
0x32, // maxpower
|
||||
}
|
||||
|
||||
var ConfigurationCDCHID = ConfigurationType{
|
||||
data: configurationCDCHID[:],
|
||||
}
|
||||
|
||||
var interfaceHID = [interfaceTypeLen]byte{
|
||||
interfaceTypeLen,
|
||||
TypeInterface,
|
||||
0x02, // InterfaceNumber
|
||||
0x00, // AlternateSetting
|
||||
0x01, // NumEndpoints
|
||||
0x03, // InterfaceClass
|
||||
0x00, // InterfaceSubClass
|
||||
0x00, // InterfaceProtocol
|
||||
0x00, // Interface
|
||||
}
|
||||
|
||||
var InterfaceHID = InterfaceType{
|
||||
data: interfaceHID[:],
|
||||
}
|
||||
|
||||
const (
|
||||
ClassHIDTypeLen = 9
|
||||
)
|
||||
|
||||
type ClassHIDType struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (d ClassHIDType) Bytes() []byte {
|
||||
return d.data[:]
|
||||
}
|
||||
|
||||
func (d ClassHIDType) Length(v uint8) {
|
||||
d.data[0] = byte(v)
|
||||
}
|
||||
|
||||
func (d ClassHIDType) Type(v uint8) {
|
||||
d.data[1] = byte(v)
|
||||
}
|
||||
|
||||
func (d ClassHIDType) HID(v uint16) {
|
||||
binary.LittleEndian.PutUint16(d.data[2:4], v)
|
||||
}
|
||||
|
||||
func (d ClassHIDType) CountryCode(v uint8) {
|
||||
d.data[4] = byte(v)
|
||||
}
|
||||
|
||||
func (d ClassHIDType) NumDescriptors(v uint8) {
|
||||
d.data[5] = byte(v)
|
||||
}
|
||||
|
||||
func (d ClassHIDType) ClassType(v uint8) {
|
||||
d.data[6] = byte(v)
|
||||
}
|
||||
|
||||
func (d ClassHIDType) ClassLength(v uint16) {
|
||||
binary.LittleEndian.PutUint16(d.data[7:9], v)
|
||||
}
|
||||
|
||||
var errNoClassHIDFound = errors.New("no classHID found")
|
||||
|
||||
// FindClassHIDType tries to find the ClassHID class in the descriptor.
|
||||
func FindClassHIDType(des, class []byte) (ClassHIDType, error) {
|
||||
if len(des) < ClassHIDTypeLen || len(class) == 0 {
|
||||
return ClassHIDType{}, errNoClassHIDFound
|
||||
}
|
||||
|
||||
// search only for ClassHIDType without the ClassLength,
|
||||
// in case it has already been set.
|
||||
idx := bytes.Index(des, class[:ClassHIDTypeLen-2])
|
||||
if idx == -1 {
|
||||
return ClassHIDType{}, errNoClassHIDFound
|
||||
}
|
||||
|
||||
return ClassHIDType{data: des[idx : idx+ClassHIDTypeLen]}, nil
|
||||
}
|
||||
|
||||
var classHID = [ClassHIDTypeLen]byte{
|
||||
ClassHIDTypeLen,
|
||||
TypeClassHID,
|
||||
0x11, // HID version L
|
||||
0x01, // HID version H
|
||||
0x00, // CountryCode
|
||||
0x01, // NumDescriptors
|
||||
0x22, // ClassType
|
||||
0x7E, // ClassLength L
|
||||
0x00, // ClassLength H
|
||||
}
|
||||
|
||||
var ClassHID = ClassHIDType{
|
||||
data: classHID[:],
|
||||
}
|
||||
|
||||
var CDCHID = Descriptor{
|
||||
Device: DeviceCDC.Bytes(),
|
||||
Configuration: Append([][]byte{
|
||||
ConfigurationCDCHID.Bytes(),
|
||||
InterfaceAssociationCDC.Bytes(),
|
||||
InterfaceCDCControl.Bytes(),
|
||||
ClassSpecificCDCHeader.Bytes(),
|
||||
ClassSpecificCDCACM.Bytes(),
|
||||
ClassSpecificCDCUnion.Bytes(),
|
||||
ClassSpecificCDCCallManagement.Bytes(),
|
||||
EndpointEP1IN.Bytes(),
|
||||
InterfaceCDCData.Bytes(),
|
||||
EndpointEP2OUT.Bytes(),
|
||||
EndpointEP3IN.Bytes(),
|
||||
InterfaceHID.Bytes(),
|
||||
ClassHID.Bytes(),
|
||||
EndpointEP4IN.Bytes(),
|
||||
}),
|
||||
HID: map[uint16][]byte{
|
||||
2: Append([][]byte{
|
||||
HIDUsagePageGenericDesktop,
|
||||
HIDUsageDesktopKeyboard,
|
||||
HIDCollectionApplication,
|
||||
HIDReportID(2),
|
||||
|
||||
HIDUsagePageKeyboard,
|
||||
HIDUsageMinimum(224),
|
||||
HIDUsageMaximum(231),
|
||||
HIDLogicalMinimum(0),
|
||||
HIDLogicalMaximum(1),
|
||||
HIDReportSize(1),
|
||||
HIDReportCount(8),
|
||||
HIDInputDataVarAbs,
|
||||
HIDReportCount(1),
|
||||
HIDReportSize(8),
|
||||
HIDInputConstVarAbs,
|
||||
HIDReportCount(6),
|
||||
HIDReportSize(8),
|
||||
HIDLogicalMinimum(0),
|
||||
HIDLogicalMaximum(255),
|
||||
|
||||
HIDUsagePageKeyboard,
|
||||
HIDUsageMinimum(0),
|
||||
HIDUsageMaximum(255),
|
||||
HIDInputDataAryAbs,
|
||||
HIDCollectionEnd,
|
||||
|
||||
HIDUsagePageGenericDesktop,
|
||||
HIDUsageDesktopMouse,
|
||||
HIDCollectionApplication,
|
||||
HIDUsageDesktopPointer,
|
||||
HIDCollectionPhysical,
|
||||
HIDReportID(1),
|
||||
|
||||
HIDUsagePageButton,
|
||||
HIDUsageMinimum(1),
|
||||
HIDUsageMaximum(5),
|
||||
HIDLogicalMinimum(0),
|
||||
HIDLogicalMaximum(1),
|
||||
HIDReportCount(5),
|
||||
HIDReportSize(1),
|
||||
HIDInputDataVarAbs,
|
||||
HIDReportCount(1),
|
||||
HIDReportSize(3),
|
||||
HIDInputConstVarAbs,
|
||||
|
||||
HIDUsagePageGenericDesktop,
|
||||
HIDUsageDesktopX,
|
||||
HIDUsageDesktopY,
|
||||
HIDUsageDesktopWheel,
|
||||
HIDLogicalMinimum(-127),
|
||||
HIDLogicalMaximum(127),
|
||||
HIDReportSize(8),
|
||||
HIDReportCount(3),
|
||||
HIDInputDataVarRel,
|
||||
HIDCollectionEnd,
|
||||
HIDCollectionEnd,
|
||||
|
||||
HIDUsagePageConsumer,
|
||||
HIDUsageConsumerControl,
|
||||
HIDCollectionApplication,
|
||||
HIDReportID(3),
|
||||
HIDLogicalMinimum(0),
|
||||
HIDLogicalMaximum(8191),
|
||||
HIDUsageMinimum(0),
|
||||
HIDUsageMaximum(0x1FFF),
|
||||
HIDReportSize(16),
|
||||
HIDReportCount(1),
|
||||
HIDInputDataAryAbs,
|
||||
HIDCollectionEnd}),
|
||||
},
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user