Compare commits

...

10 Commits

Author SHA1 Message Date
deadprogram b9ce6c3374 build: allow building llvm image via workflow dispatch
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-11 13:08:20 +01:00
Ayke van Laethem cab3834bc9 riscv-qemu: add VirtIO RNG device
This implements machine.GetRNG() using VirtIO. This gets the tests to
pass for crypto/md5 and crypto/sha1 that use crypto/rand in their tests.
2025-03-11 04:22:16 -07:00
Elias Naur 7f970a45c2 machine: don't block the rp2xxx UART interrupt handler
Don't block forever if there's nothing to receive. On the other hand,
process the entire FIFO, not just a single byte.

This fixes an issue where the rp2350 would hang after programming through
openocd, where the UART0 interrupt would be spuriously pending.
2025-03-11 02:01:18 -07:00
Ayke van Laethem ebf70ab18e ci: add more tests for wasm and baremetal
Run a range of tests in CI, to make sure browser wasm and baremetal
don't regress too badly.

I have intentionally filtered out tests, so that newly added tests to
TEST_PACKAGES_FAST will be added here as well (and can be excluded if
needed).
2025-03-10 03:26:30 -07:00
Elias Naur 226744a538 machine: correct register address for Pin.SetInterrupt for rp2350 (#4782)
Fixes #4689
2025-03-09 10:26:38 -03:00
Ayke van Laethem dc876c6ed4 ci: add single test for wasm
It looks like we didn't have any tests for wasm. Having one tests is not
much, but it proves that the infrastructure works and it actually
verifies a fix to https://github.com/tinygo-org/tinygo/issues/4777.

We should add more packages to this list in the future.
2025-03-07 09:09:13 -08:00
Ayke van Laethem 5a09084c73 os: add stub Symlink for wasm
This doesn't do anything, but it makes tests work.
2025-03-07 09:09:13 -08:00
Ayke van Laethem 6c9074772c compiler: crypto/internal/sysrand is allowed to use unsafe signatures
Apparently this package imports `runtime.getRandomData` from the gojs
module. This is not yet implemented, but simply allowing this package to
do such imports gets crypto/sha256 to compile.
2025-03-07 09:09:13 -08:00
Ayke van Laethem e49663809b machine: fix RP2040 Pico board on the playground
Right now it doesn't compile, with errors like the following:

    # machine
    /app/tinygo/src/machine/board_pico.go:7:13: undefined: GPIO0
    /app/tinygo/src/machine/board_pico.go:8:13: undefined: GPIO1
    /app/tinygo/src/machine/board_pico.go:9:13: undefined: GPIO2
    /app/tinygo/src/machine/board_pico.go:10:13: undefined: GPIO3
    [...etc...]

This patch should fix that.
2025-03-07 07:20:40 -08:00
Elias Naur 7d6d93f7aa machine: bump rp2040 to 200MHz (#4768)
* machine: add support for core voltage adjustments to rp2040

In preparation for bumping the core frequency of the rp2040, this
change implements the required core voltage adjustment logic.

* machine: bump rp2040 to 200MHz
2025-03-04 08:57:59 -03:00
17 changed files with 286 additions and 21 deletions
+2 -1
View File
@@ -165,6 +165,7 @@ jobs:
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
- run: make tinygo-test-wasip1-fast
- run: make tinygo-test-wasip2-fast
- run: make tinygo-test-wasm
- run: make smoketest
assert-test-linux:
# Run all tests that can run on Linux, with LLVM assertions enabled to catch
@@ -277,7 +278,7 @@ jobs:
run: make tinygo-test
- run: make smoketest
- run: make wasmtest
- run: make tinygo-baremetal
- run: make tinygo-test-baremetal
build-linux-cross:
# Build ARM Linux binaries, ready for release.
# This intentionally uses an older Linux image, so that we compile against
+1
View File
@@ -11,6 +11,7 @@ name: LLVM
on:
push:
branches: [ build-llvm-image ]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+45 -5
View File
@@ -407,6 +407,43 @@ TEST_PACKAGES_WINDOWS := \
text/template/parse \
$(nil)
# These packages cannot be tested on wasm, mostly because these tests assume a
# working filesystem. This could perhaps be fixed, by supporting filesystem
# access when running inside Node.js.
TEST_PACKAGES_WASM = $(filter-out $(TEST_PACKAGES_NONWASM), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NONWASM = \
compress/lzw \
compress/zlib \
crypto/ecdsa \
debug/macho \
embed/internal/embedtest \
go/format \
os \
testing \
$(nil)
# These packages cannot be tested on baremetal.
#
# Some reasons why the tests don't pass on baremetal:
#
# * No filesystem is available, so packages like compress/zlib can't be tested
# (just like wasm).
# * picolibc math functions apparently are less precise, the math package
# fails on baremetal.
# * Some packages fail or hang for an unknown reason, this should be
# investigated and fixed.
TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NONBAREMETAL = \
$(TEST_PACKAGES_NONWASM) \
crypto/elliptic \
math \
reflect \
encoding/asn1 \
encoding/base32 \
go/ast \
$(nil)
# Report platforms on which each standard library package is known to pass tests
jointmp := $(shell echo /tmp/join.$$$$)
report-stdlib-tests-pass:
@@ -450,6 +487,8 @@ tinygo-bench-fast:
$(TINYGO) test -bench . $(TEST_PACKAGES_HOST)
# Same thing, except for wasi rather than the current platform.
tinygo-test-wasm:
$(TINYGO) test -target wasm $(TEST_PACKAGES_WASM)
tinygo-test-wasi:
$(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasip1:
@@ -484,6 +523,10 @@ tinygo-bench-wasip2:
tinygo-bench-wasip2-fast:
$(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST)
# Run tests on riscv-qemu since that one provides a large amount of memory.
tinygo-test-baremetal:
$(TINYGO) test -target riscv-qemu $(TEST_PACKAGES_BAREMETAL)
# Test external packages in a large corpus.
test-corpus:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml
@@ -494,11 +537,6 @@ test-corpus-wasi: wasi-libc
test-corpus-wasip2: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2
tinygo-baremetal:
# Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest.
# regression test for #2666: e.g. encoding/hex must pass on baremetal
$(TINYGO) test -target cortex-m-qemu encoding/hex
.PHONY: testchdir
testchdir:
# test 'build' command with{,out} -C argument
@@ -585,6 +623,8 @@ ifneq ($(WASM), 0)
@$(MD5SUM) test.wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1
@$(MD5SUM) test.wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=pico examples/blinky1
@$(MD5SUM) test.wasm
endif
# test all targets/boards
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
+1 -1
View File
@@ -446,7 +446,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
// The list of allowed types is based on this proposal:
// https://github.com/golang/go/issues/59149
func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) {
if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" {
if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" || c.pkg.Path() == "crypto/internal/sysrand" {
// The runtime is a special case. Allow all kinds of parameters
// (importantly, including pointers).
return
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)
// If you update the above build constraint, you'll probably also need to update
// src/runtime/rand_hwrng.go.
+14 -1
View File
@@ -9,7 +9,7 @@ import (
)
const (
cpuFreq = 125 * MHz
cpuFreq = 200 * MHz
_NUMBANK0_GPIOS = 30
_NUMBANK0_IRQS = 4
_NUMIRQ = 32
@@ -208,3 +208,16 @@ func (clks *clocksType) initTicks() {} // No ticks on RP2040
func (wd *watchdogImpl) startTick(cycles uint32) {
rp.WATCHDOG.TICK.Set(cycles | rp.WATCHDOG_TICK_ENABLE)
}
func adjustCoreVoltage() bool {
if cpuFreq <= 133*MHz {
return false
}
// The rp2040 is certified to run at 200MHz with the
// core voltage set to 1150mV.
const targetVoltage = 1150
// 0b0101 maps to 800mV and each step is 50mV.
const vreg = 0b0101 + (targetVoltage-800)/50
rp.VREG_AND_CHIP_RESET.SetVREG_VSEL(vreg)
return true
}
+4
View File
@@ -222,3 +222,7 @@ func EnterBootloader() {
func (wd *watchdogImpl) startTick(cycles uint32) {
rp.TICKS.WATCHDOG_CTRL.SetBits(1)
}
func adjustCoreVoltage() bool {
return false
}
+12
View File
@@ -42,6 +42,10 @@ type clock struct {
cix clockIndex
}
// The delay in seconds for core voltage adjustments to
// settle. Taken from the Pico SDK.
const _VREG_VOLTAGE_AUTO_ADJUST_DELAY = 1 / 1e3
// clock returns the clock identified by cix.
func (clks *clocksType) clock(cix clockIndex) clock {
return clock{
@@ -188,6 +192,14 @@ func (clks *clocksType) init() {
xoscFreq,
xoscFreq)
if adjustCoreVoltage() {
// Wait for the voltage to settle.
const cycles = _VREG_VOLTAGE_AUTO_ADJUST_DELAY * xoscFreq * MHz
for i := 0; i < cycles; i++ {
arm.Asm("nop")
}
}
// clkSys = pllSys (125MHz) / 1 = 125MHz
csys := clks.clock(clkSys)
csys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX,
+1
View File
@@ -33,6 +33,7 @@ type irqSummary struct {
type ioBank0Type struct {
io [_NUMBANK0_GPIOS]ioType
_ [rp2350ExtraReg][128]byte
irqsum [rp2350ExtraReg]irqSummary
intR [_NUMBANK0_IRQS]volatile.Register32
proc0IRQctrl irqCtrl
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build rp2040 || rp2350 || gopher_badge
//go:build rp2040 || rp2350 || gopher_badge || pico
package machine
+3 -3
View File
@@ -67,7 +67,7 @@ func (uart *UART) Configure(config UARTConfig) error {
uart.Interrupt.SetPriority(0x80)
uart.Interrupt.Enable()
// setup interrupt on receive
// Setup interrupt on receive.
uart.Bus.UARTIMSC.Set(rp.UART0_UARTIMSC_RXIM)
return nil
@@ -153,7 +153,7 @@ func initUART(uart *UART) {
// handleInterrupt should be called from the appropriate interrupt handler for
// this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_RXFE) {
for !uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_RXFE) {
uart.Receive(byte((uart.Bus.UARTDR.Get() & 0xFF)))
}
uart.Receive(byte((uart.Bus.UARTDR.Get() & 0xFF)))
}
+189
View File
@@ -0,0 +1,189 @@
//go:build tinygo.riscv32 && virt
// Machine implementation for VirtIO targets.
// At the moment only QEMU RISC-V is supported, but support for ARM for example
// should not be difficult to add with a change to virtioFindDevice.
package machine
import (
"errors"
"runtime/volatile"
"sync"
"unsafe"
)
const deviceName = "riscv-qemu"
func (p Pin) Set(high bool) {
// no pins defined
}
var rngLock sync.Mutex
var rngDevice *virtioDevice1
var rngBuf volatile.Register32
var errNoRNG = errors.New("machine: no entropy source found")
var errNoRNGData = errors.New("machine: entropy source didn't return enough data")
// GetRNG returns random numbers from a VirtIO entropy source.
// When running in QEMU, it requires adding the RNG device:
//
// -device virtio-rng-device
func GetRNG() (uint32, error) {
rngLock.Lock()
// Initialize the device on first use.
if rngDevice == nil {
// Search for an available RNG.
rngDevice = virtioFindDevice(virtioDeviceEntropySource)
if rngDevice == nil {
rngLock.Unlock()
return 0, errNoRNG
}
// Initialize the device.
rngDevice.status.Set(0) // reset device
rngDevice.status.Set(virtioDeviceStatusAcknowledge)
rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver)
rngDevice.hostFeaturesSel.Set(0)
rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver | virtioDeviceStatusDriverOk)
rngDevice.guestPageSize.Set(4096)
// Configure queue, according to section 4.2.4 "Legacy interface".
// Note: we're skipping checks for queuePFM and queueNumMax.
rngDevice.queueSel.Set(0) // use queue 0 (the only queue)
rngDevice.queueNum.Set(1) // use a single buffer in the queue
rngDevice.queueAlign.Set(4096) // default alignment appears to be 4096
rngDevice.queuePFN.Set(uint32(uintptr(unsafe.Pointer(&rngQueue))) / 4096)
// Configure the only buffer in the queue (but don't increment
// rngQueue.available yet).
rngQueue.buffers[0].address = uint64(uintptr(unsafe.Pointer(&rngBuf)))
rngQueue.buffers[0].length = uint32(unsafe.Sizeof(rngBuf))
rngQueue.buffers[0].flags = 2 // 2 means write-only buffer
}
// Increment the available ring buffer. This doesn't actually change the
// buffer index (it's a ring with a single entry), but the number needs to
// be incremented otherwise the device won't recognize a new buffer.
index := rngQueue.available.index
rngQueue.available.index = index + 1
rngDevice.queueNotify.Set(0) // notify the device of the 'new' (reused) buffer
for rngQueue.used.index.Get() != index+1 {
// Busy wait until the RNG buffer is filled.
// A better way would be to wait for an interrupt, but since this driver
// implementation is mostly used for testing it's good enough for now.
}
// Check that we indeed got 4 bytes back.
if rngQueue.used.ring[0].length != 4 {
rngLock.Unlock()
return 0, errNoRNGData
}
// Read the resulting random numbers.
result := rngBuf.Get()
rngLock.Unlock()
return result, nil
}
// Implement a driver for the VirtIO entropy device.
// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html
// http://wiki.osdev.org/Virtio
// http://www.dumais.io/index.php?article=aca38a9a2b065b24dfa1dee728062a12
const (
virtioDeviceStatusAcknowledge = 1
virtioDeviceStatusDriver = 2
virtioDeviceStatusDriverOk = 4
virtioDeviceStatusFeaturesOk = 8
virtioDeviceStatusFailed = 128
)
const (
virtioDeviceReserved = iota
virtioDeviceNetworkCard
virtioDeviceBlockDevice
virtioDeviceConsole
virtioDeviceEntropySource
// there are more device types
)
// VirtIO device version 1
type virtioDevice1 struct {
magic volatile.Register32 // always 0x74726976
version volatile.Register32
deviceID volatile.Register32
vendorID volatile.Register32
hostFeatures volatile.Register32
hostFeaturesSel volatile.Register32
_ [2]uint32
guestFeatures volatile.Register32
guestFeaturesSel volatile.Register32
guestPageSize volatile.Register32
_ uint32
queueSel volatile.Register32
queueNumMax volatile.Register32
queueNum volatile.Register32
queueAlign volatile.Register32
queuePFN volatile.Register32
_ [3]uint32
queueNotify volatile.Register32
_ [3]uint32
interruptStatus volatile.Register32
interruptAck volatile.Register32
_ [2]uint32
status volatile.Register32
}
// VirtIO queue, with a single buffer.
type virtioQueue struct {
buffers [1]struct {
address uint64
length uint32
flags uint16
next uint16
} // 16 bytes
available struct {
flags uint16
index uint16
ring [1]uint16
eventIndex uint16
} // 8 bytes
_ [4096 - 16*1 - 8*1]byte // padding (to align on a 4096 byte boundary)
used struct {
flags uint16
index volatile.Register16
ring [1]struct {
index uint32
length uint32
}
availEvent uint16
}
}
func virtioFindDevice(deviceID uint32) *virtioDevice1 {
// On RISC-V, QEMU defines 8 VirtIO devices starting at 0x10001000 and
// repeating every 0x1000 bytes.
// The memory map can be seen in the QEMU source code:
// https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c
for i := 0; i < 8; i++ {
dev := (*virtioDevice1)(unsafe.Pointer(uintptr(0x10001000 + i*0x1000)))
if dev.magic.Get() != 0x74726976 || dev.version.Get() != 1 || dev.deviceID.Get() != deviceID {
continue
}
return dev
}
return nil
}
// A VirtIO queue needs to be page-aligned.
//
//go:align 4096
var rngQueue virtioQueue
+4
View File
@@ -134,6 +134,10 @@ func Pipe() (r *File, w *File, err error) {
return nil, nil, ErrNotImplemented
}
func Symlink(oldname, newname string) error {
return ErrNotImplemented
}
func Readlink(name string) (string, error) {
return "", ErrNotImplemented
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey)
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt))
// If you update the above build constraint, you'll probably also need to update
// src/crypto/rand/rand_baremetal.go.
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey)
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt))
package runtime
+1 -1
View File
@@ -4,5 +4,5 @@
"build-tags": ["virt", "qemu"],
"default-stack-size": 4096,
"linkerscript": "targets/riscv-qemu.ld",
"emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -kernel {}"
"emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -device virtio-rng-device -kernel {}"
}
+5 -5
View File
@@ -1,16 +1,16 @@
/* Memory map:
* https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c
* RAM and flash are set to 1MB each. That should be enough for the foreseeable
* future. QEMU does not seem to limit the flash/RAM size and in fact doesn't
* seem to differentiate between it.
* Looks like we can use any address starting from 0x80000000 (so 2GB of space).
* However, using a large space slows down tests.
*/
MEMORY
{
FLASH_TEXT (rw) : ORIGIN = 0x80000000, LENGTH = 0x100000
RAM (xrw) : ORIGIN = 0x80100000, LENGTH = 0x100000
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 100M
}
REGION_ALIAS("FLASH_TEXT", RAM)
_stack_size = 2K;
INCLUDE "targets/riscv.ld"