2023-07-16 02:15:01 +02:00
use embassy_futures ::select ::{ select3 , Either3 } ;
use embassy_net_driver_channel as ch ;
use embassy_sync ::pubsub ::PubSubBehavior ;
2023-07-16 16:32:54 +02:00
use crate ::mac ::event ::Events ;
use crate ::mac ::ioctl ::{ IoctlState , PendingIoctl } ;
use crate ::mac ::MTU ;
2023-07-16 02:15:01 +02:00
use crate ::sub ::mac ::Mac ;
2023-07-16 16:32:54 +02:00
pub struct Runner < ' a > {
2023-07-16 02:15:01 +02:00
ch : ch ::Runner < ' a , MTU > ,
mac : Mac ,
ioctl_state : & ' a IoctlState ,
ioctl_id : u16 ,
sdpcm_seq : u8 ,
sdpcm_seq_max : u8 ,
events : & ' a Events ,
}
2023-07-16 16:32:54 +02:00
impl < ' a > Runner < ' a > {
pub ( crate ) fn new ( ch : ch ::Runner < ' a , MTU > , mac : Mac , ioctl_state : & ' a IoctlState , events : & ' a Events ) -> Self {
2023-07-16 02:15:01 +02:00
Self {
ch ,
2023-07-16 16:32:54 +02:00
mac ,
2023-07-16 02:15:01 +02:00
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 ;
}
}