Skip to content

Commit c163bd8

Browse files
committed
feat(esp32s3): add PSRAM support for QSPI and Octal chips.
Add runtime initialization, MMU layout, linker script mappings, and targets for ESP32-S3 Quad and Octal PSRAM.
1 parent 7756941 commit c163bd8

11 files changed

Lines changed: 982 additions & 26 deletions

GNUmakefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,10 @@ ifneq ($(XTENSA), 0)
10191019
@$(MD5SUM) test.bin
10201020
$(TINYGO) build -size short -o test.bin -target=esp32s3-box-3 examples/blinky1
10211021
@$(MD5SUM) test.bin
1022+
$(TINYGO) build -size short -o test.bin -target=esp32s3-psram-qspi examples/psram
1023+
@$(MD5SUM) test.bin
1024+
$(TINYGO) build -size short -o test.bin -target=esp32s3-psram-octal examples/psram
1025+
@$(MD5SUM) test.bin
10221026
endif
10231027
# esp32c3-supermini
10241028
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1

src/device/esp/esp32s3.S

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
// e. Disable caches
1919
// f. Cache_MMU_Init() — reset all MMU entries to invalid
2020
// g. Cache_Set_IDROM_MMU_Size() — set IROM/DROM entry split
21-
// h. Write MMU entries mapping flash page 0 for IROM and DROM
21+
// h. Map flash page 0 for IROM (hand-rolled loop) and DROM (ROM's
22+
// Cache_Dbus_MMU_Set)
2223
// i. Clear bus-shut bits
2324
// j. Enable caches + isync
2425

@@ -76,6 +77,8 @@
7677
.long 0x40001998
7778
.LCache_Set_IDROM_MMU_Size:
7879
.long 0x40001914
80+
.LCache_Dbus_MMU_Set:
81+
.long 0x400019b0
7982
.LCache_Enable_ICache:
8083
.long 0x40001878
8184
.LCache_Enable_DCache:
@@ -87,13 +90,13 @@
8790
.long 0x600C4064
8891
.Ldcache_ctrl1_reg:
8992
.long 0x600C4004
90-
// End-of-section symbols for multi-page MMU mapping.
93+
// Window bases and end-of-section symbols for MMU mapping.
9194
.Lirom_end:
9295
.long _irom_end
93-
.Ldrom_end:
94-
.long _drom_end
9596
.Lirom_base:
9697
.long 0x42000000
98+
.Ldrom_end:
99+
.long _drom_end
97100
.Ldrom_base:
98101
.long 0x3C000000
99102

@@ -272,13 +275,16 @@ call_start_cpu0:
272275
l32r a4, .LCache_Set_IDROM_MMU_Size
273276
callx4 a4
274277

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

283289
l32r a8, .Lmmu_table_base // a8 = 0x600C5000
284290

@@ -297,18 +303,23 @@ call_start_cpu0:
297303
blt a9, a2, .Lirom_loop
298304

299305
// --- DROM pages ---
300-
l32r a2, .Ldrom_end // a2 = _drom_end (VMA in 0x3Cxxxxxx)
301-
l32r a3, .Ldrom_base // a3 = 0x3C000000
302-
sub a2, a2, a3 // a2 = byte offset past DROM base
306+
l32r a4, .Ldrom_end // a4 = _drom_end (VMA in 0x3Cxxxxxx)
307+
l32r a5, .Ldrom_base // a5 = 0x3C000000
308+
sub a2, a4, a5 // a2 = byte offset past DROM base
303309
srli a2, a2, 16 // a2 = last page index
304-
addi a2, a2, 1 // a2 = number of pages to map
305-
movi a9, 0 // a9 = page counter (and entry value)
306-
addmi a10, a8, 0x400 // a10 = 0x600C5400 (DCache entry 256)
307-
.Ldrom_loop:
308-
s32i a9, a10, 0
309-
addi a9, a9, 1
310-
addi a10, a10, 4
311-
blt a9, a2, .Ldrom_loop
310+
addi a2, a2, 1 // a2 = number of pages to map (preserved)
311+
312+
// Cache_Dbus_MMU_Set(ext_ram=0, vaddr=0x3C000000, paddr=0, psize=64,
313+
// num=a2, fixed=0)
314+
movi a6, 0
315+
l32r a7, .Ldrom_base
316+
movi a8, 0
317+
movi a9, 64
318+
mov a10, a2
319+
movi a11, 0
320+
mov a5, a1
321+
l32r a4, .LCache_Dbus_MMU_Set
322+
callx4 a4
312323
memw
313324

314325
// 4j. Clear bus-shut bits so core 0 can access ICache and DCache buses.

src/examples/psram/main.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package main
2+
3+
// This example demonstrates how to use external PSRAM (Octal or Quad SPI) on
4+
// supported targets (e.g., -target=esp32s3-psram-qspi).
5+
//
6+
// Variables placed in the `.psram` section via the `//go:section .psram`
7+
// pragma are stored in external PSRAM rather than internal SRAM.
8+
// Note: importing "unsafe" is required to enable `//go:section`.
9+
10+
import (
11+
"fmt"
12+
"time"
13+
"unsafe"
14+
)
15+
16+
// Allocate a 1MB buffer explicitly in external PSRAM.
17+
//
18+
//go:section .psram
19+
var psramBuffer [1024 * 1024]byte
20+
21+
func main() {
22+
time.Sleep(2 * time.Second)
23+
24+
fmt.Println("=== PSRAM Example ===")
25+
fmt.Printf("psramBuffer start address: 0x%X\n", uintptr(unsafe.Pointer(&psramBuffer[0])))
26+
fmt.Printf("psramBuffer size: %d bytes\n", len(psramBuffer))
27+
28+
fmt.Println("Writing test pattern to PSRAM...")
29+
for i := range psramBuffer {
30+
psramBuffer[i] = byte(i ^ 0xAA)
31+
}
32+
33+
fmt.Println("Verifying test pattern...")
34+
errors := 0
35+
for i := range psramBuffer {
36+
if psramBuffer[i] != byte(i^0xAA) {
37+
errors++
38+
}
39+
}
40+
41+
if errors == 0 {
42+
fmt.Println("SUCCESS: PSRAM 1MB read/write verification passed!")
43+
} else {
44+
fmt.Printf("FAIL: %d readback errors detected\n", errors)
45+
printed := 0
46+
for i := range psramBuffer {
47+
if psramBuffer[i] != byte(i^0xAA) {
48+
fmt.Printf(" mismatch at idx %d (offset 0x%X): got 0x%02X, want 0x%02X\n", i, i, psramBuffer[i], byte(i^0xAA))
49+
printed++
50+
if printed >= 8 {
51+
break
52+
}
53+
}
54+
}
55+
}
56+
57+
for {
58+
time.Sleep(time.Second)
59+
}
60+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//go:build esp32s3 && !numa_psram_qspi && !numa_psram_octal
2+
3+
package runtime
4+
5+
// initPSRAM is a no-op when PSRAM support is disabled.
6+
func initPSRAM() {}

src/runtime/runtime_esp32_psram.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//go:build esp32s3 && (numa_psram_qspi || numa_psram_octal)
2+
3+
// Shared PSRAM support for ESP32-S3: linker symbols, MMU/ROM-call helpers,
4+
// and address-map documentation common to both the Quad SPI driver
5+
// (runtime_esp32_psram_qspi.go, build tag numa_psram_qspi) and the Octal
6+
// SPI driver (runtime_esp32_psram_octal.go, build tag numa_psram_octal).
7+
// Everything protocol-specific (SPI1 command sequencing, chip ID/mode
8+
// register parsing, SPI0 cache-phase configuration) lives in those files.
9+
//
10+
// Address map (esp-idf soc/ext_mem_defs.h; verified on hardware while
11+
// bringing up the Octal driver):
12+
//
13+
// 0x600C5000 MMU table, 512 x 32-bit entries, one per 64KB page.
14+
// A SINGLE table shared by IBUS (0x42000000 window) and DBUS
15+
// (0x3C000000 window), indexed by linear address:
16+
// entry = (vaddr & SOC_MMU_LINEAR_ADDR_MASK 0x1FFFFFF) >> 16.
17+
// There is no ICache/DCache half-split: 0x3C800000 and
18+
// 0x42800000 both decode to entry 128.
19+
// Entry format: bits[13:0] physical page number, bit 14 =
20+
// invalid (SOC_MMU_INVALID), bit 15 = target type, 0 = flash,
21+
// 1 = PSRAM (SOC_MMU_ACCESS_SPIRAM).
22+
//
23+
// Flash XIP identity-maps linear page N -> flash page N for both IROM
24+
// and DROM (esp32s3.S), so the flash image occupies entries
25+
// 0..(image pages - 1), up to 16M. PSRAM lives in the upper half of the
26+
// DBUS window (0x3D000000, entries 256-511; targets/esp32s3.ld),
27+
// disjoint from all flash entries.
28+
//
29+
// DCache (data cache) is the L1/L2 cache in front of the DBUS window.
30+
// It is disabled (ROM Cache_Disable_DCache, which also invalidates all
31+
// tag memory) while MMU entries are modified and re-enabled after, so
32+
// that no stale cache lines survive the remap.
33+
package runtime
34+
35+
import "unsafe"
36+
37+
//go:extern _spsram
38+
var _spsram [0]byte
39+
40+
//go:extern _epsram
41+
var _epsram [0]byte
42+
43+
//go:extern _psram_start
44+
var _psram_start [0]byte
45+
46+
//go:extern _psram_end
47+
var _psram_end [0]byte
48+
49+
// ROM functions baked into every ESP32-S3 chip (fixed addresses provided
50+
// via PROVIDE() in targets/esp32s3.ld); see esp32s3/rom/cache.h.
51+
52+
//export Cache_Dbus_MMU_Set
53+
func romCacheDbusMMUSet(extRam uint32, vaddr uint32, paddr uint32, psize uint32, num uint32, fixed uint32) int32
54+
55+
// Cache_Disable_DCache / Cache_Enable_DCache: the documented-safe pairing
56+
// around an MMU change (Cache_Disable_DCache also invalidates all DCache
57+
// tag memory). Cache_Suspend_DCache/Resume, the alternative pairing, is
58+
// explicitly documented as unsafe to use while changing the MMU.
59+
60+
//export Cache_Disable_DCache
61+
func romCacheDisableDCache() uint32
62+
63+
//export Cache_Enable_DCache
64+
func romCacheEnableDCache(autoload uint32)
65+
66+
const mmuAccessSpiram = 0x8000 // SOC_MMU_TYPE / SOC_MMU_ACCESS_SPIRAM: routes entry to PSRAM instead of flash.
67+
68+
// psramWindow returns the linker-provided PSRAM vaddr window
69+
// (targets/esp32s3.ld) and its capacity in 64KB MMU pages.
70+
func psramWindow() (start, end uintptr, pageCap int) {
71+
start = uintptr(unsafe.Pointer(&_psram_start))
72+
end = uintptr(unsafe.Pointer(&_psram_end))
73+
pageCap = int((end - start) / 65536)
74+
return
75+
}
76+
77+
// zeroInitPSRAM zero-initializes .psram BSS after PSRAM has been mapped
78+
// into the MMU, verifying with a known pattern first that writes actually
79+
// reach the chip (a hardware or timing issue could otherwise silently
80+
// drop them).
81+
func zeroInitPSRAM() {
82+
spsram := uintptr(unsafe.Pointer(&_spsram))
83+
epsram := uintptr(unsafe.Pointer(&_epsram))
84+
if epsram <= spsram {
85+
return
86+
}
87+
88+
*(*uint32)(unsafe.Pointer(spsram)) = 0x5A5A5A5A
89+
if *(*uint32)(unsafe.Pointer(spsram)) != 0x5A5A5A5A {
90+
panic("PSRAM readback test failed")
91+
}
92+
*(*uint32)(unsafe.Pointer(spsram)) = 0
93+
if *(*uint32)(unsafe.Pointer(spsram)) != 0 {
94+
panic("PSRAM readback test failed")
95+
}
96+
97+
for addr := spsram + 4; addr < epsram; addr += 4 {
98+
*(*uint32)(unsafe.Pointer(addr)) = 0
99+
}
100+
}

0 commit comments

Comments
 (0)