//! Shared packet-level USB hardware fake; runs the real CDC/USB stack. extern crate std; use super::UsbMavlink; use std::{ collections::VecDeque, sync::{Arc, Mutex}, vec::Vec, }; use usb_device::{ UsbDirection, UsbError, bus::{PollResult, UsbBus, UsbBusAllocator}, device::{UsbDeviceBuilder, UsbVidPid}, endpoint::{EndpointAddress, EndpointType}, }; use usbd_serial::CdcAcmClass; const RXFLVL: u32 = 1 << 4; const USBSUSP: u32 = 1 << 11; const USBRST: u32 = 1 << 12; const ENUMDNE: u32 = 1 << 13; const IEPINT: u32 = 1 << 18; const WKUPINT: u32 = 1 << 31; struct RxPacket { endpoint: usize, setup: bool, bytes: Vec, } #[derive(Default)] struct BusState { offline: bool, fail_poll: bool, read_error: Option, write_error: Option, events: VecDeque, input: [Option>; 4], output: [Option>; 4], sizes: [[u16; 4]; 2], stalled: [[bool; 4]; 2], bulk_in: usize, bulk_out: usize, // Hardware FIFO is distinct from the driver's one-packet endpoint buffer // (`output`). RXFLVL stays asserted if that buffer prevents popping the FIFO. rx_fifo: VecDeque, setup: u16, gintmsk: u32, nvic_enabled: bool, nvic_pending: bool, endpoints_configured: bool, } impl BusState { fn raw_status(&self) -> u32 { self.events.iter().fold( if self.rx_fifo.is_empty() { 0 } else { RXFLVL }, |status, event| { status | match event { // synopsys-usb-otg returns Reset only for ENUMDNE. PollResult::Reset => ENUMDNE, PollResult::Suspend => USBSUSP, PollResult::Resume => WKUPINT, PollResult::Data { ep_in_complete, .. } if *ep_in_complete != 0 => IEPINT, _ => 0, } }, ) } fn latch_interrupt(&mut self) { self.nvic_pending |= self.raw_status() & self.gintmsk != 0; } } #[derive(Clone, Default)] pub(crate) struct Host(Arc>); pub(crate) struct PacketBus(Arc>); impl super::UsbBusHealth for PacketBus { fn is_offline(&self) -> bool { self.0.lock().unwrap().offline } fn disable(&self) { let mut state = self.0.lock().unwrap(); state.offline = true; state.nvic_enabled = false; state.nvic_pending = false; } } impl UsbBus for PacketBus { fn alloc_ep( &mut self, direction: UsbDirection, address: Option, kind: EndpointType, size: u16, _: u8, ) -> Result { let mut state = self.0.lock().unwrap(); let sizes = &mut state.sizes[usize::from(direction == UsbDirection::In)]; let index = match address { Some(address) => address.index(), None => (1..sizes.len()) .find(|index| sizes[*index] == 0) .ok_or(UsbError::EndpointOverflow)?, }; if index >= sizes.len() || sizes[index] != 0 { return Err(UsbError::InvalidEndpoint); } sizes[index] = size; if kind == EndpointType::Bulk { match direction { UsbDirection::In => state.bulk_in = index, UsbDirection::Out => state.bulk_out = index, } } Ok(EndpointAddress::from_parts(index, direction)) } fn enable(&mut self) { let mut state = self.0.lock().unwrap(); state.gintmsk = RXFLVL | USBSUSP | USBRST | ENUMDNE | IEPINT | WKUPINT; state.nvic_enabled = true; } fn reset(&self) { let mut state = self.0.lock().unwrap(); state.input.fill(None); // configure_all flushes FIFOs but does not reset the bulk OUT software // EndpointBuffer::has_data. Transport cleanup must discard that packet. let bulk_out = state.bulk_out; let buffered = state.output[bulk_out].take(); state.output.fill(None); state.output[bulk_out] = buffered; state.events.clear(); state.rx_fifo.clear(); state.setup = 0; state.endpoints_configured = true; } fn set_device_address(&self, _: u8) {} fn write(&self, address: EndpointAddress, bytes: &[u8]) -> Result { let mut state = self.0.lock().unwrap(); assert!(!state.offline, "write after terminal USB fault"); if let Some(error) = state.write_error.take() { return Err(error); } if bytes.len() > usize::from(state.sizes[1][address.index()]) { return Err(UsbError::BufferOverflow); } let packet = &mut state.input[address.index()]; if packet.is_some() { return Err(UsbError::WouldBlock); } *packet = Some(bytes.to_vec()); Ok(bytes.len()) } fn read(&self, address: EndpointAddress, bytes: &mut [u8]) -> Result { let mut state = self.0.lock().unwrap(); assert!(!state.offline, "read after terminal USB fault"); if let Some(error) = state.read_error.take() { return Err(error); } let packet = state.output[address.index()] .as_ref() .ok_or(UsbError::WouldBlock)?; if packet.len() > bytes.len() { return Err(UsbError::BufferOverflow); } let packet = state.output[address.index()].take().unwrap(); state.setup &= !(1 << address.index()); bytes[..packet.len()].copy_from_slice(&packet); Ok(packet.len()) } fn set_stalled(&self, address: EndpointAddress, stalled: bool) { self.0.lock().unwrap().stalled[usize::from(address.is_in())][address.index()] = stalled; } fn is_stalled(&self, address: EndpointAddress) -> bool { self.0.lock().unwrap().stalled[usize::from(address.is_in())][address.index()] } fn suspend(&self) {} fn resume(&self) {} fn poll(&self) -> PollResult { let mut state = self.0.lock().unwrap(); assert!(!state.offline, "poll after terminal USB fault"); if state.fail_poll { state.offline = true; state.nvic_enabled = false; state.nvic_pending = false; return PollResult::None; } // Like synopsys-usb-otg 0.4.0, polling reads raw status, not GINTMSK. // Non-RX sources remain serviceable even with a blocked FIFO head. let mut ep_in_complete = 0; if let Some(event) = state.events.pop_front() { match event { PollResult::Data { ep_in_complete: complete, .. } => ep_in_complete = complete, _ => return event, } } if let Some(packet) = state.rx_fifo.front() && state.output[packet.endpoint].is_none() { let packet = state.rx_fifo.pop_front().unwrap(); if packet.setup { state.setup |= 1 << packet.endpoint; } state.output[packet.endpoint] = Some(packet.bytes); } let ep_setup = state.setup; let ep_out = state .output .iter() .enumerate() .fold(0, |mask, (index, packet)| { mask | if packet.is_some() { 1 << index } else { 0 } }) & !ep_setup; // A still-asserted level can latch before the following gate write. state.latch_interrupt(); if ep_out | ep_in_complete | ep_setup != 0 { PollResult::Data { ep_out, ep_in_complete, ep_setup, } } else { PollResult::None } } } impl Host { pub(crate) fn fail_poll(&self) { self.0.lock().unwrap().fail_poll = true; } pub(crate) fn fail_read(&self) { self.0.lock().unwrap().read_error = Some(UsbError::InvalidState); } pub(crate) fn fail_write(&self) { self.0.lock().unwrap().write_error = Some(UsbError::InvalidState); } pub(crate) fn enumeration_done(&self) { let mut state = self.0.lock().unwrap(); state.events.push_front(PollResult::Reset); state.latch_interrupt(); } pub(crate) fn event(&self, event: PollResult) { let mut state = self.0.lock().unwrap(); state.events.push_back(event); state.latch_interrupt(); } #[allow(dead_code)] pub(crate) fn events_empty(&self) -> bool { self.0.lock().unwrap().events.is_empty() } fn control(&self, link: &mut UsbMavlink<'_, PacketBus>, request: [u8; 8]) { self.queue_control(request); link.poll(); assert_eq!(self.take_input(0), Some(Vec::new())); link.poll(); } pub(crate) fn queue_control(&self, request: [u8; 8]) { let mut state = self.0.lock().unwrap(); // EP0 shares the hardware FIFO. Never inject SETUP past a blocked bulk head. state.rx_fifo.push_back(RxPacket { endpoint: 0, setup: true, bytes: request.to_vec(), }); state.latch_interrupt(); } pub(crate) fn configure(&self, link: &mut UsbMavlink<'_, PacketBus>, configured: bool) { self.control(link, [0, 9, u8::from(configured), 0, 0, 0, 0, 0]); } pub(crate) fn dtr(&self, link: &mut UsbMavlink<'_, PacketBus>, open: bool) { self.control(link, [0x21, 0x22, u8::from(open), 0, 0, 0, 0, 0]); } pub(crate) fn write_packet(&self, bytes: &[u8]) { assert!( self.try_write_packet(bytes), "host must retry when the RX FIFO is full" ); } pub(crate) fn try_write_packet(&self, bytes: &[u8]) -> bool { assert!(bytes.len() <= 64); let mut state = self.0.lock().unwrap(); // Driver allocation: two 16-word OUT buffers + 30 words. Model packet // data and one receive status word, not cycle-level USB handshakes. let occupied: usize = state .rx_fifo .iter() .map(|packet| packet.bytes.len().div_ceil(4) + 1) .sum(); if occupied + bytes.len().div_ceil(4) + 1 > 62 { return false; } let endpoint = state.bulk_out; state.rx_fifo.push_back(RxPacket { endpoint, setup: false, bytes: bytes.to_vec(), }); state.latch_interrupt(); true } fn take_input(&self, endpoint: usize) -> Option> { let mut state = self.0.lock().unwrap(); let packet = state.input[endpoint].take()?; state.events.push_back(PollResult::Data { ep_out: 0, ep_in_complete: 1 << endpoint, ep_setup: 0, }); state.latch_interrupt(); Some(packet) } pub(crate) fn set_rx_interrupt_enabled(&self, enabled: bool) { let mut state = self.0.lock().unwrap(); state.gintmsk = if enabled { state.gintmsk | RXFLVL } else { state.gintmsk & !RXFLVL }; // Masking never acknowledges pending status. Rearm sees the current level. state.latch_interrupt(); } #[allow(dead_code)] // Also shared with transport-only tests without a scheduler. pub(crate) fn rx_interrupt_enabled(&self) -> bool { self.0.lock().unwrap().gintmsk & RXFLVL != 0 } #[allow(dead_code)] pub(crate) fn nvic_enabled(&self) -> bool { self.0.lock().unwrap().nvic_enabled } #[allow(dead_code)] pub(crate) fn interrupt_status(&self) -> (u32, u32) { let state = self.0.lock().unwrap(); (state.raw_status(), state.gintmsk) } #[allow(dead_code)] pub(crate) fn enter_irq(&self) -> bool { let mut state = self.0.lock().unwrap(); state.latch_interrupt(); if !state.nvic_enabled || !state.nvic_pending { return false; } state.nvic_pending = false; // Exception entry, not an enable/disable side effect. true } #[allow(dead_code)] pub(crate) fn receive_backlog(&self) -> (usize, bool) { let state = self.0.lock().unwrap(); (state.rx_fifo.len(), state.output[state.bulk_out].is_some()) } /// Host ACK without polling device software. The device must service TX completion. pub(crate) fn take_packet(&self) -> Option> { let state = self.0.lock().unwrap(); if !state.endpoints_configured { return None; } let endpoint = state.bulk_in; drop(state); self.take_input(endpoint) } pub(crate) fn collect_packets(&self, link: &mut UsbMavlink<'_, PacketBus>) -> Vec> { let mut packets = Vec::new(); for _ in 0..16 { let Some(packet) = self.take_packet() else { return packets; }; packets.push(packet); link.poll(); } panic!("transmission did not finish"); } } pub(crate) fn with_link(test: impl FnOnce(&Host, &mut UsbMavlink<'_, PacketBus>)) { let host = Host::default(); let allocator = UsbBusAllocator::new(PacketBus(host.0.clone())); let class = CdcAcmClass::new(&allocator, 64); let device = UsbDeviceBuilder::new(&allocator, UsbVidPid(0x1209, 1)) .max_packet_size_0(64) .unwrap() .build(); let mut link = UsbMavlink::new(device, class); host.enumeration_done(); link.poll(); host.configure(&mut link, true); host.dtr(&mut link, true); link.with_rx_interrupt_gate( |link| assert!(link.is_open()), |enabled| host.set_rx_interrupt_enabled(enabled), ); // Finish initialization's latched exceptions before scheduling test traffic. while host.enter_irq() { link.with_rx_interrupt_gate( |link| link.poll(), |enabled| host.set_rx_interrupt_enabled(enabled), ); } test(&host, &mut link); }