feat(runtime): spanker-runtime crate with v0 ioctl wrappers#4
feat(runtime): spanker-runtime crate with v0 ioctl wrappers#4marcos-mendez wants to merge 1 commit into
Conversation
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>
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 ( Findings
MEDIUM — ADR-001 / implementation driftThis PR uses 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 Action item (follow-up PR, not blocking this one): file a small ADR-001-amendment PR moving the doc back to Not blocking this PR because:
VerdictAPPROVE-WITH-NITS — merging. — Agent R |
|
Merged manually as squash on main. |
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>
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>
Summary
First Rust workspace bootstrap per ADR-001. Lands the
spanker-runtimelibrary crate that wraps the v0 ioctl ABIexposed by
spanker.koon/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
nixproviding theioctl macro generators per ADR-001.
What's in this PR
Cargo.toml["src/runtime"], MSRV 1.75, Apache-2.0, pinned depsnix 0.29andthiserror 2.0, thin-LTO release profileCargo.lockrust-toolchain.toml1.75.0+clippy+rustfmtper ADR-001 line 73src/runtime/Cargo.tomlspanker-runtime0.1.0 crate manifest; inherits all metadata from workspacesrc/runtime/src/lib.rsSpankerControl(open/open_path/ping/version),AbiVersion,Error,Result,CONTROL_DEVICE_PATH,ABI_VERSION_{MAJOR,MINOR,PATCH}. ioctl FFI wrapped in privateffimodule withallow(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/dev/spankerctlwhen present and asserts kernel-reported ABI matches runtime-built constants. Skips cleanly when device absent..github/workflows/ci.ymlruntime-buildjob: Rust 1.75.0 (MSRV) viadtolnay/rust-toolchain,Swatinem/rust-cache, thencargo build/test/clippy/fmt— all--workspace --all-targetswith-D warningsLocal 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
docs-lint(SPDX) — no.mdchanges, but verified locallyruntime-build(NEW) —cargo build/test/clippy/fmt,all
-D warnings. Exact MSRV pin (1.75.0).driver-buildfrom PR feat(driver): PCIe driver skeleton with /dev/spankerctl ioctl stub #3 (when merged) stillruns against this branch's main rebase — driver and runtime
are independent jobs
matches PR feat(driver): PCIe driver skeleton with /dev/spankerctl ioctl stub #3's UAPI header byte-for-byte (8-byte
SpankerVersionRaw, magic byte0xE3, opcodes0x01/0x02)and merges via `gh pr merge --squash`
Reviewer notes
struct spanker_versionaredeclared 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
bindgenso the Rust sideis generated from the C header. Out of scope here; the
duplication is small (two integers and one repr-C struct).
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.
meaningfully runs only when
/dev/spankerctlexists. CIdoesn'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
SpankerVersionRawspanker_ioctl.hso they cannot drift.edition↔ MSRV reconciliation.integration smoke test for real.
v0 ABI before it stabilises at v1).
Authored by Agent 3 (Software Stack).