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
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,32 @@ jobs:
files=$(git ls-files 'source/*.c' ':!source/zephyr')
clang-tidy -p build $files

zephyr:
name: zephyr native_sim (examples + tests)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Cache the Zephyr build image
id: image-cache
uses: actions/cache@v4
with:
path: /tmp/dmosal-zephyr.tar
key: zephyr-image-${{ hashFiles('zephyr/Dockerfile') }}
- name: Load cached image
if: steps.image-cache.outputs.cache-hit == 'true'
run: docker load -i /tmp/dmosal-zephyr.tar
- name: Build the Zephyr image
if: steps.image-cache.outputs.cache-hit != 'true'
run: |
docker build -t dmosal-zephyr:local -f zephyr/Dockerfile zephyr
docker save -o /tmp/dmosal-zephyr.tar dmosal-zephyr:local
- name: Build examples
run: zephyr/docker.sh build
- name: Run examples
run: zephyr/docker.sh run
- name: Build and run cmocka tests
run: zephyr/docker.sh test

docs:
name: docs (build)
runs-on: ubuntu-22.04
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build
GPATH
GRTAGS
GTAGS
build-*/
12 changes: 6 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ set(osal_SRC
source/osal_log.c
source/osal_rm.c
source/osal_tmcheck.c
source/posix/osal_mutex.c
source/posix/osal_queue.c
source/posix/osal_sem.c
source/posix/osal_task.c
source/posix/osal_time.c
source/posix/osal_timer.c
source/linux/osal_mutex.c
source/linux/osal_queue.c
source/linux/osal_sem.c
source/linux/osal_task.c
source/linux/osal_time.c
source/linux/osal_timer.c
)

# Generated headers live under the build tree — never in the source tree.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ semantics across POSIX and RTOS platforms.
Everything (tasks, mutexes, semaphores, queues, timers, ...) is allocated
statically up front through a central resource manager, so there's no malloc
on the hot path and the worst-case memory usage is known ahead of time. A
POSIX/Linux backend and a Zephyr backend are included; other targets can be
Linux backend and a Zephyr backend are included; other targets can be
added behind the same API without breaking existing callers.

```
Expand Down Expand Up @@ -87,7 +87,7 @@ backend maps to Zephyr primitives.
## Examples

Sample programs live in [examples/](examples/). Build them with `just build`
(CMake target `examples`) for POSIX, or with `zephyr/docker.sh build` for
(CMake target `examples`) for Linux, or with `zephyr/docker.sh build` for
Zephyr.

## Documentation
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Doxygen-generated API reference (built via `make doc`).
| [architecture.md](architecture.md) | Layered design, subsystem overview, resource-manager pattern, callback threading model, backend model. |
| [api-spec.md](api-spec.md) | Behavioral specification per subsystem — functions, states, error codes, invariants. |
| [lifetime.md](lifetime.md) | Init/deinit ordering, resource ownership, when it is safe to use / release a resource. |
| [porting.md](porting.md) | How to add a new backend (e.g. an RTOS or bare-metal target). Reference: the POSIX backend. |
| [porting.md](porting.md) | How to add a new backend (e.g. an RTOS or bare-metal target). Reference: the Linux backend. |
| [thread-safety.md](thread-safety.md) | Per-API table classifying every public call as thread-safe, single-owner, or init-thread-only. |

If you're just getting started, read them in this order:
Expand Down
6 changes: 3 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ behaviour matters more than dynamic flexibility.
| dmosal (public API) |
+---------------+---------------+
| portable | backend |
| layer | (POSIX...) |
| layer | (Linux...) |
+---------------+---------------+
| OS user-space API |
+-------------------------------+
Expand All @@ -34,7 +34,7 @@ behaviour matters more than dynamic flexibility.
across every backend (resource manager, LIFO free-list, error strings,
logging, time-checkpoint utility, top-level init).
- **Backend** — `source/<backend>/*.c`. Only these files touch OS-specific
APIs. The POSIX backend under `source/posix/` is the reference
APIs. The Linux backend under `source/linux/` is the reference
implementation.

## Subsystems
Expand Down Expand Up @@ -93,7 +93,7 @@ Two subsystems invoke user code from a thread other than the caller's:
- **Task** — `osal_task_create(cfg)` spawns a new thread that runs
`cfg->task_handler`. The handler runs to completion on that thread.
- **Timer** — the timer callback fires on a backend-managed context, not
the caller's thread. On the POSIX backend that means a fresh helper
the caller's thread. On the Linux backend that means a fresh helper
thread per expiration (via `SIGEV_THREAD`). User code must not assume
any particular thread identity inside the callback.

Expand Down
2 changes: 1 addition & 1 deletion docs/lifetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ create -> [started/stopped, callback fires 0+ times] -> stop -> delete
create --opens or attaches--> [send/recv N times] --unlinks if creator--> delete
```

- On the POSIX backend, `osal_queue_create` may either create the
- On the Linux backend, `osal_queue_create` may either create the
underlying kernel queue or attach to an existing one with the same
name. `osal_queue_delete` unlinks the kernel queue only when this
handle originally created it.
Expand Down
18 changes: 9 additions & 9 deletions docs/porting.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ under `source/`. The rest of the library — the resource manager, LIFO,
logging, error strings, tmcheck, and top-level init — is portable C and
unchanged across backends.

The reference implementation lives at `source/posix/` and targets Linux /
The reference implementation lives at `source/linux/` and targets Linux /
glibc. This document walks through what a new backend must provide.

## Files a backend must implement
Expand All @@ -31,7 +31,7 @@ For each function, honour the behavior documented in
[lifetime.md](lifetime.md). Concretely:

- **Pool ops must be thread-safe** — `_create` and `_delete` need to
serialize their pool touches. On POSIX we do this by threading the
serialize their pool touches. On Linux we do this by threading the
shared `osal_mutex_t *mutex` through each `_man_t`'s `osal_rm_t`. Reuse
that pattern via `OSAL_RM_USEROBJMAN_DECLARE` / `_INIT` if the target
OS has a mutex primitive.
Expand All @@ -43,7 +43,7 @@ For each function, honour the behavior documented in
without a helper-thread mechanism, provide one (e.g. a dedicated
timer dispatch task).
- **`osal_timer_delete` must reject in-callback invocation.** Use the
`in_callback` bool pattern from the POSIX backend: set true around the
`in_callback` bool pattern from the Linux backend: set true around the
callback invocation, `OSAL_RUNTIME_ASSERT` in delete if it's true.

## Steps
Expand All @@ -61,13 +61,13 @@ For each function, honour the behavior documented in

2. **Add sources to `CMakeLists.txt`.** Extend the `osal_SRC` list to
include `source/mybackend/*.c` when the target backend is selected.
The simplest scheme: add an option `DMOSAL_BACKEND=posix|mybackend`
The simplest scheme: add an option `DMOSAL_BACKEND=linux|mybackend`
and switch the source glob accordingly. Only one backend is compiled
in at a time.

3. **Implement `osal_mutex.c` first.** Everything else depends on it —
the shared mutex from this pool is passed to every other subsystem's
init. See `source/posix/osal_mutex.c` for the reference:
init. See `source/linux/osal_mutex.c` for the reference:
- `s_mutex_man` static holding the pool.
- A bare backend-level mutex (`s_mutex_man.resrc_mutex`) is used
inside `osal_mutex_create` because it can't recursively use its own
Expand All @@ -77,11 +77,11 @@ For each function, honour the behavior documented in

4. **Implement `osal_sem.c`, `osal_task.c`, `osal_timer.c`, `osal_queue.c`,
`osal_time.c`.** Each mirrors the same `_man_t` + `osal_rm_t` structure.
Copy the POSIX file and swap the OS calls.
Copy the Linux file and swap the OS calls.

5. **Wire in the build.** `CMakeLists.txt` is the only place where the
backend directory is referenced. Neither the public headers nor the
portable `source/*.c` files hard-code POSIX.
portable `source/*.c` files hard-code Linux.

6. **Run the existing test suite.** All six cmocka tests exercise only
the public API and should pass unchanged. If they fail, the failure
Expand All @@ -96,8 +96,8 @@ Some behaviors are inherently backend-specific and may need adjustment:
the microsecond field will just always show 0 — no code change needed.
- **`osal_task_cfg_t` hints** — `stack_addr`, `stack_size`, `priority`,
`name` are best-effort. Honour them if your target OS supports them.
The POSIX reference implementation ignores them.
- **Timer callback thread** — the POSIX backend spawns one helper thread
The Linux reference implementation ignores them.
- **Timer callback thread** — the Linux backend spawns one helper thread
per expiration (`SIGEV_THREAD`). On memory-constrained targets, use a
single dedicated timer-dispatch task that walks a pending list.

Expand Down
8 changes: 8 additions & 0 deletions include/dmosal/osal_tmcheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ extern "C" {
*
* A static checkpoint is created (if not already allocated) based on the function
* name and line number, and the current timestamp is captured for it.
*
* @warning The checkpoint is a per-call-site static, so the same code in the same
* function must not be run from multiple threads through this macro: all threads would
* share one checkpoint (capture is one-shot, so only the first timestamp is kept), and
* the lazy creation is not thread-safe. In that case, allocate a unique id per thread,
* create one checkpoint per id with osal_tmcheck_create() (for example named
* "func:line:id"), and call osal_tmcheck_capture_ts() with that index instead of
* using this macro.
*/
#define OSAL_TMCHECK_CAPTURE() \
do { \
Expand Down
12 changes: 9 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# just tidy # run clang-tidy
# just zephyr-build # build the examples for Zephyr native_sim (Docker)
# just zephyr-run # run the Zephyr examples
# just zephyr-test # run the cmocka tests on Zephyr native_sim (TEST=<name> for one)
# just zephyr-shell # shell inside the Zephyr container
# just clean # remove build directories

Expand Down Expand Up @@ -53,7 +54,8 @@ check-asan:
# Build and run tests with ThreadSanitizer
check-tsan:
just build_dir=build-tsan configure -DDMOSAL_ENABLE_TSAN=ON
just build_dir=build-tsan check
# TSan aborts with "unexpected memory mapping" when ASLR is on; disable it for the run.
setarch "$(uname -m)" -R just build_dir=build-tsan check

# Build and run tests with gcov coverage, generate HTML report
check-coverage:
Expand All @@ -76,7 +78,7 @@ doc: configure
format:
pre-commit run clang-format --all-files

# Run clang-tidy over the portable + POSIX sources via pre-commit
# Run clang-tidy over the portable + Linux sources via pre-commit
tidy:
cmake -S . -B {{build_dir}} -DCMAKE_BUILD_TYPE={{build_type}} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build {{build_dir}} --target dmosal -j{{jobs}}
Expand All @@ -90,6 +92,10 @@ zephyr-build:
zephyr-run:
zephyr/docker.sh run

# Build and run the cmocka tests (test/osal) on Zephyr native_sim
zephyr-test:
zephyr/docker.sh test

# Open a shell in the Zephyr container
zephyr-shell:
zephyr/docker.sh shell
Expand All @@ -100,4 +106,4 @@ clean:

# Remove all build directories (default + sanitizer variants)
distclean:
rm -rf {{build_dir}} build-asan build-tsan build-zephyr
rm -rf {{build_dir}} build-asan build-tsan build-zephyr build-zephyr-test
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion source/zephyr/osal_queue.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
#include "osal_assert.h"
#include "osal_config.h"

/* Shared by every handle created with the same name (mirrors POSIX mq_open attach). */
/* Shared by every handle created with the same name (mirrors Linux mq_open attach). */
typedef struct {
struct k_msgq msgq;
char buf[CONFIG_DMOSAL_QUEUE_BUF_SIZE] __aligned(sizeof(void *));
Expand Down
4 changes: 3 additions & 1 deletion source/zephyr/osal_time.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ osal_error_t osal_clock_time(uint64_t *nsec)
return OSAL_E_PARAM;
}

*nsec = k_ticks_to_ns_floor64(k_uptime_ticks());
/* Uptime is 0 right after boot (always so on native_sim before time advances); callers
* such as tmcheck use 0 as "not set", so never return it. */
*nsec = k_ticks_to_ns_floor64(k_uptime_ticks()) + 1U;
return OSAL_E_OK;
}
7 changes: 7 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,10 @@ add_executable(${OSAL_TEST} osal/osal_test.c)
target_link_libraries(${OSAL_TEST} dmosal ${CMOCKA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
add_dependencies(check ${OSAL_TEST})
add_test(${OSAL_TEST} ${OSAL_TEST})

# Linux-backend-only tests (not portable to other backends).
set(QUEUE_UNLINK_TEST queue_unlink_test)
add_executable(${QUEUE_UNLINK_TEST} linux/queue_unlink_test.c)
target_link_libraries(${QUEUE_UNLINK_TEST} dmosal ${CMOCKA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
add_dependencies(check ${QUEUE_UNLINK_TEST})
add_test(${QUEUE_UNLINK_TEST} ${QUEUE_UNLINK_TEST})
82 changes: 82 additions & 0 deletions test/linux/queue_unlink_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* BSD 2-Clause License
*
* Copyright (c) 2023, nguyenvannam142@gmail.com
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/* Linux-only: the Linux queue backend is backed by a POSIX message queue, visible in
* /dev/mqueue. Other backends have no such kernel object. */
#include <errno.h>
#include "cmocka_include.h"
#include "osal.h"

static void test_queue_delete_unlinks(void **state)
{
(void)state;
osal_queue_cfg_t cfg;
osal_queue_t *queue;
char path[256];

memset(&cfg, 0, sizeof(cfg));
snprintf((char *)cfg.name, sizeof(cfg.name), "dmosal_qu_%d", (int)getpid());
cfg.msglen = 8;
cfg.qsize = 2;

queue = osal_queue_create(&cfg);
assert_non_null(queue);

snprintf(path, sizeof(path), "/dev/mqueue/%s", (char *)cfg.name);
assert_int_equal(access(path, F_OK), 0);

osal_queue_delete(queue);

assert_int_not_equal(access(path, F_OK), 0);
assert_int_equal(errno, ENOENT);
}

static int setup(void **state)
{
osal_config_t cfg;

(void)state;
memset(&cfg, 0, sizeof(cfg));
osal_init(&cfg);
return 0;
}

static int teardown(void **state)
{
(void)state;
osal_deinit();
return 0;
}

int main(void)
{
setenv("CMOCKA_TEST_ABORT", "1", 1);

const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_queue_delete_unlinks, setup, teardown),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
Loading
Loading