diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index 022639934..3b5418978 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -256,7 +256,7 @@ features = ["stm32h753", "usart6", "baud_rate_3M", "hardware_flow_control", "vla uses = ["usart6", "dbgmcu"] interrupts = {"usart6.irq" = "usart-irq"} priority = 9 -max-sizes = {flash = 69120, ram = 65536} +max-sizes = {flash = 69792, ram = 65536} stacksize = 5400 start = true task-slots = ["sys", { cpu_seq = "cosmo_seq" }, "hf", "control_plane_agent", "net", "packrat", "i2c_driver", { spi_driver = "spi2_driver" }, "sprot", "auxflash"] diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index 6515e04c9..6943d50bb 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -242,7 +242,7 @@ features = ["stm32h753", "uart7", "baud_rate_3M", "hardware_flow_control", "vlan uses = ["uart7", "dbgmcu"] interrupts = {"uart7.irq" = "usart-irq"} priority = 8 -max-sizes = {flash = 67680, ram = 65536} +max-sizes = {flash = 68608, ram = 65536} stacksize = 5376 start = true task-slots = ["sys", { cpu_seq = "gimlet_seq" }, "hf", "control_plane_agent", "net", "packrat", "i2c_driver", { spi_driver = "spi2_driver" }, "sprot"] diff --git a/task/host-sp-comms/src/main.rs b/task/host-sp-comms/src/main.rs index 9ee87bc4f..a95e50886 100644 --- a/task/host-sp-comms/src/main.rs +++ b/task/host-sp-comms/src/main.rs @@ -104,12 +104,25 @@ const MAX_HOST_FAIL_MESSAGE_LEN: usize = 4096; // of that for us. const NUM_HOST_MAC_ADDRESSES: u16 = 3; +// The same IO path can be used for both IPCC, and a lower-level debugging +// interface, for testing the IO path itself. We differentiate between the two +// by detecting specific header types, and parsing the corresponding messages +// into a typed enum: debug message handling is then short-circuited. +enum DebugCmd<'a> { + Discard, + Echo(&'a [u8]), + CharGen(u16), +} + #[derive(Debug, Clone, Copy, PartialEq, counters::Count)] enum Trace { #[count(skip)] None, UartRxOverrun, ParseError(#[count(children)] DecodeFailureReason), + DebugDiscard, + DebugEcho(u64), + DebugCharGen(u16), SetState { now: u64, #[count(children)] @@ -786,6 +799,56 @@ impl ServerImpl { } } + // Process a request message from the host. + fn process_message( + &mut self, + reset_tx_buf: bool, + ) -> Result<(), DecodeFailureReason> { + // Debug messages have a distinct header that separates them from normal + // IPCC messages. + if is_debug_message(self.rx_buf) { + self.process_debug_message(reset_tx_buf) + } else { + self.process_ipcc_message(reset_tx_buf) + } + } + + // Process a framed debug packet + fn process_debug_message( + &mut self, + reset_tx_buf: bool, + ) -> Result<(), DecodeFailureReason> { + match parse_debug_message(self.rx_buf) { + Ok(cmd) => { + if reset_tx_buf { + self.tx_buf.reset(); + } + match cmd { + DebugCmd::Discard => ringbuf_entry!(Trace::DebugDiscard), + DebugCmd::Echo(data) => { + ringbuf_entry!(Trace::DebugEcho(data.len() as u64)); + let _ = self.tx_buf.try_copy_raw_data(data); + } + DebugCmd::CharGen(count) => { + ringbuf_entry!(Trace::DebugCharGen(count)); + // const CHARGEN: &'static [u8] = + // b"!\"#$%&'()*+,-./0123456789:;\ + // <=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]\ + // ^_abcdefghijklmnopqrstuvwxyz{|}~`"; + let mut it = (b'!'..=b'~').cycle().take(count.into()); + let _ = self.tx_buf.try_fill(&mut it); + } + } + Ok(()) + } + Err(err) => { + ringbuf_entry!(Trace::ParseError(err)); + self.rx_buf.clear(); + Err(err) + } + } + } + // Process the framed packet sitting in `self.rx_buf`. If it warrants a // response, we configure `self.tx_buf` appropriate: either populating it // with a response if we can come up with that response immediately, or @@ -800,10 +863,11 @@ impl ServerImpl { // // This method always (i.e., on success or failure) clears `rx_buf` before // returning to prepare for the next packet. - fn process_message( + fn process_ipcc_message( &mut self, reset_tx_buf: bool, ) -> Result<(), DecodeFailureReason> { + // If the message was Not a debug message, then parse it normally. let (header, request, data) = match parse_received_message(self.rx_buf) { Ok((header, request, data)) => (header, request, data), @@ -1663,8 +1727,9 @@ impl NotificationHandler for ServerImpl { } } -// This is conceptually a method on `ServerImpl`, but it takes a reference to -// `rx_buf` instead of `self` to avoid borrow checker issues. +// Parse a received message. This is conceptually a method on `ServerImpl`, +// but it takes a reference to `rx_buf` instead of `self` to avoid borrow +// checker issues. fn parse_received_message( rx_buf: &mut [u8], ) -> Result<(Header, HostToSp, &[u8]), DecodeFailureReason> { @@ -1690,6 +1755,48 @@ fn parse_received_message( Ok((header, request, data)) } +// Debug messages have a distinct header that separates them from IPCC +// messages. The first 5 bytes are the ASCII characters, "DEBUG". The +// 6th and 7th bytes encode the command number, as 0 padded ASCII hexadecimal +// string (using upper case for the non-numeric hexadigits). The commands +// are: +// +// "07" (0x07 == 7 dec) Echo +// "09" (0x09 == 9 dec) Discard +// "13" (0x13 == 19 dec) CharGen +// +// The command values correspond to the IANA-assigned port numbers for the +// corresponding UDP and TCP/IP services. +// +// This is conceptually a method on `ServerImpl`, but it takes references to +// several of its fields instead of `self` to avoid borrow checker issues. +fn is_debug_message(msg: &[u8]) -> bool { + if msg.len() < 7 { + return false; + } + msg[0..5] == *b"DEBUG" +} + +// This is conceptually a method on `ServerImpl`, but it takes references to +// several of its fields instead of `self` to avoid borrow checker issues. +fn parse_debug_message( + msg: &[u8], +) -> Result, DecodeFailureReason> { + assert!(is_debug_message(msg)); + match &msg[5..7] { + b"07" => Ok(DebugCmd::Echo(&msg[7..])), + b"09" => Ok(DebugCmd::Discard), + b"13" if msg.len() == 11 => { + let nstr = str::from_utf8(&msg[7..11]) + .map_err(|_| DecodeFailureReason::Deserialize)?; + let n = u16::from_str_radix(nstr, 16) + .map_err(|_| DecodeFailureReason::Deserialize)?; + Ok(DebugCmd::CharGen(n)) + } + _ => Err(DecodeFailureReason::Deserialize), + } +} + // This is conceptually a method on `ServerImpl`, but it takes references to // several of its fields instead of `self` to avoid borrow checker issues. fn handle_reboot_waiting_in_a2_timer( diff --git a/task/host-sp-comms/src/tx_buf.rs b/task/host-sp-comms/src/tx_buf.rs index a37834f86..d2c2c4fcb 100644 --- a/task/host-sp-comms/src/tx_buf.rs +++ b/task/host-sp-comms/src/tx_buf.rs @@ -293,6 +293,40 @@ impl TxBuf { let n = corncobs::encode_buf(&self.msg[..msg_len], &mut self.pkt[1..]); self.state = State::ToSend(0..n + 1); } + + // Copies a "raw" slice of bytes into the output packet buffer. + pub(crate) fn try_copy_raw_data(&mut self, bs: &[u8]) -> Result { + let n = usize::min(self.pkt.len() - 2, bs.len()); + if bs[..n].contains(&0) { + return Err(()); + } + let end = n + 1; + self.pkt[0] = 0; + self.pkt[1..end].copy_from_slice(&bs[..n]); + self.pkt[end] = 0; + self.state = State::ToSend(0..end + 1); + Ok(n) + } + + pub(crate) fn try_fill( + &mut self, + it: &mut impl Iterator, + ) -> Result<(), ()> { + let max = self.pkt.len() - 2; + let mut idx = 0; + self.pkt[idx] = 0; + idx += 1; + for b in it.take(max) { + if b == 0 { + return Err(()); + } + self.pkt[idx] = b; + idx += 1; + } + self.pkt[idx] = 0; + self.state = State::ToSend(0..idx + 1); + Ok(()) + } } enum State {