@@ -4,10 +4,17 @@ package runtime
44
55import (
66 "device/arm"
7+ "device/rp"
8+ "internal/task"
79 "machine"
810 "machine/usb/cdc"
11+ "runtime/interrupt"
12+ "runtime/volatile"
13+ "unsafe"
914)
1015
16+ const numCPU = 2
17+
1118// machineTicks is provided by package machine.
1219func machineTicks () uint64
1320
@@ -43,6 +50,284 @@ func sleepTicks(d timeUnit) {
4350 }
4451}
4552
53+ // Currently sleeping core, or 0xff.
54+ // Must only be accessed with the scheduler lock held.
55+ var sleepingCore uint8 = 0xff
56+
57+ // Return whether another core is sleeping.
58+ // May only be called with the scheduler lock held.
59+ func hasSleepingCore () bool {
60+ return sleepingCore != 0xff
61+ }
62+
63+ // Almost identical to sleepTicks, except that it will unlock/lock the scheduler
64+ // while sleeping and is interruptible by interruptSleepTicksMulticore.
65+ // This may only be called with the scheduler lock held.
66+ func sleepTicksMulticore (d timeUnit ) {
67+ sleepingCore = uint8 (currentCPU ())
68+
69+ // Note: interruptSleepTicksMulticore will be able to interrupt this, since
70+ // it executes the "sev" instruction which would make sleepTicks return
71+ // immediately without sleeping. Even if it happens while configuring the
72+ // sleep operation.
73+
74+ schedulerLock .Unlock ()
75+ sleepTicks (d )
76+ schedulerLock .Lock ()
77+
78+ sleepingCore = 0xff
79+ }
80+
81+ // Interrupt an ongoing call to sleepTicksMulticore on another core.
82+ func interruptSleepTicksMulticore (wakeup timeUnit ) {
83+ arm .Asm ("sev" )
84+ }
85+
86+ // Number of cores that are currently in schedulerUnlockAndWait.
87+ // It is possible for both cores to be sleeping, if the program is waiting for
88+ // an interrupt (or is deadlocked).
89+ var waitingCore uint8
90+
91+ // Put the scheduler to sleep, since there are no tasks to run.
92+ // This will unlock the scheduler lock, and must be called with the scheduler
93+ // lock held.
94+ func schedulerUnlockAndWait () {
95+ waitingCore ++
96+ schedulerLock .Unlock ()
97+ arm .Asm ("wfe" )
98+ schedulerLock .Lock ()
99+ waitingCore --
100+ }
101+
102+ // Wake another core, if one is sleeping. Must be called with the scheduler lock
103+ // held.
104+ func schedulerWake () {
105+ if waitingCore != 0 {
106+ arm .Asm ("sev" )
107+ }
108+ }
109+
110+ // Return the current core number: 0 or 1.
111+ func currentCPU () uint32 {
112+ return rp .SIO .CPUID .Get ()
113+ }
114+
115+ // Start the secondary cores for this chip.
116+ // On the RP2040, there is only one other core to start.
117+ func startSecondaryCores () {
118+ // Start the second core of the RP2040.
119+ // See section 2.8.2 in the datasheet.
120+ seq := 0
121+ for {
122+ cmd := core1StartSequence [seq ]
123+ if cmd == 0 {
124+ multicore_fifo_drain ()
125+ arm .Asm ("sev" )
126+ }
127+ multicore_fifo_push_blocking (cmd )
128+ response := multicore_fifo_pop_blocking ()
129+ if cmd != response {
130+ seq = 0
131+ continue
132+ }
133+ seq = seq + 1
134+ if seq >= len (core1StartSequence ) {
135+ break
136+ }
137+ }
138+
139+ // Enable the FIFO interrupt for the GC stop the world phase.
140+ // We can only do this after we don't need the FIFO anymore for starting the
141+ // second core.
142+ intr := interrupt .New (rp .IRQ_SIO_IRQ_PROC0 , func (intr interrupt.Interrupt ) {
143+ switch rp .SIO .FIFO_RD .Get () {
144+ case 1 :
145+ gcInterruptHandler (0 )
146+ }
147+ })
148+ intr .Enable ()
149+ intr .SetPriority (0xff )
150+ }
151+
152+ var core1StartSequence = [... ]uint32 {
153+ 0 , 0 , 1 ,
154+ uint32 (uintptr (unsafe .Pointer (& __isr_vector ))),
155+ uint32 (uintptr (unsafe .Pointer (& stack1TopSymbol ))),
156+ uint32 (exportedFuncPtr (runCore1 )),
157+ }
158+
159+ //go:extern __isr_vector
160+ var __isr_vector [0 ]uint32
161+
162+ //go:extern _stack1_top
163+ var stack1TopSymbol [0 ]uint32
164+
165+ // The function that is started on the second core.
166+ //
167+ //export tinygo_runCore1
168+ func runCore1 () {
169+ // Clear sticky bit that seems to have been set while starting this core.
170+ rp .SIO .FIFO_ST .Set (rp .SIO_FIFO_ST_ROE )
171+
172+ // Enable the FIFO interrupt, mainly used for the stop-the-world phase of
173+ // the GC.
174+ // Use the lowest possible priority (highest priority value), so that other
175+ // interrupts can still happen while the GC is running.
176+ intr := interrupt .New (rp .IRQ_SIO_IRQ_PROC1 , func (intr interrupt.Interrupt ) {
177+ switch rp .SIO .FIFO_RD .Get () {
178+ case 1 :
179+ gcInterruptHandler (1 )
180+ }
181+ })
182+ intr .Enable ()
183+ intr .SetPriority (0xff )
184+
185+ // Now start running the scheduler on this core.
186+ schedulerLock .Lock ()
187+ scheduler (false )
188+ schedulerLock .Unlock ()
189+
190+ // The main function returned.
191+ exit (0 )
192+ }
193+
194+ // The below multicore_fifo_* functions have been translated from the Raspberry
195+ // Pi Pico SDK.
196+
197+ func multicore_fifo_rvalid () bool {
198+ return rp .SIO .FIFO_ST .Get ()& rp .SIO_FIFO_ST_VLD != 0
199+ }
200+
201+ func multicore_fifo_wready () bool {
202+ return rp .SIO .FIFO_ST .Get ()& rp .SIO_FIFO_ST_RDY != 0
203+ }
204+
205+ func multicore_fifo_drain () {
206+ for multicore_fifo_rvalid () {
207+ rp .SIO .FIFO_RD .Get ()
208+ }
209+ }
210+
211+ func multicore_fifo_push_blocking (data uint32 ) {
212+ for ! multicore_fifo_wready () {
213+ }
214+ rp .SIO .FIFO_WR .Set (data )
215+ arm .Asm ("sev" )
216+ }
217+
218+ func multicore_fifo_pop_blocking () uint32 {
219+ for ! multicore_fifo_rvalid () {
220+ arm .Asm ("wfe" )
221+ }
222+
223+ return rp .SIO .FIFO_RD .Get ()
224+ }
225+
226+ // Value used to communicate between the GC core and the other (paused) cores.
227+ var gcSignalWait volatile.Register8
228+
229+ // The GC interrupted this core for the stop-the-world phase.
230+ // This function handles that, and only returns after the stop-the-world phase
231+ // ended.
232+ func gcInterruptHandler (hartID uint32 ) {
233+ // Let the GC know we're ready.
234+ gcScanState .Add (1 )
235+ arm .Asm ("sev" )
236+
237+ // Wait until we get a signal to start scanning.
238+ for gcSignalWait .Get () == 0 {
239+ arm .Asm ("wfe" )
240+ }
241+ gcSignalWait .Set (0 )
242+
243+ // Scan the stack(s) of this core.
244+ scanCurrentStack ()
245+ if ! task .OnSystemStack () {
246+ // Mark system stack.
247+ markRoots (task .SystemStack (), coreStackTop (hartID ))
248+ }
249+
250+ // Signal we've finished scanning.
251+ gcScanState .Store (1 )
252+ arm .Asm ("sev" )
253+
254+ // Wait until we get a signal that the stop-the-world phase has ended.
255+ for gcSignalWait .Get () == 0 {
256+ arm .Asm ("wfe" )
257+ }
258+ gcSignalWait .Set (0 )
259+
260+ // Signal we received the signal and are going to exit the interrupt.
261+ gcScanState .Add (1 )
262+ arm .Asm ("sev" )
263+ }
264+
265+ // Pause the given core by sending it an interrupt.
266+ func gcPauseCore (core uint32 ) {
267+ rp .SIO .FIFO_WR .Set (1 )
268+ }
269+
270+ // Signal the given core that it can resume one step.
271+ // This is called twice after gcPauseCore: the first time to scan the stack of
272+ // the core, and the second time to end the stop-the-world phase.
273+ func gcSignalCore (core uint32 ) {
274+ gcSignalWait .Set (1 )
275+ arm .Asm ("sev" )
276+ }
277+
278+ // Returns the stack top (highest address) of the system stack of the given
279+ // core.
280+ func coreStackTop (core uint32 ) uintptr {
281+ switch core {
282+ case 0 :
283+ return uintptr (unsafe .Pointer (& stackTopSymbol ))
284+ case 1 :
285+ return uintptr (unsafe .Pointer (& stack1TopSymbol ))
286+ default :
287+ runtimePanic ("unexpected core" )
288+ return 0
289+ }
290+ }
291+
292+ // These spinlocks are needed by the runtime.
293+ var (
294+ printLock = spinLock {id : 0 }
295+ schedulerLock = spinLock {id : 1 }
296+ atomicsLock = spinLock {id : 2 }
297+ futexLock = spinLock {id : 3 }
298+ )
299+
300+ // A hardware spinlock, one of the 32 spinlocks defined in the SIO peripheral.
301+ type spinLock struct {
302+ id uint8
303+ }
304+
305+ // Return the spinlock register: rp.SIO.SPINLOCKx
306+ func (l * spinLock ) spinlock () * volatile.Register32 {
307+ return (* volatile .Register32 )(unsafe .Add (unsafe .Pointer (& rp .SIO .SPINLOCK0 ), l .id * 4 ))
308+ }
309+
310+ func (l * spinLock ) Lock () {
311+ // Wait for the lock to be available.
312+ spinlock := l .spinlock ()
313+ for spinlock .Get () == 0 {
314+ // TODO: use wfe and send an event when unlocking so the CPU can go to
315+ // sleep while waiting for the lock.
316+ // Unfortunately when doing that, time.Sleep() seems to hang somewhere.
317+ // This needs some debugging to figure out.
318+ }
319+ }
320+
321+ func (l * spinLock ) Unlock () {
322+ l .spinlock ().Set (0 )
323+ }
324+
325+ // Wait until a signal is received, indicating that it can resume from the
326+ // spinloop.
327+ func spinLoopWait () {
328+ arm .Asm ("wfe" )
329+ }
330+
46331func waitForEvents () {
47332 arm .Asm ("wfe" )
48333}
0 commit comments