Skip to content

Conversation

mikesmitty
Copy link
Contributor

@mikesmitty mikesmitty commented Sep 10, 2025

As it turns out, the RP2350 has hardware spinlocks that can be unlocked by writes to nearby addresses, the lower spinlocks currently in use in TinyGo happen to be unlocked by writes to the doorbell interrupt registers used to signal between cores, very possibly leading to some unexpected unlocks. This was not corrected in the A3 or A4 steppings and instead software spinlocks are used by default on RP2350 in pico-sdk:

RP2350 Warning. Due to erratum RP2350-E2, writes to new SIO registers above an offset of +0x180 alias the spinlocks, causing spurious lock releases. This SDK by default use atomic memory accesses to implement the hardware_sync_spin_lock API, as a workaround on RP2350 A2.

https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_hardware_sync

Another thing I noticed in that section, they also always disable interrupts when the spinlocks are being held, we may want to do the same:

[...] the default spinlock related methods here (e.g. spin_lock_blocking) always disable interrupts while the lock is held as use by IRQ handlers and user code is common/desirable, and spin locks are only expected to be held for brief periods.

These are the software spinlock macros ported over:
https://github.com/raspberrypi/pico-sdk/blob/2.2.0/src/rp2_common/hardware_sync_spin_lock/include/hardware/sync/spin_lock.h#L112
https://github.com/raspberrypi/pico-sdk/blob/2.2.0/src/rp2_common/hardware_sync_spin_lock/include/hardware/sync/spin_lock.h#L197

@mikesmitty
Copy link
Contributor Author

Oh, whoops. My go fmt extension has been flaking out on me. Will have the missing rp2040 imports updated in a moment. Here's the lock/unlock disassembled output with inlining disabled:

   0x10001178 <(*runtime.spinLock).Lock+0>:     cbz     r0, 0x10001192 <(*runtime.spinLock).Lock+26>
   0x1000117a <(*runtime.spinLock).Lock+2>:     ldaexb  r2, [r0]
   0x1000117e <(*runtime.spinLock).Lock+6>:     movs    r1, #1
   0x10001180 <(*runtime.spinLock).Lock+8>:     cmp     r2, #0
   0x10001182 <(*runtime.spinLock).Lock+10>:    bne.n   0x1000117a <(*runtime.spinLock).Lock+2>
   0x10001184 <(*runtime.spinLock).Lock+12>:    strexb  r2, r1, [r0]
   0x10001188 <(*runtime.spinLock).Lock+16>:    cmp     r2, #0
   0x1000118a <(*runtime.spinLock).Lock+18>:    bne.n   0x1000117a <(*runtime.spinLock).Lock+2>
   0x1000118c <(*runtime.spinLock).Lock+20>:    dmb     sy
   0x10001190 <(*runtime.spinLock).Lock+24>:    bx      lr
   0x10001192 <(*runtime.spinLock).Lock+26>:    bl      0x100013f4 <runtime.nilPanic>
   0x100013e4 <(*runtime.spinLock).Unlock+0>:   cbz     r0, 0x100013ee <(*runtime.spinLock).Unlock+10>
   0x100013e6 <(*runtime.spinLock).Unlock+2>:   movs    r1, #0
   0x100013e8 <(*runtime.spinLock).Unlock+4>:   stlb    r1, [r0]
   0x100013ec <(*runtime.spinLock).Unlock+8>:   bx      lr
   0x100013ee <(*runtime.spinLock).Unlock+10>:  bl      0x100013f4 <runtime.nilPanic>

Copy link
Contributor

@eliasnaur eliasnaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I support the switch to atomic instructions, but if you need something that works right away, RP2350-E2 mentions that some spinlocks are not affected:

The following SIO spinlocks can be used normally because they don’t alias with writable registers: 5, 6, 7,
10, 11, and 18 through 31. Some of the other lock addresses may be used safely depending on which of
the high-addressed SIO registers are in use.
Locks 18 through 24 alias with some read-only TMDS encoder registers, which is safe as only writes are
mis-decoded.

Copy link
Member

@aykevl aykevl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment below.

Also,

Another thing I noticed in that section, they also always disable interrupts when the spinlocks are being held, we may want to do the same: [...]

The runtime does this in various places if needed. The spinlock implementation doesn't need to disable interrupts too.

@mikesmitty
Copy link
Contributor Author

I support the switch to atomic instructions, but if you need something that works right away, RP2350-E2 mentions that some spinlocks are not affected:

The following SIO spinlocks can be used normally because they don’t alias with writable registers: 5, 6, 7,
10, 11, and 18 through 31. Some of the other lock addresses may be used safely depending on which of
the high-addressed SIO registers are in use.
Locks 18 through 24 alias with some read-only TMDS encoder registers, which is safe as only writes are
mis-decoded.

Thank you for tracking those down. I figured there would probably be some, but I couldn't find them

Copy link
Contributor

@eliasnaur eliasnaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than a nit, this looks good to me. @aykevl WDYT?

printLock = spinLock{id: 0}
schedulerLock = spinLock{id: 1}
atomicsLock = spinLock{id: 2}
futexLock = spinLock{id: 3}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id is an implementation detail of rp2040 spinlocks, whereas on rp2350 ids have some meaning but are unusued. I suggest moving the spinlock variables to the rpXXXX.go files and avoid the id field on rp2350.


type spinLock struct {
atomic.Uint32
id uint8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That one's still needed for compatibility with how the rp2040 hardware spinlocks are initialized here:

printLock = spinLock{id: 20}
schedulerLock = spinLock{id: 21}
atomicsLock = spinLock{id: 22}
futexLock = spinLock{id: 23}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Above, I suggested moving that var () block to the runtime_rp2xxx.go files to get rid of that dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yep, I missed that

@mikesmitty
Copy link
Contributor Author

After doing some testing, I think this might not actually be locking properly. Going to do some more thorough testing

@aykevl
Copy link
Member

aykevl commented Sep 23, 2025

Yes, this looks good. @mikesmitty can you apply the changes proposed by @eliasnaur?

Also, you may want to rebase on the dev branch to hopefully fix the assert-test-linux CI failure.

@mikesmitty
Copy link
Contributor Author

Sure, here's the rebase. I haven't had time to test or diagnose it yet, been stuck working on a project that's consumed all my free time, but I'm pretty sure it's not functioning properly as confirmed by that CI failure

@mikesmitty
Copy link
Contributor Author

Hmm, no I guess it is actually locking. I'm not sure what's happening with that CI test though

@aykevl aykevl changed the base branch from release to dev September 29, 2025 12:59
Copy link
Member

@aykevl aykevl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

@aykevl
Copy link
Member

aykevl commented Sep 29, 2025

Restarting CI, these are probably flaky tests.


// #include <stdint.h>
//
// static void spinlock_lock(void *lock) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was wrong with the sync/atomic operations? Custom assembly for just one target (rp2350) seems to me like a maintenance headache for little gain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants