Skip to content
Merged
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
12 changes: 9 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ jobs:
count="$(grep -c 'throw std::bad_alloc' src/strata/Allocator.h)"
test "$count" -eq 1

- name: Audit FreeRTOS mutex allocation boundary
- name: Audit FreeRTOS semaphore allocation boundary
run: |
if grep -RInE 'xSemaphoreCreateMutex[[:space:]]*\(|xSemaphoreCreateRecursiveMutex[[:space:]]*\(' src; then
echo "Strata mutex wrappers must use static FreeRTOS creation APIs"
if grep -RInE 'xSemaphoreCreateMutex[[:space:]]*\(|xSemaphoreCreateRecursiveMutex[[:space:]]*\(|xSemaphoreCreateBinary[[:space:]]*\(' src; then
echo "Strata semaphore wrappers must use static FreeRTOS creation APIs"
exit 1
fi

Expand Down Expand Up @@ -123,6 +123,9 @@ jobs:
g++ $common_flags -Itests/host/freertos_fakes tests/host/freertos_mutex_contract_test.cpp $common_sources -o build/freertos-mutex-contract-test
./build/freertos-mutex-contract-test

g++ $common_flags -Itests/host/freertos_fakes tests/host/freertos_binary_semaphore_contract_test.cpp $common_sources -o build/freertos-binary-semaphore-contract-test
./build/freertos-binary-semaphore-contract-test

g++ $common_flags -Itests/host/arduinojson_fakes tests/host/arduinojson_allocator_contract_test.cpp $common_sources -o build/arduinojson-allocator-contract-test
./build/arduinojson-allocator-contract-test

Expand Down Expand Up @@ -160,6 +163,9 @@ jobs:
g++ $flags -Itests/host/freertos_fakes tests/host/esp32_freertos_mutex_backend_test.cpp $sources -o build/esp32-freertos-mutex-backend-test
./build/esp32-freertos-mutex-backend-test

g++ $flags -Itests/host/freertos_fakes tests/host/esp32_freertos_binary_semaphore_backend_test.cpp $sources -o build/esp32-freertos-binary-semaphore-backend-test
./build/esp32-freertos-binary-semaphore-backend-test

g++ $flags -Itests/host/arduinojson_fakes tests/host/esp32_arduinojson_allocator_backend_test.cpp $sources -o build/esp32-arduinojson-allocator-backend-test
./build/esp32-arduinojson-allocator-backend-test

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to Strata are documented in this file.

## 0.1.2

- Add move-only `Strata::FreeRTOS::BinarySemaphore` ownership using static FreeRTOS creation and internal Strata-backed control storage.
- Add task and ISR-safe take/give operations with explicit failure reporting and initially-empty semaphore semantics.
- Add host and ESP32 backend contracts covering lifecycle, ISR behavior, creation failure, allocation failure, and internal-only control storage.
- Extend the FreeRTOS source audit to prevent dynamic binary-semaphore creation and add ESP32/ESP32-S3/ESP32-C3/ESP32-P4 example coverage.
- Document the binary semaphore API, configuration requirements, example, and `v0.1.2` release contract.

## 0.1.1

- Add reusable `Strata::MemoryPolicy` with consistent general-allocation and task-stack placement fields for consuming ZekStack library configuration.
Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ It gives applications and libraries one vocabulary for allocation intent while p
- **PMR integration** — use the same placement policies through standard polymorphic allocators and nested PMR containers.
- **Runtime diagnostics** — inspect actual memory regions, heap statistics, and current/peak region usage.
- **Optional advanced diagnostics** — opt into allocation/failure/fallback counters without a global allocation registry.
- **Optional FreeRTOS memory primitives** — explicitly place task stacks and queue item storage, and own static mutex control blocks, while keeping FreeRTOS out of the core headers.
- **Optional FreeRTOS memory primitives** — explicitly place task stacks and queue item storage, and own static mutex/binary-semaphore control blocks, while keeping FreeRTOS out of the core headers.
- **Optional ArduinoJson allocation** — route ArduinoJson 7 document memory through the same Strata placement policies.
- **Standalone core** — Strata does not depend on other ZekStack libraries.

Expand Down Expand Up @@ -118,11 +118,12 @@ config.memory.taskStack = Strata::Placement::Internal;
- `Strata::Allocator<T>` follows standard allocator expectations and throws `std::bad_alloc` when exceptions are enabled.
- The optional PMR adapter requires standard-library `<memory_resource>` support and exceptions to preserve `std::pmr::memory_resource` failure semantics.
- External RAM is not automatically safe for DMA, ISR use, or cache-disabled flash windows.
- FreeRTOS task, queue, and mutex integrations are opt-in and require static allocation support. The task wrapper also requires `INCLUDE_vTaskDelete == 1` and `INCLUDE_uxTaskGetStackHighWaterMark == 1`; the mutex wrapper requires mutex and recursive-mutex support.
- FreeRTOS task, queue, mutex, and binary-semaphore integrations are opt-in and require static allocation support. The task wrapper also requires `INCLUDE_vTaskDelete == 1` and `INCLUDE_uxTaskGetStackHighWaterMark == 1`; the mutex wrapper requires mutex and recursive-mutex support.
- A `Strata::FreeRTOS::Task` owner must be destroyed/reset from a different task context than the task it owns; managed tasks must not self-delete.
- Tasks that can execute while flash/cache is disabled should keep their stacks in internal memory.
- ISR-accessible Strata queues require internal item storage; external queue storage is task-only.
- Strata mutex control blocks are always internal and use FreeRTOS static creation APIs.
- Strata mutex and binary-semaphore control blocks are always internal and use FreeRTOS static creation APIs.
- `BinarySemaphore::create()` produces an initially-empty semaphore; task and ISR give/take operations report success explicitly.
- ArduinoJson integration is opt-in, targets ArduinoJson 7, and requires the Strata allocator object to outlive the `JsonDocument` using it.
- Advanced allocation counters are disabled by default; enable them build-wide with `STRATA_ENABLE_ADVANCED_DIAGNOSTICS=1`.

Expand All @@ -141,6 +142,7 @@ config.memory.taskStack = Strata::Placement::Internal;
| `FreeRTOSTask` | Optional placed FreeRTOS task stacks and diagnostics. |
| `FreeRTOSQueue` | Optional typed FreeRTOS queues with placed item storage. |
| `FreeRTOSMutex` | Optional static mutex and recursive-mutex ownership. |
| `FreeRTOSBinarySemaphore` | Optional static binary semaphore signaling with task and ISR APIs. |
| `ArduinoJson` | Optional ArduinoJson 7 document allocation through Strata placement. |

Start with:
Expand Down Expand Up @@ -171,6 +173,7 @@ examples/Basic
| [`docs/freertos-tasks.md`](docs/freertos-tasks.md) | Optional task-stack placement and static task creation. |
| [`docs/freertos-queues.md`](docs/freertos-queues.md) | Optional typed queue storage placement and ISR safety. |
| [`docs/freertos-mutexes.md`](docs/freertos-mutexes.md) | Optional internal static mutex and recursive-mutex ownership. |
| [`docs/freertos-binary-semaphores.md`](docs/freertos-binary-semaphores.md) | Optional internal static binary semaphore ownership and ISR signaling. |
| [`docs/arduinojson.md`](docs/arduinojson.md) | Optional ArduinoJson 7 custom allocator integration. |
| [`docs/roadmap.md`](docs/roadmap.md) | Completed `v0.1.0` roadmap and post-release planning boundary. |
| [`docs/ecosystem-adoption.md`](docs/ecosystem-adoption.md) | Planned adoption across ZekStack and Core. |
Expand Down Expand Up @@ -239,6 +242,7 @@ Optional FreeRTOS integrations:
#include <strata/freertos/Task.h>
#include <strata/freertos/Queue.h>
#include <strata/freertos/Mutex.h>
#include <strata/freertos/BinarySemaphore.h>

Strata::FreeRTOS::Task task = Strata::FreeRTOS::Task::create(worker, nullptr, {
.name = "worker",
Expand All @@ -256,6 +260,8 @@ auto queue = Strata::FreeRTOS::Queue<Event>::create({

auto mutex = Strata::FreeRTOS::Mutex::create();
auto recursiveMutex = Strata::FreeRTOS::RecursiveMutex::create();
auto ready = Strata::FreeRTOS::BinarySemaphore::create();
ready.give();
```

## Compatibility
Expand All @@ -269,16 +275,16 @@ auto recursiveMutex = Strata::FreeRTOS::RecursiveMutex::create();
| External memory | ESP32 PSRAM through ESP-IDF heap capabilities |
| Core dependencies | none |
| Optional PMR integration | Standard-library `<memory_resource>` with exceptions enabled |
| Optional FreeRTOS integration | FreeRTOS with static allocation; task deletion/high-water-mark APIs for tasks; mutex and recursive-mutex support for mutex wrappers |
| Optional FreeRTOS integration | FreeRTOS with static allocation; task deletion/high-water-mark APIs for tasks; mutex and recursive-mutex support for mutex wrappers; binary semaphore task/ISR support |
| Optional ArduinoJson integration | ArduinoJson 7; CI compatibility target 7.4.3 |
| Advanced diagnostics | Optional compile-time counters; disabled by default |
| Exceptions | Not required by core APIs; STL/PMR standard allocator surfaces follow standard semantics |
| Status | `v0.1.1` compatibility release for ZekStack memory-policy adoption |
| Status | `v0.1.2` binary semaphore compatibility release |

## License

MIT — see [`LICENSE.md`](LICENSE.md).

## ZekStack

Part of the ZekStack library stack. `v0.1.1` adds the common consuming-library memory-policy contract and static FreeRTOS mutex ownership so Worker and subsequent libraries can migrate without inventing library-specific placement vocabulary.
Part of the ZekStack library stack. `v0.1.2` adds static FreeRTOS binary semaphore ownership while preserving the shared memory-policy and placement contracts established for Worker and subsequent ZekStack libraries.
13 changes: 11 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Optional integrations are intentionally separate:
#include <strata/freertos/Task.h>
#include <strata/freertos/Queue.h>
#include <strata/freertos/Mutex.h>
#include <strata/freertos/BinarySemaphore.h>
#include <strata/arduinojson/Allocator.h>
#include <strata/pmr/MemoryResource.h>
```
Expand Down Expand Up @@ -126,6 +127,14 @@ See `freertos-queues.md` for lifecycle and ISR safety details.

Both wrappers expose `lock()`, `tryLock()`, `unlock()`, `reset()`, `handle()`, `controlPlacement()`, and `controlRegion()`. See `freertos-mutexes.md` for compile-time requirements and ownership semantics.

## FreeRTOS binary semaphores

`Strata::FreeRTOS::BinarySemaphore` is a move-only owner created through `xSemaphoreCreateBinaryStatic()`. Its `StaticSemaphore_t` control storage is always allocated through Strata with `Placement::Internal` and a newly created semaphore starts empty.

The task-context API exposes `take()`, `tryTake()`, and `give()`. `giveFromISR()` and `takeFromISR()` forward the optional `higherPriorityTaskWoken` pointer to FreeRTOS. Give/take operations return `bool` so an already-given semaphore or unavailable take is not silently hidden.

See `freertos-binary-semaphores.md` for signaling semantics, ISR usage, and ownership details.

## ArduinoJson

`Strata::ArduinoJson::Allocator` implements the ArduinoJson 7 custom allocator interface and forwards allocation, reallocation, and deallocation through a single Strata `Placement` policy.
Expand All @@ -141,7 +150,7 @@ See `arduinojson.md` for placement, failure, lifetime, and PSRAM details.

## Stable API boundary

The core API remains the stable base for ecosystem migrations. `v0.1.1` adds the shared `MemoryPolicy` vocabulary and FreeRTOS mutex ownership without weakening the `v0.1.0` placement/failure contracts.
The core API remains the stable base for ecosystem migrations. `v0.1.2` adds FreeRTOS binary semaphore ownership without weakening the placement, failure, or memory-policy contracts established by earlier releases.

The following semantic contracts are intentionally protected by tests and CI:

Expand All @@ -153,6 +162,6 @@ The following semantic contracts are intentionally protected by tests and CI:
- optional integrations are not pulled into `Strata.h`;
- platform-specific allocator flags do not become part of the core public vocabulary;
- advanced diagnostics remain optional and do not require an allocation registry;
- FreeRTOS mutex wrappers use static creation and internal Strata-backed control storage.
- FreeRTOS mutex and binary-semaphore wrappers use static creation and internal Strata-backed control storage.

See `architecture.md` for the layering contract and `migration.md` for ecosystem adoption recipes.
4 changes: 3 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,14 @@ Keep the `MemoryResource` object alive for at least as long as every PMR contain

## FreeRTOS integration

FreeRTOS support is opt-in through `<strata/freertos/Task.h>`, `<strata/freertos/Queue.h>`, and `<strata/freertos/Mutex.h>` and requires static allocation support.
FreeRTOS support is opt-in through `<strata/freertos/Task.h>`, `<strata/freertos/Queue.h>`, `<strata/freertos/Mutex.h>`, and `<strata/freertos/BinarySemaphore.h>` and requires static allocation support.

The task integration additionally requires `INCLUDE_vTaskDelete == 1` and `INCLUDE_uxTaskGetStackHighWaterMark == 1`. `Task.h` checks these settings at compile time so a FreeRTOS configuration that cannot satisfy the public task API fails with an actionable error instead of failing later on missing symbols.

The mutex integration additionally requires `configUSE_MUTEXES == 1` and `configUSE_RECURSIVE_MUTEXES == 1`. Mutex control storage is always internal and is allocated through Strata before using FreeRTOS static creation APIs.

The binary-semaphore integration only adds the static-allocation requirement. Its `StaticSemaphore_t` control storage is always internal, newly created semaphores start empty, and the wrapper provides both task-context and ISR take/give operations. There is intentionally no placement configuration for semaphore control storage.

`TaskConfig` configures task name, stack bytes, stack placement, priority, and affinity. Use internal stack placement for tasks that may execute while flash/cache is disabled.

A `Strata::FreeRTOS::Task` must be reset or destroyed from a different task context than the task it owns. Self-deletion cannot return through the owner cleanup path to release the caller-owned static stack and control block safely. Tasks owned by this wrapper must also not independently call `vTaskDelete(nullptr)`.
Expand Down
1 change: 1 addition & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Strata ships focused Arduino sketches under `examples/`.
| `FreeRTOSTask` | Optional FreeRTOS stack placement and task diagnostics. |
| `FreeRTOSQueue` | Optional typed FreeRTOS queue storage placement and task-only queue use. |
| `FreeRTOSMutex` | Optional statically created mutex and recursive-mutex ownership with internal control storage. |
| `FreeRTOSBinarySemaphore` | Optional statically created binary semaphore signaling with internal control storage. |
| `ArduinoJson` | Optional ArduinoJson 7 document allocation through Strata placement. |

Start with `examples/Basic`. Move to the specialized example that matches the API you need rather than combining all Strata features into one sketch.
Expand Down
103 changes: 103 additions & 0 deletions docs/freertos-binary-semaphores.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# FreeRTOS binary semaphores

`Strata::FreeRTOS::BinarySemaphore` is an optional move-only owner for a FreeRTOS binary semaphore created with static FreeRTOS storage.

Include it explicitly:

```cpp
#include <strata/freertos/BinarySemaphore.h>
```

It is intentionally not included by `Strata.h`, so the core Strata API remains FreeRTOS-independent.

## Creation and initial state

```cpp
auto ready = Strata::FreeRTOS::BinarySemaphore::create();
if (!ready) {
// Strata could not allocate the control block or FreeRTOS creation failed.
}
```

A newly created binary semaphore is **empty**. `tryTake()` therefore fails until the semaphore is given.

```cpp
assert(!ready.tryTake());
assert(ready.give());
assert(ready.tryTake());
```

Giving an already available binary semaphore returns `false` instead of hiding the FreeRTOS failure.

## Task-context API

```cpp
bool take(TickType_t ticksToWait = portMAX_DELAY) noexcept;
bool tryTake() noexcept;
bool give() noexcept;
```

`take()` forwards the requested FreeRTOS timeout. `tryTake()` is equivalent to `take(0)`.

The API uses `take`/`give` terminology rather than mutex-style `lock`/`unlock`: a binary semaphore is a signaling primitive and does not provide mutex ownership or priority-inheritance semantics.

## ISR API

```cpp
BaseType_t taskWoken = pdFALSE;
if (ready.giveFromISR(&taskWoken)) {
// Signal published from the ISR.
}
```

The wrapper exposes:

```cpp
bool giveFromISR(BaseType_t *higherPriorityTaskWoken = nullptr) noexcept;
bool takeFromISR(BaseType_t *higherPriorityTaskWoken = nullptr) noexcept;
```

The optional `higherPriorityTaskWoken` pointer is forwarded directly to FreeRTOS. The caller remains responsible for performing the platform-appropriate ISR yield when FreeRTOS indicates that a higher-priority task was woken.

## Memory placement

The FreeRTOS `StaticSemaphore_t` control block is always allocated through Strata with:

```cpp
Strata::Placement::Internal
```

There is deliberately no placement configuration. Synchronization primitives may be used from ISR or cache-sensitive contexts, so Strata keeps the control storage internal rather than allowing PSRAM placement.

The requested and observed locations can be inspected with:

```cpp
ready.controlPlacement(); // Placement::Internal
ready.controlRegion();
```

## Ownership

`BinarySemaphore` is move-only. Its destructor and `reset()` delete the FreeRTOS semaphore before releasing the Strata-owned control block.

```cpp
auto first = Strata::FreeRTOS::BinarySemaphore::create();
auto second = std::move(first);

assert(!first);
assert(second);

second.reset();
```

Calling `reset()` repeatedly is safe.

## Requirements

The integration requires FreeRTOS static allocation support:

```text
configSUPPORT_STATIC_ALLOCATION == 1
```

The wrapper uses `xSemaphoreCreateBinaryStatic()` and never falls back to dynamic FreeRTOS semaphore allocation.
9 changes: 5 additions & 4 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ Before tagging a release:

`scripts/validate_release_version.py` enforces the metadata/version/changelog requirements in CI. Repository visibility is an organization/repository setting and must be checked separately.

## Create v0.1.1
## Create a release

Tag the validated `main` commit and push the tag:
Derive the intended release version from the validated PlatformIO metadata, tag the validated `main` commit, and push the tag:

```bash
git checkout main
git pull --ff-only
git tag v0.1.1
git push origin v0.1.1
VERSION="$(python3 -c 'import json; print(json.load(open("library.json"))["version"])')"
git tag "v${VERSION}"
git push origin "v${VERSION}"
```

The `CI` workflow runs again for the tag. The release job waits for metadata validation, source audits, host contracts, ESP32 backend contracts, and the full ESP32/ESP32-S3/ESP32-C3/ESP32-P4 example matrix.
Expand Down
33 changes: 33 additions & 0 deletions examples/FreeRTOSBinarySemaphore/FreeRTOSBinarySemaphore.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <Arduino.h>
#include <strata/freertos/BinarySemaphore.h>

Strata::FreeRTOS::BinarySemaphore ready;

void setup() {
Serial.begin(115200);

ready = Strata::FreeRTOS::BinarySemaphore::create();
if (!ready) {
Serial.println("binary semaphore creation failed");
return;
}

Serial.println("binary semaphore starts empty");
}

void loop() {
if (!ready) {
delay(1000);
return;
}

if (ready.give()) {
Serial.println("signaled");
}

if (ready.take(pdMS_TO_TICKS(100))) {
Serial.println("signal consumed");
}

delay(1000);
}
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Strata",
"version": "0.1.1",
"version": "0.1.2",
"description": "Portable memory placement and allocation utilities for embedded and standard C++ applications.",
"keywords": [
"memory",
Expand Down
Loading
Loading