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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ User-visible changes are recorded here. There are no released versions or releas

### Added

- Static definitions for the other object types (A64): `EMB_SEM_DEFINE`, `EMB_MUTEX_DEFINE`, `EMB_EVENT_DEFINE`, `EMB_COND_DEFINE`, `EMB_MSGQ_DEFINE`, `EMB_WORKQ_DEFINE` and, for interrupt-context callbacks, `EMB_TIMER_DEFINE` declare storage (with the ring or stack) and the handle and construct the object in `emb_kernel_init()`.

- Round-robin time slicing (`CONFIG_EMB_TIME_SLICING`, off by default): a thread at or below `CONFIG_EMB_TIMESLICE_PRIORITY` that runs for `CONFIG_EMB_TIMESLICE_TICKS` without blocking moves behind its READY equal-priority peers; without a peer its quantum is renewed (KRN-SCH-007, 026, 037; A63). The quantum is a deadline in the tickless computation and a comparison in the periodic tick. The reference model implements it, the new `native-timeslice` preset checks it through the differential bridge, and `native-timeslice-tickless` runs the conformance cases with the tickless clock. List scheduler only.

- The M2 contract decisions are accepted: amendments A29 to A61 and ADR-045 to ADR-048 and ADR-050 (project-owner review, 2026-10-31).
Expand Down
4 changes: 2 additions & 2 deletions docs/requirements/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ Group `API`. Design: `docs/specs/SPEC-001-api-conventions.md`. Decisions: ADR-00
### API-021 Construction and destruction
**Statement.** Every object type shall provide `emb_<object>_init(storage, attr, out_handle)` with context class pre-kernel and thread, which shall set `*out_handle` to `EMB_HANDLE_NULL` on failure, and `emb_<object>_destroy(handle)`. A compile-time `EMB_<OBJECT>_DEFINE` form shall declare storage and handle and register the object in the generated initialization table.
**Rationale.** Static definition without hand-written init code is how tables of objects stay in data, not code (principle 14).
**Status.** Accepted 2026-10-07
**Verification.** Test: conformance init/destroy and `_DEFINE` tests per object type.
**Status.** Accepted 2026-10-07; implemented for threads, semaphores, mutexes, event flags, condition variables, message queues, work queues and interrupt-context timers (A64).
**Verification.** Test: conformance init/destroy and `_DEFINE` tests per object type (`thread_define_registers_in_init_table` and the `*_define_constructs_in_init_table` cases).
**Trace.** SPEC-001 §6.4; KRN-OBJ-001, KRN-OBJ-003

### API-022 Distinct time types
Expand Down
1 change: 1 addition & 0 deletions docs/specs/AMENDMENTS-M2.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ Corrections found while preparing the M2 primitives, where an accepted specifica
| A61 | SPEC-006 §4.1, §4.2, §4.3, KRN-WQ-007 | `emb_dwork_t` is a public `emb_work_t` (the handler's argument) followed by opaque storage for an internal ISR_CONTEXT timer. The queue is named at schedule time and holds a timer reference while the delay runs, so it cannot be destroyed. `schedule` returns `EMB_EEXIST` while the delay runs or the item is queued, `EMB_EBUSY` while its handler runs on another queue; `reschedule` replaces a running delay and returns `EMB_EEXIST` for a queued item. Cancel reports `EMB_WORK_PENDING` for a stopped delay or a removed item. The trace record is `EMB_TRACE_DWORK_SCHEDULE`; the delay's own start and expiry are the timer records. | `kernel/work/dwork.c`, `include/emb/work.h` |
| A62 | KRN-SCH-036, 03 §2.1 | The priority count's lower bound is 2, as `CONFIG_EMB_PRIORITY_COUNT` has always allowed, not 4: idle plus one application level is a valid tiny configuration. Accepted by the project owner 2026-10-31. | `kernel/Kconfig` |
| A63 | SPEC-003 §8, KRN-SCH-007, KRN-SCH-026, KRN-SCH-037, KRN-SCH-041, KRN-TIM-029 | Time slicing is implemented with one quantum (`CONFIG_EMB_TIMESLICE_TICKS`) and one threshold (`CONFIG_EMB_TIMESLICE_PRIORITY`, 255 for every level). It requires the list scheduler: `CONFIG_EMB_TIME_SLICING` depends on `!CONFIG_EMB_SCHED_TABLE`, since a table level holds one thread. The quantum restarts at every dispatch, also for a preempted thread resuming ahead of its peers, and a yield without a peer keeps it running. Expiry renews the quantum and, with a READY peer, sets a per-CPU rotation flag that makes the next decision place the thread behind its peers. A thread raised above the threshold stops being sliced at its next expiry; one lowered below it starts at its next dispatch. The reference model implements the same rules; the differential bridge checks them with the periodic clock only (`native-timeslice`), because a tickless quantum starts from the free-running counter, which can run ahead of the ticks the model counts. Implemented 2026-10-31 at the project owner's request; the details await review. | `kernel/time/time.c`, `kernel/sched/sched.c`, `kernel/Kconfig` |
| A64 | SPEC-001 §6.4, SPEC-009 §5.2, API-021, A40 | The deferred cross-type `_DEFINE` change: `EMB_SEM_DEFINE(sym, initial, max)`, `EMB_MUTEX_DEFINE(sym, protocol, ceiling, flags)`, `EMB_EVENT_DEFINE(sym, initial, flags)`, `EMB_COND_DEFINE(sym, flags)`, `EMB_MSGQ_DEFINE(sym, item_size, capacity, flags)` with its ring, and `EMB_WORKQ_DEFINE(sym, priority, stack_size)` with its stack. They take positional fields like `EMB_THREAD_DEFINE` rather than an attribute initializer, because a partial initializer would zero fields whose defaults are not zero (a semaphore's `max`, a work queue's priority). The table entry stays one init-wrapper pointer, as for threads, not `{init_fn, &storage, &attr, &handle}`. `EMB_TIMER_DEFINE(sym, fn, arg)` defines interrupt-context timers only: a work-queue timer needs its queue at construction and table entries, the system queue's included, run in link order, while `EMB_INIT_AFTER` is not implemented. The object name is the symbol where `CONFIG_EMB_OBJ_NAMES` keeps names, else none. A construction failure faults through `emb_check_status()`. | `include/emb/*.h` |
4 changes: 3 additions & 1 deletion docs/specs/SPEC-001-api-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,11 @@ emb_status_t emb_mutex_destroy(emb_mutex_t mutex);
`init` is `prekernel thread`. On failure `*out_mutex` is `EMB_HANDLE_NULL`. `destroy` follows KRN-OBJ-001. Compile-time definition helpers exist for the common static case:

```c
EMB_MUTEX_DEFINE(name, attr_initializer...) /* storage + handle variable + registration in the init table */
EMB_MUTEX_DEFINE(name, protocol, ceiling, flags) /* storage + handle variable + registration in the init table */
```

Each type's macro takes the attribute fields a static object needs as positional arguments, like `EMB_THREAD_DEFINE`; the rest keep their `attr_default` values (A64).

The `_DEFINE` form registers the object in a generated initialization table so it is constructed during `emb_kernel_init()`, before the scheduler starts without hand-written init code; the table is one of the bracketed sections `embld` and GNU ld both provide (09 §7).

## 7. Time
Expand Down
2 changes: 1 addition & 1 deletion docs/specs/SPEC-009-objects-capabilities-and-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Two passes over the compiler are the price; CMake handles them as one target wit

### 5.2 `_DEFINE` and the init table

`EMB_<OBJ>_DEFINE(name, attr...)` expands to the storage, the handle variable, a constant attribute struct, and an entry `{init_fn, &storage, &attr, &handle}` in the bracketed section `.emb_init_table`, which `embld` and GNU ld both provide on every target (09 §7; no linker script is needed on AVR). `emb_kernel_init()` walks the bracketed table in link order and calls each init wrapper before scheduler start; thread entries are also started at `emb_kernel_start()` unless marked `EMB_THREAD_NO_AUTOSTART` (SPEC-008 §4). Entries from different translation units have no defined relative order; an object that depends on another at init time (a timer on a work queue) must be initialized in code or by the `EMB_INIT_AFTER(other)` attribute, which the generator honours by ordering the table it emits for system-described objects.
`EMB_<OBJ>_DEFINE(name, fields...)` expands to the storage, the handle variable, an init wrapper that fills the attributes from their defaults and the positional fields and calls `emb_<obj>_init()`, and an entry holding the wrapper in the bracketed section `.emb_init_table` (A64), which `embld` and GNU ld both provide on every target (09 §7; no linker script is needed on AVR). `emb_kernel_init()` walks the bracketed table in link order and calls each init wrapper before scheduler start; thread entries are also started at `emb_kernel_start()` unless marked `EMB_THREAD_NO_AUTOSTART` (SPEC-008 §4). Entries from different translation units have no defined relative order; an object that depends on another at init time (a timer on a work queue) must be initialized in code or by the `EMB_INIT_AFTER(other)` attribute, which the generator honours by ordering the table it emits for system-described objects.

### 5.3 Objects in flash

Expand Down
2 changes: 2 additions & 0 deletions docs/user/synchronization.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ int main(void)

Expected output is `produced=5 consumed=5` and exit status zero. This is a unit-counting example: it does not transmit payloads or claim to implement a message queue. The default mutex uses inheritance; this program requires `CONFIG_EMB_SEM`, `CONFIG_EMB_NOTIFY`, `CONFIG_EMB_MUTEX` and `CONFIG_EMB_MUTEX_INHERIT`. The reference native GCC configurations provide them.

Objects that live for the whole program can be defined statically instead, like the threads: `EMB_SEM_DEFINE(ready, 1u, EMB_SEM_COUNT_MAX)` and `EMB_MUTEX_DEFINE(state, EMB_MUTEX_INHERIT, 0u, 0u)` declare the storage and the handle and construct the object in `emb_kernel_init()`; a construction failure faults. `EMB_EVENT_DEFINE`, `EMB_COND_DEFINE`, `EMB_MSGQ_DEFINE` (with its ring), `EMB_WORKQ_DEFINE` (with its stack) and `EMB_TIMER_DEFINE` (interrupt-context callbacks only) follow the same pattern. Definitions run in link order, so nothing defined may depend on another definition at construction time: bindings, and timers whose callbacks run on a work queue, are set up in code after `emb_kernel_init()`.

## Why the bound consumer drains first

Binding takes a **one-bit mask**, such as `EMB_NOTIFY_BIT(0)`, rather than the integer bit index. It registers one target per semaphore. The implementation sets that bit on a later count transition from zero to one; binding an already-positive semaphore does not set it. The initial unit above therefore needs an initial readiness check.
Expand Down
20 changes: 20 additions & 0 deletions include/emb/cond.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#ifndef EMB_COND_H
#define EMB_COND_H

#include <emb/kernel.h>
#include <emb/mutex.h>
#include <emb/status.h>
#include <emb/time.h>
Expand Down Expand Up @@ -167,6 +168,25 @@ emb_status_t emb_cond_signal(emb_cond_t cond);
*/
emb_status_t emb_cond_broadcast(emb_cond_t cond);

/* EMB_COND_DEFINE(sym, flags): storage, the handle variable `sym`, and an init-table
* entry that constructs the condition variable in emb_kernel_init() (SPEC-001 §6.4, A64);
* a failure faults. MISRA Dev CS-12: rule 20.7, `sym` is a token. */
/* NOLINTBEGIN(bugprone-macro-parentheses): MISRA Dev CS-12 rule 20.7, as above */
#define EMB_COND_DEFINE(sym, flags_) \
static emb_cond_storage_t sym##_storage_; \
emb_cond_t sym; \
static void sym##_init_(void) \
{ \
emb_cond_attr_t attr_; \
emb_cond_attr_default(&attr_); \
attr_.name = EMB_DEFINE_NAME_(#sym); \
attr_.flags = (uint8_t)(flags_); \
(void)emb_check_status(emb_cond_init(&sym##_storage_, &attr_, &sym), \
"EMB_COND_DEFINE(" #sym ")"); \
} \
EMB_INIT_TABLE_ENTRY(sym##_init_)
/* NOLINTEND(bugprone-macro-parentheses) */

#ifdef __cplusplus
}
#endif
Expand Down
20 changes: 20 additions & 0 deletions include/emb/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,26 @@ emb_status_t emb_event_bind_notify(emb_event_t event, emb_thread_t thread, emb_n
emb_status_t emb_event_unbind_notify(emb_event_t event);
#endif

/* EMB_EVENT_DEFINE(sym, initial, flags): storage, the handle variable `sym`, and an
* init-table entry that constructs the event flags in emb_kernel_init() (SPEC-001 §6.4,
* A64); a failure faults. MISRA Dev CS-12: rule 20.7, `sym` is a token. */
/* NOLINTBEGIN(bugprone-macro-parentheses): MISRA Dev CS-12 rule 20.7, as above */
#define EMB_EVENT_DEFINE(sym, initial_, flags_) \
static emb_event_storage_t sym##_storage_; \
emb_event_t sym; \
static void sym##_init_(void) \
{ \
emb_event_attr_t attr_; \
emb_event_attr_default(&attr_); \
attr_.name = EMB_DEFINE_NAME_(#sym); \
attr_.initial = (emb_event_bits_t)(initial_); \
attr_.flags = (uint8_t)(flags_); \
(void)emb_check_status(emb_event_init(&sym##_storage_, &attr_, &sym), \
"EMB_EVENT_DEFINE(" #sym ")"); \
} \
EMB_INIT_TABLE_ENTRY(sym##_init_)
/* NOLINTEND(bugprone-macro-parentheses) */

#ifdef __cplusplus
}
#endif
Expand Down
8 changes: 8 additions & 0 deletions include/emb/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ typedef struct EMB_ALIGNED(sizeof(void *)) emb_init_entry {
static EMB_TABLE_CONST emb_init_entry_t fn##_init_entry_ EMB_USED EMB_TABLE_ENTRY_ATTR \
EMB_SECTION(EMB_TABLE_SECTION("emb_init_table")) = {fn}

/* The name an object _DEFINE macro gives its object: none where the kernel keeps no
* names, so Harvard ports do not copy the literal to RAM. */
#if CONFIG_EMB_OBJ_NAMES
#define EMB_DEFINE_NAME_(s) (s)
#else
#define EMB_DEFINE_NAME_(s) ((const char *)0)
#endif

/* ---- EMB_CHECK --------------------------------------------------------------- */

/**
Expand Down
25 changes: 25 additions & 0 deletions include/emb/msgq.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,31 @@ emb_status_t emb_msgq_bind_notify(emb_msgq_t msgq, emb_thread_t thread, emb_noti
emb_status_t emb_msgq_unbind_notify(emb_msgq_t msgq);
#endif

/* EMB_MSGQ_DEFINE(sym, item_size, capacity, flags): storage, a ring of @capacity items
* of @item_size bytes, the handle variable `sym`, and an init-table entry that constructs
* the queue in emb_kernel_init() (SPEC-001 §6.4, A64); a failure faults. MISRA Dev CS-12:
* rule 20.7, `sym` is a token. */
/* NOLINTBEGIN(bugprone-macro-parentheses): MISRA Dev CS-12 rule 20.7, as above */
#define EMB_MSGQ_DEFINE(sym, item_size_, capacity_, flags_) \
static emb_msgq_storage_t sym##_storage_; \
static EMB_MSGQ_RING(sym##_ring_, (item_size_), (capacity_)); \
emb_msgq_t sym; \
static void sym##_init_(void) \
{ \
emb_msgq_attr_t attr_; \
emb_msgq_attr_default(&attr_); \
attr_.name = EMB_DEFINE_NAME_(#sym); \
attr_.ring = sym##_ring_; \
attr_.ring_size = sizeof(sym##_ring_); \
attr_.item_size = (uint16_t)(item_size_); \
attr_.capacity = (uint16_t)(capacity_); \
attr_.flags = (uint8_t)(flags_); \
(void)emb_check_status(emb_msgq_init(&sym##_storage_, &attr_, &sym), \
"EMB_MSGQ_DEFINE(" #sym ")"); \
} \
EMB_INIT_TABLE_ENTRY(sym##_init_)
/* NOLINTEND(bugprone-macro-parentheses) */

#ifdef __cplusplus
}
#endif
Expand Down
22 changes: 22 additions & 0 deletions include/emb/mutex.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#ifndef EMB_MUTEX_H
#define EMB_MUTEX_H

#include <emb/kernel.h>
#include <emb/status.h>
#include <emb/time.h>
#include <emb/types.h>
Expand Down Expand Up @@ -189,6 +190,27 @@ bool emb_mutex_is_owner(emb_mutex_t mutex);
*/
emb_status_t emb_mutex_mark_consistent(emb_mutex_t mutex);

/* EMB_MUTEX_DEFINE(sym, protocol, ceiling, flags): storage, the handle variable `sym`,
* and an init-table entry that constructs the mutex in emb_kernel_init() (SPEC-001 §6.4,
* A64); a failure faults. MISRA Dev CS-12: rule 20.7, `sym` is a token. */
/* NOLINTBEGIN(bugprone-macro-parentheses): MISRA Dev CS-12 rule 20.7, as above */
#define EMB_MUTEX_DEFINE(sym, protocol_, ceiling_, flags_) \
static emb_mutex_storage_t sym##_storage_; \
emb_mutex_t sym; \
static void sym##_init_(void) \
{ \
emb_mutex_attr_t attr_; \
emb_mutex_attr_default(&attr_); \
attr_.name = EMB_DEFINE_NAME_(#sym); \
attr_.protocol = (uint8_t)(protocol_); \
attr_.ceiling = (uint8_t)(ceiling_); \
attr_.flags = (uint8_t)(flags_); \
(void)emb_check_status(emb_mutex_init(&sym##_storage_, &attr_, &sym), \
"EMB_MUTEX_DEFINE(" #sym ")"); \
} \
EMB_INIT_TABLE_ENTRY(sym##_init_)
/* NOLINTEND(bugprone-macro-parentheses) */

#ifdef __cplusplus
}
#endif
Expand Down
20 changes: 20 additions & 0 deletions include/emb/sem.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,26 @@ emb_status_t emb_sem_bind_notify(emb_sem_t sem, emb_thread_t thread, emb_notify_
emb_status_t emb_sem_unbind_notify(emb_sem_t sem);
#endif

/* EMB_SEM_DEFINE(sym, initial, max): storage, the handle variable `sym`, and an
* init-table entry that constructs the semaphore in emb_kernel_init() (SPEC-001 §6.4,
* A64); a failure faults. MISRA Dev CS-12: rule 20.7, `sym` is a token. */
/* NOLINTBEGIN(bugprone-macro-parentheses): MISRA Dev CS-12 rule 20.7, as above */
#define EMB_SEM_DEFINE(sym, initial_, max_) \
static emb_sem_storage_t sym##_storage_; \
emb_sem_t sym; \
static void sym##_init_(void) \
{ \
emb_sem_attr_t attr_; \
emb_sem_attr_default(&attr_); \
attr_.name = EMB_DEFINE_NAME_(#sym); \
attr_.initial = (emb_sem_count_t)(initial_); \
attr_.max = (emb_sem_count_t)(max_); \
(void)emb_check_status(emb_sem_init(&sym##_storage_, &attr_, &sym), \
"EMB_SEM_DEFINE(" #sym ")"); \
} \
EMB_INIT_TABLE_ENTRY(sym##_init_)
/* NOLINTEND(bugprone-macro-parentheses) */

#ifdef __cplusplus
}
#endif
Expand Down
Loading