Skip to content

About

Async typed access to the HDMI 2.1 SCDC register map

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

culvert-async

CI crates.io docs.rs License: MPL-2.0 Rust 1.85+ SLSA Level 2 trusted publishing only

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.

Usage

cargo add culvert-async

Wrap 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-async

Scdc<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 coverage

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.

Features

Feature Default Description
plumbob-async no Implements plumbob_async::ScdcClient for Scdc<T>

no_std

culvert-async is #![no_std] throughout. All output types are stack-allocated; no allocator is required in any configuration.

Stack position

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
Loading

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.

Out of scope

  • Sync API — callers on sync transports use culvert directly.
  • Link training state machine — the sequencing of FRL training belongs in plumbob-async. culvert-async provides the register operations; the state machine decides when to call them.
  • PHY configuration — HdmiPhy operations are the link training layer's concern.
  • I²C / DDC transport — platform backends implement hdmi_hal_async::scdc::ScdcTransport. culvert-async never touches I²C directly.

Documentation

  • doc/setup.md — build, test, coverage, and example commands
  • doc/architecture.md — role, scope, the client, the plumbob-async feature, and design principles
  • doc/testing.md — testing strategy, transport harness, and CI expectations
  • doc/roadmap.md — deferred register groups and planned API surfaces

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:

gh attestation verify culvert-async-X.Y.Z.crate --repo ashlar-display/culvert-async

The 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-async

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:

curl -A "your-tool (contact)" https://crates.io/api/v1/crates/culvert-async/X.Y.Z
# 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.

About

Async typed access to the HDMI 2.1 SCDC register map

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages