-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
RP2040: avoid XIP hangs during flash operations with scheduler=cores #5411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 8 commits
c988628
8ffa17f
61fc69f
26f34aa
3ee9ac6
591c5a7
4c44c4a
9fc5ca7
e3dc1bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) |
| 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") | ||
| } |
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you add the above comment you can remove this line.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, done. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| //go:build rp2350 | ||
|
|
||
| package runtime | ||
|
|
||
| func rp2FlashSafeInterruptHandler(core uint32) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same feedback as above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, done. |
||
| _ = core | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could add a comment here that it is a no-op on single core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, done.