Skip to content

Commit 068f4e6

Browse files
avrabeclaude
andauthored
feat(wasm-dist): k_msgq_put wasm-cross-LTO module (complete: build + consume) (#70)
Adds the k_msgq_put wasm-cross-LTO module (3rd primitive after sem #59 / mutex #60), unblocked by synth v0.11.48 (#372+#359). Build+consume complete; build-pipeline + rivet + cargo oracles green. CI: zephyr-tests core (qemu_cortex_m3 + mps2) all green incl. the msgq suite. The only reds are pre-existing/non-blocking and unrelated to this change: llvm-lto docker-pull ENOSPC (gale#73, red on main) + smp_sched (continue-on-error). Same explained pattern as merged #71. Production note: CONFIG_GALE_WASM_LTO_MSGQ is default-off; on-silicon validation of the generalized production shim (k_timeout_t int64 ABI / PUT_PEND / arbitrary msg_size) remains a separate gate before enabling in a safety build. The no-wait hot path is already silicon-GREEN at 673 cyc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4da207a commit 068f4e6

9 files changed

Lines changed: 294 additions & 9 deletions

File tree

.github/workflows/release-wasm.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ permissions:
1313

1414
jobs:
1515
build-wasm-dist:
16-
name: "wasm dist (sem + mutex, cortex-m4f)"
16+
name: "wasm dist (sem + mutex + msgq, cortex-m4f)"
1717
runs-on: ubuntu-22.04
1818
container:
1919
image: ghcr.io/zephyrproject-rtos/ci-base:v0.29.0

docs/wasm-module-distribution.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,20 @@ bench integration):
6161

6262
Scope note: the **give** hot path is the shipped surface today (the silicon-measured
6363
907-cyc handoff path, re-baseline pending synth#311-fix validation); take/init stay
64-
native. The same pattern extends per-primitive (`GALE_WASM_LTO_MUTEX` next, gated on
65-
synth#237/v0.11.37 validation).
64+
native. The same pattern now extends across three primitives, each shipping its hot
65+
path with the rest of the object native:
66+
67+
| module | `CONFIG_GALE_WASM_LTO_*` | dissolved surface | native | tramp |
68+
|--------|-------------------------|-------------------|--------|-------|
69+
| sem | `_SEM` | `z_impl_k_sem_give` | take/init | value-path (none) |
70+
| mutex | `_MUTEX` | `z_impl_k_mutex_unlock` | lock/init | `r11=0` (`gale_wasm_mutex_tramp.S`) |
71+
| msgq | `_MSGQ` | `z_impl_k_msgq_put` (wake-reader / put-ok / return-full) | put-pend (`gale_w_msgq_pend`), get/init | `r11=0` (`gale_wasm_msgq_tramp.S`) |
72+
73+
msgq is the first module whose dissolved surface delegates one decided action
74+
(`PUT_PEND`, the blocking full-queue case) back to a native wrapper — the wait
75+
queue and scheduling stay native, so only the non-blocking hot path is dissolved.
76+
Its third argument (`k_timeout_t`, an 8-byte `{ ticks }` struct) crosses the wasm
77+
seam as an `int64_t` (AAPCS r2:r3-identical).
6678

6779
## Release flow
6880

scripts/build-wasm-dist.sh

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env bash
2-
# Build the gale wasm-cross-LTO release artifacts (sem + mutex, cortex-m4f lane).
2+
# Build the gale wasm-cross-LTO release artifacts (sem + mutex + msgq, cortex-m4f lane).
33
#
44
# Pipeline per docs/wasm-module-distribution.md:
55
# clang(wasm32) shim+FFI -> wasm-ld (DCE, exported entry) -> loom inline
@@ -73,16 +73,35 @@ build_module mutex "$GALE_ROOT/zephyr/wasm/mutex_unlock_shim.c" \
7373
z_impl_k_mutex_unlock gale_k_mutex_unlock_decide synth_k_mutex_unlock_body \
7474
"--native-pointer-abi" "${MTX_RENAMES[@]}"
7575

76+
# msgq imports the same spinlock/wake set as mutex (k_spin_lock + k_spin_unlock,
77+
# unpend, ready, return_value_set, reschedule). gale_w_thread_swap_data /
78+
# gale_w_memcpy / gale_w_msgq_pend are already gale_w_* symbols (undefined
79+
# imports resolved at consume link) so they need no rename.
80+
MSGQ_RENAMES=(
81+
k_spin_lock=gale_w_spin_lock
82+
k_spin_unlock=gale_w_spin_unlock
83+
z_unpend_first_thread=gale_w_unpend_first_thread
84+
z_ready_thread=gale_w_ready_thread
85+
arch_thread_return_value_set=gale_w_arch_thread_return_value_set
86+
z_reschedule=gale_w_reschedule
87+
)
88+
89+
# 2/3. msgq (pointer-arg path -> --native-pointer-abi + r11=0 trampoline at consume time)
90+
build_module msgq "$GALE_ROOT/zephyr/wasm/msgq_put_shim.c" \
91+
z_impl_k_msgq_put gale_k_msgq_put_decide synth_k_msgq_put_body \
92+
"--native-pointer-abi" "${MSGQ_RENAMES[@]}"
93+
7694
# 4. manifest (the trust anchor; sigil signs this)
7795
cat > "$OUT/gale-wasm-manifest-$VER.json" <<JSON
7896
{
7997
"version": "$VER",
80-
"primitives": ["sem", "mutex"],
98+
"primitives": ["sem", "mutex", "msgq"],
8199
"surfaces": {
82100
"sem": "z_impl_k_sem_give (give hot path; take/init native)",
83-
"mutex": "z_impl_k_mutex_unlock (unlock hot path; lock/init native; needs r11=0 trampoline)"
101+
"mutex": "z_impl_k_mutex_unlock (unlock hot path; lock/init native; needs r11=0 trampoline)",
102+
"msgq": "z_impl_k_msgq_put (put hot path: wake-reader / put-ok / return-full dissolved; pend delegates to native gale_w_msgq_pend; get/init native; needs r11=0 trampoline)"
84103
},
85-
"pipeline": "clang -> wasm-ld -> loom optimize --passes inline -> synth --target cortex-m4f [--native-pointer-abi for mutex] --all-exports --relocatable -> objcopy gale_w_* renames",
104+
"pipeline": "clang -> wasm-ld -> loom optimize --passes inline -> synth --target cortex-m4f [--native-pointer-abi for mutex+msgq] --all-exports --relocatable -> objcopy gale_w_* renames",
86105
"tools": {
87106
"clang": "$($CLANG --version | head -1)",
88107
"wasm-ld": "$($WASMLD --version | head -1)",
@@ -93,9 +112,11 @@ cat > "$OUT/gale-wasm-manifest-$VER.json" <<JSON
93112
"gale-wasm-sem-$VER.wasm": "$(sha "$OUT/gale-wasm-sem-$VER.wasm")",
94113
"gale-wasm-sem-$VER-cortex-m4f.o": "$(sha "$OUT/gale-wasm-sem-$VER-cortex-m4f.o")",
95114
"gale-wasm-mutex-$VER.wasm": "$(sha "$OUT/gale-wasm-mutex-$VER.wasm")",
96-
"gale-wasm-mutex-$VER-cortex-m4f.o": "$(sha "$OUT/gale-wasm-mutex-$VER-cortex-m4f.o")"
115+
"gale-wasm-mutex-$VER-cortex-m4f.o": "$(sha "$OUT/gale-wasm-mutex-$VER-cortex-m4f.o")",
116+
"gale-wasm-msgq-$VER.wasm": "$(sha "$OUT/gale-wasm-msgq-$VER.wasm")",
117+
"gale-wasm-msgq-$VER-cortex-m4f.o": "$(sha "$OUT/gale-wasm-msgq-$VER-cortex-m4f.o")"
97118
},
98-
"consume": "CONFIG_GALE_KERNEL_{SEM,MUTEX}=y CONFIG_GALE_WASM_LTO_{SEM,MUTEX}=y + -DGALE_WASM_LTO_OBJ_DIR=<this dir>; the mutex object links with gale_wasm_mutex_tramp.S (r11=0); verify manifest signature first (sigil)"
119+
"consume": "CONFIG_GALE_KERNEL_{SEM,MUTEX,MSGQ}=y CONFIG_GALE_WASM_LTO_{SEM,MUTEX,MSGQ}=y + -DGALE_WASM_LTO_OBJ_DIR=<this dir>; the mutex+msgq objects link with gale_wasm_{mutex,msgq}_tramp.S (r11=0); verify manifest signature first (sigil)"
99120
}
100121
JSON
101122
echo "wasm dist -> $OUT"; ls -la "$OUT"

zephyr/CMakeLists.txt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,46 @@ zephyr_library_include_directories(
710710
zephyr_library_link_libraries(${GALE_FFI_LIB})
711711
add_dependencies(gale_msgq gale_ffi_build)
712712

713+
# --------------------------------------------------------------------------
714+
# wasm-cross-LTO msgq variant (CONFIG_GALE_WASM_LTO_MSGQ):
715+
# link the RELEASED dissolved object (gale-wasm-msgq-<ver>-<target>.o) in
716+
# place of the native z_impl_k_msgq_put. Artifact discovery:
717+
# -DGALE_WASM_LTO_MSGQ_OBJ=<path to the .o> (explicit), or
718+
# -DGALE_WASM_LTO_OBJ_DIR=<release-assets dir> (matches gale-wasm-msgq-*-cortex-m4f.o)
719+
# The object's kernel imports are pre-renamed to gale_w_* at release-build
720+
# time; wasm/gale_wasm_wrappers.c provides those entry points (incl the
721+
# msgq-only gale_w_msgq_pend / gale_w_thread_swap_data / gale_w_memcpy) and
722+
# wasm/gale_wasm_msgq_tramp.S provides the r11=0 native-pointer bridge.
723+
# See docs/wasm-module-distribution.md.
724+
# --------------------------------------------------------------------------
725+
if(CONFIG_GALE_WASM_LTO_MSGQ)
726+
if(NOT DEFINED GALE_WASM_LTO_MSGQ_OBJ AND DEFINED GALE_WASM_LTO_OBJ_DIR)
727+
file(GLOB _gale_wasm_msgq_candidates "${GALE_WASM_LTO_OBJ_DIR}/gale-wasm-msgq-*cortex-m4f.o")
728+
list(LENGTH _gale_wasm_msgq_candidates _n)
729+
if(_n EQUAL 1)
730+
list(GET _gale_wasm_msgq_candidates 0 GALE_WASM_LTO_MSGQ_OBJ)
731+
elseif(_n GREATER 1)
732+
message(FATAL_ERROR "GALE_WASM_LTO_OBJ_DIR contains ${_n} msgq objects; pass -DGALE_WASM_LTO_MSGQ_OBJ explicitly")
733+
endif()
734+
endif()
735+
if(NOT DEFINED GALE_WASM_LTO_MSGQ_OBJ)
736+
message(FATAL_ERROR "CONFIG_GALE_WASM_LTO_MSGQ=y needs -DGALE_WASM_LTO_MSGQ_OBJ=<.o> or -DGALE_WASM_LTO_OBJ_DIR=<dir> (release assets; see docs/wasm-module-distribution.md)")
737+
endif()
738+
if(NOT EXISTS ${GALE_WASM_LTO_MSGQ_OBJ})
739+
message(FATAL_ERROR "wasm msgq object not found: ${GALE_WASM_LTO_MSGQ_OBJ}")
740+
endif()
741+
message(STATUS "Gale: wasm-cross-LTO msgq put -> ${GALE_WASM_LTO_MSGQ_OBJ}")
742+
zephyr_library_compile_definitions(GALE_WASM_LTO_OVERRIDE_MSGQ_PUT=1)
743+
zephyr_library_sources(wasm/gale_wasm_msgq_tramp.S)
744+
# gale_wasm_wrappers.c defines the shared gale_w_* entry points. Compile it
745+
# here only if neither the sem nor the mutex library already did — otherwise
746+
# gale_w_* would be multiply-defined at final link.
747+
if(NOT CONFIG_GALE_WASM_LTO_SEM AND NOT CONFIG_GALE_WASM_LTO_MUTEX)
748+
zephyr_library_sources(wasm/gale_wasm_wrappers.c)
749+
endif()
750+
zephyr_library_link_libraries(${GALE_WASM_LTO_MSGQ_OBJ})
751+
endif() # CONFIG_GALE_WASM_LTO_MSGQ
752+
713753
endif() # CONFIG_GALE_KERNEL_MSGQ
714754

715755
# ==========================================================================

zephyr/Kconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ config GALE_KERNEL_MSGQ
8686
polling, and tracing remain native Zephyr C.
8787
Requires a Rust toolchain for the target architecture.
8888

89+
config GALE_WASM_LTO_MSGQ
90+
bool "Link the released wasm-cross-LTO message-queue put path"
91+
depends on GALE_KERNEL_MSGQ
92+
default n
93+
help
94+
Replace the native z_impl_k_msgq_put with the prebuilt
95+
wasm-cross-LTO object shipped with gale releases
96+
(gale-wasm-msgq-<ver>-<target>.o): the verified Rust decision
97+
function seam-dissolved into the C put hot path at the wasm
98+
level (clang -> wasm-ld -> loom inline -> synth), compiled to a
99+
relocatable native object. The dissolved surface is the put hot
100+
path (wake-reader / put-ok / return-full); the blocking pend path
101+
and k_msgq_get / init / cleanup remain native Zephyr C. Point the
102+
build at the artifact via -DGALE_WASM_LTO_OBJ_DIR=<dir of release
103+
assets> or -DGALE_WASM_LTO_MSGQ_OBJ=<path to the .o>. Verify the
104+
release manifest signature (sigil) before consuming the artifacts
105+
in a safety build. See docs/wasm-module-distribution.md.
106+
89107
config GALE_KERNEL_STACK
90108
bool "Use Gale formally verified stack"
91109
depends on MULTITHREADING

zephyr/gale_msgq.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,14 @@ int z_msgq_cleanup_sched_locked(struct k_msgq *msgq)
171171
* Apply: C executes the decision (memcpy, wake, pend, or return).
172172
* ----------------------------------------------------------------------- */
173173

174+
/* GALE_WASM_LTO_OVERRIDE_MSGQ_PUT: when defined (CONFIG_GALE_WASM_LTO_MSGQ),
175+
* z_impl_k_msgq_put is supplied by the released wasm-cross-LTO object instead
176+
* (see wasm/gale_wasm_msgq_tramp.S + docs/wasm-module-distribution.md); the
177+
* native implementation below is compiled out. Same-TU callers (e.g.
178+
* z_impl_k_msgq_put_front's full-queue fallthrough) bind to the external
179+
* symbol at link time.
180+
*/
181+
#ifndef GALE_WASM_LTO_OVERRIDE_MSGQ_PUT
174182
int z_impl_k_msgq_put(struct k_msgq *msgq, const void *data,
175183
k_timeout_t timeout)
176184
{
@@ -234,6 +242,7 @@ int z_impl_k_msgq_put(struct k_msgq *msgq, const void *data,
234242

235243
return 0;
236244
}
245+
#endif /* GALE_WASM_LTO_OVERRIDE_MSGQ_PUT */
237246

238247
/* -----------------------------------------------------------------------
239248
* z_impl_k_msgq_put_front (uses Phase 1 gale_msgq_put_front)

zephyr/wasm/gale_wasm_msgq_tramp.S

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* wasm-cross-LTO trampoline for the dissolved z_impl_k_msgq_put.
2+
*
3+
* The shipped object's body (synth_k_msgq_put_body) addresses its k_msgq *
4+
* argument's fields via the wasm linear-memory base register r11: [r11 + ptr].
5+
* With r11 = 0 that is a native dereference, so this thin AAPCS-compatible
6+
* wrapper zeroes r11 around the call (the --native-pointer-abi convention).
7+
* Arguments pass straight through: r0 = msgq*, r1 = data*, r2:r3 = the 8-byte
8+
* k_timeout_t (== int64_t ticks; the shim's third parameter). Validated on
9+
* NUCLEO-G474RE silicon: k_msgq_put = 673 cyc, SELFCHECK rc=0 val=0xABCD on
10+
* synth 0.11.48 (post synth#359 .bss-sizing fix). See gale-smart-data
11+
* NOTES-wasm-cross-lto-spike.md + msgq-microbench/.
12+
*/
13+
.syntax unified
14+
.thumb
15+
.section .text.gale_wasm_msgq_tramp,"ax",%progbits
16+
.global z_impl_k_msgq_put
17+
.thumb_func
18+
z_impl_k_msgq_put:
19+
push {r11, lr}
20+
mov.w r11, #0
21+
bl synth_k_msgq_put_body
22+
pop {r11, pc}

zephyr/wasm/gale_wasm_wrappers.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
#include <zephyr/kernel.h>
1818
#include <zephyr/kernel_structs.h>
19+
#include <string.h>
1920
#include <wait_q.h>
2021
#include <ksched.h>
2122

@@ -86,3 +87,37 @@ int gale_w_thread_prio(struct k_thread *thread)
8687
{
8788
return thread->base.prio;
8889
}
90+
91+
/* msgq put shim (msgq_put_shim.c) wrappers. k_thread is opaque in the shim and
92+
* k_msgq_put can copy arbitrary-size messages and block, none of which the
93+
* dissolved object can do against an opaque thread / without Zephyr headers:
94+
* - gale_w_thread_swap_data: read base.swap_data — the waiting reader's
95+
* destination buffer (set by k_msgq_get before it pended); the put copies
96+
* the message into it on WAKE_READER.
97+
* - gale_w_memcpy: out-of-line memcpy (the wasm shim has no libc; this keeps
98+
* the byte copy a clean native call rather than a wasm bulk-memory op).
99+
* - gale_w_msgq_pend: the PUT_PEND blocking path. wait_q / scheduling stay
100+
* native (docs/wasm-module-distribution.md). The shim passes &msgq (==
101+
* &msgq->wait_q, first member) and the 8-byte timeout as int64 ticks; we
102+
* stash the message pointer in swap_data and z_pend_curr on gale_wasm_lock
103+
* — the same lock the shim's spin ops use, so the put-pend / get-wake
104+
* handshake shares one critical section (valid for the !SMP 0-byte-spinlock
105+
* config these modules target). */
106+
void *gale_w_thread_swap_data(struct k_thread *thread)
107+
{
108+
return thread->base.swap_data;
109+
}
110+
111+
void gale_w_memcpy(void *dst, const void *src, uint32_t n)
112+
{
113+
(void)memcpy(dst, src, (size_t)n);
114+
}
115+
116+
int gale_w_msgq_pend(void *wait_q, k_spinlock_key_t key,
117+
const void *data, int64_t timeout_ticks)
118+
{
119+
k_timeout_t timeout = { .ticks = (k_ticks_t)timeout_ticks };
120+
121+
_current->base.swap_data = (void *)data;
122+
return z_pend_curr(&gale_wasm_lock, key, (_wait_q_t *)wait_q, timeout);
123+
}

zephyr/wasm/msgq_put_shim.c

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Minimal wasm-side host of z_impl_k_msgq_put — the message-queue analogue of
3+
* sem_give_shim.c / mutex_unlock_shim.c. Replicates gale_msgq.c's put hot path
4+
* with the Zephyr kernel APIs as externs (which become wasm imports), so the
5+
* shim itself compiles to wasm32-unknown-unknown without pulling in Zephyr
6+
* headers. This puts the C <-> Rust seam (gale_k_msgq_put_decide) INSIDE the
7+
* wasm bundle: wasm-ld merges it with gale-ffi.wasm, loom inlines through it,
8+
* synth produces ARM with the seam dissolved (no `bl gale_k_msgq_put_decide`).
9+
*
10+
* Surface: z_impl_k_msgq_put (put hot path). k_msgq_get / init / cleanup stay
11+
* native (gale_msgq.c). All four decided actions are handled faithfully:
12+
* WAKE_READER — copy bytes into the waiting reader's buffer + wake
13+
* PUT_OK — copy bytes into the ring buffer at the write slot
14+
* RETURN_FULL — non-blocking full: return d.ret (-ENOMSG)
15+
* PUT_PEND — blocking full: delegate to gale_w_msgq_pend (native
16+
* z_pend_curr; the wait queue / scheduling stay native C,
17+
* exactly as docs/wasm-module-distribution.md prescribes).
18+
*
19+
* SHIM DISCIPLINE (default config: !SMP / !SPIN_VALIDATE / !NONZERO_SPINLOCK):
20+
* struct k_spinlock is a ZERO-size empty struct, so we OMIT the embedded `lock`
21+
* field (modelling it as sized would shift every later field and corrupt the
22+
* struct on store) and use a file-scope static spinlock. In this config every
23+
* k_spin_lock degenerates to arch_irq_lock(), so the shim's static lock and
24+
* gale_msgq.c's per-object lock are the SAME critical section — which is what
25+
* makes the pend(here)/wake(native get) handshake on msgq->wait_q safe across
26+
* the wasm-put / native-get boundary. Same constraint the sem/mutex modules
27+
* assume; not valid for SMP builds (those keep the native z_impl_k_msgq_put).
28+
*
29+
* ABI: the third argument k_timeout_t is a struct { k_ticks_t ticks } (one
30+
* 8-byte member), passed in r2:r3 under AAPCS — identical to a bare int64_t.
31+
* The shim therefore takes int64_t timeout_ticks (K_NO_WAIT.ticks == 0,
32+
* K_FOREVER.ticks == -1) and reconstructs k_timeout_t inside gale_w_msgq_pend.
33+
*
34+
* Faithful Zephyr v4.4.0 struct k_msgq field offsets (0-byte spinlock omitted,
35+
* WAITQ_DUMB): wait_q@0(8) | msg_size@8 | max_msgs@12 | buffer_start@16 |
36+
* buffer_end@20 | read_ptr@24 | write_ptr@28 | used_msgs@32. (Z_DECL_POLL_EVENT
37+
* / flags come AFTER used_msgs, so CONFIG_POLL doesn't affect any touched field;
38+
* poll-event handling on PUT_OK stays native — not exercised by this surface.)
39+
*/
40+
41+
#include <stdint.h>
42+
43+
/* Opaque k_thread — never deref'd in the shim; the kernel owns its layout. */
44+
struct k_thread;
45+
struct k_spinlock { uint8_t lock_internal; }; /* type only; never embedded (0-byte real) */
46+
typedef struct { uint32_t key; } k_spinlock_key_t;
47+
48+
struct k_msgq {
49+
void *wq_head; /* @0 _wait_q_t wait_q (head,tail) */
50+
void *wq_tail; /* @4 */
51+
uint32_t msg_size; /* @8 (size_t on 32-bit) — 0-byte k_spinlock omitted */
52+
uint32_t max_msgs; /* @12 */
53+
char *buffer_start; /* @16 */
54+
char *buffer_end; /* @20 */
55+
char *read_ptr; /* @24 */
56+
char *write_ptr; /* @28 */
57+
uint32_t used_msgs; /* @32 */
58+
};
59+
static struct k_spinlock msgq_lock;
60+
61+
/* Kernel API externs -> wasm imports -> native `bl` after synth-emit (renamed
62+
* to the gale_w_* wrappers by build-wasm-dist.sh's objcopy pass). */
63+
extern k_spinlock_key_t k_spin_lock(struct k_spinlock *);
64+
extern void k_spin_unlock(struct k_spinlock *, k_spinlock_key_t);
65+
extern struct k_thread * z_unpend_first_thread(void *wait_q);
66+
extern void z_ready_thread(struct k_thread *);
67+
extern void arch_thread_return_value_set(struct k_thread *, uint32_t);
68+
extern int z_reschedule(struct k_spinlock *, k_spinlock_key_t);
69+
/* k_thread is opaque here: these out-of-line wrappers do what the native
70+
* z_impl_k_msgq_put reaches via struct fields / static helpers. */
71+
extern void * gale_w_thread_swap_data(struct k_thread *); /* reader's dest buffer */
72+
extern void gale_w_memcpy(void *dst, const void *src, uint32_t n);
73+
extern int gale_w_msgq_pend(void *wait_q, k_spinlock_key_t key,
74+
const void *data, int64_t timeout_ticks);
75+
76+
/* #[repr(C)] GaleMsgqPutDecision — 16 bytes, returned by value (sret). */
77+
struct gale_msgq_put_decision { int32_t ret; uint8_t action; uint32_t new_write_idx; uint32_t new_used; };
78+
extern struct gale_msgq_put_decision gale_k_msgq_put_decide(
79+
uint32_t write_idx, uint32_t used_msgs, uint32_t max_msgs,
80+
uint32_t has_waiter, uint32_t is_no_wait);
81+
82+
#define GALE_MSGQ_ACTION_PUT_OK 0
83+
#define GALE_MSGQ_ACTION_WAKE_READER 1
84+
#define GALE_MSGQ_ACTION_PUT_PEND 2
85+
#define GALE_MSGQ_ACTION_RETURN_FULL 3
86+
87+
int z_impl_k_msgq_put(struct k_msgq *msgq, const void *data, int64_t timeout_ticks)
88+
{
89+
uint32_t is_no_wait = (timeout_ticks == 0) ? 1U : 0U; /* K_NO_WAIT.ticks == 0 */
90+
91+
k_spinlock_key_t key = k_spin_lock(&msgq_lock);
92+
93+
/* Extract: try to unpend first waiter (side effect: removes from queue).
94+
* wait_q is k_msgq's first member, so &msgq == &msgq->wait_q. */
95+
struct k_thread *reader = z_unpend_first_thread((void *)msgq);
96+
97+
uint32_t write_idx = (uint32_t)(msgq->write_ptr - msgq->buffer_start) / msgq->msg_size;
98+
99+
struct gale_msgq_put_decision d = gale_k_msgq_put_decide(
100+
write_idx, msgq->used_msgs, msgq->max_msgs,
101+
reader != (struct k_thread *)0 ? 1U : 0U, is_no_wait);
102+
103+
switch (d.action) {
104+
case GALE_MSGQ_ACTION_WAKE_READER:
105+
/* Receiver was waiting — copy the message into its buffer
106+
* (reader stashed its dest in swap_data before pending in get). */
107+
gale_w_memcpy(gale_w_thread_swap_data(reader), data, msgq->msg_size);
108+
arch_thread_return_value_set(reader, 0U);
109+
z_ready_thread(reader);
110+
z_reschedule(&msgq_lock, key);
111+
return d.ret;
112+
case GALE_MSGQ_ACTION_PUT_OK:
113+
/* Space available — store at the write slot, advance ring. */
114+
gale_w_memcpy(msgq->write_ptr, data, msgq->msg_size);
115+
msgq->write_ptr = msgq->buffer_start + (uint64_t)d.new_write_idx * msgq->msg_size;
116+
msgq->used_msgs = d.new_used;
117+
k_spin_unlock(&msgq_lock, key);
118+
return d.ret;
119+
case GALE_MSGQ_ACTION_PUT_PEND:
120+
/* Queue full, blocking — pend current thread natively. The
121+
* unpend above found no reader, so the wait_q is untouched. */
122+
return gale_w_msgq_pend((void *)msgq, key, data, timeout_ticks);
123+
case GALE_MSGQ_ACTION_RETURN_FULL:
124+
default:
125+
k_spin_unlock(&msgq_lock, key);
126+
return d.ret; /* -ENOMSG */
127+
}
128+
}

0 commit comments

Comments
 (0)