use embassy_futures::select::{select3, Either3}; use embassy_net_driver_channel as ch; use embassy_sync::pubsub::PubSubBehavior; use crate::sub::mac::Mac; #[cfg(feature = "firmware-logs")] struct LogState { addr: u32, last_idx: usize, buf: [u8; 256], buf_count: usize, } #[cfg(feature = "firmware-logs")] impl Default for LogState { fn default() -> Self { Self { addr: Default::default(), last_idx: Default::default(), buf: [0; 256], buf_count: Default::default(), } } } pub struct Runner<'a, PWR, SPI> { ch: ch::Runner<'a, MTU>, mac: Mac, ioctl_state: &'a IoctlState, ioctl_id: u16, sdpcm_seq: u8, sdpcm_seq_max: u8, events: &'a Events, #[cfg(feature = "firmware-logs")] log: LogState, } impl<'a, PWR, SPI> Runner<'a, PWR, SPI> where PWR: OutputPin, SPI: SpiBusCyw43, { pub(crate) fn new( ch: ch::Runner<'a, MTU>, bus: Bus, ioctl_state: &'a IoctlState, events: &'a Events, ) -> Self { Self { ch, bus, ioctl_state, ioctl_id: 0, sdpcm_seq: 0, sdpcm_seq_max: 1, events, #[cfg(feature = "firmware-logs")] log: LogState::default(), } } 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"); } #[cfg(feature = "firmware-logs")] async fn log_init(&mut self) { // Initialize shared memory for logging. let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; let shared_addr = self.bus.bp_read32(addr).await; debug!("shared_addr {:08x}", shared_addr); let mut shared = [0; SharedMemData::SIZE]; self.bus.bp_read(shared_addr, &mut shared).await; let shared = SharedMemData::from_bytes(&shared); self.log.addr = shared.console_addr + 8; } #[cfg(feature = "firmware-logs")] async fn log_read(&mut self) { // Read log struct let mut log = [0; SharedMemLog::SIZE]; self.bus.bp_read(self.log.addr, &mut log).await; let log = SharedMemLog::from_bytes(&log); let idx = log.idx as usize; // If pointer hasn't moved, no need to do anything. if idx == self.log.last_idx { return; } // Read entire buf for now. We could read only what we need, but then we // run into annoying alignment issues in `bp_read`. let mut buf = [0; 0x400]; self.bus.bp_read(log.buf, &mut buf).await; while self.log.last_idx != idx as usize { let b = buf[self.log.last_idx]; if b == b'\r' || b == b'\n' { if self.log.buf_count != 0 { let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; debug!("LOGS: {}", s); self.log.buf_count = 0; } } else if self.log.buf_count < self.log.buf.len() { self.log.buf[self.log.buf_count] = b; self.log.buf_count += 1; } self.log.last_idx += 1; if self.log.last_idx == 0x400 { self.log.last_idx = 0; } } } 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; } async fn core_disable(&mut self, core: Core) { let base = core.base_addr(); // Dummy read? let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; // Check it isn't already reset let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; if r & AI_RESETCTRL_BIT_RESET != 0 { return; } self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; block_for(Duration::from_millis(1)); self.bus .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) .await; let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; } async fn core_reset(&mut self, core: Core) { self.core_disable(core).await; let base = core.base_addr(); self.bus .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) .await; let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; Timer::after(Duration::from_millis(1)).await; self.bus .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) .await; let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; Timer::after(Duration::from_millis(1)).await; } async fn core_is_up(&mut self, core: Core) -> bool { let base = core.base_addr(); let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); return false; } let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; if r & (AI_RESETCTRL_BIT_RESET) != 0 { debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); return false; } true } }