Skip to main content

culvert_async/client/
ced.rs

1use hdmi_hal_async::scdc::ScdcTransport;
2
3use crate::ScdcError;
4use crate::{CedCounters, RsCorrectionCount};
5use culvert::codec;
6
7use super::Scdc;
8
9impl<T: ScdcTransport> Scdc<T> {
10    /// Reads per-lane character error counts from `ERR_DET` registers (0x50–0x55 for
11    /// lanes 0–2, 0x57–0x58 for lane 3; 0x56 is the CED checksum).
12    ///
13    /// Each lane's counter is decoded from a low/high byte pair. The high byte's
14    /// bit 7 is a validity flag; if it is not set the lane's counter is `None`.
15    ///
16    /// Each byte is a separate read, so a counter the sink updates between its low and
17    /// high byte can be torn, and the CED checksum (0x56) is not verified. Treat the
18    /// counts as diagnostics.
19    pub async fn read_ced(&mut self) -> Result<CedCounters, ScdcError<T::Error>> {
20        let mut bytes = [0; 8];
21        for (byte, reg) in bytes.iter_mut().zip(codec::CED_REGISTERS) {
22            *byte = self
23                .transport
24                .read(reg)
25                .await
26                .map_err(ScdcError::Transport)?;
27        }
28        Ok(codec::decode_ced(bytes))
29    }
30
31    /// Reads the Reed-Solomon correction count from `RS_Correction_L/H` (0x59/0x5A).
32    ///
33    /// Returns `None` if the high byte's validity bit (bit 7) is not set. The count is
34    /// maintained by the sink in FRL mode; `UpdateFlags::rsed_update` signals a change.
35    ///
36    /// The two bytes are separate reads, so a count the sink updates between them can be
37    /// torn. Treat it as a diagnostic.
38    pub async fn read_rs_correction(
39        &mut self,
40    ) -> Result<Option<RsCorrectionCount>, ScdcError<T::Error>> {
41        let low = self
42            .transport
43            .read(codec::RS_CORRECTION_L)
44            .await
45            .map_err(ScdcError::Transport)?;
46        let high = self
47            .transport
48            .read(codec::RS_CORRECTION_H)
49            .await
50            .map_err(ScdcError::Transport)?;
51        Ok(codec::decode_rs_correction(low, high))
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::super::Scdc;
58    use super::super::test_transport::{Op, TestTransport};
59    use crate::CedCount;
60    use crate::ScdcError;
61
62    #[pollster::test]
63    async fn ced_validity_bit_required() {
64        // High byte with bit 7 clear → None regardless of counter value.
65        let mut sim = TestTransport::new();
66        sim.set(0x50, 0xFF);
67        sim.set(0x51, 0x7F); // valid bit clear
68        let ced = Scdc::new(sim).read_ced().await.unwrap();
69        assert_eq!(ced.lane0, None);
70    }
71
72    #[pollster::test]
73    async fn ced_counter_validity_bit_stripped() {
74        // High byte: bit 7 set (valid), counter bits = 0x01; low byte = 0x23.
75        let mut sim = TestTransport::new();
76        sim.set(0x50, 0x23);
77        sim.set(0x51, 0x81);
78        let ced = Scdc::new(sim).read_ced().await.unwrap();
79        assert_eq!(ced.lane0, Some(CedCount::new(0x0123)));
80    }
81
82    #[pollster::test]
83    async fn ced_lane3_independent() {
84        let mut sim = TestTransport::new();
85        sim.set(0x56, 0xAA); // CED checksum, must not be read as lane 3
86        sim.set(0x57, 0x01);
87        sim.set(0x58, 0x80); // lane3 valid, count = 1
88        let ced = Scdc::new(sim).read_ced().await.unwrap();
89        assert_eq!(ced.lane0, None);
90        assert_eq!(ced.lane3.map(|c| c.value()), Some(0x0001));
91    }
92
93    #[pollster::test]
94    async fn read_ced_reads_each_counter_register_once_in_order() {
95        let mut scdc = Scdc::new(TestTransport::new());
96        scdc.read_ced().await.unwrap();
97        // 0x56, the CED checksum, is not read.
98        let reads = [0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x57, 0x58].map(|r| Some(Op::Read(r)));
99        let log = scdc.into_transport().log();
100        assert_eq!(log[..8], reads);
101        assert_eq!(log[8], None);
102    }
103
104    #[pollster::test]
105    async fn read_rs_correction_reads_low_then_high() {
106        let mut scdc = Scdc::new(TestTransport::new());
107        scdc.read_rs_correction().await.unwrap();
108        assert_eq!(
109            scdc.into_transport().log()[..3],
110            [Some(Op::Read(0x59)), Some(Op::Read(0x5A)), None]
111        );
112    }
113
114    #[pollster::test]
115    async fn ced_each_lane_from_its_own_registers() {
116        // A distinct, asymmetric low/high pair per lane, so a swapped byte or lane shows.
117        let mut sim = TestTransport::new();
118        for (low, value) in [(0x50, 0x02), (0x52, 0x04), (0x54, 0x06)] {
119            sim.set(low, value);
120            sim.set(low + 1, 0x80 | (value + 1));
121        }
122        sim.set(0x56, 0xFF); // CED checksum, read as no lane
123        sim.set(0x57, 0x08);
124        sim.set(0x58, 0x89);
125        let ced = Scdc::new(sim).read_ced().await.unwrap();
126        assert_eq!(ced.lane0.map(|c| c.value()), Some(0x0302));
127        assert_eq!(ced.lane1.map(|c| c.value()), Some(0x0504));
128        assert_eq!(ced.lane2.map(|c| c.value()), Some(0x0706));
129        assert_eq!(ced.lane3.map(|c| c.value()), Some(0x0908));
130    }
131
132    #[pollster::test]
133    async fn transport_error_on_any_read() {
134        for n in 0..8 {
135            assert!(
136                Scdc::new(TestTransport::failing_after(n))
137                    .read_ced()
138                    .await
139                    .is_err_and(|error| matches!(error, ScdcError::Transport(())))
140            );
141        }
142    }
143
144    #[pollster::test]
145    async fn rs_correction_valid_and_invalid() {
146        let mut sim = TestTransport::new();
147        sim.set(0x59, 0x34);
148        sim.set(0x5A, 0x81); // valid, upper counter bits = 0x01
149        assert_eq!(
150            Scdc::new(sim)
151                .read_rs_correction()
152                .await
153                .unwrap()
154                .map(|c| c.value()),
155            Some(0x0134)
156        );
157
158        let mut sim = TestTransport::new();
159        sim.set(0x59, 0xFF);
160        sim.set(0x5A, 0x7F); // validity bit clear
161        assert_eq!(Scdc::new(sim).read_rs_correction().await.unwrap(), None);
162    }
163
164    #[pollster::test]
165    async fn rs_correction_transport_error() {
166        for n in 0..2 {
167            assert!(
168                Scdc::new(TestTransport::failing_after(n))
169                    .read_rs_correction()
170                    .await
171                    .is_err_and(|error| matches!(error, ScdcError::Transport(())))
172            );
173        }
174    }
175}