extern crate std;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use std::vec::Vec;
use display_types::cea861::hdmi_forum::HdmiForumFrl;
use hdmi_hal::phy::{EqParams, FrlOutput, HdmiPhy, LanePatterns};
use crate::scdc::ScdcClient;
use crate::types::{
CedCounters, ClearableUpdateFlags, FrlConfig, LtpReq, LtpRequests, SourceTestConfig,
UpdateFlags,
};
pub fn all(req: LtpReq) -> LtpRequests {
LtpRequests {
lane0: req,
lane1: req,
lane2: req,
lane3: req,
}
}
#[derive(Debug, Clone, Copy)]
pub struct Round {
pub after_polls: u32,
pub requests: LtpRequests,
pub source_test: Option<SourceTestConfig>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Call {
Sink(SinkCall),
Phy(PhyCall),
}
pub type Log = Rc<RefCell<Vec<Call>>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SinkOp {
ReadFltReady,
ReadUpdateFlags,
ClearUpdateFlags,
ReadLtpRequests,
ReadSourceTestConfig,
WriteConfig0Defaults,
WriteFrlConfig,
ReadCed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SinkCall {
ReadFltReady(bool),
ReadUpdateFlags(UpdateFlags),
ClearUpdateFlags(ClearableUpdateFlags),
ReadLtpRequests(LtpRequests),
ReadSourceTestConfig(SourceTestConfig),
WriteConfig0Defaults,
WriteFrlConfig(FrlConfig),
ReadCed,
}
#[derive(Debug, Default)]
pub struct SimSink {
flt_ready_after: Option<u32>,
rounds: VecDeque<Round>,
frl_start_after: Option<u32>,
frl_start_with_round: Option<usize>,
source_test: SourceTestConfig,
fail: Option<Failure<SinkOp>>,
log: Option<Log>,
flt_ready_polls: u32,
configured: bool,
update_polls: u32,
rounds_posted: usize,
flags: UpdateFlags,
source_test_change: Option<(u32, SourceTestConfig)>,
configured_reads: u32,
pub calls: Vec<SinkCall>,
}
impl SimSink {
pub fn new() -> Self {
Self::default()
}
pub fn flt_ready_after(mut self, polls: u32) -> Self {
self.flt_ready_after = Some(polls);
self
}
pub fn round(mut self, after_polls: u32, requests: LtpRequests) -> Self {
self.rounds.push_back(Round {
after_polls,
requests,
source_test: None,
});
self
}
pub fn round_with_source_test(
mut self,
after_polls: u32,
requests: LtpRequests,
source_test: SourceTestConfig,
) -> Self {
self.rounds.push_back(Round {
after_polls,
requests,
source_test: Some(source_test),
});
self
}
pub fn frl_start_with_round(mut self, index: usize) -> Self {
self.frl_start_with_round = Some(index);
self
}
pub fn source_test_after(mut self, reads: u32, config: SourceTestConfig) -> Self {
self.source_test_change = Some((reads, config));
self
}
pub fn other_flags(mut self, flags: UpdateFlags) -> Self {
self.flags.status_update |= flags.status_update;
self.flags.ced_update |= flags.ced_update;
self.flags.rr_test |= flags.rr_test;
self.flags.rsed_update |= flags.rsed_update;
self
}
pub fn frl_start_after(mut self, polls: u32) -> Self {
self.frl_start_after = Some(polls);
self
}
pub fn source_test(mut self, config: SourceTestConfig) -> Self {
self.source_test = config;
self.flags.source_test_update = true;
self
}
pub fn fail(mut self, op: SinkOp) -> Self {
self.fail = Some(Failure::every(op));
self
}
pub fn fail_call(mut self, op: SinkOp, nth: u32) -> Self {
self.fail = Some(Failure::nth(op, nth));
self
}
pub fn log_to(mut self, log: &Log) -> Self {
self.log = Some(log.clone());
self
}
fn log_call(&mut self, call: SinkCall) {
if let Some(log) = &self.log {
log.borrow_mut().push(Call::Sink(call));
}
self.calls.push(call);
}
fn check(&mut self, op: SinkOp) -> Result<(), ()> {
match &mut self.fail {
Some(failure) => failure.check(op),
None => Ok(()),
}
}
}
impl ScdcClient for SimSink {
type Error = ();
fn read_flt_ready(&mut self) -> Result<bool, ()> {
self.check(SinkOp::ReadFltReady)?;
let ready = self
.flt_ready_after
.is_some_and(|n| self.flt_ready_polls >= n);
self.flt_ready_polls += 1;
self.log_call(SinkCall::ReadFltReady(ready));
Ok(ready)
}
fn read_update_flags(&mut self) -> Result<UpdateFlags, ()> {
self.check(SinkOp::ReadUpdateFlags)?;
if self.configured {
if let Some((reads, config)) = self.source_test_change {
if self.configured_reads >= reads {
self.source_test = config;
self.flags.source_test_update = true;
self.source_test_change = None;
}
}
self.configured_reads += 1;
match self.rounds.front().copied() {
Some(round) if !self.flags.flt_update && self.update_polls >= round.after_polls => {
self.flags.flt_update = true;
if self.frl_start_with_round == Some(self.rounds_posted) {
self.flags.frl_start = true;
}
self.rounds_posted += 1;
if let Some(config) = round.source_test {
self.source_test = config;
self.flags.source_test_update = true;
}
}
Some(_) => {}
None => {
self.flags.frl_start |=
self.frl_start_after.is_some_and(|n| self.update_polls >= n)
}
}
self.update_polls += 1;
}
self.log_call(SinkCall::ReadUpdateFlags(self.flags));
Ok(self.flags)
}
fn clear_update_flags(&mut self, flags: ClearableUpdateFlags) -> Result<(), ()> {
self.check(SinkOp::ClearUpdateFlags)?;
if flags.flt_update && self.flags.flt_update {
self.flags.flt_update = false;
self.rounds.pop_front();
self.update_polls = 0;
}
if flags.frl_start {
self.flags.frl_start = false;
if self.rounds.is_empty() {
self.frl_start_after = None;
}
}
self.flags.source_test_update &= !flags.source_test_update;
self.flags.status_update &= !flags.status_update;
self.flags.ced_update &= !flags.ced_update;
self.flags.rsed_update &= !flags.rsed_update;
self.log_call(SinkCall::ClearUpdateFlags(flags));
Ok(())
}
fn read_ltp_requests(&mut self) -> Result<LtpRequests, ()> {
self.check(SinkOp::ReadLtpRequests)?;
let requests = self
.rounds
.front()
.map_or(all(LtpReq::None), |round| round.requests);
self.log_call(SinkCall::ReadLtpRequests(requests));
Ok(requests)
}
fn read_source_test_config(&mut self) -> Result<SourceTestConfig, ()> {
self.check(SinkOp::ReadSourceTestConfig)?;
self.log_call(SinkCall::ReadSourceTestConfig(self.source_test));
Ok(self.source_test)
}
fn write_config_0_defaults(&mut self) -> Result<(), ()> {
self.check(SinkOp::WriteConfig0Defaults)?;
self.log_call(SinkCall::WriteConfig0Defaults);
Ok(())
}
fn write_frl_config(&mut self, config: FrlConfig) -> Result<(), ()> {
self.check(SinkOp::WriteFrlConfig)?;
self.configured = config.frl_rate != HdmiForumFrl::NotSupported;
self.update_polls = 0;
self.log_call(SinkCall::WriteFrlConfig(config));
Ok(())
}
fn read_ced(&mut self) -> Result<CedCounters, ()> {
self.check(SinkOp::ReadCed)?;
self.log_call(SinkCall::ReadCed);
Ok(CedCounters {
lane0: None,
lane1: None,
lane2: None,
lane3: None,
})
}
}
#[derive(Debug, Clone, Copy)]
struct Failure<Op> {
op: Op,
nth: Option<u32>,
calls: u32,
}
impl<Op: PartialEq> Failure<Op> {
fn every(op: Op) -> Self {
Self {
op,
nth: None,
calls: 0,
}
}
fn nth(op: Op, nth: u32) -> Self {
Self {
op,
nth: Some(nth),
calls: 0,
}
}
fn check(&mut self, op: Op) -> Result<(), ()> {
if op != self.op {
return Ok(());
}
self.calls += 1;
match self.nth {
Some(nth) if nth != self.calls => Ok(()),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhyOp {
SetFrlRate,
SendLtp,
SetFrlOutput,
AdjustEqualization,
SetScrambling,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhyCall {
SetFrlRate(HdmiForumFrl),
SendLtp(LanePatterns),
SetFrlOutput(FrlOutput),
AdjustEqualization(EqParams),
SetScrambling(bool),
}
#[derive(Debug, Default)]
pub struct SimPhy {
fail: Option<Failure<PhyOp>>,
log: Option<Log>,
pub calls: Vec<PhyCall>,
}
impl SimPhy {
pub fn new() -> Self {
Self::default()
}
pub fn fail(mut self, op: PhyOp) -> Self {
self.fail = Some(Failure::every(op));
self
}
pub fn fail_call(mut self, op: PhyOp, nth: u32) -> Self {
self.fail = Some(Failure::nth(op, nth));
self
}
pub fn log_to(mut self, log: &Log) -> Self {
self.log = Some(log.clone());
self
}
fn record(&mut self, op: PhyOp, call: PhyCall) -> Result<(), ()> {
if let Some(failure) = &mut self.fail {
failure.check(op)?;
}
if let Some(log) = &self.log {
log.borrow_mut().push(Call::Phy(call));
}
self.calls.push(call);
Ok(())
}
}
impl HdmiPhy for SimPhy {
type Error = ();
fn set_frl_rate(&mut self, rate: HdmiForumFrl) -> Result<(), ()> {
self.record(PhyOp::SetFrlRate, PhyCall::SetFrlRate(rate))
}
fn send_ltp(&mut self, patterns: LanePatterns) -> Result<(), ()> {
self.record(PhyOp::SendLtp, PhyCall::SendLtp(patterns))
}
fn set_frl_output(&mut self, output: FrlOutput) -> Result<(), ()> {
self.record(PhyOp::SetFrlOutput, PhyCall::SetFrlOutput(output))
}
fn adjust_equalization(&mut self, params: EqParams) -> Result<(), ()> {
self.record(
PhyOp::AdjustEqualization,
PhyCall::AdjustEqualization(params),
)
}
fn set_scrambling(&mut self, enabled: bool) -> Result<(), ()> {
self.record(PhyOp::SetScrambling, PhyCall::SetScrambling(enabled))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::FfeLevels;
use crate::lts::FLT_UPDATE;
fn configured(mut sink: SimSink) -> SimSink {
sink.write_frl_config(FrlConfig {
frl_rate: HdmiForumFrl::Rate6Gbps4Lanes,
ffe_levels: FfeLevels::default(),
})
.unwrap();
sink
}
#[test]
fn flt_ready_is_false_until_after_the_given_polls() {
let mut sink = SimSink::new().flt_ready_after(2);
assert_eq!(sink.read_flt_ready(), Ok(false));
assert_eq!(sink.read_flt_ready(), Ok(false));
assert_eq!(sink.read_flt_ready(), Ok(true));
assert_eq!(sink.read_flt_ready(), Ok(true));
}
#[test]
fn flt_ready_never_asserts_unless_scripted() {
let mut sink = SimSink::new();
for _ in 0..10 {
assert_eq!(sink.read_flt_ready(), Ok(false));
}
}
#[test]
fn round_raises_flt_update_after_its_polls() {
let mut sink = configured(SimSink::new().round(1, all(LtpReq::Lfsr0)));
assert!(!sink.read_update_flags().unwrap().flt_update);
assert!(sink.read_update_flags().unwrap().flt_update);
assert_eq!(sink.read_ltp_requests(), Ok(all(LtpReq::Lfsr0)));
}
#[test]
fn clearing_flt_update_moves_to_the_next_round() {
let mut sink = configured(
SimSink::new()
.round(0, all(LtpReq::Lfsr0))
.round(0, all(LtpReq::None)),
);
assert!(sink.read_update_flags().unwrap().flt_update);
sink.clear_update_flags(FLT_UPDATE).unwrap();
assert!(sink.read_update_flags().unwrap().flt_update);
assert_eq!(sink.read_ltp_requests(), Ok(all(LtpReq::None)));
}
#[test]
fn clearing_flt_update_when_not_set_keeps_the_round() {
let mut sink = configured(SimSink::new().round(1, all(LtpReq::Lfsr1)));
sink.clear_update_flags(FLT_UPDATE).unwrap();
assert!(!sink.read_update_flags().unwrap().flt_update);
assert!(sink.read_update_flags().unwrap().flt_update);
assert_eq!(sink.read_ltp_requests(), Ok(all(LtpReq::Lfsr1)));
}
#[test]
fn requests_read_as_none_once_rounds_are_used_up() {
let mut sink = SimSink::new();
assert_eq!(sink.read_ltp_requests(), Ok(all(LtpReq::None)));
}
#[test]
fn frl_start_follows_the_last_round() {
let mut sink = configured(
SimSink::new()
.round(0, all(LtpReq::None))
.frl_start_after(1),
);
assert!(sink.read_update_flags().unwrap().flt_update);
sink.clear_update_flags(FLT_UPDATE).unwrap();
assert!(!sink.read_update_flags().unwrap().frl_start);
assert!(sink.read_update_flags().unwrap().frl_start);
sink.clear_update_flags(crate::lts::FRL_START).unwrap();
assert!(!sink.read_update_flags().unwrap().frl_start);
}
#[test]
fn rounds_wait_for_config_1_with_an_frl_rate() {
let mut sink = SimSink::new()
.round(0, all(LtpReq::Lfsr0))
.frl_start_after(0);
assert!(!sink.read_update_flags().unwrap().flt_update);
sink.write_frl_config(FrlConfig {
frl_rate: HdmiForumFrl::NotSupported,
ffe_levels: FfeLevels::default(),
})
.unwrap();
assert!(!sink.read_update_flags().unwrap().flt_update);
let mut sink = configured(sink);
assert!(sink.read_update_flags().unwrap().flt_update);
}
#[test]
fn round_with_source_test_changes_the_configuration() {
let config = SourceTestConfig::new(false, false, false, true, false, false);
let mut sink =
configured(SimSink::new().round_with_source_test(0, all(LtpReq::NyquistClock), config));
let flags = sink.read_update_flags().unwrap();
assert!(flags.flt_update && flags.source_test_update);
assert_eq!(sink.read_source_test_config(), Ok(config));
}
#[test]
fn frl_start_never_asserts_unless_scripted() {
let mut sink = configured(SimSink::new());
for _ in 0..10 {
assert!(!sink.read_update_flags().unwrap().frl_start);
}
}
#[test]
fn source_test_sets_update_until_cleared() {
let config = SourceTestConfig::new(false, false, false, true, false, false);
let mut sink = SimSink::new().source_test(config);
assert!(sink.read_update_flags().unwrap().source_test_update);
assert_eq!(sink.read_source_test_config(), Ok(config));
sink.clear_update_flags(crate::lts::SOURCE_TEST_UPDATE)
.unwrap();
assert!(!sink.read_update_flags().unwrap().source_test_update);
}
#[test]
fn source_test_config_defaults_to_timeouts_kept() {
let mut sink = SimSink::new();
assert_eq!(
sink.read_source_test_config(),
Ok(SourceTestConfig::default())
);
}
#[test]
fn sink_records_calls_in_order() {
let config = FrlConfig {
frl_rate: HdmiForumFrl::Rate6Gbps4Lanes,
ffe_levels: FfeLevels::default(),
};
let mut sink = SimSink::new();
sink.write_config_0_defaults().unwrap();
sink.write_frl_config(config).unwrap();
sink.read_ced().unwrap();
assert_eq!(
sink.calls,
[
SinkCall::WriteConfig0Defaults,
SinkCall::WriteFrlConfig(config),
SinkCall::ReadCed,
]
);
}
#[test]
fn sink_ced_reads_as_all_invalid() {
let ced = SimSink::new().read_ced().unwrap();
assert_eq!(ced.lane0, None);
assert_eq!(ced.lane1, None);
assert_eq!(ced.lane2, None);
assert_eq!(ced.lane3, None);
}
#[test]
fn sink_fails_the_chosen_operation_only() {
let config = FrlConfig {
frl_rate: HdmiForumFrl::Rate6Gbps4Lanes,
ffe_levels: FfeLevels::default(),
};
let ops = [
SinkOp::ReadFltReady,
SinkOp::ReadUpdateFlags,
SinkOp::ClearUpdateFlags,
SinkOp::ReadLtpRequests,
SinkOp::ReadSourceTestConfig,
SinkOp::WriteConfig0Defaults,
SinkOp::WriteFrlConfig,
SinkOp::ReadCed,
];
for op in ops {
let mut sink = SimSink::new().fail(op);
let results = [
sink.read_flt_ready().map(drop),
sink.read_update_flags().map(drop),
sink.clear_update_flags(ClearableUpdateFlags::default()),
sink.read_ltp_requests().map(drop),
sink.read_source_test_config().map(drop),
sink.write_config_0_defaults(),
sink.write_frl_config(config),
sink.read_ced().map(drop),
];
for (candidate, result) in ops.iter().zip(results) {
assert_eq!(
result.is_err(),
*candidate == op,
"{candidate:?} with {op:?}"
);
}
assert_eq!(sink.calls.len(), ops.len() - 1);
}
}
#[test]
fn phy_records_calls_in_order() {
let mut phy = SimPhy::new();
phy.set_frl_rate(HdmiForumFrl::Rate6Gbps4Lanes).unwrap();
phy.send_ltp(LanePatterns::default()).unwrap();
phy.set_frl_output(FrlOutput::GapOnly).unwrap();
phy.adjust_equalization(EqParams::new()).unwrap();
phy.set_scrambling(true).unwrap();
assert_eq!(
phy.calls,
[
PhyCall::SetFrlRate(HdmiForumFrl::Rate6Gbps4Lanes),
PhyCall::SendLtp(LanePatterns::default()),
PhyCall::SetFrlOutput(FrlOutput::GapOnly),
PhyCall::AdjustEqualization(EqParams::new()),
PhyCall::SetScrambling(true),
]
);
}
#[test]
fn phy_fails_the_chosen_operation_only() {
let ops = [
PhyOp::SetFrlRate,
PhyOp::SendLtp,
PhyOp::SetFrlOutput,
PhyOp::AdjustEqualization,
PhyOp::SetScrambling,
];
for op in ops {
let mut phy = SimPhy::new().fail(op);
let results = [
phy.set_frl_rate(HdmiForumFrl::Rate6Gbps4Lanes),
phy.send_ltp(LanePatterns::default()),
phy.set_frl_output(FrlOutput::Active),
phy.adjust_equalization(EqParams::new()),
phy.set_scrambling(false),
];
for (candidate, result) in ops.iter().zip(results) {
assert_eq!(
result.is_err(),
*candidate == op,
"{candidate:?} with {op:?}"
);
}
assert_eq!(phy.calls.len(), ops.len() - 1);
}
}
#[test]
fn frl_start_with_round_raises_both_flags_and_keeps_the_final_start() {
let config = FrlConfig {
frl_rate: HdmiForumFrl::Rate6Gbps4Lanes,
ffe_levels: FfeLevels::default(),
};
let mut sink = SimSink::new()
.round(0, all(LtpReq::None))
.round(0, all(LtpReq::None))
.frl_start_with_round(1)
.frl_start_after(0);
sink.write_frl_config(config).unwrap();
let both = |flags: UpdateFlags| flags.flt_update && flags.frl_start;
assert!(!both(sink.read_update_flags().unwrap()));
sink.clear_update_flags(crate::lts::FLT_UPDATE).unwrap();
assert!(both(sink.read_update_flags().unwrap()));
sink.clear_update_flags(crate::lts::FRL_START).unwrap();
sink.clear_update_flags(crate::lts::FLT_UPDATE).unwrap();
assert!(sink.read_update_flags().unwrap().frl_start);
}
#[test]
fn fail_call_fails_only_that_call() {
let mut sink = SimSink::new().fail_call(SinkOp::ReadFltReady, 2);
let results = [
sink.read_flt_ready(),
sink.read_flt_ready(),
sink.read_flt_ready(),
];
assert_eq!(results.map(|result| result.is_err()), [false, true, false]);
assert!(sink.read_update_flags().is_ok());
let mut phy = SimPhy::new().fail_call(PhyOp::SendLtp, 1);
assert!(phy.send_ltp(LanePatterns::default()).is_err());
assert!(phy.send_ltp(LanePatterns::default()).is_ok());
assert!(phy.set_frl_output(FrlOutput::GapOnly).is_ok());
}
}