Skip to content
Open
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

- Work-queue statistics (`CONFIG_EMB_WORKQ_STATS`, off by default; KRN-WQ-011, A65): `emb_workq_get_stats()` reports the worst submit-to-start latency, the longest handler with its function and the deepest backlog per queue, in ticks; `emb_workq_reset_stats()` restarts them.

- 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.
Expand Down
2 changes: 2 additions & 0 deletions configs/native-timeslice.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
CONFIG_EMB_TIME_SLICING=y
CONFIG_EMB_TIMESLICE_TICKS=2
CONFIG_EMB_TIMESLICE_PRIORITY=20
# Work-queue statistics (KRN-WQ-011), also off by default, are built and tested here too.
CONFIG_EMB_WORKQ_STATS=y
# Periodic clock: the quantum then counts delivered ticks exactly, as the model does. In
# tickless builds it starts from the free-running counter, which the model cannot see.
# CONFIG_EMB_TICKLESS is not set
2 changes: 1 addition & 1 deletion docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This page is the authoritative inventory of implementation and available evidenc
| Synchronization | Semaphores, mutexes with inheritance, ceiling, recursion and owner-death policy; event flags with ANY/ALL/CLEAR waits and binding (M2-05); condition variables with the mandatory relock (M2-06) | Conformance and native differential bridge; inheritance and deadlock detection have a configured depth limit |
| Communication | Copy-by-value message queues: direct hand-off to a waiting receiver, slot hand-off from a waiting sender, send to front, peek, `EMB_NO_WAIT` calls from ISRs and a receive-side binding (M2-07) | Conformance and native differential bridge; no pipes, buffer pools or zero-copy ownership queues; the masked copy time is bounded by configuration, not measured on hardware |
| Software timers | One-shot and drift-free periodic timers in their own deadline list (ADR-047), with callbacks on a work queue or in interrupt context, closed-form catch-up and overrun counts, stop states and `stop_sync` (M2-09) | Conformance and native differential bridge; list scheduler only (not tiny); arming overhead and jitter not measured on hardware |
| Deferred work | Work queues on a kernel thread with FIFO execution, idempotent and ISR-safe submit, resubmission while running, cancel and synchronous cancel, flush, destroy joining the thread, a system queue linked only when referenced (M2-08, ADR-048), and delayed work items over internal timers (M2-09) | Conformance and native differential bridge; list scheduler only (not tiny); no statistics |
| Deferred work | Work queues on a kernel thread with FIFO execution, idempotent and ISR-safe submit, resubmission while running, cancel and synchronous cancel, flush, destroy joining the thread, a system queue linked only when referenced (M2-08, ADR-048), and delayed work items over internal timers (M2-09) | Conformance and native differential bridge; list scheduler only (not tiny); optional per-queue statistics (A65), conformance-tested on native and spot-checked on QEMU Cortex-M4 |
| Notifications | Per-thread bits; bindings from semaphores, event flags and message queues to a thread, with unbind; a bound thread cannot be destroyed (ADR-046) | Binding takes a one-bit mask; pipes and pools do not exist to bind; restart generations need the isolated profile |
| Objects | Caller-owned generated storage and POINTER handles; per-type active-object lists for every handle object type (`CONFIG_EMB_OBJ_LIST`) used by init to refuse live storage (ADR-045) | No INDEXED or TABLE handles, capability rights, partition restart, system freeze, `emb_obj_foreach()` or debug-descriptor export |
| Native port | POSIX host-thread gate, deterministic IRQ injection, virtual time | Base/tiny development workflow; no current wall-clock, asynchronous signal stress, simulated isolated profile or Win32 backend |
Expand Down
4 changes: 2 additions & 2 deletions docs/requirements/KRN-WQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ KRN-WQ-001 to 004 originate in `docs/architecture/03-kernel-architecture.md` §6
### KRN-WQ-011 Trace and statistics
**Statement.** Submit, start with submit-to-start latency, end with duration, cancel, and delayed scheduling shall be trace events; maximum FIFO length, worst latency, and longest handler with its address shall be optional statistics per queue.
**Rationale.** Work queue latency is the latency of every timer callback and bottom half.
**Status.** Accepted 2026-10-07; trace records implemented (A52); statistics are not.
**Verification.** Test: trace decoder (not written); statistics API test (no API).
**Status.** Accepted 2026-10-07; trace records implemented (A52); statistics implemented with `CONFIG_EMB_WORKQ_STATS` (A65).
**Verification.** Test: `workq_stats_record_depth_latency_and_longest_handler` on `native-timeslice`; trace decoder (not written).
**Trace.** SPEC-006 §7; OBS-005
1 change: 1 addition & 0 deletions docs/specs/AMENDMENTS-M2.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ Corrections found while preparing the M2 primitives, where an accepted specifica
| 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` |
| A65 | SPEC-006 §7, KRN-WQ-011, A52 | Work-queue statistics are `CONFIG_EMB_WORKQ_STATS` (off by default): `emb_workq_get_stats()` fills `emb_workq_stats_t` (worst submit-to-start latency, longest handler and its function, items queued now and at most), and `emb_workq_reset_stats()` restarts the maxima, the pending maximum from the items queued now. Both are `thread isr` and O(1). Times are kernel-clock ticks, not cycles, so a handler shorter than a tick reads as 0. A submit while the item's handler runs starts the latency of the run it queues, and flush markers are not items. Each work item gains a submit timestamp. The per-thread count of notification sets is not implemented. | `kernel/work/workq.c`, `include/emb/work.h` |
2 changes: 1 addition & 1 deletion docs/specs/SPEC-006-notifications-and-work-queues.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Handler latency is the sum of the queue thread's dispatch latency at its priorit

## 7. Observability

Trace events: `notify_set(target, bits, woke)`, `notify_wait(thread, mask, mode, result)`, `bind_signal(object, thread, bit)`, `work_submit(queue, item, result)`, `work_start(queue, item, latency)`, `work_end(queue, item, duration)`, `work_cancel(item, state)`, `dwork_schedule(item, deadline)`. Statistics (optional): per queue, maximum FIFO length, worst submit-to-start latency, longest handler with its address; per thread, notification sets received. The debug descriptor (ADR-011) exports the work item layout so a debugger can list a queue's pending items.
Trace events: `notify_set(target, bits, woke)`, `notify_wait(thread, mask, mode, result)`, `bind_signal(object, thread, bit)`, `work_submit(queue, item, result)`, `work_start(queue, item, latency)`, `work_end(queue, item, duration)`, `work_cancel(item, state)`, `dwork_schedule(item, deadline)`. Statistics (optional): per queue, maximum FIFO length, worst submit-to-start latency, longest handler with its address, read with `emb_workq_get_stats()` and restarted with `emb_workq_reset_stats()` under `CONFIG_EMB_WORKQ_STATS` (A65); per thread, notification sets received (not implemented). The debug descriptor (ADR-011) exports the work item layout so a debugger can list a queue's pending items.

## 8. Tiny profile

Expand Down
1 change: 1 addition & 0 deletions docs/user/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The later [timer-delivery validation](../engineering/m2-01-timer-correction.md)
| `CONFIG_EMB_MSGQ`, `CONFIG_EMB_MSGQ_MAX_ITEM` | y; 64 bytes (16 on tiny) | message queues; the item bound limits the copy made with interrupts masked |
| `CONFIG_EMB_IPC_SMALL` | n (y on tiny) | 8-bit queue counts, at most 255 items |
| `CONFIG_EMB_WORKQ` | y | work queues and, with timers, delayed work; not available with the tiny profile's table scheduler |
| `CONFIG_EMB_WORKQ_STATS` | n | per-queue maximum backlog, worst latency and longest handler (`emb_workq_get_stats()`) |
| `CONFIG_EMB_SYSTEM_WORKQ`, `_PRIO`, `_STACK` | y; 0 (second-highest priority); 2048 bytes | the system work queue, linked only when `emb_workq_system()` is referenced |
| `CONFIG_EMB_TIMER` | y | software timers; not available with the table scheduler |
| `CONFIG_EMB_OBJ_LIST` | y (n on tiny) | exact refusal of init on live storage |
Expand Down
2 changes: 1 addition & 1 deletion docs/user/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ emb_work_init(&job.work, process);
(void)emb_work_submit(jobs, &job.work); /* EMB_EEXIST if it is already queued */
```

An item is never queued twice and never runs concurrently with itself; submitting it while its handler runs queues it once more for after the handler returns. `emb_work_cancel()` reports whether the item was pending (now removed), running (it completes) or idle. Before reusing an item's storage, call `emb_work_cancel_sync()`, which also waits for a running handler. `emb_workq_flush()` waits until everything queued before the call has run. Calling either from the queue's own handlers would wait forever and is misuse, as is returning from a handler with a mutex held. The queue's thread belongs to the kernel: thread calls on it are refused and it cannot be notified directly. `emb_workq_destroy()` needs an idle queue and returns after the thread has exited. Referencing `emb_workq_system()` links in a shared system queue at a high priority; keep its handlers short.
An item is never queued twice and never runs concurrently with itself; submitting it while its handler runs queues it once more for after the handler returns. `emb_work_cancel()` reports whether the item was pending (now removed), running (it completes) or idle. Before reusing an item's storage, call `emb_work_cancel_sync()`, which also waits for a running handler. `emb_workq_flush()` waits until everything queued before the call has run. Calling either from the queue's own handlers would wait forever and is misuse, as is returning from a handler with a mutex held. The queue's thread belongs to the kernel: thread calls on it are refused and it cannot be notified directly. `emb_workq_destroy()` needs an idle queue and returns after the thread has exited. Referencing `emb_workq_system()` links in a shared system queue at a high priority; keep its handlers short. With `CONFIG_EMB_WORKQ_STATS`, `emb_workq_get_stats()` shows whether they are: it reports each queue's worst submit-to-start latency, its longest handler and which function that was, and its deepest backlog.

For work that should run later, an `emb_dwork_t` adds a delay: `emb_dwork_init(&dw, fn)`, then `emb_dwork_schedule(queue, &dw, EMB_MS(50))` submits it when the delay expires. Scheduling it again while the delay runs returns `EMB_EEXIST`; `emb_dwork_reschedule()` replaces the delay instead. `emb_dwork_cancel()` stops the delay or removes the queued item, and `emb_dwork_cancel_sync()` also waits for a running handler. The handler receives `&dw.work`.

Expand Down
2 changes: 2 additions & 0 deletions include/emb/api_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,7 @@
#define EMB_API_ID_DWORK_RESCHEDULE 0x008Cu
#define EMB_API_ID_DWORK_CANCEL 0x008Du
#define EMB_API_ID_DWORK_CANCEL_SYNC 0x008Eu
#define EMB_API_ID_WORKQ_GET_STATS 0x008Fu
#define EMB_API_ID_WORKQ_RESET_STATS 0x0090u

#endif /* EMB_API_IDS_H */
52 changes: 52 additions & 0 deletions include/emb/work.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,58 @@ emb_status_t emb_workq_destroy(emb_workq_t workq);
*/
emb_status_t emb_workq_flush(emb_workq_t workq, emb_timeout_t timeout);

#if CONFIG_EMB_WORKQ_STATS
/* A queue's statistics (KRN-WQ-011): maxima since init or the last reset. */
typedef struct emb_workq_stats {
emb_duration_t max_latency; /* worst time from a submit to its handler's start */
emb_duration_t max_duration; /* longest handler run */
emb_work_fn_t max_duration_fn; /* the handler that took it; NULL before the first run */
uint16_t pending; /* items queued now */
uint16_t max_pending; /* most items queued at once */
} emb_workq_stats_t;

/**
* emb_workq_get_stats() - Read a queue's statistics.
* @workq: Initialized work queue.
* @out_stats: Receives the statistics.
*
* @ctx thread isr
* @blocks no
* @time O(1)
* @owns none
* @config CONFIG_EMB_WORKQ_STATS
* @req KRN-WQ-011
* @since 0.3
* @stable no
*
* Times are kernel-clock ticks. A submit while the item's handler runs starts the latency
* of the run it queues. Flush markers are not items.
*
* Return: EMB_OK; EMB_EINVAL for a bad handle or a NULL @out_stats.
*/
emb_status_t emb_workq_get_stats(emb_workq_t workq, emb_workq_stats_t *out_stats);

/**
* emb_workq_reset_stats() - Restart a queue's maxima from now.
* @workq: Initialized work queue.
*
* @ctx thread isr
* @blocks no
* @time O(1)
* @owns none
* @config CONFIG_EMB_WORKQ_STATS
* @req KRN-WQ-011
* @since 0.3
* @stable no
*
* The latency and duration maxima return to zero and the pending maximum to the items
* queued now.
*
* Return: EMB_OK; EMB_EINVAL for a bad handle.
*/
emb_status_t emb_workq_reset_stats(emb_workq_t workq);
#endif

#if CONFIG_EMB_SYSTEM_WORKQ
/**
* emb_workq_system() - The system work queue.
Expand Down
10 changes: 10 additions & 0 deletions kernel/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,16 @@ config EMB_WORKQ
A kernel thread per queue runs submitted items in order. The tiny profile's table
scheduler has no work queues in M2.

config EMB_WORKQ_STATS
bool "Per-queue work statistics (KRN-WQ-011)"
depends on EMB_WORKQ
default n
help
Each queue records the most items it held at once, the worst submit-to-start
latency and the longest handler with its address, read with
emb_workq_get_stats(). Costs a timestamp per work item and two clock reads per
handler run.

config EMB_SYSTEM_WORKQ
bool "The system work queue, linked only when referenced (ADR-048)"
depends on EMB_WORKQ
Expand Down
10 changes: 10 additions & 0 deletions kernel/include/embk/work.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ typedef struct embk_work {
uint8_t tag; /* EMBK_WORK_TAG once initialized */
uint8_t state; /* EMB_WORK_IDLE | EMB_WORK_PENDING | EMB_WORK_RUNNING */
uint8_t flags; /* EMBK_WORK_RESUBMITTED | EMBK_WORK_MARKER */
#if CONFIG_EMB_WORKQ_STATS
emb_tick_t submitted; /* the last submit: the start of its latency */
#endif
} embk_work_t;

typedef struct embk_workq {
Expand All @@ -32,6 +35,13 @@ typedef struct embk_workq {
embk_work_t *running; /* the item whose handler runs, or NULL */
uint8_t stopping; /* destroy has begun: the thread exits at an empty FIFO */
uint8_t timer_refs; /* timers whose callbacks run here: destroy is refused */
#if CONFIG_EMB_WORKQ_STATS
uint16_t pending; /* items queued now, flush markers excluded */
uint16_t max_pending; /* KRN-WQ-011 statistics from here */
emb_tick_t max_latency; /* worst submit to start */
emb_tick_t max_duration; /* longest handler */
void (*max_duration_fn)(struct emb_work *work); /* the handler that took it */
#endif
#if CONFIG_EMB_OBJ_LIST
embk_list_node_t all_node; /* in embk_workq_all from init to destroy */
#endif
Expand Down
Loading
Loading