From ae60b18ae5abd6a1084820a5b31d8d7b0c3133f8 Mon Sep 17 00:00:00 2001 From: rdon Date: Sun, 10 May 2026 02:22:55 +0900 Subject: [PATCH 01/11] runtime, machine: add RP2040 flash-safe section --- src/machine/machine_rp2040_flashsafe.go | 14 +++ src/machine/machine_rp2040_rom.go | 14 +-- src/runtime/runtime_rp2040.go | 4 + src/runtime/runtime_rp2040_flashsafe_cores.go | 93 +++++++++++++++++++ .../runtime_rp2040_flashsafe_single.go | 17 ++++ src/runtime/runtime_rp2350.go | 2 + src/runtime/runtime_rp2350_flashsafe_stub.go | 7 ++ 7 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 src/machine/machine_rp2040_flashsafe.go create mode 100644 src/runtime/runtime_rp2040_flashsafe_cores.go create mode 100644 src/runtime/runtime_rp2040_flashsafe_single.go create mode 100644 src/runtime/runtime_rp2350_flashsafe_stub.go diff --git a/src/machine/machine_rp2040_flashsafe.go b/src/machine/machine_rp2040_flashsafe.go new file mode 100644 index 0000000000..c9e7ca6ab2 --- /dev/null +++ b/src/machine/machine_rp2040_flashsafe.go @@ -0,0 +1,14 @@ +//go:build tinygo && rp2040 + +package machine + +import ( + "runtime/interrupt" + _ "unsafe" +) + +//go:linkname rp2040EnterFlashSafeSection runtime.rp2040EnterFlashSafeSection +func rp2040EnterFlashSafeSection() interrupt.State + +//go:linkname rp2040ExitFlashSafeSection runtime.rp2040ExitFlashSafeSection +func rp2040ExitFlashSafeSection(state interrupt.State) diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index 0f7c6819a5..973792bfd5 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -3,7 +3,6 @@ package machine import ( - "runtime/interrupt" "unsafe" ) @@ -207,6 +206,9 @@ func doFlashCommand(tx []byte, rx []byte) error { return errFlashInvalidWriteLength } + state := rp2040EnterFlashSafeSection() + defer rp2040ExitFlashSafeSection(state) + C.flash_do_cmd( (*C.uint8_t)(unsafe.Pointer(&tx[0])), (*C.uint8_t)(unsafe.Pointer(&rx[0])), @@ -223,15 +225,15 @@ func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { return 0, errFlashCannotWritePastEOF } - state := interrupt.Disable() - defer interrupt.Restore(state) - // rp2040 writes to offset, not actual address // e.g. real address 0x10003000 is written to at // 0x00003000 address := writeAddress(off) padded := flashPad(p, int(f.WriteBlockSize())) + state := rp2040EnterFlashSafeSection() + defer rp2040ExitFlashSafeSection(state) + C.flash_range_write(C.uint32_t(address), (*C.uint8_t)(unsafe.Pointer(&padded[0])), C.ulong(len(padded))) @@ -245,8 +247,8 @@ func (f flashBlockDevice) eraseBlocks(start, length int64) error { return errFlashCannotErasePastEOF } - state := interrupt.Disable() - defer interrupt.Restore(state) + state := rp2040EnterFlashSafeSection() + defer rp2040ExitFlashSafeSection(state) C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize())) diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go index 56c7da1028..73e4127298 100644 --- a/src/runtime/runtime_rp2040.go +++ b/src/runtime/runtime_rp2040.go @@ -31,6 +31,8 @@ func handleSIOFifoInterruptCore0(intr interrupt.Interrupt) { switch rp.SIO.FIFO_RD.Get() { case 1: gcInterruptHandler(0) + case 2: + rp2FlashSafeInterruptHandler(0) } } @@ -38,5 +40,7 @@ func handleSIOFifoInterruptCore1(intr interrupt.Interrupt) { switch rp.SIO.FIFO_RD.Get() { case 1: gcInterruptHandler(1) + case 2: + rp2FlashSafeInterruptHandler(1) } } diff --git a/src/runtime/runtime_rp2040_flashsafe_cores.go b/src/runtime/runtime_rp2040_flashsafe_cores.go new file mode 100644 index 0000000000..7cd001ce8b --- /dev/null +++ b/src/runtime/runtime_rp2040_flashsafe_cores.go @@ -0,0 +1,93 @@ +//go:build rp2040 && scheduler.cores + +package runtime + +import ( + "device/arm" + "device/rp" + "runtime/interrupt" + "runtime/volatile" +) + +const ( + rp2040FlashSafeFIFOCommand = 2 + + rp2040FlashSafeIdle = 0 + rp2040FlashSafeLocked = 1 + rp2040FlashSafeRelease = 2 +) + +// rp2040FlashSafeState is used to synchronize the core that performs a flash +// operation with the other core that must stop executing from XIP flash. +var rp2040FlashSafeState volatile.Register8 + +// rp2040EnterFlashSafeSection enters a section in which RP2040 flash operations +// may temporarily disable XIP. +// +// The multicore path asks the other core to enter the flash-safe interrupt +// handler and waits until it acknowledges that it is parked. Local interrupts +// are disabled after the other core is parked. +func rp2040EnterFlashSafeSection() interrupt.State { + if !secondaryCoresStarted { + return interrupt.Disable() + } + + core := currentCPU() + rp2040FlashSafeState.Set(rp2040FlashSafeIdle) + + for i := uint32(0); i < numCPU; i++ { + if i == core { + continue + } + rp2040FlashSafePauseCore(i) + } + + for rp2040FlashSafeState.Get() != rp2040FlashSafeLocked { + spinLoopWait() + } + + return interrupt.Disable() +} + +// rp2040ExitFlashSafeSection exits a section entered by +// rp2040EnterFlashSafeSection. +func rp2040ExitFlashSafeSection(state interrupt.State) { + if secondaryCoresStarted { + rp2040FlashSafeState.Set(rp2040FlashSafeRelease) + arm.Asm("sev") + + for rp2040FlashSafeState.Get() != rp2040FlashSafeIdle { + spinLoopWait() + } + } + + interrupt.Restore(state) +} + +func rp2040FlashSafePauseCore(core uint32) { + _ = core // RP2040 SIO FIFO writes to the other core. + rp.SIO.FIFO_WR.Set(rp2040FlashSafeFIFOCommand) + arm.Asm("sev") +} + +// rp2FlashSafeInterruptHandler is called from the SIO FIFO interrupt handler. +// +// NOTE: this first draft keeps the lockout handler in Go. The final version +// should ensure that the wait loop runs from RAM while XIP is disabled. +func rp2FlashSafeInterruptHandler(core uint32) { + _ = core + + state := interrupt.Disable() + + rp2040FlashSafeState.Set(rp2040FlashSafeLocked) + arm.Asm("sev") + + for rp2040FlashSafeState.Get() == rp2040FlashSafeLocked { + arm.Asm("wfe") + } + + interrupt.Restore(state) + + rp2040FlashSafeState.Set(rp2040FlashSafeIdle) + arm.Asm("sev") +} diff --git a/src/runtime/runtime_rp2040_flashsafe_single.go b/src/runtime/runtime_rp2040_flashsafe_single.go new file mode 100644 index 0000000000..b68adbfee9 --- /dev/null +++ b/src/runtime/runtime_rp2040_flashsafe_single.go @@ -0,0 +1,17 @@ +//go:build rp2040 && !scheduler.cores + +package runtime + +import "runtime/interrupt" + +func rp2040EnterFlashSafeSection() interrupt.State { + return interrupt.Disable() +} + +func rp2040ExitFlashSafeSection(state interrupt.State) { + interrupt.Restore(state) +} + +func rp2FlashSafeInterruptHandler(core uint32) { + _ = core +} diff --git a/src/runtime/runtime_rp2350.go b/src/runtime/runtime_rp2350.go index 02c00b61ea..58396462c4 100644 --- a/src/runtime/runtime_rp2350.go +++ b/src/runtime/runtime_rp2350.go @@ -35,5 +35,7 @@ func handleSIOFifoInterrupt(intr interrupt.Interrupt) { switch rp.SIO.FIFO_RD.Get() { case 1: gcInterruptHandler(currentCPU()) + case 2: + rp2FlashSafeInterruptHandler(currentCPU()) } } diff --git a/src/runtime/runtime_rp2350_flashsafe_stub.go b/src/runtime/runtime_rp2350_flashsafe_stub.go new file mode 100644 index 0000000000..211b920cc5 --- /dev/null +++ b/src/runtime/runtime_rp2350_flashsafe_stub.go @@ -0,0 +1,7 @@ +//go:build rp2350 + +package runtime + +func rp2FlashSafeInterruptHandler(core uint32) { + _ = core +} From caa56dadac8624a93537a6d24fcf825b2d7d8c92 Mon Sep 17 00:00:00 2001 From: rdon Date: Wed, 20 May 2026 16:10:27 +0900 Subject: [PATCH 02/11] runtime: harden flash-safe section for multicore --- src/machine/machine_rp2040_flashsafe.go | 6 + src/runtime/runtime_rp2040_flashsafe_cores.go | 112 ++++++++++-------- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/src/machine/machine_rp2040_flashsafe.go b/src/machine/machine_rp2040_flashsafe.go index c9e7ca6ab2..d071dca1c1 100644 --- a/src/machine/machine_rp2040_flashsafe.go +++ b/src/machine/machine_rp2040_flashsafe.go @@ -1,5 +1,11 @@ //go:build tinygo && rp2040 +// "Flash safe" follows the RP2040/Pico SDK terminology: flash operations +// must run while the other core is not executing from XIP flash. +// +// Use linkname to call runtime hooks from package machine without creating +// an import cycle. + package machine import ( diff --git a/src/runtime/runtime_rp2040_flashsafe_cores.go b/src/runtime/runtime_rp2040_flashsafe_cores.go index 7cd001ce8b..c75daeea34 100644 --- a/src/runtime/runtime_rp2040_flashsafe_cores.go +++ b/src/runtime/runtime_rp2040_flashsafe_cores.go @@ -3,24 +3,33 @@ package runtime import ( - "device/arm" - "device/rp" - "runtime/interrupt" - "runtime/volatile" + "device/arm" + "device/rp" + "runtime/interrupt" + "runtime/volatile" + _ "unsafe" // required for //go:section ) const ( - rp2040FlashSafeFIFOCommand = 2 + rp2040FlashSafeFIFOCommand = 2 - rp2040FlashSafeIdle = 0 - rp2040FlashSafeLocked = 1 - rp2040FlashSafeRelease = 2 + rp2040FlashSafeIdle = 0 + rp2040FlashSafeLocked = 1 + rp2040FlashSafeRelease = 2 ) // rp2040FlashSafeState is used to synchronize the core that performs a flash // operation with the other core that must stop executing from XIP flash. var rp2040FlashSafeState volatile.Register8 +// flashSafeLock serializes Enter/Exit so that only one core at a time +// owns the flash-safe state machine. The other core can still participate +// as a victim through the FIFO interrupt while spinning on this lock. +// +// id: 24 is reserved here. ids 20-23 are already used by printLock, +// schedulerLock, atomicsLock, futexLock (see runtime_rp2.go). +var flashSafeLock = spinLock{id: 24} + // rp2040EnterFlashSafeSection enters a section in which RP2040 flash operations // may temporarily disable XIP. // @@ -28,66 +37,75 @@ var rp2040FlashSafeState volatile.Register8 // handler and waits until it acknowledges that it is parked. Local interrupts // are disabled after the other core is parked. func rp2040EnterFlashSafeSection() interrupt.State { - if !secondaryCoresStarted { - return interrupt.Disable() - } + if !secondaryCoresStarted { + return interrupt.Disable() + } + + flashSafeLock.Lock() - core := currentCPU() - rp2040FlashSafeState.Set(rp2040FlashSafeIdle) + core := currentCPU() + rp2040FlashSafeState.Set(rp2040FlashSafeIdle) - for i := uint32(0); i < numCPU; i++ { - if i == core { - continue - } - rp2040FlashSafePauseCore(i) - } + for i := uint32(0); i < numCPU; i++ { + if i == core { + continue + } + rp2040FlashSafePauseCore(i) + } - for rp2040FlashSafeState.Get() != rp2040FlashSafeLocked { - spinLoopWait() - } + for rp2040FlashSafeState.Get() != rp2040FlashSafeLocked { + spinLoopWait() + } - return interrupt.Disable() + return interrupt.Disable() } // rp2040ExitFlashSafeSection exits a section entered by // rp2040EnterFlashSafeSection. func rp2040ExitFlashSafeSection(state interrupt.State) { - if secondaryCoresStarted { - rp2040FlashSafeState.Set(rp2040FlashSafeRelease) - arm.Asm("sev") + if secondaryCoresStarted { + rp2040FlashSafeState.Set(rp2040FlashSafeRelease) + arm.Asm("sev") - for rp2040FlashSafeState.Get() != rp2040FlashSafeIdle { - spinLoopWait() - } - } + for rp2040FlashSafeState.Get() != rp2040FlashSafeIdle { + spinLoopWait() + } - interrupt.Restore(state) + flashSafeLock.Unlock() + } + + interrupt.Restore(state) } func rp2040FlashSafePauseCore(core uint32) { - _ = core // RP2040 SIO FIFO writes to the other core. - rp.SIO.FIFO_WR.Set(rp2040FlashSafeFIFOCommand) - arm.Asm("sev") + _ = core // RP2040 SIO FIFO writes to the other core. + rp.SIO.FIFO_WR.Set(rp2040FlashSafeFIFOCommand) + arm.Asm("sev") } -// rp2FlashSafeInterruptHandler is called from the SIO FIFO interrupt handler. +// rp2FlashSafeInterruptHandler runs on the other core while this core is +// performing a flash operation that temporarily disables XIP. +// +// This function MUST be placed in RAM (.ramfuncs section). During the +// flash operation the QSPI flash is in non-XIP mode and instruction +// fetches from the 0x10000000 region will fail. The wait loop below +// runs entirely from RAM so that the parked core can keep executing. // -// NOTE: this first draft keeps the lockout handler in Go. The final version -// should ensure that the wait loop runs from RAM while XIP is disabled. +//go:section .ramfuncs func rp2FlashSafeInterruptHandler(core uint32) { - _ = core + _ = core - state := interrupt.Disable() + state := interrupt.Disable() - rp2040FlashSafeState.Set(rp2040FlashSafeLocked) - arm.Asm("sev") + rp2040FlashSafeState.Set(rp2040FlashSafeLocked) + arm.Asm("sev") - for rp2040FlashSafeState.Get() == rp2040FlashSafeLocked { - arm.Asm("wfe") - } + for rp2040FlashSafeState.Get() == rp2040FlashSafeLocked { + arm.Asm("wfe") + } - interrupt.Restore(state) + interrupt.Restore(state) - rp2040FlashSafeState.Set(rp2040FlashSafeIdle) - arm.Asm("sev") + rp2040FlashSafeState.Set(rp2040FlashSafeIdle) + arm.Asm("sev") } From 920df83033e8c9747e0d6597f858ed42b59f0739 Mon Sep 17 00:00:00 2001 From: rdon Date: Wed, 20 May 2026 17:51:52 +0900 Subject: [PATCH 03/11] machine: copy RP2040 flash write buffer to RAM --- src/machine/machine_rp2040_rom.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index 973792bfd5..0b3d056e5b 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -230,15 +230,17 @@ func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { // 0x00003000 address := writeAddress(off) padded := flashPad(p, int(f.WriteBlockSize())) + buf := make([]byte, len(padded)) + copy(buf, padded) state := rp2040EnterFlashSafeSection() defer rp2040ExitFlashSafeSection(state) C.flash_range_write(C.uint32_t(address), - (*C.uint8_t)(unsafe.Pointer(&padded[0])), - C.ulong(len(padded))) + (*C.uint8_t)(unsafe.Pointer(&buf[0])), + C.ulong(len(buf))) - return len(padded), nil + return len(buf), nil } func (f flashBlockDevice) eraseBlocks(start, length int64) error { From c90784212d42d768a7ddd2c8d489fa6d73417a5f Mon Sep 17 00:00:00 2001 From: rdon Date: Wed, 20 May 2026 17:57:17 +0900 Subject: [PATCH 04/11] gofmt files --- src/runtime/runtime_rp2040_flashsafe_cores.go | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/runtime/runtime_rp2040_flashsafe_cores.go b/src/runtime/runtime_rp2040_flashsafe_cores.go index c75daeea34..ac2df6796a 100644 --- a/src/runtime/runtime_rp2040_flashsafe_cores.go +++ b/src/runtime/runtime_rp2040_flashsafe_cores.go @@ -3,19 +3,19 @@ package runtime import ( - "device/arm" - "device/rp" - "runtime/interrupt" - "runtime/volatile" - _ "unsafe" // required for //go:section + "device/arm" + "device/rp" + "runtime/interrupt" + "runtime/volatile" + _ "unsafe" // required for //go:section ) const ( - rp2040FlashSafeFIFOCommand = 2 + rp2040FlashSafeFIFOCommand = 2 - rp2040FlashSafeIdle = 0 - rp2040FlashSafeLocked = 1 - rp2040FlashSafeRelease = 2 + rp2040FlashSafeIdle = 0 + rp2040FlashSafeLocked = 1 + rp2040FlashSafeRelease = 2 ) // rp2040FlashSafeState is used to synchronize the core that performs a flash @@ -37,50 +37,50 @@ var flashSafeLock = spinLock{id: 24} // handler and waits until it acknowledges that it is parked. Local interrupts // are disabled after the other core is parked. func rp2040EnterFlashSafeSection() interrupt.State { - if !secondaryCoresStarted { - return interrupt.Disable() - } + if !secondaryCoresStarted { + return interrupt.Disable() + } - flashSafeLock.Lock() + flashSafeLock.Lock() - core := currentCPU() - rp2040FlashSafeState.Set(rp2040FlashSafeIdle) + core := currentCPU() + rp2040FlashSafeState.Set(rp2040FlashSafeIdle) - for i := uint32(0); i < numCPU; i++ { - if i == core { - continue - } - rp2040FlashSafePauseCore(i) - } + for i := uint32(0); i < numCPU; i++ { + if i == core { + continue + } + rp2040FlashSafePauseCore(i) + } - for rp2040FlashSafeState.Get() != rp2040FlashSafeLocked { - spinLoopWait() - } + for rp2040FlashSafeState.Get() != rp2040FlashSafeLocked { + spinLoopWait() + } - return interrupt.Disable() + return interrupt.Disable() } // rp2040ExitFlashSafeSection exits a section entered by // rp2040EnterFlashSafeSection. func rp2040ExitFlashSafeSection(state interrupt.State) { - if secondaryCoresStarted { - rp2040FlashSafeState.Set(rp2040FlashSafeRelease) - arm.Asm("sev") + if secondaryCoresStarted { + rp2040FlashSafeState.Set(rp2040FlashSafeRelease) + arm.Asm("sev") - for rp2040FlashSafeState.Get() != rp2040FlashSafeIdle { - spinLoopWait() - } + for rp2040FlashSafeState.Get() != rp2040FlashSafeIdle { + spinLoopWait() + } - flashSafeLock.Unlock() - } + flashSafeLock.Unlock() + } - interrupt.Restore(state) + interrupt.Restore(state) } func rp2040FlashSafePauseCore(core uint32) { - _ = core // RP2040 SIO FIFO writes to the other core. - rp.SIO.FIFO_WR.Set(rp2040FlashSafeFIFOCommand) - arm.Asm("sev") + _ = core // RP2040 SIO FIFO writes to the other core. + rp.SIO.FIFO_WR.Set(rp2040FlashSafeFIFOCommand) + arm.Asm("sev") } // rp2FlashSafeInterruptHandler runs on the other core while this core is @@ -93,19 +93,19 @@ func rp2040FlashSafePauseCore(core uint32) { // //go:section .ramfuncs func rp2FlashSafeInterruptHandler(core uint32) { - _ = core + _ = core - state := interrupt.Disable() + state := interrupt.Disable() - rp2040FlashSafeState.Set(rp2040FlashSafeLocked) - arm.Asm("sev") + rp2040FlashSafeState.Set(rp2040FlashSafeLocked) + arm.Asm("sev") - for rp2040FlashSafeState.Get() == rp2040FlashSafeLocked { - arm.Asm("wfe") - } + for rp2040FlashSafeState.Get() == rp2040FlashSafeLocked { + arm.Asm("wfe") + } - interrupt.Restore(state) + interrupt.Restore(state) - rp2040FlashSafeState.Set(rp2040FlashSafeIdle) - arm.Asm("sev") + rp2040FlashSafeState.Set(rp2040FlashSafeIdle) + arm.Asm("sev") } From 51901b29f82f498d933839580564a12b03f6fa70 Mon Sep 17 00:00:00 2001 From: rdon Date: Wed, 20 May 2026 18:13:43 +0900 Subject: [PATCH 05/11] copy doFlashCommand tx buffer to RAM --- src/machine/machine_rp2040_rom.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index 0b3d056e5b..f66b8cfd51 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -205,14 +205,20 @@ func doFlashCommand(tx []byte, rx []byte) error { if len(tx) != len(rx) { return errFlashInvalidWriteLength } + if len(tx) == 0 { + return nil + } + + txbuf := make([]byte, len(tx)) + copy(txbuf, tx) state := rp2040EnterFlashSafeSection() defer rp2040ExitFlashSafeSection(state) C.flash_do_cmd( - (*C.uint8_t)(unsafe.Pointer(&tx[0])), + (*C.uint8_t)(unsafe.Pointer(&txbuf[0])), (*C.uint8_t)(unsafe.Pointer(&rx[0])), - C.ulong(len(tx))) + C.ulong(len(txbuf))) return nil } From d1af9b47e8de29a2acd6c847746f889383a72748 Mon Sep 17 00:00:00 2001 From: rdon Date: Wed, 20 May 2026 18:18:55 +0900 Subject: [PATCH 06/11] handle empty flash buffers --- src/machine/machine_rp2040_rom.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index f66b8cfd51..5601ec975c 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -238,6 +238,9 @@ func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { padded := flashPad(p, int(f.WriteBlockSize())) buf := make([]byte, len(padded)) copy(buf, padded) + if len(buf) == 0 { + return 0, nil + } state := rp2040EnterFlashSafeSection() defer rp2040ExitFlashSafeSection(state) From d9f4924b93b3e1a1a33ec61e4eac382540eef654 Mon Sep 17 00:00:00 2001 From: rdon Date: Wed, 20 May 2026 19:14:13 +0900 Subject: [PATCH 07/11] name RP2 FIFO and flash-safe states --- src/runtime/runtime_rp2.go | 7 ++++++- src/runtime/runtime_rp2040.go | 8 ++++---- src/runtime/runtime_rp2040_flashsafe_cores.go | 10 ++++------ src/runtime/runtime_rp2350.go | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/runtime/runtime_rp2.go b/src/runtime/runtime_rp2.go index f6d31f9806..c601209ff0 100644 --- a/src/runtime/runtime_rp2.go +++ b/src/runtime/runtime_rp2.go @@ -12,6 +12,11 @@ import ( "unsafe" ) +const ( + rp2SIOFIFOCommandGC uint32 = iota + 1 + rp2SIOFIFOCommandFlashSafe +) + const numCPU = 2 const numSpinlocks = 32 @@ -250,7 +255,7 @@ func gcInterruptHandler(hartID uint32) { // Pause the given core by sending it an interrupt. func gcPauseCore(core uint32) { - rp.SIO.FIFO_WR.Set(1) + rp.SIO.FIFO_WR.Set(rp2SIOFIFOCommandGC) } // Signal the given core that it can resume one step. diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go index 73e4127298..d0f61b9093 100644 --- a/src/runtime/runtime_rp2040.go +++ b/src/runtime/runtime_rp2040.go @@ -29,18 +29,18 @@ func enableSIOFifoInterruptCore1() { func handleSIOFifoInterruptCore0(intr interrupt.Interrupt) { switch rp.SIO.FIFO_RD.Get() { - case 1: + case rp2SIOFIFOCommandGC: gcInterruptHandler(0) - case 2: + case rp2SIOFIFOCommandFlashSafe: rp2FlashSafeInterruptHandler(0) } } func handleSIOFifoInterruptCore1(intr interrupt.Interrupt) { switch rp.SIO.FIFO_RD.Get() { - case 1: + case rp2SIOFIFOCommandGC: gcInterruptHandler(1) - case 2: + case rp2SIOFIFOCommandFlashSafe: rp2FlashSafeInterruptHandler(1) } } diff --git a/src/runtime/runtime_rp2040_flashsafe_cores.go b/src/runtime/runtime_rp2040_flashsafe_cores.go index ac2df6796a..e58115e1c6 100644 --- a/src/runtime/runtime_rp2040_flashsafe_cores.go +++ b/src/runtime/runtime_rp2040_flashsafe_cores.go @@ -11,11 +11,9 @@ import ( ) const ( - rp2040FlashSafeFIFOCommand = 2 - - rp2040FlashSafeIdle = 0 - rp2040FlashSafeLocked = 1 - rp2040FlashSafeRelease = 2 + rp2040FlashSafeIdle uint8 = iota + rp2040FlashSafeLocked + rp2040FlashSafeRelease ) // rp2040FlashSafeState is used to synchronize the core that performs a flash @@ -79,7 +77,7 @@ func rp2040ExitFlashSafeSection(state interrupt.State) { func rp2040FlashSafePauseCore(core uint32) { _ = core // RP2040 SIO FIFO writes to the other core. - rp.SIO.FIFO_WR.Set(rp2040FlashSafeFIFOCommand) + rp.SIO.FIFO_WR.Set(rp2SIOFIFOCommandFlashSafe) arm.Asm("sev") } diff --git a/src/runtime/runtime_rp2350.go b/src/runtime/runtime_rp2350.go index 58396462c4..2ec90138a8 100644 --- a/src/runtime/runtime_rp2350.go +++ b/src/runtime/runtime_rp2350.go @@ -33,9 +33,9 @@ func enableSIOFifoInterruptCore1() { func handleSIOFifoInterrupt(intr interrupt.Interrupt) { switch rp.SIO.FIFO_RD.Get() { - case 1: + case rp2SIOFIFOCommandGC: gcInterruptHandler(currentCPU()) - case 2: + case rp2SIOFIFOCommandFlashSafe: rp2FlashSafeInterruptHandler(currentCPU()) } } From 7e24ce08d8555818bee3c9c7b14a210ec1801aa0 Mon Sep 17 00:00:00 2001 From: rdon Date: Tue, 9 Jun 2026 22:45:58 +0900 Subject: [PATCH 08/11] ci: rerun checks From 27373a0ef664fd518514701240359501d0d1b9fa Mon Sep 17 00:00:00 2001 From: rdon Date: Sun, 14 Jun 2026 01:43:07 +0900 Subject: [PATCH 09/11] runtime: update comments for RP2 flash-safe no-op handlers --- src/runtime/runtime_rp2040_flashsafe_single.go | 4 ++-- src/runtime/runtime_rp2350_flashsafe_stub.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runtime/runtime_rp2040_flashsafe_single.go b/src/runtime/runtime_rp2040_flashsafe_single.go index b68adbfee9..3f5ec7bcd1 100644 --- a/src/runtime/runtime_rp2040_flashsafe_single.go +++ b/src/runtime/runtime_rp2040_flashsafe_single.go @@ -12,6 +12,6 @@ func rp2040ExitFlashSafeSection(state interrupt.State) { interrupt.Restore(state) } -func rp2FlashSafeInterruptHandler(core uint32) { - _ = core +func rp2FlashSafeInterruptHandler(uint32) { + // No-op on single-core schedulers. } diff --git a/src/runtime/runtime_rp2350_flashsafe_stub.go b/src/runtime/runtime_rp2350_flashsafe_stub.go index 211b920cc5..630ffac9a0 100644 --- a/src/runtime/runtime_rp2350_flashsafe_stub.go +++ b/src/runtime/runtime_rp2350_flashsafe_stub.go @@ -2,6 +2,6 @@ package runtime -func rp2FlashSafeInterruptHandler(core uint32) { - _ = core +func rp2FlashSafeInterruptHandler(uint32) { + // No-op on RP2350. RP2350 flash-safe handling is intentionally unchanged. } From c1f3c64406edccdae30b9693d81fb5814d2f6377 Mon Sep 17 00:00:00 2001 From: rdon-key Date: Tue, 7 Jul 2026 01:56:07 +0900 Subject: [PATCH 10/11] runtime/rp2: keep flash-safe spinlock with runtime locks --- src/runtime/runtime_rp2.go | 9 +++++++++ src/runtime/runtime_rp2040_flashsafe_cores.go | 12 +----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/runtime/runtime_rp2.go b/src/runtime/runtime_rp2.go index c601209ff0..e2c2559e35 100644 --- a/src/runtime/runtime_rp2.go +++ b/src/runtime/runtime_rp2.go @@ -286,6 +286,15 @@ var ( schedulerLock = spinLock{id: 21} atomicsLock = spinLock{id: 22} futexLock = spinLock{id: 23} + + // flashSafeLock serializes RP2040 flash-safe Enter/Exit so that only one + // core owns the flash-safe state machine at a time. The other core can + // still participate as a victim through the FIFO interrupt while spinning + // on this lock. + // + // It is defined here with the other RP2 spinLocks so spinLock IDs remain + // visible in one place. + flashSafeLock = spinLock{id: 24} ) func resetSpinLocks() { diff --git a/src/runtime/runtime_rp2040_flashsafe_cores.go b/src/runtime/runtime_rp2040_flashsafe_cores.go index e58115e1c6..427fc2e3c4 100644 --- a/src/runtime/runtime_rp2040_flashsafe_cores.go +++ b/src/runtime/runtime_rp2040_flashsafe_cores.go @@ -20,14 +20,6 @@ const ( // operation with the other core that must stop executing from XIP flash. var rp2040FlashSafeState volatile.Register8 -// flashSafeLock serializes Enter/Exit so that only one core at a time -// owns the flash-safe state machine. The other core can still participate -// as a victim through the FIFO interrupt while spinning on this lock. -// -// id: 24 is reserved here. ids 20-23 are already used by printLock, -// schedulerLock, atomicsLock, futexLock (see runtime_rp2.go). -var flashSafeLock = spinLock{id: 24} - // rp2040EnterFlashSafeSection enters a section in which RP2040 flash operations // may temporarily disable XIP. // @@ -76,7 +68,7 @@ func rp2040ExitFlashSafeSection(state interrupt.State) { } func rp2040FlashSafePauseCore(core uint32) { - _ = core // RP2040 SIO FIFO writes to the other core. + // RP2040 SIO FIFO writes to the other core. rp.SIO.FIFO_WR.Set(rp2SIOFIFOCommandFlashSafe) arm.Asm("sev") } @@ -91,8 +83,6 @@ func rp2040FlashSafePauseCore(core uint32) { // //go:section .ramfuncs func rp2FlashSafeInterruptHandler(core uint32) { - _ = core - state := interrupt.Disable() rp2040FlashSafeState.Set(rp2040FlashSafeLocked) From ddc5485b96bfe77c590a0da4d5b1b8801df47613 Mon Sep 17 00:00:00 2001 From: rdon-key Date: Tue, 7 Jul 2026 13:31:27 +0900 Subject: [PATCH 11/11] machine/rp2040: avoid unconditional flash buffer copies --- src/machine/machine_rp2040_rom.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index 5601ec975c..3afef547a3 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -209,16 +209,13 @@ func doFlashCommand(tx []byte, rx []byte) error { return nil } - txbuf := make([]byte, len(tx)) - copy(txbuf, tx) - state := rp2040EnterFlashSafeSection() defer rp2040ExitFlashSafeSection(state) C.flash_do_cmd( - (*C.uint8_t)(unsafe.Pointer(&txbuf[0])), + (*C.uint8_t)(unsafe.Pointer(&tx[0])), (*C.uint8_t)(unsafe.Pointer(&rx[0])), - C.ulong(len(txbuf))) + C.ulong(len(tx))) return nil } @@ -236,9 +233,7 @@ func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { // 0x00003000 address := writeAddress(off) padded := flashPad(p, int(f.WriteBlockSize())) - buf := make([]byte, len(padded)) - copy(buf, padded) - if len(buf) == 0 { + if len(padded) == 0 { return 0, nil } @@ -246,10 +241,10 @@ func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { defer rp2040ExitFlashSafeSection(state) C.flash_range_write(C.uint32_t(address), - (*C.uint8_t)(unsafe.Pointer(&buf[0])), - C.ulong(len(buf))) + (*C.uint8_t)(unsafe.Pointer(&padded[0])), + C.ulong(len(padded))) - return len(buf), nil + return len(padded), nil } func (f flashBlockDevice) eraseBlocks(start, length int64) error {