plumbob-async
Async FRL link training state machine for HDMI 2.1.
plumbob-async is the async companion to plumbob.
It runs plumbob's link training state machine — plumbob::lts::run, which performs no
I/O itself — over an async ScdcClient and an async HdmiPhy, .awaiting each
operation, so training yields to the executor wherever the I/O waits. There is one state
machine for sync and async training: the outcomes, errors and traces are exactly
plumbob's. All shared protocol and training types are re-exported from plumbob.
Usage
Implement ScdcClient against your async SCDC transport, one method per register
operation (the async counterpart of plumbob::ScdcClient; see its documentation for what
each method must guarantee):
use ;
Construct an FrlTrainer and train over the rates to try, highest first; the sink's
requests for a lower rate step down through the list:
use ;
let mut trainer = new;
let config = default;
let rates = ;
let trained = trainer.train.await?;
match trained.outcome
for warning in trained.iter_warnings
After Success, start video on the PHY the trainer holds —
trainer.phy_mut().set_frl_output(FrlOutput::Active).await, with FrlOutput and the
HdmiPhy trait imported from plumbob_async — or on the PHY into_parts returns. scdc, scdc_mut, phy and phy_mut give access to both in between.
The HdmiPhy bound on P is hdmi_hal_async::phy::HdmiPhy. The FrlConfig,
TrainingConfig, Trained, TrainingOutcome, TrainingWarning, TrainingError, and
all protocol types are re-exported from plumbob and are identical between the sync and
async APIs: train returns the outcome with the warnings the attempt produced, such as
a sink requesting an undefined pattern value.
Multi-threaded executors
The plain traits promise nothing about their futures, so code generic over ScdcClient
and HdmiPhy cannot spawn training on a multi-threaded executor. For that, implement the
Send twins, SendScdcClient and hdmi-hal-async's SendHdmiPhy (re-exported): the same
methods, returning futures that are Send, with errors that are Send. With both,
the futures of train, the other training methods and exit_to_tmds are Send in
generic code (train_with_events given a Send callback). Bound the code by the twins
alone, as below: adding + ScdcClient or + HdmiPhy, which the twins imply anyway, makes
the compiler use that bound instead of the blanket implementation, and the futures are no
longer known to be Send:
use ;
A twin implementation is also an implementation of the plain trait, so it works with
code written against either. Import the trait your code is bounded by: with both in
scope, method calls on a twin implementation are ambiguous. Code that keeps the types
concrete needs no twins: its train future is Send when the client's and PHY's futures
and error types are.
Training procedure
The procedure is plumbob's — LTS:2 (prepare), LTS:3 (train), LTS:P (pass), with LTS:4
(lower rate) on the sink's request and LTS:L (exit to TMDS) before every
FallbackRequired and after an I/O error — because it is plumbob's code: FrlTrainer runs plumbob::lts::run
over its async SCDC client and PHY. See
plumbob's README and
architecture document for the states, the poll and retrain limits, and the per-lane rules.
FallbackRequired { reason } means no listed rate trained; TrainingError means a hard
I/O failure from the SCDC client or PHY. The two are kept distinct so a caller diagnosing
a failure knows whether it came from the protocol or the bus.
trainer.exit_to_tmds().await runs LTS:L on its own, to take a trained link down when
the display is disabled or unplugged, or before a mode change.
Cancellation
train is not cancel-safe. Dropping its future — losing a select! against a
hot-unplug event, or hitting a timeout — stops training wherever it is, and a dropped
future cannot run LTS:L: the sink may be left configured for an FRL rate and the PHY
driving training patterns. After dropping one, return both ends to TMDS yourself:
select!
To bound how long training takes, prefer the poll limits in TrainingConfig: every
attempt that hits one ends in a defined state — with LTS:L already run, or, while the sink
sets FLT_no_timeout (compliance testing), with the link held for the test equipment
(TrainingOutcome::NoTimeoutHold).
For a wall-clock deadline, or to stop on a signal without dropping the future, fail one
polled ScdcClient read with an error of your own and let every later call through:
training stops, LTS:L returns both ends to TMDS, and train returns
TrainingError::Scdc with your error. Never fail a read made after LTS:L has begun
(after a write_frl_config with HdmiForumFrl::NotSupported), until a
write_frl_config with an FRL rate starts training again. LTS:L is skipped with
TrainingConfig::exit_to_tmds_on_error off, and the link is held while the sink sets
FLT_no_timeout; plumbob::ScdcClient's "Stopping early" has the details.
Diagnostics
Enable the alloc feature to get train_traced (and train_at_rate_traced), which
return a TrainingTrace alongside the result, whether that is an outcome or an error.
The trace and its TrainingEvents are plumbob's: a sync trace and an async trace of the
same sink are identical.
let = trainer.train_traced.await;
println!;
for event in &trace.events
Without an allocator, train_with_events(&rates, &config, &mut |event| …).await delivers
the same events as they occur, to a plain callback.
Features
| Feature | Default | Description |
|---|---|---|
std |
no | Implies alloc; no additional API surface |
alloc |
no | Enables TrainingTrace, train_traced and train_at_rate_traced |
No features are enabled by default.
no_std builds
plumbob-async declares #![no_std] throughout. The bare crate provides full async
training without an allocator. The alloc feature adds TrainingTrace, train_traced
and train_at_rate_traced; std implies alloc.
# no_std, no heap
# no_std + trace support
# std targets
No executor is assumed. The traits impose no constraint on which async runtime is used — Embassy, Tokio, and async-std are all valid implementors.
Stack position
flowchart LR
dt["display-types"]
hal["hdmi-hal"]
hal_async["hdmi-hal-async"]
plumbob["plumbob"]
plumbob_async["plumbob-async"]
dt --> hal --> hal_async --> plumbob_async
dt --> plumbob --> plumbob_async
plumbob-async depends on plumbob for the state machine and all shared data types,
and on hdmi-hal-async for the async HdmiPhy trait. It does not depend on any executor
or runtime crate.
Out of scope
- The training procedure — it is
plumbob's (plumbob::lts::run); this crate supplies async I/O to it. - Rate fallback policy — the caller decides which rates to pass to
trainand what to do with aFallbackRequired. - SCDC register decoding — plumbob-async reads typed values from
ScdcClientand does not decode raw register bytes. - PHY vendor sequences — plumbob-async calls
HdmiPhymethods; the register sequences live in platform PHY backends. - Timing and inter-poll delay — the poll limits in
TrainingConfigare the only timeout mechanism, and a wall-clock deadline is the caller's (see "Cancellation"); the interval belongs inside the polledScdcClientmethods, and awaiting it is what lets other tasks run: anScdcClientwhose futures are always ready keeps the executor for the wholetraincall (up to 7000 polls with the defaults; see plumbob's architecture document for the bound). - Executor dependency — no runtime crate is in the dependency graph. The plain
traits' futures are not required to be
Send; for a multi-threaded executor, implement theSendtwins (see "Multi-threaded executors").
Documentation
doc/architecture.md— role, scope, the async trait and trainer, and design principlesdoc/model.md— the re-exported types and the culvert-async boundarydoc/testing.md— what the async driver's tests cover
Verifying releases
Releases are built on GitHub Actions and attested with
SLSA Build Level 2 provenance (from 0.2.0). To verify a release
.crate against its signed provenance, install the
GitHub CLI and run:
The attested .crate is attached to each
GitHub release.
Releases from 0.2.0 are uploaded through crates.io trusted publishing: the publish workflow obtains a short-lived token via OpenID Connect instead of using a stored API token. crates.io can also reject uploads made with an API token for this crate; the badge above shows whether that setting is on. Each version's publishing details, including the workflow run and commit, are available from the crates.io API:
# see .version.trustpub_data
0.1.0 was uploaded by hand with an API token; it carries no provenance and has no GitHub release.
License
Licensed under the Mozilla Public License 2.0.