//! K404: real PL011 receive bytes exercise CR, LF and CRLF line boundaries. use super::*; use turkos_kernel::serial_rx::{LineEndings, LineEvent, PushResult, RxBuffer}; const RXFE: u32 = 1 << 4; const DATA_ERROR: u32 = 0x0f00; fn fail(reason: &str) -> ! { write_str("TURKOS/SERIAL-LINE-ERROR: "); write_line(reason); park() } fn ticks() -> u64 { let value: u64; unsafe { asm!("mrs {value}, CNTPCT_EL0", value = out(reg) value, options(nomem, nostack, preserves_flags)) }; value } pub(super) fn smoke_test() -> ! { let base = UART_BASE.load(Ordering::Relaxed); let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)) }; if frequency == 0 { fail("zero counter frequency"); } let mut bytes = RxBuffer::new(); let mut endings = LineEndings::new(); let mut lines = 0usize; let mut suppressed = 0usize; let mut actions = [0u8; 4]; write_line("TURKOS/SERIAL-LINE: READY EXPECTED=0xa"); for index in 0..10usize { let deadline = ticks() .checked_add(frequency.saturating_mul(5)) .unwrap_or(u64::MAX); while unsafe { read_volatile((base + 0x18) as *const u32) } & RXFE != 0 { if ticks() >= deadline { fail("input timeout"); } core::hint::spin_loop(); } let data = unsafe { read_volatile(base as *const u32) }; if data & DATA_ERROR != 0 { fail("PL011 data error"); } let byte = data as u8; let event = endings.feed(byte); let label = match event { LineEvent::Data(value) => { if bytes.push(value) != PushResult::Stored { fail("line buffer full"); } "DATA" } LineEvent::End => { if lines >= 4 || bytes.len() != 1 { fail("line count/content"); } let value = bytes.pop().unwrap_or_else(|| fail("missing line byte")); if value != b'A' + lines as u8 { fail("line count/content"); } // This is a bounded stand-in for later shell dispatch: a // duplicate CRLF End would run the same action twice. actions[lines] += 1; if actions[lines] != 1 { fail("duplicate line action"); } lines += 1; write_str("TURKOS/SERIAL-LINE-ACTION: NUMBER=0x"); write_hex(lines); write_str(" KEY="); write_byte(value); write_line(" RUNS=0x1"); write_str("TURKOS/SERIAL-LINE-TEXT: NUMBER=0x"); write_hex(lines); write_str(" TEXT="); write_byte(value); write_str("\r\n"); "END" } LineEvent::SuppressedLf => { suppressed += 1; "SUPPRESS" } }; write_str("TURKOS/SERIAL-LINE-BYTE: INDEX=0x"); write_hex(index); write_str(" VALUE=0x"); write_hex(byte as usize); write_str(" EVENT="); write_str(label); write_str(" LINES=0x"); write_hex(lines); write_str(" SUPPRESSED=0x"); write_hex(suppressed); write_str("\r\n"); } if lines != 4 || suppressed != 2 || bytes.len() != 0 || bytes.dropped() != 0 || actions != [1, 1, 1, 1] { fail("final line accounting"); } write_line("TURKOS/SERIAL-LINE: PASS LINES=0x4 SUPPRESSED=0x2 BYTES=0xa EMPTY=0x1"); park() }