use embassy_futures::select::{select3, Either3}; use embassy_net_driver_channel as ch; use embassy_sync::pubsub::PubSubBehavior; use crate::mac::event::Events; use crate::mac::ioctl::{IoctlState, PendingIoctl}; use crate::mac::MTU; use crate::sub::mac::Mac; pub struct Runner<'a> { ch: ch::Runner<'a, MTU>, mac: Mac, ioctl_state: &'a IoctlState, ioctl_id: u16, sdpcm_seq: u8, sdpcm_seq_max: u8, events: &'a Events, } impl<'a> Runner<'a> { pub(crate) fn new(ch: ch::Runner<'a, MTU>, mac: Mac, ioctl_state: &'a IoctlState, events: &'a Events) -> Self { Self { ch, mac, ioctl_state, ioctl_id: 0, sdpcm_seq: 0, sdpcm_seq_max: 1, events, } } pub(crate) async fn init(&mut self, firmware: &[u8]) { self.bus.init().await; #[cfg(feature = "firmware-logs")] self.log_init().await; debug!("wifi init done"); } pub async fn run(mut self) -> ! { let mut buf = [0; 512]; loop { #[cfg(feature = "firmware-logs")] self.log_read().await; if self.has_credit() { let ioctl = self.ioctl_state.wait_pending(); let tx = self.ch.tx_buf(); let ev = self.bus.wait_for_event(); match select3(ioctl, tx, ev).await { Either3::First(PendingIoctl { buf: iobuf, kind, cmd, iface, }) => { self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await; self.check_status(&mut buf).await; } Either3::Second(packet) => { trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); // There MUST be 2 bytes of padding between the SDPCM and BDC headers. // And ONLY for data packets! // No idea why, but the firmware will append two zero bytes to the tx'd packets // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it // be oversized and get dropped. // WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90 // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597 // ¯\_(ツ)_/¯ const PADDING_SIZE: usize = 2; let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len(); let seq = self.sdpcm_seq; self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); let sdpcm_header = SdpcmHeader { len: total_len as u16, // TODO does this len need to be rounded up to u32? len_inv: !total_len as u16, sequence: seq, channel_and_flags: CHANNEL_TYPE_DATA, next_length: 0, header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _, wireless_flow_control: 0, bus_data_credit: 0, reserved: [0, 0], }; let bdc_header = BdcHeader { flags: BDC_VERSION << BDC_VERSION_SHIFT, priority: 0, flags2: 0, data_offset: 0, }; trace!("tx {:?}", sdpcm_header); trace!(" {:?}", bdc_header); buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE] .copy_from_slice(&bdc_header.to_bytes()); buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()] .copy_from_slice(packet); let total_len = (total_len + 3) & !3; // round up to 4byte trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); self.bus.wlan_write(&buf[..(total_len / 4)]).await; self.ch.tx_done(); self.check_status(&mut buf).await; } Either3::Third(()) => { self.handle_irq(&mut buf).await; } } } else { warn!("TX stalled"); self.bus.wait_for_event().await; self.handle_irq(&mut buf).await; } } } /// Wait for IRQ on F2 packet available async fn handle_irq(&mut self, buf: &mut [u32; 512]) { // Receive stuff let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; trace!("irq{}", FormatInterrupt(irq)); if irq & IRQ_F2_PACKET_AVAILABLE != 0 { self.check_status(buf).await; } if irq & IRQ_DATA_UNAVAILABLE != 0 { // TODO what should we do here? warn!("IRQ DATA_UNAVAILABLE, clearing..."); self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await; } } /// Handle F2 events while status register is set async fn check_status(&mut self, buf: &mut [u32; 512]) { loop { let status = self.bus.status(); trace!("check status{}", FormatStatus(status)); if status & STATUS_F2_PKT_AVAILABLE != 0 { let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; self.bus.wlan_read(buf, len).await; trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); self.rx(&mut slice8_mut(buf)[..len as usize]); } else { break; } } } fn rx(&mut self, packet: &mut [u8]) { let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { return; }; self.update_credit(&sdpcm_header); let channel = sdpcm_header.channel_and_flags & 0x0f; match channel { CHANNEL_TYPE_CONTROL => { let Some((cdc_header, response)) = CdcHeader::parse(payload) else { return; }; trace!(" {:?}", cdc_header); if cdc_header.id == self.ioctl_id { if cdc_header.status != 0 { // TODO: propagate error instead panic!("IOCTL error {}", cdc_header.status as i32); } self.ioctl_state.ioctl_done(response); } } CHANNEL_TYPE_EVENT => { let Some((_, bdc_packet)) = BdcHeader::parse(payload) else { warn!("BDC event, incomplete header"); return; }; let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else { warn!("BDC event, incomplete data"); return; }; const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h if event_packet.eth.ether_type != ETH_P_LINK_CTL { warn!( "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", event_packet.eth.ether_type, ETH_P_LINK_CTL ); return; } const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; if event_packet.hdr.oui != BROADCOM_OUI { warn!( "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", Bytes(&event_packet.hdr.oui), Bytes(BROADCOM_OUI) ); return; } const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { warn!("unexpected subtype {}", event_packet.hdr.subtype); return; } const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { warn!("unexpected user_subtype {}", event_packet.hdr.subtype); return; } let evt_type = events::Event::from(event_packet.msg.event_type as u8); debug!( "=== EVENT {:?}: {:?} {:02x}", evt_type, event_packet.msg, Bytes(evt_data) ); if self.events.mask.is_enabled(evt_type) { let status = event_packet.msg.status; let event_payload = match evt_type { Event::ESCAN_RESULT if status == EStatus::PARTIAL => { let Some((_, bss_info)) = ScanResults::parse(evt_data) else { return; }; let Some(bss_info) = BssInfo::parse(bss_info) else { return; }; events::Payload::BssInfo(*bss_info) } Event::ESCAN_RESULT => events::Payload::None, _ => events::Payload::None, }; // this intentionally uses the non-blocking publish immediate // publish() is a deadlock risk in the current design as awaiting here prevents ioctls // The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event // (if they are actively awaiting the queue) self.events.queue.publish_immediate(events::Message::new( Status { event_type: evt_type, status, }, event_payload, )); } } CHANNEL_TYPE_DATA => { let Some((_, packet)) = BdcHeader::parse(payload) else { return; }; trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); match self.ch.try_rx_buf() { Some(buf) => { buf[..packet.len()].copy_from_slice(packet); self.ch.rx_done(packet.len()) } None => warn!("failed to push rxd packet to the channel."), } } _ => {} } } fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { if sdpcm_header.channel_and_flags & 0xf < 3 { let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { sdpcm_seq_max = self.sdpcm_seq + 2; } self.sdpcm_seq_max = sdpcm_seq_max; } } fn has_credit(&self) -> bool { self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 } async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) { let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); let sdpcm_seq = self.sdpcm_seq; self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); self.ioctl_id = self.ioctl_id.wrapping_add(1); let sdpcm_header = SdpcmHeader { len: total_len as u16, // TODO does this len need to be rounded up to u32? len_inv: !total_len as u16, sequence: sdpcm_seq, channel_and_flags: CHANNEL_TYPE_CONTROL, next_length: 0, header_length: SdpcmHeader::SIZE as _, wireless_flow_control: 0, bus_data_credit: 0, reserved: [0, 0], }; let cdc_header = CdcHeader { cmd: cmd, len: data.len() as _, flags: kind as u16 | (iface as u16) << 12, id: self.ioctl_id, status: 0, }; trace!("tx {:?}", sdpcm_header); trace!(" {:?}", cdc_header); buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); let total_len = (total_len + 3) & !3; // round up to 4byte trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); self.bus.wlan_write(&buf[..total_len / 4]).await; } }