-
Notifications
You must be signed in to change notification settings - Fork 6
Fix issue #120: Add HW spinlock usage examples (assembly/C) in academy/spinlock #142
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: main
Are you sure you want to change the base?
Changes from all commits
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,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 <stdint.h> | ||
|
|
||
| /* | ||
| * 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); | ||
|
Comment on lines
+28
to
+29
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. 3. Multiple spaces in main.c academy/spinlock/main.c contains multiple consecutive spaces within declarations for alignment (for example between void and spinlock_release). This violates the whitespace policy that restricts whitespace to single spaces and newlines. Agent Prompt
|
||
|
|
||
| /* 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. */ | ||
|
Comment on lines
+35
to
+36
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. 2. todo marker in main.c academy/spinlock/main.c introduces a TODO marker, which is disallowed by the repository marker standard. This makes tracking inconsistent and violates the required pending-work marker convention. Agent Prompt
|
||
| 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(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Comment on lines
+8
to
+21
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. 1. Busy-wait worst-case undocumented The README describes spinning until acquisition succeeds but does not explicitly document that the busy-wait can be unbounded in the worst case, as required. This can mislead users about timing/determinism implications of the blocking acquire loop. Agent Prompt
|
||
| 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". | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Comment on lines
+57
to
+60
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. 5. Acquire return not boolean spinlock_acquire() claims to return 0/1 but actually returns the raw R1.b3 status byte; main.c spins on result != 1, so it can keep spinning even when bit0 indicates the lock is acquired (if any other status bits are set). This makes the example’s acquire loop depend on an exact byte value instead of the documented “bit0=success” contract. Agent Prompt
|
||
|
|
||
| ;****************************************************************************** | ||
| ; 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 | ||
|
Comment on lines
+35
to
+84
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. 2. spinlock.asm missing required metadata The new assembly functions spinlock_acquire/spinlock_release are added without the mandatory function metadata (pseudo code, peak/worst-case cycles, and registers modified). This reduces reviewability and can hide timing/register-clobber assumptions in a low-level synchronization primitive. Agent Prompt
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Comment on lines
+29
to
+51
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. 4. Macros missing required docs The new PRU assembly macros M_SPINLOCK_ACQUIRE and M_SPINLOCK_RELEASE are documented but do not include all mandatory fields (pseudo code and registers modified). This reduces reviewability and can hide register-clobber and timing assumptions. Agent Prompt
|
||
|
|
||
| ;******** | ||
| ;* 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 | ||
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.
1. Main.c header missing filename
📘 Rule violation⚙ MaintainabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools