| name | stable-unstable |
| description | Manage the stable/unstable API gating in hisi-hal: add a new unstable surface, graduate one to stable (write HIL + remove the gate), or audit which items should be gated. Use when adding a driver/API, reviewing a PR that touches pub items, or checking gating compliance. |
Stable/Unstable API Gating Skill
This skill manages the #[instability::unstable] / unstable_module! gating in hisi-hal. The policy: an API is STABLE only if a named HIL test exercises it on real WS63 silicon; everything else is UNSTABLE (gated behind the unstable cargo feature).
When to use
- Adding a new driver or API → it starts UNSTABLE (no HIL yet); use the "Add unstable" workflow.
- An unstable API got a HIL test passing on silicon → graduate it to STABLE; use the "Graduate" workflow.
- Reviewing a PR that adds/changes
pub items → check gating compliance; use the "Audit" workflow.
- Checking which items are gated → run the scanner script.
Quick reference: the mechanism
| Surface | Gating form | When unstable OFF | When unstable ON |
|---|
pub fn / pub struct / pub enum (item-level) | #[instability::unstable] | pub(crate) + #[allow(dead_code)] (soft) | pub |
pub mod foo; (module-level) | unstable_module! { pub mod foo; } | pub(crate) mod + #[allow(unused)] (soft) | pub mod |
| Standalone driver (nothing stable depends on it) | unstable_driver! { pub mod foo; } | absent (hard delete) | pub mod |
Rules (critical)
- Inherent
impl blocks: UNGATED. Gate each pub fn inside individually. instability hard-deletes impl blocks when off → would make private helpers dead-code.
impl Drop: UNGATED (keeps private helpers live).
- Trait impls: MAY be whole-block gated (
#[instability::unstable] impl Trait for Foo).
- STABLE
pub fn taking an UNSTABLE type as param/return: FORBIDDEN (private_interfaces lint). If write_dma (STABLE) takes DmaChannel, then DmaChannel must also be STABLE.
async/embassy: feature-gates are consent, not automatic stability. embassy is ALSO unstable-gated (no end-to-end HIL). async alone exposes only the blocking-backed SPI/I2C async trait impls; interrupt/waker helpers (asynch::block_on, IrqSignal, GPIO wait, timer async delay, UART async I/O, DMA/LSADC async hooks) also require unstable until lost-wake/cancellation invariants and HIL are closed.
- Doc comments inside
unstable_module! { ... }: put the /// INSIDE the macro body (it forwards $(#[$meta])* to both cfg branches). A /// outside the macro is an orphaned "unused doc comment".
- HIL tests that reference UNSTABLE items: gate with
#[cfg(feature = "unstable")] (the external integration test crate can't see pub(crate)).
- In-module host tests (
#[cfg(test)] mod tests): do NOT gate — the soft-gate keeps items pub(crate) (in-crate visible to test modules).
prelude.rs re-exports of UNSTABLE modules: gate the pub use with #[cfg(feature = "unstable")].
- Raw PAC escape hatches:
register_block() / raw register-block access bypass typed-config, driver ownership, and module gating. Keep these #[instability::unstable] and unsafe unless a specific raw API has its own HIL + safety story.
Workflow 1: Add a new unstable surface
When adding a new driver/API that has no HIL test yet:
- Item-level (struct/fn/enum inside a file): add
#[instability::unstable] before the pub keyword. If the item is chip-ws63-only, stack #[cfg(feature = "chip-ws63")] + #[instability::unstable].
- Module-level (new
pub mod foo; in src/lib.rs): wrap in unstable_module! { /// Doc... pub mod foo; }. Put the doc INSIDE the macro.
- Check signature constraint: does any STABLE
pub fn now take/return this new UNSTABLE type? If yes → either make the type STABLE, or gate the method too.
- Check
prelude.rs: if you re-export from the new module, gate the pub use.
- Examples: if an example uses the new surface, add
unstable to its hal dep features.
- HIL tests: if you add a HIL test for it, gate the test
#[cfg(feature = "unstable")].
- Verify:
cargo clippy --no-default-features --features chip-ws63 --target x86_64-unknown-linux-gnu (unstable OFF — no private_interfaces/dead_code warnings) + cargo clippy --features chip-ws63,unstable (ON).
Workflow 2: Graduate (unstable → stable)
When an unstable API gets a HIL test passing on real WS63 silicon:
- Write the HIL test with the current embedded-test layout: put the helper in
crates/hisi-hal/tests/hil/<driver>.rs, then register a tiny wrapper in
crates/hisi-hal/tests/hil.rs. Gate it #[cfg(feature = "unstable")]
for now if it exercises unstable public API.
- Run it on silicon through
hil/embedded-test-runner.sh with the needed
features; see docs/src/how-to/07-run-hil-tests.md. It must PASS.
- Remove the gate: delete
#[instability::unstable] from the item, OR move the module out of unstable_module! to a plain pub mod foo;.
- Remove
#[cfg(feature = "unstable")] from the HIL test (it's now stable — should run in the default suite).
- Update docs: update
docs/src/reference/10-stable-api.md in the same
change; optionally add #[instability::stable(since = "0.x.0")] to keep a
"Stabilized in version X" note.
- Update examples: if the example had
unstable just for this surface, check if it still needs it (other surfaces may still be unstable).
- Verify:
cargo clippy (unstable OFF — the now-stable item must not produce
dead_code since it's pub) plus the embedded-test HIL run. Once the gate is
removed, the graduated test should run without unstable unless it also needs
a separate opt-in such as hil-loopback.
Workflow 3: Audit gating compliance
To check whether an item should be STABLE or UNSTABLE:
- Find the item in
src/*.rs (grep for the struct/fn/enum/mod name).
- Check for a HIL test: inspect
tests/hil.rs and the corresponding
tests/hil/<driver>.rs helper for a test that calls/constructs this item. Does
it run on WS63 silicon? (self-contained tests run by default; hil-loopback
tests need jumpers; hil-rtc is opt-in and may not have run on this board.)
- Verdict: HIL test exists + runs on connected silicon → STABLE. No HIL test (or opt-in never ran) → UNSTABLE.
- Check the current gate: is the item
#[instability::unstable] or in unstable_module!? If STABLE but gated → it's a graduation candidate. If UNSTABLE but not gated → it's a gap (gate it).
Scanner script
Run this to list all currently-gated items + check for ungapped unstable surfaces:
grep -rn '#\[instability::unstable\]' crates/hisi-hal/src/
grep -rn 'unstable_module!\|unstable_driver!' crates/hisi-hal/src/lib.rs
grep -n 'feature = "unstable"' crates/hisi-hal/tests/hil.rs
What's currently STABLE vs UNSTABLE
See docs/src/reference/10-stable-api.md for the current stable/unstable split,
and docs/src/explanation/policies/02-stable-unstable.md for the
policy/mechanism. The split is audited against tests/hil.rs plus the helper files
under tests/hil/: the rule is "HIL-proven on WS63 silicon = STABLE; everything
else = UNSTABLE".