diff --git a/academy/spinlock/main.c b/academy/spinlock/main.c new file mode 100644 index 00000000..d848a317 --- /dev/null +++ b/academy/spinlock/main.c @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * Copyright (C) 2024-2026 Texas Instruments Incorporated - http://www.ti.com/ + * + * Spinlock Example - C Version (PRU_ICSSG local hardware spinlock) + * + * Demonstrates mutual exclusion between PRU0 and PRU1 using the PRU-local + * hardware spinlock, accessed over the broadside interface (XIN/XOUT). The + * low-level acquire/release are implemented in spinlock.asm; this file shows + * the usage pattern. + * + * Each PRU repeatedly takes the same spinlock, drives a debug GPO high while it + * holds the lock, waits a (per-core different) number of cycles, then releases + * and drives the debug GPO low. Probing the two debug pins shows that the two + * cores never hold the lock at the same time. + */ + +#include + +/* + * Acquire/release are defined in spinlock.asm (separately linked). They use + * broadside XID 0x90, which is the spinlock in the *local* PRU subsystem; a + * separate function would be needed for a spinlock in a different subsystem. + * + * spinlock_acquire() is a single non-blocking attempt: it returns 1 if the + * lock was acquired, 0 otherwise. The spin loop lives here in C. + */ +uint8_t spinlock_acquire(uint8_t flag_id); +void spinlock_release(uint8_t flag_id); + +/* Spinlock flag id (valid range 0-63). Both PRUs must use the same id. */ +#define SPINLOCK_FLAG 11 + +/* R30 drives the PRU GPO signals; use a debug pin to scope lock ownership. */ +/* TODO: pinmux the debug signals (see the GPIO lab) and update the shift to */ +/* match your board. PRU0 is selected via -DPRU0 in the makefile. */ +volatile register uint32_t __R30; +#if PRU0 +#define DEBUG_PIN_SHIFT 4 /* PRU0 debug pin */ +#else +#define DEBUG_PIN_SHIFT 5 /* PRU1 debug pin */ +#endif + +/* Hold the lock for a different time per core so contention is observable. */ +#if PRU0 +#define HOLD_SPINLOCK_TIME 1000 /* PRU0 holds for 1000 cycles */ +#else +#define HOLD_SPINLOCK_TIME 2000 /* PRU1 holds for 2000 cycles */ +#endif + +void main(void) +{ + uint8_t result = 0; + + /* Start with all GPO signals low. */ + __R30 = 0x00000000; + + while (1) { + /* Spin until we own the lock (acquire returns 1 on success). */ + result = 0; + do { + result = spinlock_acquire(SPINLOCK_FLAG); + } while (result != 1); + + /* Critical section: we hold the lock. Drive the debug pin high. */ + __R30 |= (1 << DEBUG_PIN_SHIFT); + + __delay_cycles(HOLD_SPINLOCK_TIME); + + /* Release the lock, then drop the debug pin. */ + spinlock_release(SPINLOCK_FLAG); + __R30 &= ~(1 << DEBUG_PIN_SHIFT); + } + + /* Not reached because of the while(1) loop above. */ + __halt(); +} diff --git a/academy/spinlock/readme.md b/academy/spinlock/readme.md new file mode 100644 index 00000000..80d9f7a7 --- /dev/null +++ b/academy/spinlock/readme.md @@ -0,0 +1,114 @@ +# Spinlock Usage Examples + +This lab shows how a PRU core uses the **PRU_ICSSG local hardware spinlock** to get +mutual exclusion over a resource shared between PRU0, PRU1 and/or the host CPU. + +## How the PRU-local spinlock works + +The PRU_ICSSG includes a hardware spinlock accelerator with **64 ownership flags** +(lock id `0`–`63`). A PRU core reaches it over the **broadside interface** using the +`XIN`/`XOUT` instructions — there is no memory-mapped load/store on the critical path, +so acquire/release are only a couple of cycles. + +- The broadside device id (XID) **`0x90` (= 144)** selects the spinlock in the + **local** PRU subsystem. A different XID is needed to reach a spinlock in another + PRU subsystem. +- The **lock id** to operate on is placed in **`R1.b0`**. +- **Acquire** (`XIN 0x90, &R1.b3, 1`): the accelerator returns the acquisition status + in **`R1.b3`** — **bit 0 = 1** means the lock is now owned by this core, **bit 0 = 0** + means it is held by someone else, so retry. A single `XIN` is one attempt; busy-wait + until you get a 1. +- **Release** (`XOUT 0x90, &R1.b3, 1`): with the same lock id still in `R1.b0`, frees + the lock. + +Consequences: + +- You acquire by **spinning on the status bit**, not by writing a value. +- A spinlock provides **mutual exclusion only** — it is not a mailbox. To pass data or + events between cores, guard a shared buffer with the lock and signal separately (ICSS + shared RAM + a system event / interrupt; see *Signalling* below). +- Only the owner should release a lock, and it must release promptly. + +## Files + +- `spinlock.asm`: C-callable `spinlock_acquire()` / `spinlock_release()` built on the + broadside `XIN`/`XOUT` spinlock primitives. +- `main.c`: C example — both PRUs take the same lock, drive a debug GPO while holding it, + and release. The spin loop lives in C; the per-attempt acquire is in `spinlock.asm`. +- `spinlock_example.asm`: pure-assembly example using `M_SPINLOCK_ACQUIRE` / + `M_SPINLOCK_RELEASE` macros. + +## How to Build + +Adapt the silicon version to your device (`--silicon_version=3` for AM62x/AM64x/AM243x +class ICSSG). + +### Assembly only + +```bash +clpru --silicon_version=3 spinlock_example.asm +``` + +### C + assembly + +Compile both and link together; select the core with `-DPRU0` for the PRU0 build: + +```bash +clpru --silicon_version=3 -DPRU0=1 main.c spinlock.asm # PRU0 firmware +clpru --silicon_version=3 main.c spinlock.asm # PRU1 firmware +``` + +## Acquire / release pattern + +### Assembly (macros) + +```asm +INT_SPIN_XID .set 144 ; XID 0x90 - local PRU spinlock +SPINLOCK_ID .set 0 ; lock 0-63 + +M_SPINLOCK_ACQUIRE .macro + .newblock + LDI R1.b0, SPINLOCK_ID +$1: + XIN INT_SPIN_XID, &R1.b3, 1 ; status in R1.b3 + QBBC $1, R1.b3, 0 ; bit 0 clear -> retry + .endm + +M_SPINLOCK_RELEASE .macro + XOUT INT_SPIN_XID, &R1.b3, 1 + .endm +``` + +### C (calling the assembly helpers) + +```c +uint8_t spinlock_acquire(uint8_t flag_id); /* returns 1 if acquired */ +void spinlock_release(uint8_t flag_id); + +/* Acquire: spin until acquire returns 1. */ +while (spinlock_acquire(SPINLOCK_FLAG) != 1) { /* busy-wait */ } + +/* ... critical section ... */ + +spinlock_release(SPINLOCK_FLAG); +``` + +## Signalling (what NOT to do with a spinlock) + +A spinlock only provides mutual exclusion. To pass data or events between the PRU and +another core, guard a shared buffer with the lock and signal separately: + +```c +while (spinlock_acquire(0) != 1) { } +shared->command = value; /* protected write to ICSS shared RAM */ +spinlock_release(0); +/* then raise a PRU system event (R31) to interrupt the other core */ +``` + +## References + +- Device-specific Technical Reference Manual (TRM) — PRU_ICSSG broadside interface and + spinlock accelerator. +- *PRU Assembly Instruction User Guide* (SPRUIJ2) — `XIN` / `XOUT` / `XCHG`. +- *PRU Optimizing C/C++ Compiler User's Guide* — function calling conventions. +- E2E reference thread: "PRU accessing HW-Spinlocks with __xin __xout". diff --git a/academy/spinlock/spinlock.asm b/academy/spinlock/spinlock.asm new file mode 100644 index 00000000..ec482af7 --- /dev/null +++ b/academy/spinlock/spinlock.asm @@ -0,0 +1,84 @@ +; SPDX-License-Identifier: BSD-3-Clause +; Copyright (C) 2024-2026 Texas Instruments Incorporated - http://www.ti.com/ + +;****************************************************************************** +; File: spinlock.asm +; +; Brief: C-callable acquire/release for the PRU-local hardware spinlock, +; accessed over the broadside interface with XIN/XOUT. +; +; Hardware semantics (PRU_ICSSG local spinlock accelerator): +; - The spinlock accelerator owns 64 ownership flags (lock id 0..63). +; - It is reached from the PRU over the broadside interface using the +; XIN/XOUT instructions with broadside device id (XID) 0x90 (= 144), +; which selects the PRU's *local* spinlock. A different XID is needed +; to reach a spinlock in another PRU subsystem. +; - The lock id is presented to the accelerator in R1.b0. +; - ACQUIRE: an XIN returns the acquisition status in R1.b3 (bit 0): +; * bit0 == 1 -> the lock is now OWNED BY YOU +; * bit0 == 0 -> the lock is held by someone else; retry +; The acquire is non-blocking - one XIN is one attempt. The caller +; (here, C) spins until it gets a 1. +; - RELEASE: an XOUT with the same lock id in R1.b0 frees the lock. +; +; Note: +; These helpers use XID 0x90 (local PRU subsystem). A separate function +; would be needed for accessing spinlocks in a different PRU subsystem. +;****************************************************************************** + +; Required for building .out with an assembly file + .retain + .retainrefs + +INT_SPIN_XID .set 0x90 ; broadside XID for the local PRU spinlock + +;****************************************************************************** +; uint8_t spinlock_acquire(uint8_t flag_id); +; +; One non-blocking attempt to acquire spinlock 'flag_id'. +; Returns 1 if the lock was acquired, 0 if it is held by someone else. +; +; Calling convention (PRU C/C++ compiler): the uint8_t argument arrives in +; R14.b0 and the uint8_t return value is passed back in R14.b0. The return +; address is in r3.w2. +; +; Pseudocode: +; R1.b0 = flag_id ; present lock id to the accelerator +; XIN(XID 0x90) -> R1.b3 ; one acquire attempt, bit0 = acquired +; return R1.b3 ; 1 = acquired, 0 = held by another +; +; Registers modified : R1 (R1.b0, R1.b3), R14.b0 (return value) +; Peak Cycles : 4 (one acquire attempt; excludes any C-side retry spin) +;****************************************************************************** + .sect ".text:spinlock_acquire" + .clink + .global spinlock_acquire +spinlock_acquire: + MOV R1.b0, R14.b0 ; lock id -> R1.b0 (presented to accel) + XIN INT_SPIN_XID, &R1.b3, 1 ; status returned in R1.b3 (bit0=acquired) + MOV R14.b0, R1.b3 ; return status + JMP r3.w2 + +;****************************************************************************** +; void spinlock_release(uint8_t flag_id); +; +; Release spinlock 'flag_id'. Only the owner should release, and it must do +; so promptly. +; +; Calling convention (PRU C/C++ compiler): the uint8_t argument arrives in +; R14.b0. The return address is in r3.w2. +; +; Pseudocode: +; R1.b0 = flag_id ; present lock id to the accelerator +; XOUT(XID 0x90) ; free the lock +; +; Registers modified : R1.b0 +; Peak Cycles : 3 +;****************************************************************************** + .sect ".text:spinlock_release" + .clink + .global spinlock_release +spinlock_release: + MOV R1.b0, R14.b0 ; lock id -> R1.b0 + XOUT INT_SPIN_XID, &R1.b3, 1 ; release the lock + JMP r3.w2 diff --git a/academy/spinlock/spinlock_example.asm b/academy/spinlock/spinlock_example.asm new file mode 100644 index 00000000..88f57e7f --- /dev/null +++ b/academy/spinlock/spinlock_example.asm @@ -0,0 +1,70 @@ +; SPDX-License-Identifier: BSD-3-Clause +; Copyright (C) 2024-2026 Texas Instruments Incorporated - http://www.ti.com/ + +;****************************************************************************** +; File: spinlock_example.asm +; +; Brief: Pure-assembly example of acquiring and releasing the PRU-local +; hardware spinlock over the broadside interface (XIN/XOUT). +; +; Hardware semantics (PRU_ICSSG local spinlock accelerator): +; - Reached over the broadside interface with XIN/XOUT using broadside +; device id (XID) 0x90 (= 144), which selects the *local* spinlock. +; - The lock id (0..63) is presented in R1.b0. +; - ACQUIRE: XIN returns acquisition status in R1.b3; bit 0 set means the +; lock is now owned by this core. Busy-wait until bit 0 is set. +; - RELEASE: XOUT with the same lock id in R1.b0 frees the lock. +; +; Steps to build: +; clpru --silicon_version=3 spinlock_example.asm +;****************************************************************************** + +; Required for building .out with an assembly file + .retain + .retainrefs + +INT_SPIN_XID .set 144 ; XID 0x90 - local PRU spinlock accelerator +SPINLOCK_ID .set 0 ; lock id 0-63, fixed in PRU firmware + +;****************************************************************************** +; Macro: M_SPINLOCK_ACQUIRE +; Acquire the spinlock and busy-wait until successful. +; R1.b0 - Input : spinlock id (set to SPINLOCK_ID) +; R1.b3 - Output: acquisition status (bit 0: 0=failed, 1=acquired) +; Best case 3 cycles; worst case unbounded (busy-wait). +;****************************************************************************** +M_SPINLOCK_ACQUIRE .macro + .newblock + LDI R1.b0, SPINLOCK_ID ; lock id (0-63) +$1: + XIN INT_SPIN_XID, &R1.b3, 1 ; attempt acquire; status in R1.b3 + QBBC $1, R1.b3, 0 ; bit 0 clear -> not acquired, retry + .endm + +;****************************************************************************** +; Macro: M_SPINLOCK_RELEASE +; Release the spinlock. Must follow M_SPINLOCK_ACQUIRE (lock id is still +; in R1.b0). 2 cycles. +;****************************************************************************** +M_SPINLOCK_RELEASE .macro + XOUT INT_SPIN_XID, &R1.b3, 1 ; release lock held in R1.b0 + .endm + +;******** +;* MAIN * +;******** + .global main + .sect ".text" +main: + ; Clear the register space before starting. + zero &r0, 120 + + M_SPINLOCK_ACQUIRE + + ;-------------------------------------------------------------------------- + ; Critical section: we now own SPINLOCK_ID. Access the shared resource here. + ;-------------------------------------------------------------------------- + + M_SPINLOCK_RELEASE + + halt