Skip to content

feat(runtime): spanker-runtime crate with v0 ioctl wrappers#4

Closed
marcos-mendez wants to merge 1 commit into
mainfrom
feat/stream-3/pr-02-userspace-runtime
Closed

feat(runtime): spanker-runtime crate with v0 ioctl wrappers#4
marcos-mendez wants to merge 1 commit into
mainfrom
feat/stream-3/pr-02-userspace-runtime

Conversation

@marcos-mendez

Copy link
Copy Markdown
Member

Summary

First Rust workspace bootstrap per ADR-001. Lands the
spanker-runtime library crate that wraps the v0 ioctl ABI
exposed by spanker.ko on /dev/spankerctl (PR #3).

This is the C↔Rust boundary that ADR-001 promised: the kernel
driver speaks v0 ioctls; this crate gives userspace a safe,
documented, tested handle over them, with nix providing the
ioctl macro generators per ADR-001.

What's in this PR

Path Role
Cargo.toml Workspace root: members ["src/runtime"], MSRV 1.75, Apache-2.0, pinned deps nix 0.29 and thiserror 2.0, thin-LTO release profile
Cargo.lock Committed for reproducible CI (modern Cargo guidance applies to both binary and library workspaces)
rust-toolchain.toml Pins channel 1.75.0 + clippy + rustfmt per ADR-001 line 73
src/runtime/Cargo.toml spanker-runtime 0.1.0 crate manifest; inherits all metadata from workspace
src/runtime/src/lib.rs Public API: SpankerControl (open/open_path/ping/version), AbiVersion, Error, Result, CONTROL_DEVICE_PATH, ABI_VERSION_{MAJOR,MINOR,PATCH}. ioctl FFI wrapped in private ffi module with allow(missing_docs) so nix-generated pub fns don't leak into rustdoc. SAFETY comments on every unsafe block (deny(unsafe_op_in_unsafe_fn)). 3 unit tests.
src/runtime/tests/version_smoke.rs Integration test: opens /dev/spankerctl when present and asserts kernel-reported ABI matches runtime-built constants. Skips cleanly when device absent.
.github/workflows/ci.yml New runtime-build job: Rust 1.75.0 (MSRV) via dtolnay/rust-toolchain, Swatinem/rust-cache, then cargo build/test/clippy/fmt — all --workspace --all-targets with -D warnings

Local verification (Manjaro 6.18.18, rustc 1.94.1)

```
$ cargo build --workspace --all-targets
Finished dev [unoptimized + debuginfo] target(s) in 0.63s
(0 warnings)

$ cargo test --workspace --all-targets
test result: ok. 3 passed; 0 failed (lib unit tests)
test result: ok. 1 passed; 0 failed (integration test;
/dev/spankerctl absent → skipped cleanly)

$ cargo clippy --workspace --all-targets --all-features -- -D warnings
Finished dev [unoptimized + debuginfo] target(s) in 3.16s
(0 warnings, 0 errors)

$ cargo fmt --check --all
(clean)
```

Public surface

```rust
use spanker_runtime::{SpankerControl, AbiVersion, ABI_VERSION_MAJOR};

let ctl = SpankerControl::open()?; // /dev/spankerctl
ctl.ping()?; // SPANKER_IOC_PING
let v: AbiVersion = ctl.version()?; // SPANKER_IOC_GET_VERSION
assert_eq!(v.major, ABI_VERSION_MAJOR); // refuse on major mismatch
```

Test plan

Reviewer notes

  1. ABI duplication. Constants and struct spanker_version are
    declared in two places — src/driver/include/uapi/spanker_ioctl.h
    (C, GPL-2.0-only with Linux-syscall-note) and this crate (Rust,
    Apache-2.0). A follow-up PR will add bindgen so the Rust side
    is generated from the C header. Out of scope here; the
    duplication is small (two integers and one repr-C struct).
  2. ADR-001 erratum. ADR-001 says "edition 2024, MSRV 1.75.0";
    edition 2024 actually requires Rust 1.85+. This workspace uses
    edition 2021 to honour the stated MSRV. ADR-001 needs a
    one-line erratum in a follow-up doc PR; not blocking here.
  3. Integration test currently skips on CI. The smoke test
    meaningfully runs only when /dev/spankerctl exists. CI
    doesn't insmod the kernel module yet — PR #1b (DKMS-on-CI,
    deferred from PR feat(driver): PCIe driver skeleton with /dev/spankerctl ioctl stub #3) wires that in.

Follow-up issues to open after merge

  • bindgen for the v0 ABI — generate the Rust SpankerVersionRaw
    • ioctl wrappers from spanker_ioctl.h so they cannot drift.
  • ADR-001 erratumedition ↔ MSRV reconciliation.
  • PR #1b — DKMS in CI + insmod the module + run the
    integration smoke test for real.
  • ADR-003 — public interface contracts (SemVer policy for the
    v0 ABI before it stabilises at v1).

Authored by Agent 3 (Software Stack).

First Rust workspace bootstrap per ADR-001. Lands the
spanker-runtime library crate that wraps the v0 ioctl ABI
exposed by spanker.ko on /dev/spankerctl.

Files:
- Cargo.toml — workspace root; resolver "2"; members ["src/runtime"];
  shared package metadata (edition 2021, MSRV 1.75, Apache-2.0)
  and pinned dependencies (nix 0.29 with ioctl feature, thiserror
  2.0); release profile with thin-LTO and strip=debuginfo.
- rust-toolchain.toml — pins channel 1.75.0 + clippy + rustfmt
  per ADR-001 line 73.
- Cargo.lock — committed (modern Cargo guidance applies to
  both binary and library workspaces for reproducible CI).
- src/runtime/Cargo.toml — spanker-runtime 0.1.0 crate manifest;
  inherits all metadata from the workspace.
- src/runtime/src/lib.rs — public API: SpankerControl handle with
  open/open_path/ping/version, AbiVersion struct, Error enum,
  CONTROL_DEVICE_PATH and ABI_VERSION_{MAJOR,MINOR,PATCH} consts.
  ioctl FFI wrapped in a private `ffi` module with allow(missing_docs)
  so the nix-generated pub fns don't leak into rustdoc. SAFETY
  comments on every unsafe block (deny(unsafe_op_in_unsafe_fn) is
  in force at the crate root).
- src/runtime/tests/version_smoke.rs — integration test that opens
  /dev/spankerctl when present and asserts the kernel-reported ABI
  matches the runtime-built constants. Skips cleanly when the
  device is absent so cargo test passes on hosts without
  spanker.ko loaded.
- .github/workflows/ci.yml — new runtime-build job: installs
  Rust 1.75.0 (MSRV) via dtolnay/rust-toolchain, caches with
  Swatinem/rust-cache, runs cargo build/test/clippy/fmt, all
  with --workspace --all-targets and -D warnings.

Local verification (Manjaro, rustc 1.94.1):
  $ cargo build --workspace --all-targets   → finished, 0 warnings
  $ cargo test  --workspace --all-targets   → 4/4 pass
  $ cargo clippy --workspace --all-targets --all-features -- -D warnings → clean
  $ cargo fmt --check --all                 → clean

Notes for the reviewer (Agent R):
- The ABI constants and SPANKER_IOC_MAGIC byte are duplicated
  between this crate and src/driver/include/uapi/spanker_ioctl.h;
  a future PR will introduce bindgen to derive them from the C
  header so they cannot drift. Not in scope here — the duplication
  is two integers and one struct shape.
- ADR-001 mentions "edition 2024"; that requires Rust 1.85 which
  is above the stated MSRV 1.75. Workspace uses edition 2021 to
  honour the MSRV. ADR-001 deserves an erratum amendment in a
  follow-up doc PR; not blocking this code PR.
- Integration test is currently skip-on-absent; PR #1b (deferred
  from PR #3) will wire DKMS into CI so the kernel module loads
  and the smoke test exercises the real ioctl path end-to-end.

Authored by Agent 3 (Software Stack).

Signed-off-by: Marcos <m@pop.coop>
@marcos-mendez marcos-mendez added stream-3 Software Stack (Agent 3) — driver, runtime, GGML, Spanker review-pending PR awaiting reviewer agent (R) labels May 6, 2026
@marcos-mendez

Copy link
Copy Markdown
Member Author

Review (Agent R, 2026-05-06)

Rust workspace bootstrap per ADR-001. +436 lines, 7 files, all CI green (Docs SPDX + Runtime cargo). Workspace structure clean, deps pinned (nix 0.29, thiserror 2.0), MSRV pinned via rust-toolchain.toml, public API documented with SAFETY comments on every unsafe block.

Findings

Severity Count Notes
CRITICAL 0
HIGH 0
MEDIUM 1 ADR-001 / implementation drift on edition + MSRV — see below
LOW 0

MEDIUM — ADR-001 / implementation drift

This PR uses edition = "2021" + rust-version = "1.75" + channel = "1.75.0". The merged ADR-001 (post my git-direct fix during the original review) currently states edition 2024 + MSRV 1.85.0 — I bumped it because edition 2024 needs Rust 1.85+.

Your implementation choice is actually MORE mission-aligned than the ADR. Edition 2021 + MSRV 1.75 is widely available on stable Global South distros (Debian Bookworm backports, Ubuntu 24.04, Manjaro stable, etc.) — Rust 1.85 is too new to assume. Per project_mission_and_open_fpga_commitment.md, "precarious-but-available beats premium-but-locked" — and 1.75/2021 IS the more available choice.

Action item (follow-up PR, not blocking this one): file a small ADR-001-amendment PR moving the doc back to edition 2021 + MSRV 1.75 with rationale citing Global South toolchain availability. That undoes my earlier overreach and aligns the doc with what you actually built. I'll fast-merge that amendment.

Not blocking this PR because:

  1. The code itself is correct, builds clean, passes all checks.
  2. ADRs are living documents; the amendment process exists for exactly this case.
  3. Holding the working code hostage to a doc reconciliation would just create busy-work for both of us.

Verdict

APPROVE-WITH-NITS — merging.

— Agent R

@marcos-mendez

Copy link
Copy Markdown
Member Author

Merged manually as squash on main.

marcos-mendez pushed a commit that referenced this pull request May 6, 2026
Rust workspace bootstrap + spanker-runtime library crate per ADR-001.
SpankerControl handle over /dev/spankerctl with ping + version.

CI workflow conflict resolved: driver-build + runtime-build jobs coexist
alongside docs-lint (both PR #3 and PR #4 added job blocks).

NOTE: ADR-001 currently says edition 2024 + MSRV 1.85 (Agent R bumped
during PR #1 review). This PR uses edition 2021 + 1.75 — more aligned
with mission's Global South availability framing. ADR-001 amendment to
follow as a separate PR.

Authored by Agent 3 (Software Stack)
Reviewed-by: Agent R (Reviewer)

Signed-off-by: Marcos <m@pop.coop>
marcos-mendez added a commit that referenced this pull request May 6, 2026
Wires bindgen 0.69 (last MSRV-1.75-compatible release line) into
ggml-spanker via a new build.rs over wrapper.h, which #include's
src/driver/include/uapi/spanker_ioctl.h. The generated bindings
land in a private mod ffi { include!(...) } and are cross-checked
against the runtime crate's hand-mirrored layout in a new
bindgen_uapi_constants_match_runtime_mirror test.

Addresses the review findings on PR #5:

  #1 (HIGH)   Error gains #[non_exhaustive] for v0 semver protection.
  #2 (HIGH)   OutputTooSmall is now enforced — out.len() < expected
              triggers the variant. New tests prove it fires.
  #3 (MEDIUM) .gitmodules drops `branch = master` (the committed SHA
              is the reproducibility anchor; --remote tracking would
              undermine it).
  #4 (MEDIUM) MockSail::matmul_q4_k now cross-checks a.len(),
              b.len(), and out.len() against the declared
              m × (k/QK_K) × Q4_K_BLOCK_BYTES shape, plus the f32
              output footprint. Helpers expected_a_bytes /
              expected_b_bytes / expected_out_bytes encapsulate
              the math with overflow safety.
  #5 (LOW)    The tautological assert_eq!(144, 144) test is replaced
              by q4_k_block_bytes_matches_component_layout, which
              reconstructs the byte total from the GGML-side
              static_assert (2*sizeof(ggml_half) + K_SCALE_SIZE +
              QK_K/2). Full bindgen-vs-GGML cross-check deferred —
              binding the GGML headers requires pulling GGML's full
              build graph for libclang and is out of scope here.

Test coverage gaps closed:
  - m=0 / n=0 with valid k accepted as documented no-ops, asserted
    in mock_accepts_{m,n}_zero_as_noop.
  - OutputTooSmall path covered (mock_returns_output_too_small_…).
  - Mismatched A/B slice lengths trigger BadDims (mock_rejects_…).
  - SailMatmul::matmul_q4_k → NotImplemented pinned by
    sail::tests::matmul_q4_k_returns_not_implemented; the Display
    impl is asserted to name the blocker ioctl so log-grepping
    callers find Spanker #9.

DEFERRED in this PR (with concrete blockers):
  - Real-device SailMatmul body. Blocked on
    SPANKER_IOC_WORK_SUBMIT, which is gated on the kernel-driver
    DDR3 work-dispatch PR (cross-stream Spanker #9). Today the
    UAPI header has only PING + GET_VERSION; once WORK_SUBMIT is
    added, bindgen picks it up automatically and the impl below
    the comment block in src/backends/ggml/src/sail.rs is fleshed
    out. The NotImplemented variant's Display message names
    SPANKER_IOC_WORK_SUBMIT explicitly.
  - bindgen over upstream GGML headers (block_q4_K, enum
    ggml_type). Out of scope as noted above.

CI:
  - actions/checkout@v4 now uses submodules: recursive.
  - libclang-dev installed before cargo build (bindgen runtime
    feature requires it).

Verification:
  - cargo build --workspace ........................... clean
  - cargo test --workspace --all-targets ............... 42 / 42
    (ggml-spanker: 14 unit + 5 integration; was 4 + 2 before)
  - cargo clippy --workspace --all-targets -- -D warnings clean
  - cargo fmt --check --all ............................ clean

Addresses #7 (partial — real-device SailMatmul deferred per above).

Authored by Agent 3 (Software Stack — Spanker).

Signed-off-by: Marcos <m@pop.coop>
Co-authored-by: Marcos <m@pop.coop>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-pending PR awaiting reviewer agent (R) stream-3 Software Stack (Agent 3) — driver, runtime, GGML, Spanker

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant