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
13 changes: 13 additions & 0 deletions components/zkernel/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ menu "Boreas Kernel (zkernel)"
responsiveness for equal-or-lower-priority work. Mirrors
upstream Zephyr's CONFIG_SYSTEM_WORKQUEUE_NO_YIELD.

config K_TIMER_DISPATCH_ISR
bool "Dispatch k_timer callbacks in ISR context"
default y
select ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
help
When enabled, k_timer expiry callbacks run in true ISR context
(ESP_TIMER_ISR dispatch), matching upstream Zephyr's contract.
Callbacks must be IRAM_ATTR and must not call blocking APIs.

When disabled, callbacks run on the ESP_TIMER_TASK (a normal
FreeRTOS task). Blocking calls are permitted but the behavior
diverges from upstream Zephyr.

config ZKERNEL_FATAL_CAPTURE
bool "Capture fatal error context to NVS"
default n
Expand Down
61 changes: 44 additions & 17 deletions components/zkernel/include/boreas/zephyr/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ struct k_event {

int k_event_init(struct k_event *event);
uint32_t k_event_post(struct k_event *event, uint32_t events);
/** @note In ISR context, returns 0 on failure (timer command queue full)
* rather than the previous event state. FreeRTOS's
* xEventGroupSetBitsFromISR defers to the timer daemon and cannot
* report the prior bits synchronously. */
uint32_t k_event_set(struct k_event *event, uint32_t events);
uint32_t k_event_clear(struct k_event *event, uint32_t events);
uint32_t k_event_wait(struct k_event *event, uint32_t events, bool reset, k_timeout_t timeout);
Expand All @@ -302,21 +306,25 @@ struct k_timer;
/**
* Timer expiry function type.
*
* @warning Callback context divergence from upstream Zephyr.
* Upstream Zephyr documents this callback as running in
* "system clock interrupt handler" context. Boreas dispatches
* via esp_timer with ESP_TIMER_TASK, which is a normal
* FreeRTOS task -- NOT an ISR. Practical consequences:
* - Blocking calls (k_sem_take, k_mutex_lock, k_msleep) are
* permitted from this callback. Upstream forbids them.
* - There is no K_ISR_LOCK-equivalent guarantee. The
* callback IS preemptible by higher-priority tasks.
* - Code ported from upstream that assumes ISR semantics
* (e.g. relying on irq_lock for atomicity vs. user code)
* must be reviewed.
*
* Code that needs both behaviors should not assume either --
* use k_sem / k_mutex for synchronization regardless.
* @warning Callback context — matches upstream Zephyr when
* CONFIG_K_TIMER_DISPATCH_ISR=y (the default).
*
* **Default (ISR dispatch):** Callback runs in true ISR context
* via ESP_TIMER_ISR. The function must be declared IRAM_ATTR
* and must be ISR-safe:
* - Must NOT call blocking APIs (k_sem_take with timeout,
* k_mutex_lock, k_msleep, k_thread_join).
* - The following are ISR-safe and may be called:
* k_sem_give, k_work_submit, k_event_set/post/clear,
* k_msgq_put (K_NO_WAIT).
* - LOG_* is NOT ISR-safe (vsnprintf in the emit path is
* flash-resident). Defer logging via k_work_submit.
* - Must not call malloc, printf, or any flash-resident
* function (IRAM_ATTR is required for cache survival).
Comment thread
swoisz marked this conversation as resolved.
*
* **Fallback (CONFIG_K_TIMER_DISPATCH_ISR=n):** Callback runs
* on the ESP_TIMER_TASK, a normal FreeRTOS task. Blocking calls
* are permitted. This diverges from upstream Zephyr.
*/
typedef void (*k_timer_expiry_t)(struct k_timer *timer);

Expand Down Expand Up @@ -345,6 +353,18 @@ struct k_timer {
}

void k_timer_init(struct k_timer *timer, k_timer_expiry_t expiry_fn, k_timer_stop_t stop_fn);
/**
* @brief Start or restart a timer.
*
* @param timer Timer to start.
* @param duration Time until the first expiry.
* @param period Repeat interval after the first expiry (K_NO_WAIT for one-shot).
*
* @warning Must not be called while the timer's expiry callback is
* executing (same constraint as upstream Zephyr). With
* CONFIG_K_TIMER_DISPATCH_ISR=y, esp_timer_stop does not
* synchronize with an in-flight ISR callback on SMP targets.
*/
void k_timer_start(struct k_timer *timer, k_timeout_t duration, k_timeout_t period);
void k_timer_stop(struct k_timer *timer);

Expand Down Expand Up @@ -400,8 +420,15 @@ k_ticks_t k_timer_remaining_ticks(const struct k_timer *timer);
*/
k_ticks_t k_timer_expires_ticks(const struct k_timer *timer);

void k_timer_user_data_set(struct k_timer *timer, void *user_data);
void *k_timer_user_data_get(struct k_timer *timer);
static ALWAYS_INLINE void k_timer_user_data_set(struct k_timer *timer, void *user_data)
{
timer->user_data = user_data;
}

static ALWAYS_INLINE void *k_timer_user_data_get(const struct k_timer *timer)
{
return timer->user_data;
}

/* ----------------------------------------------------------------
* Work Queue
Expand Down
18 changes: 17 additions & 1 deletion components/zkernel/include/boreas/zephyr/sys/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#pragma once

#include "esp_attr.h"

#ifdef __cplusplus
extern "C" {
#endif
Expand Down Expand Up @@ -101,7 +103,9 @@ extern "C" {
#define ALWAYS_INLINE __attribute__((always_inline)) inline
#endif

/* Runtime assertion -- logs and aborts */
/* Runtime assertion -- logs and aborts.
* NOT safe from IRAM ISR context (ESP_LOGE + abort are flash-resident).
* Use k_panic() for unrecoverable errors in ISR/IRAM context. */
#ifndef __ASSERT
#include "esp_log.h"
#define __ASSERT(cond, msg) \
Expand All @@ -113,6 +117,18 @@ extern "C" {
} while (0)
#endif

/* IRAM-safe panic -- triggers an illegal-instruction exception caught by
* the ESP-IDF panic handler (which is IRAM-resident). Produces a full
* backtrace on UART and reboots. Safe to call from IRAM_ATTR ISR context.
* Mirrors upstream Zephyr's k_panic() contract. */
#define k_panic() __builtin_trap()

/* Attribute for kernel functions callable from ISR context.
* Always IRAM-resident: upstream Zephyr marks these isr-ok
* unconditionally, and ESP-IDF ISRs registered with
* ESP_INTR_FLAG_IRAM fire during cache-disabled windows. */
#define K_ISR_SAFE IRAM_ATTR

#ifdef __cplusplus
}
#endif
8 changes: 5 additions & 3 deletions components/zkernel/src/k_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

#include <errno.h>

#include "esp_attr.h"
#include "esp_log.h"
#include "sdkconfig.h"

static const char *TAG = "k_event";

Expand All @@ -21,12 +23,12 @@ int k_event_init(struct k_event *event)
return 0;
}

uint32_t k_event_post(struct k_event *event, uint32_t events)
uint32_t K_ISR_SAFE k_event_post(struct k_event *event, uint32_t events)
{
return k_event_set(event, events);
}

uint32_t k_event_set(struct k_event *event, uint32_t events)
uint32_t K_ISR_SAFE k_event_set(struct k_event *event, uint32_t events)
{
if (xPortInIsrContext()) {
BaseType_t wake = pdFALSE;
Expand All @@ -40,7 +42,7 @@ uint32_t k_event_set(struct k_event *event, uint32_t events)
return (uint32_t)xEventGroupSetBits(event->handle, (EventBits_t)events);
}

uint32_t k_event_clear(struct k_event *event, uint32_t events)
uint32_t K_ISR_SAFE k_event_clear(struct k_event *event, uint32_t events)
{
if (xPortInIsrContext()) {
return (uint32_t)xEventGroupClearBitsFromISR(event->handle, (EventBits_t)events);
Expand Down
4 changes: 3 additions & 1 deletion components/zkernel/src/k_msgq.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

#include <errno.h>

#include "esp_attr.h"
#include "esp_log.h"
#include "sdkconfig.h"

static const char *TAG = "k_msgq";

Expand All @@ -24,7 +26,7 @@ int k_msgq_init(struct k_msgq *msgq, char *buffer, size_t msg_size, uint32_t max
return 0;
}

int k_msgq_put(struct k_msgq *msgq, const void *data, k_timeout_t timeout)
int K_ISR_SAFE k_msgq_put(struct k_msgq *msgq, const void *data, k_timeout_t timeout)
{
BaseType_t ret;

Expand Down
4 changes: 3 additions & 1 deletion components/zkernel/src/k_sem.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

#include <errno.h>

#include "esp_attr.h"
#include "esp_log.h"
#include "sdkconfig.h"

static const char *TAG = "k_sem";

Expand All @@ -30,7 +32,7 @@ int k_sem_take(struct k_sem *sem, k_timeout_t timeout)
return k_timeout_is_no_wait(timeout) ? -EBUSY : -EAGAIN;
}

void k_sem_give(struct k_sem *sem)
void K_ISR_SAFE k_sem_give(struct k_sem *sem)
{
if (xPortInIsrContext()) {
BaseType_t wake = pdFALSE;
Expand Down
49 changes: 41 additions & 8 deletions components/zkernel/src/k_thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include "zephyr/kernel.h"

#include <errno.h>
#include "esp_attr.h"
#include "freertos/timers.h"
#include "sdkconfig.h"

/* Trampoline: adapts Zephyr's 3-arg entry to FreeRTOS's 1-arg entry.
* If _start_suspended is set, suspends self before calling entry
Expand All @@ -24,13 +27,38 @@ static void k_thread_entry_wrapper(void *arg)
vTaskSuspend(NULL);
}

#ifdef CONFIG_K_TIMER_DISPATCH_ISR
static void k_thread_delay_resume_pended(void *param, uint32_t unused)
{
(void)unused;
struct k_thread *thread = (struct k_thread *)param;
if (thread && thread->handle) {
vTaskResume(thread->handle);
}
}

static void K_ISR_SAFE k_thread_delay_expiry(struct k_timer *timer)
{
struct k_thread *thread = (struct k_thread *)k_timer_user_data_get(timer);
if (thread) {
BaseType_t woken = pdFALSE;
BaseType_t ret = xTimerPendFunctionCallFromISR(k_thread_delay_resume_pended, thread,
0, &woken);
if (ret != pdPASS) {
k_panic();
}
portYIELD_FROM_ISR(woken);
}
}
#else
static void k_thread_delay_expiry(struct k_timer *timer)
{
struct k_thread *thread = (struct k_thread *)k_timer_user_data_get(timer);
if (thread && thread->handle) {
vTaskResume(thread->handle);
}
}
#endif

k_tid_t k_thread_create(struct k_thread *thread, StackType_t *stack, size_t stack_size,
k_thread_entry_t entry, void *p1, void *p2, void *p3, int prio,
Expand All @@ -49,18 +77,23 @@ k_tid_t k_thread_create(struct k_thread *thread, StackType_t *stack, size_t stac
/* Set flag BEFORE creating task so the wrapper sees it immediately */
thread->_start_suspended = !k_timeout_is_no_wait(delay);

thread->handle = xTaskCreateStatic(
/* Pin to core 0. ESP-IDF's vPortCleanUpCoprocArea passes
* tskNO_AFFINITY (0x7FFFFFFF) to _xt_coproc_release, which
* overflows the owner-table index and silently skips the real
* entries — leaking stale FPU ownership. */
thread->handle = xTaskCreateStaticPinnedToCore(
k_thread_entry_wrapper, thread->name ? thread->name : "k_thread",
stack_size / sizeof(StackType_t), thread, prio, stack, &thread->tcb);
stack_size / sizeof(StackType_t), thread, prio, stack, &thread->tcb, 0);

/* xTaskCreateStatic only fails on programmer error (misaligned
* stack, NULL stack pointer, etc.). Match upstream Zephyr's
* "always returns a valid tid" contract by asserting here rather
* than returning NULL -- callers ported from Zephyr won't check. */
__ASSERT(thread->handle != NULL, "k_thread_create: xTaskCreateStatic failed");
/* xTaskCreateStaticPinnedToCore only fails on programmer error
* (misaligned stack, NULL stack pointer, etc.). Match upstream
* Zephyr's "always returns a valid tid" contract by asserting
* here rather than returning NULL -- callers ported from Zephyr
* won't check. */
__ASSERT(thread->handle != NULL, "k_thread_create: xTaskCreateStaticPinnedToCore failed");

/* Finite delay: thread self-suspended, set up timer to resume */
if (!k_timeout_is_forever(delay) && !k_timeout_is_no_wait(delay)) {
/* Finite delay: thread self-suspended, set up timer to resume */
k_timer_init(&thread->_delay_timer, k_thread_delay_expiry, NULL);
k_timer_user_data_set(&thread->_delay_timer, thread);
k_timer_start(&thread->_delay_timer, delay, K_NO_WAIT);
Expand Down
38 changes: 20 additions & 18 deletions components/zkernel/src/k_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@

#include "zephyr/kernel.h"

#include "esp_attr.h"
#include "esp_log.h"
#include "sdkconfig.h"

static const char *TAG = "k_timer";

static void k_timer_esp_callback(void *arg)
static void K_ISR_SAFE k_timer_esp_callback(void *arg)
{
struct k_timer *timer = (struct k_timer *)arg;

/* First-interval transition: first expiry was start_once, now switch to periodic */
if (__atomic_load_n(&timer->first_interval_pending, __ATOMIC_ACQUIRE)) {
__atomic_store_n(&timer->first_interval_pending, false, __ATOMIC_RELAXED);
esp_timer_start_periodic(timer->handle, timer->period_us);
/* Cannot fail: timer_process_alarm zeroes alarm before callback,
* so timer_armed() is false and start_periodic succeeds. */
esp_err_t err = esp_timer_start_periodic(timer->handle, timer->period_us);
if (err != ESP_OK) {
k_panic();
}
}

__atomic_fetch_add(&timer->status, 1, __ATOMIC_RELEASE);
Expand Down Expand Up @@ -50,20 +57,25 @@ void k_timer_init(struct k_timer *timer, k_timer_expiry_t expiry_fn, k_timer_sto
const esp_timer_create_args_t args = {
.callback = k_timer_esp_callback,
.arg = timer,
#ifdef CONFIG_K_TIMER_DISPATCH_ISR
.dispatch_method = ESP_TIMER_ISR,
Comment thread
swoisz marked this conversation as resolved.
#else
.dispatch_method = ESP_TIMER_TASK,
#endif
Comment thread
swoisz marked this conversation as resolved.
.name = "k_timer",
};
esp_err_t err = esp_timer_create(&args, &timer->handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_timer_create failed: %s", esp_err_to_name(err));
}
__ASSERT(err == ESP_OK, "k_timer_init: esp_timer_create failed");
}

void k_timer_start(struct k_timer *timer, k_timeout_t duration, k_timeout_t period)
{
/* Stop any in-flight callback synchronously; esp_timer_stop blocks
* until the dispatch task is idle, so subsequent stores are
* race-free with respect to the callback. */
/* Stop any in-flight timer. With ESP_TIMER_TASK dispatch,
* esp_timer_stop blocks until the callback finishes; with
* ESP_TIMER_ISR dispatch it only removes the timer from the
* armed list (the ISR callback may still be executing on
* another core). Callers must not restart a timer whose
* callback is still running — same constraint as upstream Zephyr. */
if (__atomic_load_n(&timer->running, __ATOMIC_ACQUIRE)) {
esp_timer_stop(timer->handle);
}
Expand Down Expand Up @@ -204,13 +216,3 @@ k_ticks_t k_timer_expires_ticks(const struct k_timer *timer)
}
return (k_ticks_t)(expiry / (uint64_t)tick_us);
}

void k_timer_user_data_set(struct k_timer *timer, void *user_data)
{
timer->user_data = user_data;
}

void *k_timer_user_data_get(struct k_timer *timer)
{
return timer->user_data;
}
Loading
Loading