Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions academy/spinlock/main.c
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.
*/
Comment on lines +1 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Main.c header missing filename 📘 Rule violation ⚙ Maintainability

academy/spinlock/main.c has a copyright/SPDX header but does not denote the file name in the
header section. This violates the required source file header convention and reduces traceability.
Agent Prompt
## Issue description
`academy/spinlock/main.c` is missing the required header field that denotes the file name.

## Issue Context
The compliance checklist requires each source file header to include both the file name and the TI copyright statement.

## Fix Focus Areas
- academy/spinlock/main.c[1-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


#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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Multiple spaces in main.c 📘 Rule violation ⚙ Maintainability

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
## Issue description
The file uses multiple consecutive spaces for alignment, but the whitespace policy requires single spaces only.

## Issue Context
This applies to changed content in source files; alignment should be done without introducing multiple spaces (use one space and rely on formatting tools/settings if needed).

## Fix Focus Areas
- academy/spinlock/main.c[28-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


/* 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. todo marker in main.c 📘 Rule violation ⚙ Maintainability

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
## Issue description
The code introduces a `TODO` marker; the repository standard requires `FIXME` instead.

## Issue Context
Use `FIXME` for pending work markers to keep consistency across the codebase.

## Fix Focus Areas
- academy/spinlock/main.c[35-36]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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();
}
114 changes: 114 additions & 0 deletions academy/spinlock/readme.md
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Busy-wait worst-case undocumented 📎 Requirement gap ☼ Reliability

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
## Issue description
The documentation explains the status bit and that the PRU should busy-wait, but it does not explicitly state the worst-case acquisition time is unbounded.

## Issue Context
PR Compliance requires documenting busy-wait semantics including best/worst-case timing characteristics and how acquisition success is detected.

## Fix Focus Areas
- academy/spinlock/readme.md[8-22]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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".
84 changes: 84 additions & 0 deletions academy/spinlock/spinlock.asm
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

5. Acquire return not boolean 🐞 Bug ≡ Correctness

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
## Issue description
`spinlock_acquire()` is documented as returning `1` when acquired and `0` otherwise, but it currently returns the entire status byte from `R1.b3`. The C example (`main.c`) and README usage pattern compare the return value to `1`, so the loop is only correct if the hardware always returns exactly `0x00` or `0x01`.

## Issue Context
The README and the pure-assembly macro example both treat only **bit 0** as meaningful (they test the bit), not the entire byte.

## Fix Focus Areas
- academy/spinlock/spinlock.asm[48-52]
- academy/spinlock/main.c[58-63]

## Suggested fix
1. Normalize the return value in `spinlock_acquire()` to `0`/`1` by masking `R1.b3` to bit0 before moving into `R14.b0` (e.g., `AND` with `0x01`).
2. (Optional hardening) Update the C loop to test bit0 (`(spinlock_acquire(...) & 1) != 0`) so the usage pattern matches the documented semantics even if the helper is reused elsewhere.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


;******************************************************************************
; 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. spinlock.asm missing required metadata 📘 Rule violation ⚙ Maintainability

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
## Issue description
The assembly functions added in `spinlock.asm` do not include the required metadata fields: pseudo code, peak/worst-case cycle count, and registers modified.

## Issue Context
The compliance checklist requires standardized documentation for PRU assembly functions/macros to prevent timing and register-corruption bugs.

## Fix Focus Areas
- academy/spinlock/spinlock.asm[35-66]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

70 changes: 70 additions & 0 deletions academy/spinlock/spinlock_example.asm
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Macros missing required docs 📘 Rule violation ⚙ Maintainability

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
## Issue description
PRU assembly macro documentation is missing mandatory fields (pseudo code, registers modified).

## Issue Context
The compliance checklist requires PRU macros/functions to document pseudo code, peak/worst-case cycle budget, and registers modified.

## Fix Focus Areas
- academy/spinlock/spinlock_example.asm[29-51]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


;********
;* 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
Loading