Context
The simple_someip API as of 0.8.0 exposes the protocol state machine as a single 'static future per side (Server::run / run_with_buffers, plus the run-future returned from Client::new_with_deps_local). These futures are deliberately executor-agnostic — anything that can poll a Future will drive them.
We use tokio on std-side tests and examples/client_server, and we exercise embassy via simple-someip-embassy-net's loopback test. Neither is viable on the TC4 target. The TC4 host runs a C super-loop that schedules QM/ASIL tasks via hardware interrupts; the planned integration is a Rust executor polled cooperatively from C land.
"We have an input queue and an output queue, if those aren't empty, do work, else return."
— design discussion from the TC4 bring-up planning meeting
Goal
Provide a small async executor — target footprint ~30 LOC of Rust plus a thin C-FFI shim — that:
- Owns one or more long-lived futures (the
Server::run future, the Client run-future, possibly a UDS run-future) in static storage.
- Exposes
extern "C" fn rust_tick() (name TBD) that the C super-loop calls each iteration. The function polls every owned future once, returns control when none can make further progress.
- Is
no_alloc-clean: must not require a heap allocator. Uses the same static-pool primitives the bare-metal Client / Server already use.
- Is small enough to self-certify if we eventually take simple_someip down a certification path.
Sketch
// In a new crate (or a `c_host` module of `simple-someip`).
static SERVER_RUN: StaticCell<Pin<&'static mut dyn Future<Output = Result<(), Error>>>> = StaticCell::new();
#[no_mangle]
pub extern "C" fn simple_someip_init(/* config refs */) -> i32 {
// Build server + client via `new_with_handles` (sync constructors).
// Stash their run-futures in static slots.
}
#[no_mangle]
pub extern "C" fn simple_someip_tick() {
let waker = noop_waker(); // or a "set a wake-flag" waker if we want to be smart
let mut cx = Context::from_waker(&waker);
for fut in pinned_iter() {
let _ = fut.as_mut().poll(&mut cx);
}
}
Strawman waker is a no-op (every tick polls every future); smarter waker is a per-slot AtomicBool "needs poll" flag set from inside futures' wake-up paths. Smarter waker only matters if the polling cost becomes measurable; busy-poll-each-tick is fine for the message rates simple_someip handles.
Open questions
- Use
lilos vs. hand-roll: lilos is a small, tested embedded executor by Cliff Biffle — but it's designed as a primary RTOS, not a child executor inside another scheduler. Its scheduling primitives may not align with "called once per super-loop iteration, return as soon as everything's pending." A hand-rolled 30-LOC executor that does exactly that may be a better fit. Evaluate both.
- Where it lives: inside
simple-someip (as a feature-gated c_host module), in a sibling crate (mirroring simple-someip-embassy-net's pattern), or in the integrating firmware tree. Sibling crate is the cleanest separation; firmware tree keeps the integration concerns out of the protocol library entirely.
- Waker design: no-op vs wake-flag. Affects how often we waste cycles polling a future that has nothing to do.
- Construction-time async:
Server::new_with_deps is async fn (the bind step needs to await on tokio). For C-host construction we go through new_with_handles (sync — caller provides pre-bound sockets). This is fine; just noting that the executor doesn't need to drive an async constructor.
Blocking dependency
TC4 build infrastructure must be live before this can be exercised on hardware. See #117 for the CI matrix and the upstream-Rust-vs-HighTec toolchain question. This issue can be prototyped on thumbv7em-none-eabihf (the no_std proxy target we use today) without TC4 access — just need the executor and a smoke test that polls a stub future from a C harness.
Pairing
Companion to #121 (C-FFI queue surface). The executor polls run-futures; the queue is how C-land submits work to and reads results from those futures.
Context
The
simple_someipAPI as of 0.8.0 exposes the protocol state machine as a single'staticfuture per side (Server::run/run_with_buffers, plus the run-future returned fromClient::new_with_deps_local). These futures are deliberately executor-agnostic — anything that can poll aFuturewill drive them.We use
tokioon std-side tests andexamples/client_server, and we exercise embassy viasimple-someip-embassy-net's loopback test. Neither is viable on the TC4 target. The TC4 host runs a C super-loop that schedules QM/ASIL tasks via hardware interrupts; the planned integration is a Rust executor polled cooperatively from C land.— design discussion from the TC4 bring-up planning meeting
Goal
Provide a small async executor — target footprint ~30 LOC of Rust plus a thin C-FFI shim — that:
Server::runfuture, theClientrun-future, possibly a UDS run-future) in static storage.extern "C" fn rust_tick()(name TBD) that the C super-loop calls each iteration. The function polls every owned future once, returns control when none can make further progress.no_alloc-clean: must not require a heap allocator. Uses the same static-pool primitives the bare-metalClient/Serveralready use.Sketch
Strawman waker is a no-op (every tick polls every future); smarter waker is a per-slot
AtomicBool"needs poll" flag set from inside futures' wake-up paths. Smarter waker only matters if the polling cost becomes measurable; busy-poll-each-tick is fine for the message rates simple_someip handles.Open questions
lilosvs. hand-roll:lilosis a small, tested embedded executor by Cliff Biffle — but it's designed as a primary RTOS, not a child executor inside another scheduler. Its scheduling primitives may not align with "called once per super-loop iteration, return as soon as everything's pending." A hand-rolled 30-LOC executor that does exactly that may be a better fit. Evaluate both.simple-someip(as a feature-gatedc_hostmodule), in a sibling crate (mirroringsimple-someip-embassy-net's pattern), or in the integrating firmware tree. Sibling crate is the cleanest separation; firmware tree keeps the integration concerns out of the protocol library entirely.Server::new_with_depsisasync fn(the bind step needs to await on tokio). For C-host construction we go throughnew_with_handles(sync — caller provides pre-bound sockets). This is fine; just noting that the executor doesn't need to drive an async constructor.Blocking dependency
TC4 build infrastructure must be live before this can be exercised on hardware. See #117 for the CI matrix and the upstream-Rust-vs-HighTec toolchain question. This issue can be prototyped on
thumbv7em-none-eabihf(the no_std proxy target we use today) without TC4 access — just need the executor and a smoke test that polls a stub future from a C harness.Pairing
Companion to #121 (C-FFI queue surface). The executor polls run-futures; the queue is how C-land submits work to and reads results from those futures.