ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_ipc_sem_ring.c
Go to the documentation of this file.
1
23
24#include <stdint.h>
25
26#include "ra8_attributes.h"
27#include "ra8_check.h"
28#include "ra8_err.h"
29#include "ra8_hw_err.h"
30#include "ra8_ipc.h"
31#include "ra8_ipc_regs.h"
32
33static const char* s_tag = "IPC";
34
36static void* s_ipc_nmi_context;
37
38/* =============================================================================
39 * Internal helpers
40 * =============================================================================
41 */
42
47static volatile r_ipc_nmi_regs_t* internal_ra8_ipc_get_nmi(uint8_t unit)
48{
49 if ((uint16_t)unit >= (uint16_t)k_ra8_ipc_nmi_unit_count) {
50 return nullptr;
51 }
52 return ra8_ipc_nmi(unit);
53}
54
85static uint32_t internal_ra8_ipc_sem_read_take(volatile uint32_t* sem)
86{
87#if defined(RA8_OFF_TARGET) && defined(UNIT_TEST)
88 /* HUM Ch 3.2.3 "IPCSEMn" p 210 -- the seam performs the driver's read
89 * and applies the "Set condition: Reading this register" latch that
90 * dumb host RAM cannot. */
91 return ra8_fake_mmio_read_to_set32(sem, k_ra8_ipc_sem_mask_lock) & k_ra8_ipc_sem_mask_lock;
92#else
93 /* HUM Ch 3.2.3 "IPCSEMn" p 210 -- "Set condition: Reading this
94 * register". Reading the register sets LOCK to 1; the read result
95 * is the value the register held *before* the set. */
96 return *sem & k_ra8_ipc_sem_mask_lock;
97#endif
98}
99
124static void internal_ra8_ipc_sem_release_write(volatile uint32_t* sem)
125{
126#if defined(RA8_OFF_TARGET) && defined(UNIT_TEST)
127 /* HUM Ch 3.2.3 "IPCSEMn" p 210 -- the seam applies the driver's W1C
128 * release command to the RAM register file the way the silicon clear
129 * condition does. */
130 ra8_fake_mmio_write1_clear32(sem, k_ra8_ipc_sem_mask_lock, k_ra8_ipc_sem_mask_lock);
131#else
132 /* HUM Ch 3.2.3 "IPCSEMn" p 210 -- "Clear condition: Writing 1 to
133 * this bit". */
135#endif
136}
137
138/* =============================================================================
139 * Hardware semaphores
140 * =============================================================================
141 */
142
143[[nodiscard]] ra8_err_t ra8_ipc_sem_try_take(uint8_t sem_id)
144{
145 if ((uint16_t)sem_id >= (uint16_t)k_ra8_ipc_sem_count) {
147 }
148 volatile uint32_t* sem = ra8_ipc_sem(sem_id);
149 RA8_CHECK_NULL_PTR(sem, s_tag, "sem mapping failed");
150 /* HUM Ch 3.2.3 "IPCSEMn" p 210 -- 32-bit read sets LOCK; the read
151 * value reports the *previous* state. 0 -> we just acquired,
152 * 1 -> someone else already owned it. */
153 const uint32_t prev = internal_ra8_ipc_sem_read_take(sem);
154 if (prev != 0U) {
155 return k_ra8_err_busy;
156 }
157 /* Acquire barrier: the critical section the caller is about to enter
158 * must not be reordered ahead of taking the lock. */
160 return k_ra8_ok;
161}
162
163[[nodiscard]] ra8_err_t ra8_ipc_sem_take_timeout(uint8_t sem_id, uint16_t max_spins)
164{
165 if ((uint16_t)sem_id >= (uint16_t)k_ra8_ipc_sem_count) {
167 }
168 if (max_spins == 0U) {
170 }
171 if (max_spins > k_ra8_ipc_sem_take_max) {
172 max_spins = k_ra8_ipc_sem_take_max;
173 }
174 volatile uint32_t* sem = ra8_ipc_sem(sem_id);
175 RA8_CHECK_NULL_PTR(sem, s_tag, "sem mapping failed");
176 /* NASA Rule 2: bounded by ``max_spins``. */
177 for (uint16_t i = 0U; i < max_spins; ++i) {
178 /* HUM Ch 3.2.3 "IPCSEMn" p 210 */
179 const uint32_t prev = internal_ra8_ipc_sem_read_take(sem);
180 if (prev == 0U) {
181 /* Acquire barrier: order the caller's critical section after the
182 * successful take. */
184 return k_ra8_ok;
185 }
186 }
188}
189
190[[nodiscard]] ra8_err_t ra8_ipc_sem_release(uint8_t sem_id)
191{
192 if ((uint16_t)sem_id >= (uint16_t)k_ra8_ipc_sem_count) {
194 }
195 volatile uint32_t* sem = ra8_ipc_sem(sem_id);
196 RA8_CHECK_NULL_PTR(sem, s_tag, "sem mapping failed");
197 /* Release barrier: every store the caller made inside the critical
198 * section must be visible to the peer before the lock is dropped. */
201 return k_ra8_ok;
202}
203
204[[nodiscard]] ra8_err_t ra8_ipc_sem_is_locked(uint8_t sem_id, bool* out_locked)
205{
206 RA8_CHECK_NULL_PTR(out_locked, s_tag, "out_locked must not be nullptr");
207 if ((uint16_t)sem_id >= (uint16_t)k_ra8_ipc_sem_count) {
209 }
210 volatile uint32_t* sem = ra8_ipc_sem(sem_id);
211 RA8_CHECK_NULL_PTR(sem, s_tag, "sem mapping failed");
212 /* HUM Ch 3.2.3 "IPCSEMn" p 210 -- the 32-bit read takes the lock
213 * as a side-effect, so we sample then restore the previous state. */
214 const uint32_t prev = internal_ra8_ipc_sem_read_take(sem);
215 *out_locked = (prev != 0U);
216 if (prev == 0U) {
217 /* The caller observed "unlocked", but the diagnostic read itself
218 * took the lock (HUM Ch 3.2.3 set condition). Undo the probe's own
219 * take with the write-1-to-clear release command so the register
220 * reads back 0 and the side effect stays invisible. */
222 }
223 return k_ra8_ok;
224}
225
226/* =============================================================================
227 * NMI surface
228 * =============================================================================
229 */
230
231[[nodiscard]] ra8_err_t ra8_ipc_nmi_send(uint8_t unit)
232{
233 volatile r_ipc_nmi_regs_t* nmi = internal_ra8_ipc_get_nmi(unit);
234 if (nmi == nullptr) {
236 }
237 /* HUM Ch 3.2.5 "IPC0NMISET" p 211 / Ch 3.2.8 "IPC1NMISET" p 213 --
238 * write 1 to SET to assert NMI on the peer. */
240 return k_ra8_ok;
241}
242
243[[nodiscard]] ra8_err_t ra8_ipc_nmi_clear(uint8_t unit)
244{
245 volatile r_ipc_nmi_regs_t* nmi = internal_ra8_ipc_get_nmi(unit);
246 if (nmi == nullptr) {
248 }
249 /* HUM Ch 3.2.6 "IPC0NMICLR" p 212 / Ch 3.2.9 "IPC1NMICLR" p 213 --
250 * write 1 to CLR to drop NMISTA.NMI. */
252 return k_ra8_ok;
253}
254
255[[nodiscard]] ra8_err_t ra8_ipc_nmi_get_status(uint8_t unit, bool* out_pending)
256{
257 RA8_CHECK_NULL_PTR(out_pending, s_tag, "out_pending must not be nullptr");
258 volatile r_ipc_nmi_regs_t* nmi = internal_ra8_ipc_get_nmi(unit);
259 if (nmi == nullptr) {
261 }
262 /* HUM Ch 3.2.4 "IPC0NMISTA.NMI" p 210 */
263 *out_pending = ((nmi->NMISTA & k_ra8_ipc_nmi_mask_bit) != 0U);
264 return k_ra8_ok;
265}
266
268{
270 s_ipc_nmi_context = ctx;
271 return k_ra8_ok;
272}
273
274void ra8_ipc_dispatch_nmi(uint8_t unit)
275{
276 volatile r_ipc_nmi_regs_t* nmi = internal_ra8_ipc_get_nmi(unit);
277 if (nmi == nullptr) {
278 return;
279 }
280 /* HUM Ch 3.2.4 "IPC0NMISTA.NMI" p 210 -- snapshot before invoking
281 * the callback so a re-entrant SET on the peer side does not
282 * confuse our acknowledge. */
283 if ((nmi->NMISTA & k_ra8_ipc_nmi_mask_bit) == 0U) {
284 return;
285 }
287 void* const ctx = s_ipc_nmi_context;
288 if (fn != nullptr) {
289 fn(ctx, unit);
290 }
291 /* HUM Ch 3.2.6 "IPC0NMICLR.CLR" p 212 -- ack so the ICU drops the
292 * line. */
294}
295
296/* =============================================================================
297 * Shared-memory ring-buffer protocol primitives
298 * =============================================================================
299 */
300
320{
321 if (ring->capacity == 0U) {
323 }
324 if ((ring->capacity & (ring->capacity - 1U)) != 0U) {
325 /* Power-of-two capacity keeps the modulo cheap and unambiguous. */
327 }
328 if ((uint16_t)ring->channel >= (uint16_t)k_ra8_ipc_channel_count) {
330 }
331 if ((uint16_t)ring->sem_id >= (uint16_t)k_ra8_ipc_sem_count) {
333 }
334 if ((uint16_t)ring->notify_id >= (uint16_t)k_ra8_ipc_irq_event_count) {
336 }
337 return k_ra8_ok;
338}
339
341{
342 RA8_CHECK_NULL_PTR(ring, s_tag, "ring must not be nullptr");
343 RA8_CHECK_NULL_PTR(ring->slots, s_tag, "ring slots must not be nullptr");
344 RA8_CHECK_NULL_PTR(ring->head, s_tag, "ring head must not be nullptr");
345 RA8_CHECK_NULL_PTR(ring->tail, s_tag, "ring tail must not be nullptr");
347 if (err != k_ra8_ok) {
348 return err;
349 }
350 *ring->head = 0U;
351 *ring->tail = 0U;
352 return k_ra8_ok;
353}
354
355[[nodiscard]] ra8_err_t ra8_ipc_ring_produce(ra8_ipc_ring_t* ring, uint32_t payload)
356{
357 RA8_CHECK_NULL_PTR(ring, s_tag, "ring must not be nullptr");
359 if (err != k_ra8_ok) {
360 return err;
361 }
362 const uint32_t head = *ring->head;
363 const uint32_t tail = *ring->tail;
364 if ((head - tail) >= ring->capacity) {
365 /* Ring full -- release the semaphore before reporting busy so a
366 * later consumer can acquire and drain. */
367 (void)ra8_ipc_sem_release(ring->sem_id);
368 return k_ra8_err_busy;
369 }
370 ring->slots[head & (ring->capacity - 1U)] = payload;
371 *ring->head = head + 1U;
372 err = ra8_ipc_sem_release(ring->sem_id);
373 if (err != k_ra8_ok) {
374 return err; /* GCOVR_EXCL_LINE -- ring->sem_id already validated */
375 }
376 /* Wake the consumer through the configured IRQ event line. */
377 return ra8_ipc_send_event(ring->channel, ring->notify_id);
378}
379
380[[nodiscard]] ra8_err_t ra8_ipc_ring_consume(ra8_ipc_ring_t* ring, uint32_t* out_payload)
381{
382 RA8_CHECK_NULL_PTR(ring, s_tag, "ring must not be nullptr");
383 RA8_CHECK_NULL_PTR(out_payload, s_tag, "out_payload must not be nullptr");
385 if (err != k_ra8_ok) {
386 return err;
387 }
388 const uint32_t head = *ring->head;
389 const uint32_t tail = *ring->tail;
390 if (head == tail) {
391 (void)ra8_ipc_sem_release(ring->sem_id);
392 return k_ra8_err_no_data;
393 }
394 *out_payload = ring->slots[tail & (ring->capacity - 1U)];
395 *ring->tail = tail + 1U;
396 return ra8_ipc_sem_release(ring->sem_id);
397}
398
399[[nodiscard]] ra8_err_t ra8_ipc_ring_is_empty(const ra8_ipc_ring_t* ring, bool* out_empty)
400{
401 RA8_CHECK_NULL_PTR(ring, s_tag, "ring must not be nullptr");
402 RA8_CHECK_NULL_PTR(out_empty, s_tag, "out_empty must not be nullptr");
403 *out_empty = (*ring->head == *ring->tail);
404 return k_ra8_ok;
405}
406
407[[nodiscard]] ra8_err_t ra8_ipc_ring_is_full(const ra8_ipc_ring_t* ring, bool* out_full)
408{
409 RA8_CHECK_NULL_PTR(ring, s_tag, "ring must not be nullptr");
410 RA8_CHECK_NULL_PTR(out_full, s_tag, "out_full must not be nullptr");
411 *out_full = ((*ring->head - *ring->tail) >= ring->capacity);
412 return k_ra8_ok;
413}
static const char * s_tag
Logging / check tag.
Definition ra8_app.c:17
Annotation-attribute framework macros for ra8-firmware.
#define RA8_ISR_SAFE
The function is callable from interrupt context.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Validation and Error-Checking Macros for ra8-firmware.
#define RA8_CHECK_NULL_PTR(ptr, tag, message)
Reject nullptr pointer, returning k_ra8_err_null_ptr.
Definition ra8_check.h:243
Error Code Definitions for ra8-firmware.
@ k_ra8_err_busy
Resource busy – blocking operation cannot proceed.
Definition ra8_err.h:195
@ k_ra8_err_no_data
No application data available (e.g.
Definition ra8_err.h:202
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_err_hw_timeout
Hardware timed out waiting for a flag or handshake.
Definition ra8_err.h:304
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
Bounded wait-flag primitives for RA8D2 HAL drivers.
Inter-Processor Communication (IPC) HAL driver – public API.
Inter-Processor Communication (IPC) register layout for the RA8D2.
static volatile r_ipc_nmi_regs_t * ra8_ipc_nmi(uint8_t unit)
Get a pointer to one IPC NMI unit register window.
@ k_ra8_ipc_nmi_mask_bit
Single-bit NMI request / status.
static volatile uint32_t * ra8_ipc_sem(uint8_t sem_id)
Get a pointer to one IPCSEM hardware semaphore register.
@ k_ra8_ipc_sem_count
IPCSEM0..IPCSEM15.
@ k_ra8_ipc_irq_event_count
8 IRQ event lines per channel.
@ k_ra8_ipc_nmi_unit_count
IPC0 + IPC1 NMI units.
@ k_ra8_ipc_channel_count
Total FIFO channels: 2 per unit x 2.
@ k_ra8_ipc_sem_mask_lock
LOCK bit (read-to-acquire).
ra8_err_t ra8_ipc_attach_nmi_handler(ra8_ipc_nmi_fn_t fn, void *ctx)
Attach a callback for inter-processor NMI dispatch.
ra8_err_t ra8_ipc_ring_consume(ra8_ipc_ring_t *ring, uint32_t *out_payload)
Pop one word from the shared-memory ring (consumer side).
static ra8_ipc_nmi_fn_t s_ipc_nmi_callback
ra8_err_t ra8_ipc_nmi_send(uint8_t unit)
Issue an inter-processor NMI to the peer core.
static uint32_t internal_ra8_ipc_sem_read_take(volatile uint32_t *sem)
Test-and-set on IPCSEMn – a 32-bit read takes the lock and returns the previous LOCK value.
ra8_err_t ra8_ipc_ring_init(ra8_ipc_ring_t *ring)
Initialise a producer/consumer ring backed by shared SRAM.
static volatile r_ipc_nmi_regs_t * internal_ra8_ipc_get_nmi(uint8_t unit)
Validate NMI unit id and return the NMI reg pointer.
ra8_err_t ra8_ipc_ring_is_empty(const ra8_ipc_ring_t *ring, bool *out_empty)
Predicate: ring empty?
void ra8_ipc_dispatch_nmi(uint8_t unit)
Drive the NMI dispatch path for one unit.
ra8_err_t ra8_ipc_nmi_clear(uint8_t unit)
Acknowledge an inter-processor NMI on the local core.
static void internal_ra8_ipc_sem_release_write(volatile uint32_t *sem)
Write the IPCSEMn write-1-to-clear release command.
ra8_err_t ra8_ipc_sem_try_take(uint8_t sem_id)
Test-and-set acquire on one IPC hardware semaphore.
ra8_err_t ra8_ipc_sem_take_timeout(uint8_t sem_id, uint16_t max_spins)
Bounded-spin take on one IPC hardware semaphore.
static void * s_ipc_nmi_context
ra8_err_t ra8_ipc_ring_is_full(const ra8_ipc_ring_t *ring, bool *out_full)
Predicate: ring full?
ra8_err_t ra8_ipc_sem_release(uint8_t sem_id)
Release one IPC hardware semaphore.
ra8_err_t ra8_ipc_nmi_get_status(uint8_t unit, bool *out_pending)
Read NMISTA.NMI for one IPC NMI unit.
ra8_err_t ra8_ipc_sem_is_locked(uint8_t sem_id, bool *out_locked)
Predicate: is the semaphore currently locked?
ra8_err_t ra8_ipc_ring_produce(ra8_ipc_ring_t *ring, uint32_t payload)
Push one word into the shared-memory ring (producer side).
static ra8_err_t internal_ra8_ipc_ring_validate(const ra8_ipc_ring_t *ring)
Validate the scalar fields of an ra8_ipc_ring_t descriptor.
static void ra8_ipc_barrier(void)
Cross-core data-memory barrier for IPC publish + lock ordering.
@ k_ra8_ipc_sem_take_max
Hard cap on semaphore-take spins.
void(* ra8_ipc_nmi_fn_t)(void *ctx, uint8_t unit)
NMI dispatch callback signature.
ra8_err_t ra8_ipc_send_event(uint8_t channel, ra8_ipc_irq_event_id_t event_id)
Generate a maskable IRQ event on the peer core.
Definition ra8_ipc.c:355
Per-NMI-unit register window (3 x 32-bit at +0x10 stride).
volatile uint32_t NMICLR
+0x08 NMI clear (W).
volatile uint32_t NMISTA
+0x00 NMI status (R).
volatile uint32_t NMISET
+0x04 NMI set (W).
Shared-memory ring-buffer descriptor used by the producer / consumer protocol primitives.
uint8_t channel
IPC channel used for notification.
uint8_t sem_id
IPCSEM index used for exclusion.
volatile uint32_t * head
Producer write index (shared).
volatile uint32_t * tail
Consumer read index (shared).
volatile uint32_t * slots
Backing SRAM array (caller-owned).
uint32_t capacity
Number of 32-bit slots in slots.
ra8_ipc_irq_event_id_t notify_id
IRQ event line used for notify.