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
20 changes: 20 additions & 0 deletions src/machine/machine_rp2040_flashsafe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//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 (
"runtime/interrupt"
_ "unsafe"
)

//go:linkname rp2040EnterFlashSafeSection runtime.rp2040EnterFlashSafeSection
func rp2040EnterFlashSafeSection() interrupt.State

//go:linkname rp2040ExitFlashSafeSection runtime.rp2040ExitFlashSafeSection
func rp2040ExitFlashSafeSection(state interrupt.State)
35 changes: 24 additions & 11 deletions src/machine/machine_rp2040_rom.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package machine

import (
"runtime/interrupt"
"unsafe"
)

Expand Down Expand Up @@ -206,11 +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
}
Expand All @@ -223,20 +231,25 @@ 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()))
buf := make([]byte, len(padded))
copy(buf, padded)
if len(buf) == 0 {
return 0, nil
}

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 {
Expand All @@ -245,8 +258,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()))

Expand Down
15 changes: 12 additions & 3 deletions src/runtime/runtime_rp2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
"unsafe"
)

const (
rp2SIOFIFOCommandGC uint32 = iota + 1
rp2SIOFIFOCommandFlashSafe
)

const numCPU = 2
const numSpinlocks = 32

Expand Down Expand Up @@ -142,8 +147,10 @@ func startSecondaryCores() {
// second core.
intr := interrupt.New(sioIrqFifoProc0, func(intr interrupt.Interrupt) {
switch rp.SIO.FIFO_RD.Get() {
case 1:
case rp2SIOFIFOCommandGC:
gcInterruptHandler(0)
case rp2SIOFIFOCommandFlashSafe:
rp2FlashSafeInterruptHandler(0)
}
})
intr.Enable()
Expand Down Expand Up @@ -176,8 +183,10 @@ func runCore1() {
// interrupts can still happen while the GC is running.
intr := interrupt.New(sioIrqFifoProc1, func(intr interrupt.Interrupt) {
switch rp.SIO.FIFO_RD.Get() {
case 1:
case rp2SIOFIFOCommandGC:
gcInterruptHandler(1)
case rp2SIOFIFOCommandFlashSafe:
rp2FlashSafeInterruptHandler(1)
}
})
intr.Enable()
Expand Down Expand Up @@ -265,7 +274,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.
Expand Down
109 changes: 109 additions & 0 deletions src/runtime/runtime_rp2040_flashsafe_cores.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//go:build rp2040 && scheduler.cores

package runtime

import (
"device/arm"
"device/rp"
"runtime/interrupt"
"runtime/volatile"
_ "unsafe" // required for //go:section
)

const (
rp2040FlashSafeIdle uint8 = iota
rp2040FlashSafeLocked
rp2040FlashSafeRelease
)

// 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.
//
// 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()
}

flashSafeLock.Lock()

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()
}

flashSafeLock.Unlock()
}

interrupt.Restore(state)
}

func rp2040FlashSafePauseCore(core uint32) {
_ = core // RP2040 SIO FIFO writes to the other core.
rp.SIO.FIFO_WR.Set(rp2SIOFIFOCommandFlashSafe)
arm.Asm("sev")
}

// 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.
//
//go:section .ramfuncs
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")
}
17 changes: 17 additions & 0 deletions src/runtime/runtime_rp2040_flashsafe_single.go
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 7 additions & 0 deletions src/runtime/runtime_rp2350_flashsafe_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build rp2350

package runtime

func rp2FlashSafeInterruptHandler(core uint32) {
_ = core
}
Loading