Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ jobs:
cat /proc/cpuinfo
sudo apt-get update
sudo apt-get install --no-install-recommends \
libgcrypt20 \
libpixman-1-0 \
libsdl2-2.0-0 \
libslirp0 \
qemu-system-arm \
qemu-system-riscv32 \
qemu-user \
Expand All @@ -216,6 +220,14 @@ jobs:
version: "29.0.1"
- name: Setup `wasm-tools`
uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Install Espressif QEMU
run: |
release=esp-develop-9.2.2-20260417
archive=qemu-xtensa-softmmu-${release//-/_}-x86_64-linux-gnu.tar.xz
curl -fL --retry 3 -o "$RUNNER_TEMP/$archive" \
"https://github.com/espressif/qemu/releases/download/$release/$archive"
tar -xJf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/qemu/bin" >> "$GITHUB_PATH"
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
Expand Down
9 changes: 6 additions & 3 deletions builder/esp.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
// Compute the size of the RAM portion of the image (everything the ROM
// bootloader loads, up to and including the appended SHA256 hash).
ramImageSize := 0
if makeImage {
ramImageSize += 4096
}
ramImageSize += 24 // image header (8) + trailer fields (16)
for _, seg := range segments {
ramImageSize += 8 + len(seg.data) // segment header + data (4-aligned)
Expand Down Expand Up @@ -284,6 +281,9 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
dromSize := 0
if len(dromSegs) > 0 {
targetImageOffset := int(dromFlashAddr - flashBase)
if makeImage {
targetImageOffset = int(dromFlashAddr)
}
if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: RAM segments too large (%d bytes), overlap DROM at flash 0x%x", outf.Len(), dromFlashAddr)
}
Expand All @@ -304,6 +304,9 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
}
iromFlashAddr := dromFlashAddr + uint32(dromPages)*pageSize
targetImageOffset := int(iromFlashAddr - flashBase)
if makeImage {
targetImageOffset = int(iromFlashAddr)
}
if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: DROM too large, overlaps IROM at flash 0x%x", iromFlashAddr)
}
Expand Down
30 changes: 30 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,21 @@ func TestTimerStopResetRace(t *testing.T) {
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
}

func TestESP32QEMU(t *testing.T) {
t.Parallel()

options := optionsFromTarget("esp32-qemu", sema)
emuCheck(t, options)
machines, err := exec.Command("qemu-system-xtensa", "-machine", "help").Output()
if err != nil {
t.Fatal("failed to list qemu-system-xtensa machines:", err)
}
if !regexp.MustCompile(`(?m)^esp32\s`).Match(machines) {
t.Skip("qemu-system-xtensa does not support the ESP32 machine")
}
runTest("print.go", options, t, nil, nil)
}

func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
emuCheck(t, options)

Expand Down Expand Up @@ -539,6 +554,9 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
if config.EmulatorName() == "simavr" {
actual = cleanSimAVRTestOutput(actual)
}
if config.EmulatorName() == "qemu-system-xtensa" {
actual = cleanESP32QEMUOutput(actual)
}
if name == "testing.go" {
// Strip actual time.
re := regexp.MustCompile(`\([0-9]\.[0-9][0-9]s\)`)
Expand All @@ -564,6 +582,18 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
}
}

func cleanESP32QEMUOutput(output []byte) []byte {
entryLine := bytes.Index(output, []byte("\nentry "))
if entryLine < 0 {
return output
}
entryLineEnd := bytes.IndexByte(output[entryLine+1:], '\n')
if entryLineEnd < 0 {
return output
}
return output[entryLine+1+entryLineEnd+1:]
}

func cleanSimAVRTestOutput(output []byte) []byte {
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil)
Expand Down
6 changes: 0 additions & 6 deletions src/runtime/runtime_esp32.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,6 @@ var _ebss [0]byte
//go:extern _vector_table
var _vector_table [0]uintptr

func abort() {
for {
device.Asm("waiti 0")
}
}

// interruptInit installs the Xtensa vector table by writing its address
// to the VECBASE special register and ensures all CPU interrupts are
// initially disabled.
Expand Down
11 changes: 11 additions & 0 deletions src/runtime/runtime_esp32_abort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build esp32 && !qemu

package runtime

import "device"

func abort() {
for {
device.Asm("waiti 0")
}
}
28 changes: 28 additions & 0 deletions src/runtime/runtime_esp32_qemu.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//go:build esp32 && qemu

package runtime

import "device"

func exit(code int) {
qemuExit(code)
}

func abort() {
qemuExit(1)
}

func qemuExit(code int) {
// QEMU semihosting expects a2 = SYS_exit (1) and a3 = the exit code.
// AsmFull cannot declare these clobbers, so no Go code may run afterward.
device.AsmFull(
"mov a3, {code}\n"+
"movi a2, 1\n"+
"simcall",
map[string]interface{}{"code": code},
)
// This loop ensures the clobbered registers are never used again.
for {
device.Asm("waiti 0")
}
}
4 changes: 0 additions & 4 deletions src/runtime/runtime_esp32xx.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ func ticksToNanoseconds(ticks timeUnit) int64 {
return int64(ticks) * 25
}

func exit(code int) {
abort()
}

func putchar(c byte) {
machine.Serial.WriteByte(c)
}
Expand Down
7 changes: 7 additions & 0 deletions src/runtime/runtime_esp32xx_exit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build (esp32 && !qemu) || esp32c3 || esp32c6

package runtime

func exit(code int) {
abort()
}
5 changes: 5 additions & 0 deletions targets/esp32-qemu.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"inherits": ["esp32-generic"],
"build-tags": ["qemu"],
"emulator": "qemu-system-xtensa -machine esp32 -nographic -semihosting -drive file={img},if=mtd,format=raw"
}
1 change: 0 additions & 1 deletion targets/esp32.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@
],
"binary-format": "esp32",
"flash-method": "esp32flash",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"]
}
1 change: 0 additions & 1 deletion targets/esp32s3.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@
],
"binary-format": "esp32s3",
"flash-method": "esp32jtag",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"]
}
Loading