Audience: Kernel hackers adding or maintaining Rust subsystem crates.
Execution context: Kernel build tooling and kernel-linked Rust crates.
Maturity: Stable foundation; twenty-six production Rust subsystems live in the kernel tree.
Production: DuetFS, USB HID, USB class config, DHCP / DNS / TCP-options / IPv4-header byte-walkers, USB MSC SCSI responses, PNG / BMP / TGA / JPEG header validators, ELF / PE-image validators, NTFS metadata walker, exFAT metadata walker, ext4 metadata walker, ACPI table walker, ACPI AML namespace walker, IEEE 802.11 management-frame walker, Bluetooth HCI walker, SMBIOS table walker, PCI / PCIe capability list walkers, Multiboot2 info-structure walker, TLS 1.2 record + handshake walker, VT/ANSI escape parser, NVIDIA GSP firmware-image (nvfw_bin_hdr) parser, AMD GFX9+ microcode-image (gfx_firmware_header_v1_0) parser, Intel iwlwifi TLV firmware parser, Realtek rtlwifi/rtw88/rtw89 firmware-header parser, and Broadcom b43 firmware-record-stream parser.
All twenty-six crates have a current C++ caller; there are no skeleton crates left in this slice.
Rust in DuetOS is a kernel subsystem tool, not a second application framework. A Rust crate is appropriate when the subsystem owns a clear boundary with attacker-controlled structured bytes or lifetime-heavy state, such as DuetFS, USB class descriptors, or the TCP/IP stack. C++ remains the orchestration language for the kernel image.
"Attacker-controlled bytes" means bytes that cross a trust boundary before the kernel has parsed them: USB descriptors returned by a plugged-in device, network packets from the wire, filesystem metadata from removable media, and PE/ELF metadata from an executable image. The attacker is not assumed to have code execution yet; the risk is that a malicious device or image can choose lengths, offsets, counts, nesting depth, and tag ordering that stress every parser edge case.
Rust is not magic sandboxing and it does not make an unsafe FFI boundary safe by
itself. It is useful here because the byte walkers can be written as
bounds-checked slice traversal with checked/saturating arithmetic, no ambient
aliasing, and no unchecked pointer increments in the parser core. In C++ the
same parser can be made correct, but every ptr + len, cast, packed-struct
view, and manual lifetime convention must stay correct forever; one missed
bounds check can become a kernel read/write primitive. DuetOS therefore keeps
C++ as the owning/orchestration layer and uses Rust selectively for narrow
parsers where memory-safety bugs are the main risk.
The first Rust slice covers DuetFS plus USB descriptor parsers, but it does not exhaust the high-risk surface. Prioritize future Rust crates where the subsystem is mostly byte parsing or state-machine validation and can expose a small C ABI back to the C++ owner.
| Priority | Candidate | Why it is high risk | Rust boundary shape |
|---|---|---|---|
| P0 | Network packet parsers (Ethernet / ARP / IPv4 / ICMP / UDP / TCP options, DHCP / DNS where applicable) | Remote peers control packet lengths, header offsets, fragmentation state, option lists, and checksummed payload shape. A parser bug is remotely reachable before auth. | Parse borrowed RX buffers into validated header/value structs; C++ keeps NIC rings, routing, timers, and socket ownership. |
| P0 | PE/COFF and ELF metadata readers | Executable images control section tables, data-directory RVAs, imports, relocations, TLS records, resources, and symbol/export tables. This sits directly on the project pillar of running PE binaries. | Rust validates image metadata and returns a relocation/import/load plan; C++ owns address-space mapping, capability checks, and process creation. |
| P0 | Read-only disk-format parsers not already in Rust (NTFS / exFAT / FAT32 / ext4 metadata walkers) | Removable or dual-boot disks control superblocks, directory entries, extents, runlists, timestamps, and string encodings. The kernel must reject malformed metadata without trusting lengths or offsets. | Rust turns blocks into validated directory / inode / extent records; C++ VFS owns handles, caching policy, block devices, and permissions. |
| P1 | Font and image decoders used by UI or boot assets (TTF, PNG, BMP/TGA where non-trivial) | Asset files can be supplied by themes, apps, or downloaded content and tend to contain nested tables, compressed streams, and attacker-chosen dimensions. | Rust decodes/validates metadata and bounded spans; C++ renderer owns surfaces, glyph cache, and GPU upload. |
| P1 | Protocol control-plane parsers (USB RNDIS/CDC control messages, Bluetooth HCI events, Wi-Fi management frames/EAPOL) | Devices or nearby radios control variable-length protocol records and state transitions. Bugs can be reached from hardware, radio, or network-adjacent inputs. | Rust parses envelopes and validates state-machine messages; C++ owns driver rings, DMA, IRQs, and kernel object lifetimes. |
| P2 | ACPI/SMBIOS/PCI capability-table readers | Firmware controls table lengths, offsets, checksums, and nested structures before the kernel has a normal trust base. Bugs are usually local/firmware-level, but they run very early. | Rust validates table walks into plain records; C++ owns boot sequencing, MMIO mapping, and architecture effects. |
Do not move code to Rust just because it is important. Scheduler paths, address-space mutation, IRQ dispatch, DMA ring programming, and GPU command submission are high-consequence but not automatically good Rust candidates: they are dominated by hardware side effects, lock ordering, and existing C++ kernel ownership. Rust is the best fit when the risky part can be isolated as "bytes/state in, validated plan out."
The repository now has one shared Rust foundation and actual Rust subsystem code:
/rust-toolchain.tomlpins the nightly toolchain and bare-metal target./Cargo.tomlis the workspace root and owns the profiles for every Rust crate linked into the kernel./Cargo.lockis tracked so dependency resolution is reproducible; CMake invokes cargo with--locked. This freezes package selection but is not an offline or hermetic dependency source: a clean Cargo home may still fetch the registry index and crates. Use a previously populated cache for offline builds; a separately reviewed vendored-source policy is still required before the Rust build can be called network-independent./.cargo/config.tomlselectsx86_64-unknown-noneand thebuild-stdknobs needed by freestanding crates./kernel/rust/is the single Rust staticlib link unit. Subsystem crates are rlibs; the aggregate staticlib pulls incore/alloc/ panic runtime once so the C++ kernel link does not get duplicate Rust runtime objects./kernel/fs/duetfs/is the native filesystem Rust subsystem./kernel/drivers/usb/hid_rust/is the USB HID report-descriptor parser Rust subsystem; C++ HID APIs are wrappers over this parser./kernel/drivers/usb/class_rust/parses USB configuration/interface/endpoint descriptor streams for MSC, hub, UVC, and Bluetooth class-driver binding./kernel/net/parsers_rust/(duetos_net_parsers) wraps the DHCPv4 option walker and the DNSv1 name skipper. C++ callers inkernel/net/stack.cppdelegateDhcpFindOptionandDnsSkipNamethrough this crate./kernel/drivers/usb/msc_scsi_rust/(duetos_usb_msc_scsi) parses USB MSC SCSI INQUIRY / READ CAPACITY(10) / GET CONFIGURATION header / READ TOC header / READ DISC INFORMATION responses. The C++ MSC driver (kernel/drivers/usb/msc_scsi.cpp) delegates its parse functions through this crate./kernel/util/img_meta_rust/(duetos_img_meta) validates PNG, BMP, TGA, and JPEG / JFIF / EXIF image headers.kernel/util/png.cpp::PngParseHeader,kernel/util/bmp.cpp::BmpParseHeader,kernel/util/tga.cpp::TgaParseHeader, andkernel/util/jpeg.cpp::JpegParseHeaderall delegate to this crate; the C++ side keeps zlib inflate, scanline filter unwind, and pixel-copy. The JPEG validator walks segments from the SOI marker until the first Start-of-Frame (SOF) and extracts dimensions + precision + component count without entering the entropy-coded scan data. No decoder lives in the tree yet; the validator is the toolkit a future viewer / thumbnail cache / wallpaper-extension consumer will sit on./kernel/loader/exec_meta_rust/(duetos_exec_meta) validates ELF64 files (header + every PT_LOAD segment) and PE/COFF images (DOS stub + e_lfanew bounds + PE signature + AMD64 machine check + optional-header magic / section / file alignment + image-base low-half bound + section-table bounds + per-section raw extent fit).kernel/loader/elf_loader.cpp::ElfValidateand the body ofkernel/loader/pe_loader.cpp::ParseHeaders(up to but not including the data-directory walks) delegate to this crate; the C++ side keeps data-directory checks, address-space mapping, capability checks, and process creation./kernel/fs/ntfs_rust/(duetos_ntfs) parses NTFS boot sectors, MFT record headers, resident$FILE_NAMEattributes, and runlist (mapping-pairs) entries.kernel/fs/ntfs.cppdelegates byte parsing to this crate; UTF-16 → ASCII translation stays in C++./kernel/fs/exfat_rust/(duetos_exfat) parses exFAT VBRs, derives cluster geometry, walks the FAT chain (4-byte LE per cluster), and decodes dirent sets (File 0x85 + Stream-Extension 0xC0 + FileName 0xC1 tuples).kernel/fs/exfat.cppdelegates byte parsing to this crate./kernel/fs/ext4_rust/(duetos_ext4) parses ext2/3/4 superblocks, group descriptors, inode records, extent headers, extent leaves / index nodes, and linux_dirent records.kernel/fs/ext4.cppdelegates byte parsing to this crate; block I/O, scratch management, and the depth>0 extent-tree DFS stay in C++./kernel/acpi/acpi_rust/(duetos_acpi) parses RSDP v1 / v2, ACPI table headers, MADT entry headers, FADT body fields, MCFG entries, HPET descriptors, and SRAT memory-affinity entries.kernel/acpi/acpi.cpp::AcpiInitdelegates the RSDP signature + checksum validation to the crate;ParseFadtcross-validates its packed-struct overlay against the Rust decoder./kernel/acpi/aml_rust/(duetos_aml) is the recursive AML TermList walker over the DSDT / SSDT bytecode: PkgLength / NameString decode, Scope / Device / Method / Name / OperationRegion / Mutex / Event / Alias / External / Processor / ThermalZone / PowerResource records, and the constant-bound NamedField index.kernel/acpi/aml.cpp::WalkTabledelegates one table's byte parse to the crate'sduetos_aml_walk_table, which appends named-object records straight into the kernel's namespace / region / field tables (layout-asserted FFI mirrors). C++ keeps the table storage, accessors, and the offset slicers (AmlMethodBody/AmlNameValue/AmlReadS5); the AML evaluator (aml_eval.cpp, hardware FieldUnit I/O) stays C++. Continuously fuzzed bytests/fuzz/fuzz_aml./kernel/net/wifi80211_rust/(duetos_wifi80211) parses 802.11 frame headers, Beacon / Probe Response body prefixes, the IE (Information Element) list, and EAPOL-Key (4-way handshake) descriptors.kernel/net/wireless/beacon.cpp::BeaconParsedelegates the frame header, body, and IE walks to the crate./kernel/net/hci_rust/(duetos_hci) parses Bluetooth HCI event packets and the Command Complete, Command Status, Disconnection Complete, LE Meta, Read_Local_Version, and Read_BD_ADDR bodies.kernel/net/bluetooth/hci.cppdelegates the Read_Local_Version + Read_BD_ADDR rparam decoders to the crate./kernel/drivers/net/iwlwifi_fw_rust/(duetos_iwlwifi_fw) walks the Intel iwlwifi TLV firmware blob: 88-byte preamble (zero + magic + 64-byte name + ver + build + 8 ignored) + stream of(u32 type, u32 length, payload[length], pad-to-4)TLV records. Recognises INST / DATA / INIT / INIT_DATA / SEC_RT / FLAGS / NUM_OF_CPU / FW_VERSION / PHY_SKU / HW_TYPE; unknown TLVs bump a counter without failing the parse. Everyoff + 8 + lengtharithmetic uses checked_add so a hostile TLV length can't wrap into a smaller "fits the blob" value.kernel/drivers/net/iwlwifi_fw.cppdelegates byte parsing to this crate; C++ side keeps the sanitize-for-serial-print pass on the human-readable name./kernel/drivers/net/rtl88xx_fw_rust/(duetos_rtl88xx_fw) walks the Realtek rtlwifi/rtw88/rtw89 32-byte fixed firmware header. Classifies the generation by signature (rtl8192/8723/8821/8812/8814 → rtlwifi, 0x88B0 → rtw88, 0x8852 → rtw89). Rejects unknown signatures + short blobs.kernel/drivers/net/rtl88xx_fw.cppdelegates./kernel/drivers/net/bcm43xx_fw_rust/(duetos_bcm43xx_fw) walks the Broadcom b43 8-byte-big-endian record stream (type / version / reserved / be32 size / payload). Up to 8 records per blob (configurable cap); convenience indices for the first ucode / pcm / iv record. Truncated-on-overflow semantics — earlier-records still report cleanly when a later record's declared size overflows the blob. The C++ wrapper converts the Rust-side indices back to in-place convenience pointers in the caller-owned struct./kernel/drivers/gpu/nvidia_gsp_fw_rust/(duetos_nvidia_gsp_fw) parses NVIDIA Turing+ GSP firmware containers (gsp_tu10x.bin/ga10x.bin/ad10x.bin). The 24-byte outernvfw_bin_hdr+ per-arch inner descriptor (76 bytes Turing/GA100, 84 bytes GA102+) + ELF64 RISC-V payload are all attacker-controllable when the install media is hostile. Checked arithmetic ondata_offset + data_size; rejects bad magic, bad version, descriptor-too-small, data-bounds, and oversize images.kernel/drivers/gpu/nvidia_gsp_fw.cppdelegates the byte parse to this crate; the C++ side keeps the public NvidiaGspFwParse API + boot self-test./kernel/drivers/gpu/amd_gfx_fw_rust/(duetos_amd_gfx_fw) parses AMD GFX9+ microcode images (linux-firmwareblobs). 32-bytecommon_firmware_header+ optional 12-bytegfx_firmware_header_v1_0tail (feature version + jump-table offset/size) + ucode payload. Validates header_size_bytes vs blob, ucode_array_offset+size bound, ucode multiple-of-4, and jump-table fits inside the payload — every check via checked arithmetic so a hostile peer can't drive a length field into wrap-around.kernel/drivers/gpu/amd_gfx_fw.cppdelegates to this crate./kernel/util/vt_parser_rust/(duetos_vt) implements the DEC ANSI / xterm escape parser. State machine + UTF-8 decoder + CSI parameter accumulator + OSC string buffer over a&mut DuetosVtParser(the C++ side allocates the struct; Rust operates on it). Four callbacks (print/execute/csi/osc) cross the FFI wall via repr(C) function pointers; theextern "C"invocations are otherwise plain Rust calls so the parser core staysunsafe-free outside the three init/reset/feed entry points. Untrusted PTY bytes from user processes feedkernel/util/vt_parser.cpp, which delegates every operation to this crate. The compile-time static_assert onsizeof(Parser) == sizeof(DuetosVtParser)+sizeof(Callbacks) == sizeof(DuetosVtCallbacks)pins the binary equivalence so a future drift on either side can't silently desync./kernel/net/tls_rust/(duetos_tls) parses TLS 1.2 record- handshake byte streams: the 5-byte record header, the
4-byte handshake header, the ServerHello body (version +
random + session-id + cipher + compression + optional
extensions), the Certificate-message body (3-byte total
list + per-cert length prefix + leaf DER slice), and the
zero-byte ServerHelloDone. Remote peer controls every
length prefix; the Rust core uses checked arithmetic to
reject
u32length overflows that would otherwise wrap under attacker control.kernel/net/tls.cppdelegates the fiveTlsPeek*/TlsParse*entry points to this crate; the C++ side keeps AES-GCM record crypto, RSA pre-master encryption, the PRF, the transcript hash, and the connection lifecycle.
- handshake byte streams: the 5-byte record header, the
4-byte handshake header, the ServerHello body (version +
random + session-id + cipher + compression + optional
extensions), the Certificate-message body (3-byte total
list + per-cert length prefix + leaf DER slice), and the
zero-byte ServerHelloDone. Remote peer controls every
length prefix; the Rust core uses checked arithmetic to
reject
/kernel/arch/x86_64/smbios_rust/(duetos_smbios) decodes the 2.x (_SM_+_DMI_) and 3.x (_SM3_) entry-point anchors (signature + length + 8-bit checksum), then walks the variable-length structure table — each call returns the bounded(formatted_offset, strings_offset, end_offset)triple a C++ caller needs to advance to the next record. The trailing-strings walker enforces a 1 KiB per-string cap so a firmware that omits a NUL terminator can't make the walker run past the structure-table slice.kernel/arch/x86_64/smbios.cppkeeps the legacy-BIOS scan window (PhysToVirt(0xF0000)+ 16-byte stride), single-init guarding, the BIOS / system / chassis / processor field extraction, and the boot-log line./kernel/drivers/pci/caps_rust/(duetos_pci_caps) walks both the standard capability list (8-bit "next" pointers, head at config-space offset 0x34) and the PCIe extended capability list (12-bit "next" pointers, head at ECAM offset 0x100). Each chain hop is bounded; self-loops, out-of-range pointers, unaligned next-offsets, and the all-zero "no ext caps" sentinel are clamped to end-of-list.kernel/drivers/pci/pci.cppmaterialises the device's standard config into a 256-byte buffer and routesPciFindCapabilitythrough the crate. The newPciFindExtCapabilityentry point is ready for the MMCONFIG-routed read primitive that a future PCIe driver needing AER / SR-IOV / ATS will add./kernel/mm/multiboot2_rust/(duetos_multiboot2) validates the Multiboot2 info-structure header and walks the tag list + the mmap entry array. The bootloader-controlledtotal_sizeis capped at 64 MiB; each tag'ssizefield is validated to fit in the remaining slice; mmap-entry base+length overflow is rejected.kernel/mm/frame_allocator.cpp::ForEachMmapEntrydelegates every cursor advance to the crate./cmake/DuetOSRust.cmakeexposesduetos_add_rust_staticlib(...), used by/kernel/rust/CMakeLists.txtto build the aggregate Rust link unit. Its input list is derived from the root workspace bytools/test/check-rust-ffi.py; a missing aggregate dependency or non-member dependency stops configuration.
The workspace pins one [workspace.lints] block in /Cargo.toml; every
member crate inherits via [lints] workspace = true. The deny-set is
intentionally small (unsafe_op_in_unsafe_fn, unused_must_use,
non_ascii_idents, clippy::todo, clippy::unimplemented,
clippy::dbg_macro); undocumented_unsafe_blocks is documented as an
aspirational lint pending a SAFETY-comment backfill on the v0 crates.
Style follows idiomatic Rust (K&R braces, default control flow); the
C++ Allman convention does not bleed in. The pin lives in
/rustfmt.toml; the local CI preflight (tools/dev/check-local.sh)
runs cargo fmt --check, cargo clippy -- -D warnings, and a host
unit-test smoke (tools/dev/cargo-host-test.sh) against every crate
that ships #[cfg(test)] modules.
python tools/test/check-rust-ffi.py is the separate build-truth and FFI
boundary audit. It inventories every explicit workspace member and checks four
contracts:
- The aggregate crate has one local dependency and one
pub usefor every other workspace member, with no extras. - Rust sources, hand-written headers, manifests, literal data includes,
the canonical repository-root Cargo config, the lockfile, and the pinned toolchain are all
visible to the CMake archive dependency graph. Kernel crates use the
conventional in-member source layout: custom target paths,
build.rs, sourceinclude!, manifest patch/replace tables, and unlisted local path dependencies fail closed. Cargo config is exact-schema validated sopaths, source replacement, compiler/wrapper overrides, and nested configs cannot redirect the audited graph. The root toolchain file is likewise validated against the exact dated channel, component, target, and profile; no other rustup override is permitted in the tree. - Every raw-pointer export is an
unsafe extern fnusing the single supportedCABI; a safe export is accepted only by exact name in the checker's scalar-only allowlist, and a signature change away from C scalars invalidates that entry. Exported function usingC-unwind,system, or any other extern ABI is a hard finding (FFI014). The canonical parity gate enforces the same C-only rule. - Rust exports and each crate's hand-written C declarations agree by symbol,
calling convention, return type, arity, scalar width, pointer depth, and
pointee constness. The canonical parity gate is
tools/test/check-rust-ffi-signatures.py; any parser error or mismatch is a fail-closedFFI013build-truth error.FFI003also applies a conservative lexical check for direct helper signatures that return an unconstrained generic or'staticborrow from a raw pointer.
The normal audit exits nonzero for either build errors or FFI findings. CMake
uses the path-only emit mode: it fails closed on workspace/build-graph errors
and on canonical signature-parity failures, while other FFI findings remain an
explicit hardening backlog rather than being silently grandfathered into the
safe-export allowlist. Inventory is a single streaming traversal per member,
prunes member-root .git, target, and __pycache__ output only (nested
Rust modules with those identifiers remain audited), rejects symlinks and
name-surrogate reparse boundaries, permits contained nonredirecting OneDrive
cloud-filter tags, and enforces global entry, byte, record, diagnostic, and
emitted-output caps before retaining more data. Emitted paths reject CMake-list
and line-protocol delimiters. Existing inputs and every in-repository Cargo
config/toolchain candidate from kernel/rust through the repository root are
configure dependencies, so content edits and newly introduced overrides rerun
the audit before Cargo. An always-run build target rechecks the graph before
every requested Rust/kernel build. CMake consumes the bounded exact input list
rather than recursively globbing member trees, and Cargo is restricted to
--lib, so a newly compiled module requires a tracked source or manifest
change. The parity checker itself is returned as a CMake build input.
Cargo/rustup configuration above the repository root is not a trusted project
input. CMake runs Cargo from a controlled OS-temporary working directory outside
the repository, recreates its empty Cargo home, and makes the always-run audit
reject any Cargo config on either effective ancestor search path. It also pins
RUSTUP_TOOLCHAIN and clears compiler/wrapper/flag override variables. Release
jobs explicitly provision that exact dated toolchain with rust-src and the
bare-metal target before building. The source audit guarantees the complete
in-repository portion of the configuration search path even though Cargo
receives the validated target/build-std policy through explicit command flags.
FFI003 is a bounded source-signature heuristic, not a Rust borrow/lifetime
proof. It deliberately catches the current unconstrained helper pattern and may
need extension for macro-generated or type-aliased signatures; every FFI wall
still requires independent unsafe-code review.
Workspace .cargo/config.toml forces target = x86_64-unknown-none +
unstable.build-std, which makes cargo test unusable directly (the
test harness needs std). tools/dev/cargo-host-test.sh works around
this by calling rustc --test directly against each crate's
src/lib.rs, building a hosted binary with the system libcore +
libstd. New crates that ship #[cfg(test)] modules add themselves to
the HOST_TEST_CRATES list at the top of the script.
- Add the crate directory to the root
[workspace].memberslist. - Keep the crate standalone: C++ may call Rust through a narrow C FFI, but do not create C++ → Rust → C++ → Rust chains.
- Expose a hand-written C header in the crate's
include/directory. Bindgen and cbindgen are intentionally not part of the kernel build. - Keep
unsafeat the FFI wall. Convert raw C pointers into Rust references or slices once, validate null/empty cases first, then keep the parser core in safe Rust. Any internalunsafeblock needs a one-line comment naming the kernel invariant that makes it sound. - Add the crate as a dependency of
/kernel/rust/Cargo.toml. Do not link subsystem crates as independent staticlibs; multiplebuild-stdstaticlibs duplicatecore/allocsymbols. - Add the hand-written header path to
kernel/CMakeLists.txtif C++ code needs to include it directly, then expose C++ wrappers through the owning subsystem directory. Do not add a second source/header list to/kernel/rust/CMakeLists.txt; CMake derives those inputs from the workspace.
Only /kernel/rust/CMakeLists.txt calls duetos_add_rust_staticlib(...). It
builds the aggregate duetos_kernel_rust staticlib. Before defining the custom
command, duetos_collect_rust_workspace_depends(...) validates the explicit
workspace/aggregate relationship and derives all current member source, header,
manifest, and build inputs. CONFIGURE_DEPENDS globs are rooted only at those
derived member directories so adding or removing a matching input regenerates
the dependency list without a hand-maintained crate table. A separate FFI
validation stamp depends on every derived input and runs the normal audit before
Cargo, so editing an existing Rust export, header declaration, or checker cannot
reuse an earlier parity result. kernel/CMakeLists.txt links the resulting one
.a into both kernel ELF stages and includes each subsystem's hand-written C
header directory for C++ wrappers.
The Cargo custom command owns a completion stamp and declares the archive as a byproduct. Header- or checker-only edits first refresh the validation stamp; Cargo may then legitimately reuse an unchanged archive, while touching its own completion stamp records the successful dependency edge without rewriting the archive or leaving Ninja permanently dirty.
Profiles live only at the workspace root so every kernel-linked crate has the
same panic, LTO, optimization, and overflow-check behavior. Crate-local profile
sections are ignored by cargo once a workspace root exists, so do not add them
back to member crates. The panic handler lives in /kernel/rust/src/panic.rs;
subsystem rlibs must not define their own #[panic_handler].
CMake accepts only DUETOS_RUST_PROFILE=release (Cargo output directory
release) or DUETOS_RUST_PROFILE=dev (Cargo's special output directory
debug). Any other cache value fails configuration instead of naming an output
archive Cargo may never create.