Skip to content

executor: provide a minimal Rust executor pollable from a C super-loop #121

@JustinKovacich

Description

@JustinKovacich

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:

  1. Owns one or more long-lived futures (the Server::run future, the Client run-future, possibly a UDS run-future) in static storage.
  2. 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.
  3. Is no_alloc-clean: must not require a heap allocator. Uses the same static-pool primitives the bare-metal Client / Server already use.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions