blob: 60531d6d1797ba8363500fa34b17b3f0bae57773 [file]
/*
* SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org)
* SPDX-FileCopyrightText: Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com> (STK port)
* SPDX-License-Identifier: MIT
*
* This file is part of the TinyUSB stack.
*
* ---------------------------------------------------------------------------
* This port can bind to either of STK's two APIs, selected via CFG_TUH_OSAL_STK_USE_CPP:
*
* CFG_TUH_OSAL_STK_USE_CPP=0 (default) - STK's pure C API (stk_c.h). Every function below is
* a "static inline" wrapper living entirely in this header, exactly like the header-only
* osal_freertos.h / osal_threadx.h / osal_rtx4.h ports. No companion .cpp file is needed and
* this header stays includable from TinyUSB's plain-C translation units directly.
*
* CFG_TUH_OSAL_STK_USE_CPP=1 - STK's native C++ API (stk::sync::FastMutex/Semaphore/MessageQueue,
* stk::hw::CriticalSection). STK's C++ API is not includable from plain C, so in this mode
* every STK-touching function is only *declared* here (as extern "C") and *defined* in the
* companion osal_stk.cpp, which must be added to the build and is compiled as C++. Pick this
* mode only if something else in your build already requires STK's C++ API; the C backend
* above covers everything this OSAL port needs.
*
* The other consequence of the C++ backend being C++-only: TinyUSB's OSAL_*_DEF macros are meant
* to produce statically-allocated control blocks at file scope in plain C (mirroring FreeRTOS's
* configSUPPORT_STATIC_ALLOCATION path) - but a C struct literal can't invoke a C++ constructor.
* So in CFG_TUH_OSAL_STK_USE_CPP=1 mode, each opaque struct reserves fixed-size byte storage,
* sized to fit the corresponding stk::sync:: object; osal_stk.cpp placement-constructs the real
* object into that storage the first time it's created, and a static_assert there guards every
* size guess - raise the matching *_STORAGE_WORDS macro below if one of those ever fires (e.g.
* after an STK upgrade changes a class's layout). The C backend has no such concern: stk_c.h's
* own stk_sem_mem_t / stk_fastmutex_mem_t / stk_msgq_mem_t types are already correctly-sized POD
* structs, so they're embedded directly - no size macros, no static_assert, no placement-new.
* ---------------------------------------------------------------------------
*/
#ifndef TUSB_OSAL_STK_H_
#define TUSB_OSAL_STK_H_
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#define CFG_TUH_OSAL_STK_VERSION (0x20261019)
#ifndef CFG_TUH_OSAL_STK_USE_CPP
#define CFG_TUH_OSAL_STK_USE_CPP (0)
#endif
#ifndef CFG_TUH_OSAL_STK_SYNC_DEBUG_NAMES
#define CFG_TUH_OSAL_STK_SYNC_DEBUG_NAMES (0)
#endif
#ifdef __cplusplus
extern "C" {
#endif
//----------------------------------------------------------------------------+
// TASK API
//----------------------------------------------------------------------------+
/* Underlying task-id type isn't part of this header's C-visible surface in either backend (it's
* a plain STK type, not necessarily a pointer) - uintptr_t is wide enough to hold it regardless
* of size; a static_assert (osal_stk.cpp, or stk_c.h's own stk_tid_t definition) guards the fit. */
typedef void *osal_task_handle_t;
//----------------------------------------------------------------------------+
// Spinlock API
//----------------------------------------------------------------------------+
// Backed by CriticalSection (stk::hw::CriticalSection in C++ mode, stk_critical_section_enter_ex()
// / stk_critical_section_exit_ex() in C mode), which already masks local interrupts and acquires
// STK's own global cross-core spinlock as one nestable primitive - unlike the FreeRTOS port's
// ESP32 dual-core case, no separate portMUX_TYPE-style per-instance hardware spinlock is needed
// here. STK's bare, non-interrupt-masking cross-core SpinLock is deliberately NOT used for this:
// STK's own documentation warns it deadlocks if an ISR on the same core tries to acquire a
// SpinLock the interrupted task is already holding - exactly the scenario osal_spin_lock's
// in_isr=true case has to support, so CriticalSection is the correct (and only safe) choice.
//
// CriticalSection's Enter() returns a session token that its matching Exit() call must be given
// back (it encodes the privileged/unprivileged path Enter() took); osal_spinlock_t exists to
// carry that token from osal_spin_lock() to the matching osal_spin_unlock() call - it is
// byte-identical to both stk::hw::CriticalSection::Session and the C API's stk_cs_session_t.
typedef uint8_t osal_spinlock_t;
#define OSAL_SPINLOCK_DEF(_name, _int_set) osal_spinlock_t _name
#if CFG_TUH_OSAL_STK_USE_CPP
//=============================================================================
// C++ backend (stk::sync::*, stk::hw::CriticalSection) - declared here,
// defined in the companion osal_stk.cpp. See the file-level comment above for
// when to use this.
//=============================================================================
//----------------------------------------------------------------------------+
// Semaphore API
//----------------------------------------------------------------------------+
/* Sized to fit a placement-constructed stk::sync::Semaphore - see the file-level note above. */
#ifndef OSAL_STK_SEMAPHORE_STORAGE_WORDS
#define OSAL_STK_SEMAPHORE_STORAGE_WORDS (8U + (CFG_TUH_OSAL_STK_SYNC_DEBUG_NAMES ? 1U : 0U))
#endif
typedef struct {
uintptr_t storage[OSAL_STK_SEMAPHORE_STORAGE_WORDS];
} osal_semaphore_def_t;
typedef osal_semaphore_def_t *osal_semaphore_t;
//----------------------------------------------------------------------------+
// MUTEX API
//----------------------------------------------------------------------------+
/* Sized to fit a placement-constructed stk::sync::FastMutex (non-recursive) - see the file-level
* note above. Matches STK_FASTMUTEX_IMPL_SIZE of stk_c.h. */
#ifndef OSAL_STK_MUTEX_STORAGE_WORDS
#define OSAL_STK_MUTEX_STORAGE_WORDS (9U + (CFG_TUH_OSAL_STK_SYNC_DEBUG_NAMES ? 1U : 0U))
#endif
typedef struct {
uintptr_t storage[OSAL_STK_MUTEX_STORAGE_WORDS];
} osal_mutex_def_t;
typedef osal_mutex_def_t *osal_mutex_t;
//----------------------------------------------------------------------------+
// QUEUE API
//----------------------------------------------------------------------------+
/* Sized to fit a placement-constructed stk::sync::MessageQueue control block (the ring buffer
* itself is separate - see osal_queue_def_t.buf below). See the file-level note above. */
#ifndef OSAL_STK_MSGQUEUE_STORAGE_WORDS
#define OSAL_STK_MSGQUEUE_STORAGE_WORDS (6U + (2U * 8U) + (CFG_TUH_OSAL_STK_SYNC_DEBUG_NAMES ? 1U : 0U))
#endif
typedef struct {
struct {
uintptr_t storage[OSAL_STK_MSGQUEUE_STORAGE_WORDS];
} mq;
void *buf; /* item ring buffer, capacity*item_sz bytes - supplied by OSAL_QUEUE_DEF */
uint16_t depth;
uint16_t item_sz;
} osal_queue_def_t;
typedef osal_queue_def_t *osal_queue_t;
// _int_set is not used with an RTOS (STK, like the other RTOS ports here, provides its own
// synchronization - no application-supplied ISR-disable function is needed).
#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \
static _type _name##_buf[_depth]; \
osal_queue_def_t _name = { .buf = (void *) _name##_buf, .depth = (_depth), .item_sz = sizeof(_type) }
osal_task_handle_t osal_task_get_current_handle(void);
uint32_t osal_time_millis(void);
void osal_task_delay(uint32_t msec);
void osal_spin_init(osal_spinlock_t *ctx);
void osal_spin_deinit(osal_spinlock_t *ctx);
void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr);
void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr);
osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef);
bool osal_semaphore_delete(osal_semaphore_t sem_hdl);
bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr);
bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec);
void osal_semaphore_reset(osal_semaphore_t sem_hdl);
osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef);
bool osal_mutex_delete(osal_mutex_t mutex_hdl);
bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec);
bool osal_mutex_unlock(osal_mutex_t mutex_hdl);
osal_queue_t osal_queue_create(osal_queue_def_t *qdef);
bool osal_queue_delete(osal_queue_t qhdl);
bool osal_queue_receive(osal_queue_t qhdl, void *data, uint32_t msec);
bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr);
bool osal_queue_empty(osal_queue_t qhdl);
#else /* !CFG_TUH_OSAL_STK_USE_CPP */
//=============================================================================
// C backend (STK's stk_c.h) - implemented entirely inline below,
// no companion .cpp needed.
//=============================================================================
#include "stk_c.h"
//----------------------------------------------------------------------------+
// Semaphore API
//----------------------------------------------------------------------------+
/* stk_sem_mem_t is already a correctly-sized/aligned POD (see stk_c.h) - embed it directly
* instead of a hand-sized byte buffer. hdl is filled in by osal_semaphore_create(). */
typedef struct {
stk_sem_mem_t mem;
stk_sem_t *hdl;
} osal_semaphore_def_t;
typedef osal_semaphore_def_t *osal_semaphore_t;
//----------------------------------------------------------------------------+
// MUTEX API
//----------------------------------------------------------------------------+
typedef struct {
stk_fastmutex_mem_t mem;
stk_fastmutex_t *hdl;
} osal_mutex_def_t;
typedef osal_mutex_def_t *osal_mutex_t;
//----------------------------------------------------------------------------+
// QUEUE API
//----------------------------------------------------------------------------+
typedef struct {
stk_msgq_mem_t mem;
stk_msgq_t *hdl;
void *buf; /* item ring buffer, capacity*item_sz bytes - supplied by OSAL_QUEUE_DEF */
uint16_t depth;
uint16_t item_sz;
} osal_queue_def_t;
typedef osal_queue_def_t *osal_queue_t;
// _int_set is not used with an RTOS (STK, like the other RTOS ports here, provides its own
// synchronization - no application-supplied ISR-disable function is needed).
#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \
static _type _name##_buf[_depth]; \
osal_queue_def_t _name = { .buf = (void *) _name##_buf, .depth = (_depth), .item_sz = sizeof(_type) }
//----------------------------------------------------------------------------+
// Helper API
//----------------------------------------------------------------------------+
/* OSAL_TIMEOUT_WAIT_FOREVER (UINT32_MAX) -> STK_WAIT_INFINITE; anything else -> STK ticks,
* via stk_ticks_from_ms_clamped_to_timeout() for overflow-safe conversion across tick rates
* (see stk_c.h).
*
* A non-zero msec always yields at least 1 tick: with a coarse tick (e.g. 10 ms) the ms->ticks
* conversion truncates sub-tick values to 0, and STK treats 0 as NO_WAIT / sleep(0) - which neither
* blocks nor yields. For osal_task_delay() that lets a higher-priority task starve the caller
* (e.g. the polling loop in tuh_control_xfer(), which only ever calls osal_task_delay(1)); for
* timed waits it would turn a short blocking timeout into a non-blocking poll. Only msec == 0
* maps to 0 ticks. */
static inline stk_timeout_t osal_stk_timeout_from_ms(uint32_t msec)
{
stk_timeout_t ticks;
if (msec == OSAL_TIMEOUT_WAIT_FOREVER) {
ticks = STK_WAIT_INFINITE;
} else {
const stk_timeout_t ms_clamped =
(msec > (uint32_t)INT32_MAX) ? (stk_timeout_t)INT32_MAX : (stk_timeout_t) msec;
ticks = stk_ticks_from_ms_clamped_to_timeout(ms_clamped);
if ((msec != 0U) && (ticks == 0)) {
ticks = 1; /* round sub-tick durations up to one full tick */
}
}
return ticks;
}
//----------------------------------------------------------------------------+
// Task API
//----------------------------------------------------------------------------+
static inline osal_task_handle_t osal_task_get_current_handle(void)
{
return (osal_task_handle_t)stk_tid();
}
static inline uint32_t osal_time_millis(void)
{
return (uint32_t)stk_time_now_ms();
}
static inline void osal_task_delay(uint32_t msec)
{
if (msec == 0U) {
/* sleep(0) is a no-op in STK; yield instead. Note: in KERNEL_HRT mode stk_yield() signals
* job completion, so don't call this with 0 there. */
stk_yield();
} else {
/* Sleep in ticks (not stk_sleep_ms()): the ms variant truncates sub-tick delays to sleep(0). */
stk_sleep(osal_stk_timeout_from_ms(msec));
}
}
//----------------------------------------------------------------------------+
// Spinlock API
//----------------------------------------------------------------------------+
static inline void osal_spin_init(osal_spinlock_t *ctx)
{
if (ctx != NULL) {
*ctx = (osal_spinlock_t)STK_DEFAULT_CS_SESSION;
}
}
static inline void osal_spin_deinit(osal_spinlock_t *ctx)
{
(void)ctx;
}
static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr)
{
(void)in_isr; /* stk_critical_section_enter_ex() is safe from ISR context */
/* The return value encodes which privileged/unprivileged path was taken and MUST be handed
* back to the matching stk_critical_section_exit_ex() call - passing STK_DEFAULT_CS_SESSION
* there instead would restore the wrong path on unprivileged/TrustZone callers. */
const stk_cs_session_t ses = stk_critical_section_enter_ex(STK_DEFAULT_CS_SESSION);
if (ctx != NULL) {
*ctx = (osal_spinlock_t)ses;
}
}
static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr)
{
(void)in_isr; /* stk_critical_section_exit_ex() is safe from ISR context */
const stk_cs_session_t ses =
((ctx != NULL) ? (stk_cs_session_t)(*ctx) : (stk_cs_session_t)STK_DEFAULT_CS_SESSION);
stk_critical_section_exit_ex(ses);
}
//----------------------------------------------------------------------------+
// Semaphore API
//----------------------------------------------------------------------------+
static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef)
{
/* Always constructed at count 0 (not-yet-signaled), matching every other OSAL backend here.
* max_count 0 means "use STK's default maximum" (65534) - see stk_sem_create() in stk_c.h. */
semdef->hdl = stk_sem_create(&semdef->mem, 0U, 0U);
return ((semdef->hdl != NULL) ? semdef : NULL);
}
static inline bool osal_semaphore_delete(osal_semaphore_t sem_hdl)
{
stk_sem_destroy(sem_hdl->hdl);
return true;
}
static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr)
{
(void)in_isr; /* stk_sem_trysignal() is ISR-safe - no separate ISR path needed */
return stk_sem_trysignal(sem_hdl->hdl);
}
static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec)
{
return stk_sem_wait(sem_hdl->hdl, osal_stk_timeout_from_ms(msec));
}
static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl)
{
/* stk_sem_t has no dedicated reset op; drain any pending count/tokens back to 0 instead,
* mirroring what FreeRTOS's xQueueReset() does for a binary semaphore. */
while (stk_sem_trywait(sem_hdl->hdl)) {
/* keep draining */
}
}
//----------------------------------------------------------------------------+
// Mutex API (backed by STK's FastMutex: non-recursive, TinyUSB does not need recursion.
// Locking a FastMutex the caller already owns is a contract violation that panics with
// KERNEL_PANIC_SYNC_DEADLOCK - including a NO_WAIT try-lock)
//----------------------------------------------------------------------------+
static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef)
{
mdef->hdl = stk_fastmutex_create(&mdef->mem);
return ((mdef->hdl != NULL) ? mdef : NULL);
}
static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl)
{
stk_fastmutex_destroy(mutex_hdl->hdl);
return true;
}
static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec)
{
return stk_fastmutex_timed_lock(mutex_hdl->hdl, osal_stk_timeout_from_ms(msec));
}
static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl)
{
stk_fastmutex_unlock(mutex_hdl->hdl);
return true;
}
//----------------------------------------------------------------------------+
// QUEUE API
//----------------------------------------------------------------------------+
static inline osal_queue_t osal_queue_create(osal_queue_def_t *qdef)
{
qdef->hdl = stk_msgq_create(&qdef->mem,
(uint8_t *)qdef->buf, (uint32_t)(qdef->depth * qdef->item_sz),
(size_t)qdef->depth, (size_t)qdef->item_sz);
return ((qdef->hdl != NULL) ? qdef : NULL);
}
static inline bool osal_queue_delete(osal_queue_t qhdl)
{
stk_msgq_destroy(qhdl->hdl);
return true;
}
static inline bool osal_queue_receive(osal_queue_t qhdl, void *data, uint32_t msec)
{
return stk_msgq_get(qhdl->hdl, data, osal_stk_timeout_from_ms(msec));
}
static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr)
{
bool result;
/* Never block from ISR context, regardless of the caller's requested timeout - matching every
* other OSAL backend here, whose FromISR() send variants are inherently non-blocking too. */
if (in_isr) {
result = stk_msgq_tryput(qhdl->hdl, data);
} else {
result = stk_msgq_put(qhdl->hdl, data, STK_WAIT_INFINITE);
}
return result;
}
static inline bool osal_queue_empty(osal_queue_t qhdl)
{
return (stk_msgq_get_count(qhdl->hdl) == 0U);
}
#endif /* CFG_TUH_OSAL_STK_USE_CPP */
#ifdef __cplusplus
}
#endif
#endif