|
| 1 | +//go:build rp2040 || rp2350 |
| 2 | + |
| 3 | +package runtime |
| 4 | + |
| 5 | +import ( |
| 6 | + "device/arm" |
| 7 | + "device/rp" |
| 8 | + "internal/task" |
| 9 | + "machine" |
| 10 | + "machine/usb/cdc" |
| 11 | + "runtime/interrupt" |
| 12 | + "runtime/volatile" |
| 13 | + "unsafe" |
| 14 | +) |
| 15 | + |
| 16 | +const numCPU = 2 |
| 17 | + |
| 18 | +// machineTicks is provided by package machine. |
| 19 | +func machineTicks() uint64 |
| 20 | + |
| 21 | +// machineLightSleep is provided by package machine. |
| 22 | +func machineLightSleep(uint64) |
| 23 | + |
| 24 | +// ticks returns the number of ticks (microseconds) elapsed since power up. |
| 25 | +func ticks() timeUnit { |
| 26 | + t := machineTicks() |
| 27 | + return timeUnit(t) |
| 28 | +} |
| 29 | + |
| 30 | +func ticksToNanoseconds(ticks timeUnit) int64 { |
| 31 | + return int64(ticks) * 1000 |
| 32 | +} |
| 33 | + |
| 34 | +func nanosecondsToTicks(ns int64) timeUnit { |
| 35 | + return timeUnit(ns / 1000) |
| 36 | +} |
| 37 | + |
| 38 | +func sleepTicks(d timeUnit) { |
| 39 | + if hasScheduler { |
| 40 | + // With scheduler, sleepTicks may return early if an interrupt or |
| 41 | + // event fires - so scheduler can schedule any go routines now |
| 42 | + // eligible to run |
| 43 | + machineLightSleep(uint64(d)) |
| 44 | + return |
| 45 | + } |
| 46 | + |
| 47 | + // Busy loop |
| 48 | + sleepUntil := ticks() + d |
| 49 | + for ticks() < sleepUntil { |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 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/RP2350, there is only one other core to start. |
| 117 | +func startSecondaryCores() { |
| 118 | + // Start the second core of the RP2040/RP2350. |
| 119 | + // See sections 2.8.2 and 5.3 in the datasheets for RP2040 and RP2350 respectively. |
| 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(sioIrqFifoProc0, 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(sioIrqFifoProc1, 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 | + |
| 331 | +func waitForEvents() { |
| 332 | + arm.Asm("wfe") |
| 333 | +} |
| 334 | + |
| 335 | +func putchar(c byte) { |
| 336 | + machine.Serial.WriteByte(c) |
| 337 | +} |
| 338 | + |
| 339 | +func getchar() byte { |
| 340 | + for machine.Serial.Buffered() == 0 { |
| 341 | + Gosched() |
| 342 | + } |
| 343 | + v, _ := machine.Serial.ReadByte() |
| 344 | + return v |
| 345 | +} |
| 346 | + |
| 347 | +func buffered() int { |
| 348 | + return machine.Serial.Buffered() |
| 349 | +} |
| 350 | + |
| 351 | +// machineInit is provided by package machine. |
| 352 | +func machineInit() |
| 353 | + |
| 354 | +func init() { |
| 355 | + machineInit() |
| 356 | + |
| 357 | + cdc.EnableUSBCDC() |
| 358 | + machine.USBDev.Configure(machine.UARTConfig{}) |
| 359 | + machine.InitSerial() |
| 360 | +} |
| 361 | + |
| 362 | +//export Reset_Handler |
| 363 | +func main() { |
| 364 | + preinit() |
| 365 | + run() |
| 366 | + exit(0) |
| 367 | +} |
0 commit comments