Part of Ashlar — a display connection stack in Rust, HDMI 2.1 first: from EDID parsing through mode negotiation to FRL link training.
display-types · piaf · concordance · cartouche · culvert · culvert-async · plumbob · hdmi-hal · hdmi-hal-i2c-dev
Async typed access to the HDMI 2.1 SCDC register map.
culvert-async is the async companion to culvert: culvert's Scdc client with
async fn methods over an hdmi_hal_async::scdc::ScdcTransport. The register map is
culvert's — every address, bit and protocol check comes from culvert::codec, and
culvert-async only performs the reads and writes — so the sync and async clients always
agree. All register types and error types are re-exported from culvert so callers do not
need both crates.
cargo add culvert-asyncWrap your async transport in Scdc and await typed methods:
use culvert_async::{ClearableUpdateFlags, FfeLevels, FrlConfig, FrlRate, Scdc, TmdsConfig};
let mut scdc = Scdc::new(transport);
// Version negotiation
let sink_ver = scdc.read_sink_version().await?;
scdc.write_source_version(1).await?;
// Enable TMDS scrambling
scdc.write_tmds_config(TmdsConfig {
scrambling_enable: true,
high_tmds_clock_ratio: true,
}).await?;
// Wait for the sink to be ready, then request an FRL rate
let flags = scdc.read_status_flags().await?;
if flags.flt_ready {
scdc.write_frl_config(FrlConfig::new(
FrlRate::Rate12Gbps4Lanes,
FfeLevels::new(3).unwrap(),
)).await?;
}
// On FLT_Update, read the pattern each lane requests, then acknowledge the flag
let updates = scdc.read_update_flags().await?;
if updates.flt_update {
let requests = scdc.read_ltp_requests().await?;
scdc.clear_update_flags(ClearableUpdateFlags::new(false, false, false, false, true, false)).await?;
if requests.all_trained() {
// training passed; wait for FRL_Start in the update flags
}
}
// Read per-lane character error counts, the CED block in one read, checksum checked
let reading = scdc.read_ced(FrlRate::Rate12Gbps4Lanes.lanes()).await?;
for warning in reading.iter_warnings() {
// e.g. a checksum mismatch: the counts may be torn; read again or distrust them
}
if let Some(count) = reading.counters.lane0 {
// lane 0 has a valid error count
}To stop early, check a deadline before each call rather than dropping a future in flight;
Scdc's documentation says what a dropped call may have done, method by method.
To use culvert-async as the SCDC backend for plumbob-async's link training state
machine, enable the plumbob-async feature:
cargo add culvert-async --features plumbob-async
cargo add plumbob-asyncScdc<T> then implements plumbob_async::ScdcClient automatically.
Training through Scdc<T> can run on a multi-threaded executor when the transport is
Send and Sync and its futures are Send. Code generic over the transport and the PHY
proves that with the Send twins: bound the transport by SendScdcTransport
(re-exported here) and the PHY by SendHdmiPhy (re-exported by plumbob-async), and by the
twins alone. Code bounded by plumbob-async's SendScdcClient needs a small wrapper; see
doc/architecture.md ("Send futures"), which also shows where the
poll interval and a deadline belong (not in the transport).
| Register group | Addresses | Methods |
|---|---|---|
| Version | 0x01–0x02 | read_sink_version, write_source_version |
| Update flags | 0x10 | read_update_flags, clear_update_flags |
| TMDS / scrambling | 0x20–0x21 | write_tmds_config, read_scrambler_status |
| FRL config | 0x30–0x31 | write_config_0, write_frl_config |
| Source test configuration | 0x35 | read_source_test_config |
| Status flags | 0x40 | read_status_flags |
| LTP requests | 0x41–0x42 | read_ltp_requests |
| Character Error Detection | 0x50–0x58 (checksum at 0x56) | read_ced |
| RS correction count | 0x59–0x5A | read_rs_correction |
The register map is culvert's (culvert::codec); its sources are described in culvert's
architecture document, and registers not yet covered in culvert's roadmap.
| Feature | Default | Description |
|---|---|---|
plumbob-async |
no | Implements plumbob_async::ScdcClient for Scdc<T> |
culvert-async is #![no_std] throughout. All output types are stack-allocated; no
allocator is required in any configuration.
flowchart LR
dt["display-types"]
hal["hdmi-hal / hdmi-hal-async"]
culvert["culvert"]
culvert_async["culvert-async"]
plumbob_async["plumbob-async"]
integration["integration layer (planned)"]
dt --> culvert
hal --> culvert
hal --> culvert_async
culvert --> culvert_async
culvert_async -->|"implements ScdcClient"| plumbob_async
plumbob_async -.->|"will implement LinkTrainer"| integration
plumbob-async does not depend on culvert-async; culvert-async depends on plumbob-async
only with the optional plumbob-async feature, which makes Scdc<T> implement
plumbob_async::ScdcClient. Any crate that implements ScdcClient is substitutable.
- Sync API — callers on sync transports use
culvertdirectly. - Link training state machine — the sequencing of FRL training belongs in
plumbob-async.culvert-asyncprovides the register operations; the state machine decides when to call them. - PHY configuration —
HdmiPhyoperations are the link training layer's concern. - I²C / DDC transport — platform backends implement
hdmi_hal_async::scdc::ScdcTransport.culvert-asyncnever touches I²C directly.
doc/setup.md— build, test, coverage, and example commandsdoc/architecture.md— role, scope, the client, theplumbob-asyncfeature, and design principlesdoc/testing.md— testing strategy, transport harness, and CI expectationsdoc/roadmap.md— deferred register groups and planned API surfaces
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:
gh attestation verify culvert-async-X.Y.Z.crate --repo ashlar-display/culvert-asyncThe release from before the repository moved to the ashlar-display organization (0.2.0)
was attested under its previous owner, and verifies against it:
gh attestation verify culvert-async-0.2.0.crate --owner DracoWhitefire \
--signer-repo DracoWhitefire/culvert-asyncThe 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:
curl -A "your-tool (contact)" https://crates.io/api/v1/crates/culvert-async/X.Y.Z
# see .version.trustpub_data0.1.0 was uploaded by hand with an API token; it carries no provenance and has no GitHub release.
Licensed under the Mozilla Public License 2.0.