//! K418: actual PL011 ETX/Ctrl-C cancels the current bounded command line. use super::*; use turkos_kernel::serial_console::{ConsoleEvent, SerialConsole}; const RXFE: u32 = 1 << 4; const DATA_ERROR: u32 = 0x0f00; fn fail(reason: &str) -> ! { write_str("TURKOS/CANCEL-FAIL: "); 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 } fn prompt(number: u64) { write_str("TURKOS/CANCEL-PROMPT: NUMBER=0x"); write_hex(number as usize); write_str("\r\n"); } fn hex_bytes(bytes: &[u8]) { for byte in bytes { write_hex_byte(*byte); } } 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 console = SerialConsole::new(); let mut stage = 0usize; let mut variant = false; let mut line_bytes = 0usize; let mut index = 0usize; prompt(console.prompts()); write_line("TURKOS/CANCEL: READY"); loop { 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; if byte != 0x03 && byte != b'\r' && byte != b'\n' { line_bytes += 1; } let event = match console.feed(byte) { ConsoleEvent::Input => "INPUT", ConsoleEvent::Cancelled { prompt: number, stored_bytes, overflowed, } => { match stage { 0 if line_bytes == 8 && stored_bytes == 8 && !overflowed => variant = false, 0 if line_bytes == 3 && stored_bytes == 3 && !overflowed => variant = true, 2 if line_bytes == if variant { 18 } else { 17 } && stored_bytes == 16 && overflowed => {} 4 if line_bytes == 0 && stored_bytes == 0 && !overflowed => {} _ => fail("cancel stage or discarded state"), } write_str("TURKOS/CANCEL-EVENT: STAGE=0x"); write_hex(stage); write_str(" STORED=0x"); write_hex(stored_bytes); write_str(" OVERFLOW=0x"); write_hex(overflowed as usize); write_str(" RECEIVED=0x"); write_hex(line_bytes); write_str("\r\n"); prompt(number); stage += 1; line_bytes = 0; "CANCEL" } ConsoleEvent::Echo { prompt: number, bytes, len, } => { let expected: &[u8] = match stage { 1 => b"OK", 6 => b"END", _ => fail("echo stage"), }; if &bytes[..len] != expected { fail("echo bytes after cancel"); } write_str("TURKOS/CANCEL-ECHO: STAGE=0x"); write_hex(stage); write_str(" HEX="); hex_bytes(&bytes[..len]); write_str("\r\n"); prompt(number); stage += 1; line_bytes = 0; "ECHO" } ConsoleEvent::Help { prompt: number } => { if stage != 3 || line_bytes != 4 { fail("help stage"); } write_line("TURKOS/CANCEL-HELP: COUNT=0x1"); prompt(number); stage += 1; line_bytes = 0; "HELP" } ConsoleEvent::Blank { prompt: number } => { if stage != 5 || line_bytes != 0 { fail("LF not reset after Ctrl-C"); } write_line("TURKOS/CANCEL-BLANK: COUNT=0x1"); prompt(number); stage += 1; "BLANK" } ConsoleEvent::SuppressedLf => fail("LF suppressed across Ctrl-C"), _ => fail("unexpected command or overflow"), }; write_str("TURKOS/CANCEL-BYTE: INDEX=0x"); write_hex(index); write_str(" VALUE=0x"); write_hex(byte as usize); write_str(" EVENT="); write_str(event); write_str(" PROMPTS=0x"); write_hex(console.prompts() as usize); write_str("\r\n"); index += 1; if index > 80 { fail("too many input bytes"); } if stage == 7 { if console.prompts() != 8 || console.cancellations() != 3 || console.echos() != 2 || console.helps() != 1 || console.blanks() != 1 || console.runs() != 0 || console.unknowns() != 0 || console.invalid_utf8() != 0 { fail("cancel accounting"); } write_line( "TURKOS/CANCEL: PASS CANCELS=0x3 ECHOS=0x2 HELPS=0x1 BLANKS=0x1 PROMPTS=0x8", ); park(); } } }