Skip to content
Draft
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
4 changes: 4 additions & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,10 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-box-3 examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-psram-qspi examples/psram
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-psram-octal examples/psram
@$(MD5SUM) test.bin
endif
# esp32c3-supermini
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1
Expand Down
49 changes: 30 additions & 19 deletions src/device/esp/esp32s3.S
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
// e. Disable caches
// f. Cache_MMU_Init() — reset all MMU entries to invalid
// g. Cache_Set_IDROM_MMU_Size() — set IROM/DROM entry split
// h. Write MMU entries mapping flash page 0 for IROM and DROM
// h. Map flash page 0 for IROM (hand-rolled loop) and DROM (ROM's
// Cache_Dbus_MMU_Set)
// i. Clear bus-shut bits
// j. Enable caches + isync

Expand Down Expand Up @@ -76,6 +77,8 @@
.long 0x40001998
.LCache_Set_IDROM_MMU_Size:
.long 0x40001914
.LCache_Dbus_MMU_Set:
.long 0x400019b0
.LCache_Enable_ICache:
.long 0x40001878
.LCache_Enable_DCache:
Expand All @@ -87,13 +90,13 @@
.long 0x600C4064
.Ldcache_ctrl1_reg:
.long 0x600C4004
// End-of-section symbols for multi-page MMU mapping.
// Window bases and end-of-section symbols for MMU mapping.
.Lirom_end:
.long _irom_end
.Ldrom_end:
.long _drom_end
.Lirom_base:
.long 0x42000000
.Ldrom_end:
.long _drom_end
.Ldrom_base:
.long 0x3C000000

Expand Down Expand Up @@ -272,13 +275,16 @@ call_start_cpu0:
l32r a4, .LCache_Set_IDROM_MMU_Size
callx4 a4

// 4i. Map flash pages for IROM and DROM using identity mapping.
// MMU table at 0x600C5000: entries 0-255 = ICache, 256-511 = DCache.
// Entry value N = flash page N (SOC_MMU_VALID = 0 on S3).
// Each 64KB page needs one 4-byte entry.
// 4i. Map flash pages for IROM and DROM. Entry value N = flash page N
// (SOC_MMU_VALID = 0 on S3), one 4-byte entry per 64KB page.
//
// IROM: map pages 0..N where N = (_irom_end - 0x42000000) >> 16
// DROM: map pages 0..M where M = (_drom_end - 0x3C000000) >> 16
//
// IROM is a hand-rolled store loop; DROM instead calls ROM's
// Cache_Dbus_MMU_Set. The equivalent Cache_Ibus_MMU_Set for IROM
// boots but then hangs at entry for reasons not yet understood --
// do not swap the IROM loop for it without a new hypothesis.

l32r a8, .Lmmu_table_base // a8 = 0x600C5000

Expand All @@ -297,18 +303,23 @@ call_start_cpu0:
blt a9, a2, .Lirom_loop

// --- DROM pages ---
l32r a2, .Ldrom_end // a2 = _drom_end (VMA in 0x3Cxxxxxx)
l32r a3, .Ldrom_base // a3 = 0x3C000000
sub a2, a2, a3 // a2 = byte offset past DROM base
l32r a4, .Ldrom_end // a4 = _drom_end (VMA in 0x3Cxxxxxx)
l32r a5, .Ldrom_base // a5 = 0x3C000000
sub a2, a4, a5 // a2 = byte offset past DROM base
srli a2, a2, 16 // a2 = last page index
addi a2, a2, 1 // a2 = number of pages to map
movi a9, 0 // a9 = page counter (and entry value)
addmi a10, a8, 0x400 // a10 = 0x600C5400 (DCache entry 256)
.Ldrom_loop:
s32i a9, a10, 0
addi a9, a9, 1
addi a10, a10, 4
blt a9, a2, .Ldrom_loop
addi a2, a2, 1 // a2 = number of pages to map (preserved)

// Cache_Dbus_MMU_Set(ext_ram=0, vaddr=0x3C000000, paddr=0, psize=64,
// num=a2, fixed=0)
movi a6, 0
l32r a7, .Ldrom_base
movi a8, 0
movi a9, 64
mov a10, a2
movi a11, 0
mov a5, a1
l32r a4, .LCache_Dbus_MMU_Set
callx4 a4
memw

// 4j. Clear bus-shut bits so core 0 can access ICache and DCache buses.
Expand Down
60 changes: 60 additions & 0 deletions src/examples/psram/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

// This example demonstrates how to use external PSRAM (Octal or Quad SPI) on
// supported targets (e.g., -target=esp32s3-psram-qspi).
//
// Variables placed in the `.psram` section via the `//go:section .psram`
// pragma are stored in external PSRAM rather than internal SRAM.
// Note: importing "unsafe" is required to enable `//go:section`.

import (
"fmt"
"time"
"unsafe"
)

// Allocate a 1MB buffer explicitly in external PSRAM.
//
//go:section .psram
var psramBuffer [1024 * 1024]byte

func main() {
time.Sleep(2 * time.Second)

fmt.Println("=== PSRAM Example ===")
fmt.Printf("psramBuffer start address: 0x%X\n", uintptr(unsafe.Pointer(&psramBuffer[0])))
fmt.Printf("psramBuffer size: %d bytes\n", len(psramBuffer))

fmt.Println("Writing test pattern to PSRAM...")
for i := range psramBuffer {
psramBuffer[i] = byte(i ^ 0xAA)
}

fmt.Println("Verifying test pattern...")
errors := 0
for i := range psramBuffer {
if psramBuffer[i] != byte(i^0xAA) {
errors++
}
}

if errors == 0 {
fmt.Println("SUCCESS: PSRAM 1MB read/write verification passed!")
} else {
fmt.Printf("FAIL: %d readback errors detected\n", errors)
printed := 0
for i := range psramBuffer {
if psramBuffer[i] != byte(i^0xAA) {
fmt.Printf(" mismatch at idx %d (offset 0x%X): got 0x%02X, want 0x%02X\n", i, i, psramBuffer[i], byte(i^0xAA))
printed++
if printed >= 8 {
break
}
}
}
}

for {
time.Sleep(time.Second)
}
}
6 changes: 6 additions & 0 deletions src/runtime/runtime_esp32_nopsram.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build esp32s3 && !numa_psram_qspi && !numa_psram_octal

package runtime

// initPSRAM is a no-op when PSRAM support is disabled.
func initPSRAM() {}
100 changes: 100 additions & 0 deletions src/runtime/runtime_esp32_psram.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//go:build esp32s3 && (numa_psram_qspi || numa_psram_octal)

// Shared PSRAM support for ESP32-S3: linker symbols, MMU/ROM-call helpers,
// and address-map documentation common to both the Quad SPI driver
// (runtime_esp32_psram_qspi.go, build tag numa_psram_qspi) and the Octal
// SPI driver (runtime_esp32_psram_octal.go, build tag numa_psram_octal).
// Everything protocol-specific (SPI1 command sequencing, chip ID/mode
// register parsing, SPI0 cache-phase configuration) lives in those files.
//
// Address map (esp-idf soc/ext_mem_defs.h; verified on hardware while
// bringing up the Octal driver):
//
// 0x600C5000 MMU table, 512 x 32-bit entries, one per 64KB page.
// A SINGLE table shared by IBUS (0x42000000 window) and DBUS
// (0x3C000000 window), indexed by linear address:
// entry = (vaddr & SOC_MMU_LINEAR_ADDR_MASK 0x1FFFFFF) >> 16.
// There is no ICache/DCache half-split: 0x3C800000 and
// 0x42800000 both decode to entry 128.
// Entry format: bits[13:0] physical page number, bit 14 =
// invalid (SOC_MMU_INVALID), bit 15 = target type, 0 = flash,
// 1 = PSRAM (SOC_MMU_ACCESS_SPIRAM).
//
// Flash XIP identity-maps linear page N -> flash page N for both IROM
// and DROM (esp32s3.S), so the flash image occupies entries
// 0..(image pages - 1), up to 16M. PSRAM lives in the upper half of the
// DBUS window (0x3D000000, entries 256-511; targets/esp32s3.ld),
// disjoint from all flash entries.
//
// DCache (data cache) is the L1/L2 cache in front of the DBUS window.
// It is disabled (ROM Cache_Disable_DCache, which also invalidates all
// tag memory) while MMU entries are modified and re-enabled after, so
// that no stale cache lines survive the remap.
package runtime

import "unsafe"

//go:extern _spsram
var _spsram [0]byte

//go:extern _epsram
var _epsram [0]byte

//go:extern _psram_start
var _psram_start [0]byte

//go:extern _psram_end
var _psram_end [0]byte

// ROM functions baked into every ESP32-S3 chip (fixed addresses provided
// via PROVIDE() in targets/esp32s3.ld); see esp32s3/rom/cache.h.

//export Cache_Dbus_MMU_Set
func romCacheDbusMMUSet(extRam uint32, vaddr uint32, paddr uint32, psize uint32, num uint32, fixed uint32) int32

// Cache_Disable_DCache / Cache_Enable_DCache: the documented-safe pairing
// around an MMU change (Cache_Disable_DCache also invalidates all DCache
// tag memory). Cache_Suspend_DCache/Resume, the alternative pairing, is
// explicitly documented as unsafe to use while changing the MMU.

//export Cache_Disable_DCache
func romCacheDisableDCache() uint32

//export Cache_Enable_DCache
func romCacheEnableDCache(autoload uint32)

const mmuAccessSpiram = 0x8000 // SOC_MMU_TYPE / SOC_MMU_ACCESS_SPIRAM: routes entry to PSRAM instead of flash.

// psramWindow returns the linker-provided PSRAM vaddr window
// (targets/esp32s3.ld) and its capacity in 64KB MMU pages.
func psramWindow() (start, end uintptr, pageCap int) {
start = uintptr(unsafe.Pointer(&_psram_start))
end = uintptr(unsafe.Pointer(&_psram_end))
pageCap = int((end - start) / 65536)
return
}

// zeroInitPSRAM zero-initializes .psram BSS after PSRAM has been mapped
// into the MMU, verifying with a known pattern first that writes actually
// reach the chip (a hardware or timing issue could otherwise silently
// drop them).
func zeroInitPSRAM() {
spsram := uintptr(unsafe.Pointer(&_spsram))
epsram := uintptr(unsafe.Pointer(&_epsram))
if epsram <= spsram {
return
}

*(*uint32)(unsafe.Pointer(spsram)) = 0x5A5A5A5A
if *(*uint32)(unsafe.Pointer(spsram)) != 0x5A5A5A5A {
panic("PSRAM readback test failed")
}
*(*uint32)(unsafe.Pointer(spsram)) = 0
if *(*uint32)(unsafe.Pointer(spsram)) != 0 {
panic("PSRAM readback test failed")
}

for addr := spsram + 4; addr < epsram; addr += 4 {
*(*uint32)(unsafe.Pointer(addr)) = 0
}
}
Loading
Loading