Skip to content

CMSIS-RTOS2

Setup

Platform/CmsisRtos/ wraps the CMSIS-RTOS2 API (CMSIS-RTOS2 documentation). It targets the API rather than any one kernel, so the same adapter serves every kernel that implements it. Networking, storage and time come from separate platforms; the capability matrix shows which fill them.

Fills the Mutex role, plus a sysUpTime callback.

What it ships

Header What it is
SolidSyslogCmsisRtosMutex.h A Mutex over the CMSIS-RTOS2 API, for thread-safe buffers and pools on any kernel that implements it.
SolidSyslogCmsisRtosMutexErrors.h Source identity for the CmsisRtosMutex adapter; the detail codes it reports are the portable ones in SolidSyslogMutexErrors.h.
SolidSyslogCmsisRtosSysUpTime.h The CMSIS-RTOS2 SolidSyslogSysUpTimeFunction, for the MetaSd structured-data element.

Requirements

An implementation of the CMSIS-RTOS2 API, and its cmsis_os2.h on your include path. The mutex calls osMutexNew, osMutexAcquire, osMutexRelease and osMutexDelete; the uptime callback calls osKernelGetTickCount, osKernelGetTickFreq, osKernelLock and osKernelRestoreLock. Nothing else in the API needs to be present or configured, and a build that takes only one of the two needs only that one's calls.

What it has run on

The pack targets the CMSIS-RTOS2 API, so what it is checked against is an implementation of that API rather than any one kernel.

Its unit tests run against a fake that supplies the API outright, which is what lets them build in an ordinary host preset with no vendor tree present. Beyond that, a BDD target exercises it for real in CI: Arm's CMSIS-FreeRTOS wrapper on a Cortex-M3 under QEMU, carrying UDP, TCP, TLS and mutual TLS traffic plus the store suite, with the mutex guarding a buffer shared between two threads and the uptime callback reached on every message formatted.

That is one implementation. Nothing here has run on a second one, and an implementation is free to differ wherever the API leaves a choice open - the control-block size and the priority-inheritance attribute below are both such places. Treat the API as the contract and this as evidence that the contract is met somewhere real, not as a compatibility list.

Security behaviour and obligations

The mutex guards a buffer shared between tasks

The circular buffer uses it when the task calling Log is not the task calling Service. Where both run on one task, the Null mutex is the correct choice and costs nothing.

You size the control block, because only you can

CMSIS-RTOS2 does not standardise how large a mutex control block is - each implementation decides, and the API offers no way to ask. So the library does not guess: SolidSyslogCmsisRtosMutex_Create takes the storage and its size from you and passes both through untouched. The storage must outlive the mutex, and it is the RTOS object itself rather than a copy of one.

Get the size wrong and the implementation refuses it. That is reported as a CRITICAL at create time and the mutex falls back to the Null object, whose Lock and Unlock are no-ops - so a buffer shared across tasks would be left unguarded. Treat that event as a build defect rather than a runtime condition: it cannot start happening later, because the size is fixed at compile time.

Nothing is allocated, unless you ask for it

Supplying the control block is what keeps the adapter allocation-free, which is what makes it usable where a heap is unavailable or prohibited. Passing NULL and a zero size instead is the form CMSIS-RTOS2 defines for letting the implementation allocate the control block from its own pool; the adapter passes that through as readily, and an implementation configured for static allocation only will refuse it.

Priority inheritance is requested, not guaranteed

The mutex is created with osMutexPrioInherit set, so a low-priority task holding the lock is not left preempted indefinitely while a high-priority task waits. CMSIS-RTOS2 leaves the attribute optional: an implementation whose mutexes always inherit ignores the bit, and one that supports neither will create the mutex without it. Where priority inversion is a hazard you must bound, confirm your implementation honours it.

Uptime is a tick count, not a clock

The sysUpTime callback reports kernel ticks since boot. It is not wall-clock time and carries no timezone or synchronisation quality - the clock callback is a separate injection point.

SolidSyslogCmsisRtos_GetSysUpTime meets the sysUpTime contract, wrapping at about 497 days as RFC 3418 requires. The tick rate is read from osKernelGetTickFreq at each call rather than taken from a build-time constant, so whatever rate your kernel is configured for scales correctly with nothing for you to declare.

osKernelGetTickCount is 32 bits on every CMSIS-RTOS2 implementation, so above 100 Hz the counter reaches its own wrap before the RFC's - ten times sooner at 1000 Hz. The wraps are therefore counted, which costs one thing to know about: the callback has to be reached once per wrap, roughly 50 days at 1000 Hz, and formatting any message does that.

The uptime callback belongs to a task, not an interrupt

The wrap count is shared state, and osKernelLock is what makes reading the counter and counting a wrap one step. CMSIS-RTOS2 has no primitive that does that in both task and interrupt context; the alternative, a mutex, would need a control block you size and keep alive for something you otherwise only point at, and could block inside message formatting.

From an interrupt osKernelLock locks nothing and returns an error code, so the wrap count is left unguarded and can miss or double-count a wrap that races it. Log from a task. Before the scheduler starts the lock is inert in the same way, and there it is correct - nothing else is running to race.