diff --git a/ci.sh b/ci.sh index 3fe1b1ce..a03efb85 100755 --- a/ci.sh +++ b/ci.sh @@ -3,7 +3,7 @@ set -euo pipefail export RUSTFLAGS=-Dwarnings -export DEFMT_LOG=trace,cyw43=info,cyw43_pio=info,smoltcp=info +export DEFMT_LOG=trace,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info # needed by wifi examples export WIFI_NETWORK=x diff --git a/embassy-net-esp-hosted/Cargo.toml b/embassy-net-esp-hosted/Cargo.toml new file mode 100644 index 00000000..a7e18ee0 --- /dev/null +++ b/embassy-net-esp-hosted/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "embassy-net-esp-hosted" +version = "0.1.0" +edition = "2021" + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embassy-time = { version = "0.1.0", path = "../embassy-time" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} + +embedded-hal = { version = "1.0.0-alpha.10" } +embedded-hal-async = { version = "=0.2.0-alpha.1" } + +noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] } +#noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] } +heapless = "0.7.16" diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs new file mode 100644 index 00000000..fce82ade --- /dev/null +++ b/embassy-net-esp-hosted/src/control.rs @@ -0,0 +1,139 @@ +use ch::driver::LinkState; +use defmt::Debug2Format; +use embassy_net_driver_channel as ch; +use heapless::String; + +use crate::ioctl::Shared; +use crate::proto::{self, CtrlMsg}; + +#[derive(Debug)] +pub struct Error { + pub status: u32, +} + +pub struct Control<'a> { + state_ch: ch::StateRunner<'a>, + shared: &'a Shared, +} + +#[allow(unused)] +enum WifiMode { + None = 0, + Sta = 1, + Ap = 2, + ApSta = 3, +} + +impl<'a> Control<'a> { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self { + Self { state_ch, shared } + } + + pub async fn init(&mut self) { + debug!("wait for init event..."); + self.shared.init_wait().await; + + debug!("set wifi mode"); + self.set_wifi_mode(WifiMode::Sta as _).await; + + let mac_addr = self.get_mac_addr().await; + debug!("mac addr: {:02x}", mac_addr); + self.state_ch.set_ethernet_address(mac_addr); + } + + pub async fn join(&mut self, ssid: &str, password: &str) { + let req = proto::CtrlMsg { + msg_id: proto::CtrlMsgId::ReqConnectAp as _, + msg_type: proto::CtrlMsgType::Req as _, + payload: Some(proto::CtrlMsgPayload::ReqConnectAp(proto::CtrlMsgReqConnectAp { + ssid: String::from(ssid), + pwd: String::from(password), + bssid: String::new(), + listen_interval: 3, + is_wpa3_supported: false, + })), + }; + let resp = self.ioctl(req).await; + let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; + debug!("======= {:?}", Debug2Format(&resp)); + assert_eq!(resp.resp, 0); + self.state_ch.set_link_state(LinkState::Up); + } + + async fn get_mac_addr(&mut self) -> [u8; 6] { + let req = proto::CtrlMsg { + msg_id: proto::CtrlMsgId::ReqGetMacAddress as _, + msg_type: proto::CtrlMsgType::Req as _, + payload: Some(proto::CtrlMsgPayload::ReqGetMacAddress( + proto::CtrlMsgReqGetMacAddress { + mode: WifiMode::Sta as _, + }, + )), + }; + let resp = self.ioctl(req).await; + let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; + assert_eq!(resp.resp, 0); + + // WHY IS THIS A STRING? WHYYYY + fn nibble_from_hex(b: u8) -> u8 { + match b { + b'0'..=b'9' => b - b'0', + b'a'..=b'f' => b + 0xa - b'a', + b'A'..=b'F' => b + 0xa - b'A', + _ => panic!("invalid hex digit {}", b), + } + } + + let mac = resp.mac.as_bytes(); + let mut res = [0; 6]; + assert_eq!(mac.len(), 17); + for (i, b) in res.iter_mut().enumerate() { + *b = (nibble_from_hex(mac[i * 3]) << 4) | nibble_from_hex(mac[i * 3 + 1]) + } + res + } + + async fn set_wifi_mode(&mut self, mode: u32) { + let req = proto::CtrlMsg { + msg_id: proto::CtrlMsgId::ReqSetWifiMode as _, + msg_type: proto::CtrlMsgType::Req as _, + payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })), + }; + let resp = self.ioctl(req).await; + let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; + assert_eq!(resp.resp, 0); + } + + async fn ioctl(&mut self, req: CtrlMsg) -> CtrlMsg { + debug!("ioctl req: {:?}", &req); + + let mut buf = [0u8; 128]; + + let req_len = noproto::write(&req, &mut buf).unwrap(); + + struct CancelOnDrop<'a>(&'a Shared); + + impl CancelOnDrop<'_> { + fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + self.0.ioctl_cancel(); + } + } + + let ioctl = CancelOnDrop(self.shared); + + let resp_len = ioctl.0.ioctl(&mut buf, req_len).await; + + ioctl.defuse(); + + let res = noproto::read(&buf[..resp_len]).unwrap(); + debug!("ioctl resp: {:?}", &res); + + res + } +} diff --git a/embassy-net-esp-hosted/src/esp_hosted_config.proto b/embassy-net-esp-hosted/src/esp_hosted_config.proto new file mode 100644 index 00000000..aa1bfde6 --- /dev/null +++ b/embassy-net-esp-hosted/src/esp_hosted_config.proto @@ -0,0 +1,432 @@ +syntax = "proto3"; + +/* Enums similar to ESP IDF */ +enum Ctrl_VendorIEType { + Beacon = 0; + Probe_req = 1; + Probe_resp = 2; + Assoc_req = 3; + Assoc_resp = 4; +} + +enum Ctrl_VendorIEID { + ID_0 = 0; + ID_1 = 1; +} + +enum Ctrl_WifiMode { + NONE = 0; + STA = 1; + AP = 2; + APSTA = 3; +} + +enum Ctrl_WifiBw { + BW_Invalid = 0; + HT20 = 1; + HT40 = 2; +} + +enum Ctrl_WifiPowerSave { + PS_Invalid = 0; + MIN_MODEM = 1; + MAX_MODEM = 2; +} + +enum Ctrl_WifiSecProt { + Open = 0; + WEP = 1; + WPA_PSK = 2; + WPA2_PSK = 3; + WPA_WPA2_PSK = 4; + WPA2_ENTERPRISE = 5; + WPA3_PSK = 6; + WPA2_WPA3_PSK = 7; +} + +/* enums for Control path */ +enum Ctrl_Status { + Connected = 0; + Not_Connected = 1; + No_AP_Found = 2; + Connection_Fail = 3; + Invalid_Argument = 4; + Out_Of_Range = 5; +} + + +enum CtrlMsgType { + MsgType_Invalid = 0; + Req = 1; + Resp = 2; + Event = 3; + MsgType_Max = 4; +} + +enum CtrlMsgId { + MsgId_Invalid = 0; + + /** Request Msgs **/ + Req_Base = 100; + + Req_GetMACAddress = 101; + Req_SetMacAddress = 102; + Req_GetWifiMode = 103; + Req_SetWifiMode = 104; + + Req_GetAPScanList = 105; + Req_GetAPConfig = 106; + Req_ConnectAP = 107; + Req_DisconnectAP = 108; + + Req_GetSoftAPConfig = 109; + Req_SetSoftAPVendorSpecificIE = 110; + Req_StartSoftAP = 111; + Req_GetSoftAPConnectedSTAList = 112; + Req_StopSoftAP = 113; + + Req_SetPowerSaveMode = 114; + Req_GetPowerSaveMode = 115; + + Req_OTABegin = 116; + Req_OTAWrite = 117; + Req_OTAEnd = 118; + + Req_SetWifiMaxTxPower = 119; + Req_GetWifiCurrTxPower = 120; + + Req_ConfigHeartbeat = 121; + /* Add new control path command response before Req_Max + * and update Req_Max */ + Req_Max = 122; + + /** Response Msgs **/ + Resp_Base = 200; + + Resp_GetMACAddress = 201; + Resp_SetMacAddress = 202; + Resp_GetWifiMode = 203; + Resp_SetWifiMode = 204; + + Resp_GetAPScanList = 205; + Resp_GetAPConfig = 206; + Resp_ConnectAP = 207; + Resp_DisconnectAP = 208; + + Resp_GetSoftAPConfig = 209; + Resp_SetSoftAPVendorSpecificIE = 210; + Resp_StartSoftAP = 211; + Resp_GetSoftAPConnectedSTAList = 212; + Resp_StopSoftAP = 213; + + Resp_SetPowerSaveMode = 214; + Resp_GetPowerSaveMode = 215; + + Resp_OTABegin = 216; + Resp_OTAWrite = 217; + Resp_OTAEnd = 218; + + Resp_SetWifiMaxTxPower = 219; + Resp_GetWifiCurrTxPower = 220; + + Resp_ConfigHeartbeat = 221; + /* Add new control path command response before Resp_Max + * and update Resp_Max */ + Resp_Max = 222; + + /** Event Msgs **/ + Event_Base = 300; + Event_ESPInit = 301; + Event_Heartbeat = 302; + Event_StationDisconnectFromAP = 303; + Event_StationDisconnectFromESPSoftAP = 304; + /* Add new control path command notification before Event_Max + * and update Event_Max */ + Event_Max = 305; +} + +/* internal supporting structures for CtrlMsg */ +message ScanResult { + bytes ssid = 1; + uint32 chnl = 2; + int32 rssi = 3; + bytes bssid = 4; + Ctrl_WifiSecProt sec_prot = 5; +} + +message ConnectedSTAList { + bytes mac = 1; + int32 rssi = 2; +} + + +/* Control path structures */ +/** Req/Resp structure **/ +message CtrlMsg_Req_GetMacAddress { + int32 mode = 1; +} + +message CtrlMsg_Resp_GetMacAddress { + bytes mac = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_GetMode { +} + +message CtrlMsg_Resp_GetMode { + int32 mode = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_SetMode { + int32 mode = 1; +} + +message CtrlMsg_Resp_SetMode { + int32 resp = 1; +} + +message CtrlMsg_Req_GetStatus { +} + +message CtrlMsg_Resp_GetStatus { + int32 resp = 1; +} + +message CtrlMsg_Req_SetMacAddress { + bytes mac = 1; + int32 mode = 2; +} + +message CtrlMsg_Resp_SetMacAddress { + int32 resp = 1; +} + +message CtrlMsg_Req_GetAPConfig { +} + +message CtrlMsg_Resp_GetAPConfig { + bytes ssid = 1; + bytes bssid = 2; + int32 rssi = 3; + int32 chnl = 4; + Ctrl_WifiSecProt sec_prot = 5; + int32 resp = 6; +} + +message CtrlMsg_Req_ConnectAP { + string ssid = 1; + string pwd = 2; + string bssid = 3; + bool is_wpa3_supported = 4; + int32 listen_interval = 5; +} + +message CtrlMsg_Resp_ConnectAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg_Req_GetSoftAPConfig { +} + +message CtrlMsg_Resp_GetSoftAPConfig { + bytes ssid = 1; + bytes pwd = 2; + int32 chnl = 3; + Ctrl_WifiSecProt sec_prot = 4; + int32 max_conn = 5; + bool ssid_hidden = 6; + int32 bw = 7; + int32 resp = 8; +} + +message CtrlMsg_Req_StartSoftAP { + string ssid = 1; + string pwd = 2; + int32 chnl = 3; + Ctrl_WifiSecProt sec_prot = 4; + int32 max_conn = 5; + bool ssid_hidden = 6; + int32 bw = 7; +} + +message CtrlMsg_Resp_StartSoftAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg_Req_ScanResult { +} + +message CtrlMsg_Resp_ScanResult { + uint32 count = 1; + repeated ScanResult entries = 2; + int32 resp = 3; +} + +message CtrlMsg_Req_SoftAPConnectedSTA { +} + +message CtrlMsg_Resp_SoftAPConnectedSTA { + uint32 num = 1; + repeated ConnectedSTAList stations = 2; + int32 resp = 3; +} + +message CtrlMsg_Req_OTABegin { +} + +message CtrlMsg_Resp_OTABegin { + int32 resp = 1; +} + +message CtrlMsg_Req_OTAWrite { + bytes ota_data = 1; +} + +message CtrlMsg_Resp_OTAWrite { + int32 resp = 1; +} + +message CtrlMsg_Req_OTAEnd { +} + +message CtrlMsg_Resp_OTAEnd { + int32 resp = 1; +} + +message CtrlMsg_Req_VendorIEData { + int32 element_id = 1; + int32 length = 2; + bytes vendor_oui = 3; + int32 vendor_oui_type = 4; + bytes payload = 5; +} + +message CtrlMsg_Req_SetSoftAPVendorSpecificIE { + bool enable = 1; + Ctrl_VendorIEType type = 2; + Ctrl_VendorIEID idx = 3; + CtrlMsg_Req_VendorIEData vendor_ie_data = 4; +} + +message CtrlMsg_Resp_SetSoftAPVendorSpecificIE { + int32 resp = 1; +} + +message CtrlMsg_Req_SetWifiMaxTxPower { + int32 wifi_max_tx_power = 1; +} + +message CtrlMsg_Resp_SetWifiMaxTxPower { + int32 resp = 1; +} + +message CtrlMsg_Req_GetWifiCurrTxPower { +} + +message CtrlMsg_Resp_GetWifiCurrTxPower { + int32 wifi_curr_tx_power = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_ConfigHeartbeat { + bool enable = 1; + int32 duration = 2; +} + +message CtrlMsg_Resp_ConfigHeartbeat { + int32 resp = 1; +} + +/** Event structure **/ +message CtrlMsg_Event_ESPInit { + bytes init_data = 1; +} + +message CtrlMsg_Event_Heartbeat { + int32 hb_num = 1; +} + +message CtrlMsg_Event_StationDisconnectFromAP { + int32 resp = 1; +} + +message CtrlMsg_Event_StationDisconnectFromESPSoftAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg { + /* msg_type could be req, resp or Event */ + CtrlMsgType msg_type = 1; + + /* msg id */ + CtrlMsgId msg_id = 2; + + /* union of all msg ids */ + oneof payload { + /** Requests **/ + CtrlMsg_Req_GetMacAddress req_get_mac_address = 101; + CtrlMsg_Req_SetMacAddress req_set_mac_address = 102; + CtrlMsg_Req_GetMode req_get_wifi_mode = 103; + CtrlMsg_Req_SetMode req_set_wifi_mode = 104; + + CtrlMsg_Req_ScanResult req_scan_ap_list = 105; + CtrlMsg_Req_GetAPConfig req_get_ap_config = 106; + CtrlMsg_Req_ConnectAP req_connect_ap = 107; + CtrlMsg_Req_GetStatus req_disconnect_ap = 108; + + CtrlMsg_Req_GetSoftAPConfig req_get_softap_config = 109; + CtrlMsg_Req_SetSoftAPVendorSpecificIE req_set_softap_vendor_specific_ie = 110; + CtrlMsg_Req_StartSoftAP req_start_softap = 111; + CtrlMsg_Req_SoftAPConnectedSTA req_softap_connected_stas_list = 112; + CtrlMsg_Req_GetStatus req_stop_softap = 113; + + CtrlMsg_Req_SetMode req_set_power_save_mode = 114; + CtrlMsg_Req_GetMode req_get_power_save_mode = 115; + + CtrlMsg_Req_OTABegin req_ota_begin = 116; + CtrlMsg_Req_OTAWrite req_ota_write = 117; + CtrlMsg_Req_OTAEnd req_ota_end = 118; + + CtrlMsg_Req_SetWifiMaxTxPower req_set_wifi_max_tx_power = 119; + CtrlMsg_Req_GetWifiCurrTxPower req_get_wifi_curr_tx_power = 120; + CtrlMsg_Req_ConfigHeartbeat req_config_heartbeat = 121; + + /** Responses **/ + CtrlMsg_Resp_GetMacAddress resp_get_mac_address = 201; + CtrlMsg_Resp_SetMacAddress resp_set_mac_address = 202; + CtrlMsg_Resp_GetMode resp_get_wifi_mode = 203; + CtrlMsg_Resp_SetMode resp_set_wifi_mode = 204; + + CtrlMsg_Resp_ScanResult resp_scan_ap_list = 205; + CtrlMsg_Resp_GetAPConfig resp_get_ap_config = 206; + CtrlMsg_Resp_ConnectAP resp_connect_ap = 207; + CtrlMsg_Resp_GetStatus resp_disconnect_ap = 208; + + CtrlMsg_Resp_GetSoftAPConfig resp_get_softap_config = 209; + CtrlMsg_Resp_SetSoftAPVendorSpecificIE resp_set_softap_vendor_specific_ie = 210; + CtrlMsg_Resp_StartSoftAP resp_start_softap = 211; + CtrlMsg_Resp_SoftAPConnectedSTA resp_softap_connected_stas_list = 212; + CtrlMsg_Resp_GetStatus resp_stop_softap = 213; + + CtrlMsg_Resp_SetMode resp_set_power_save_mode = 214; + CtrlMsg_Resp_GetMode resp_get_power_save_mode = 215; + + CtrlMsg_Resp_OTABegin resp_ota_begin = 216; + CtrlMsg_Resp_OTAWrite resp_ota_write = 217; + CtrlMsg_Resp_OTAEnd resp_ota_end = 218; + CtrlMsg_Resp_SetWifiMaxTxPower resp_set_wifi_max_tx_power = 219; + CtrlMsg_Resp_GetWifiCurrTxPower resp_get_wifi_curr_tx_power = 220; + CtrlMsg_Resp_ConfigHeartbeat resp_config_heartbeat = 221; + + /** Notifications **/ + CtrlMsg_Event_ESPInit event_esp_init = 301; + CtrlMsg_Event_Heartbeat event_heartbeat = 302; + CtrlMsg_Event_StationDisconnectFromAP event_station_disconnect_from_AP = 303; + CtrlMsg_Event_StationDisconnectFromESPSoftAP event_station_disconnect_from_ESP_SoftAP = 304; + } +} diff --git a/embassy-net-esp-hosted/src/fmt.rs b/embassy-net-esp-hosted/src/fmt.rs new file mode 100644 index 00000000..91984bde --- /dev/null +++ b/embassy-net-esp-hosted/src/fmt.rs @@ -0,0 +1,257 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*); + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-esp-hosted/src/ioctl.rs b/embassy-net-esp-hosted/src/ioctl.rs new file mode 100644 index 00000000..e2a6815a --- /dev/null +++ b/embassy-net-esp-hosted/src/ioctl.rs @@ -0,0 +1,123 @@ +use core::cell::RefCell; +use core::future::poll_fn; +use core::task::Poll; + +use embassy_sync::waitqueue::WakerRegistration; + +use crate::fmt::Bytes; + +#[derive(Clone, Copy)] +pub struct PendingIoctl { + pub buf: *mut [u8], + pub req_len: usize, +} + +#[derive(Clone, Copy)] +enum IoctlState { + Pending(PendingIoctl), + Sent { buf: *mut [u8] }, + Done { resp_len: usize }, +} + +pub struct Shared(RefCell); + +struct SharedInner { + ioctl: IoctlState, + is_init: bool, + control_waker: WakerRegistration, + runner_waker: WakerRegistration, +} + +impl Shared { + pub fn new() -> Self { + Self(RefCell::new(SharedInner { + ioctl: IoctlState::Done { resp_len: 0 }, + is_init: false, + control_waker: WakerRegistration::new(), + runner_waker: WakerRegistration::new(), + })) + } + + pub async fn ioctl_wait_complete(&self) -> usize { + poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if let IoctlState::Done { resp_len } = this.ioctl { + Poll::Ready(resp_len) + } else { + this.control_waker.register(cx.waker()); + Poll::Pending + } + }) + .await + } + + pub async fn ioctl_wait_pending(&self) -> PendingIoctl { + let pending = poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if let IoctlState::Pending(pending) = this.ioctl { + Poll::Ready(pending) + } else { + this.runner_waker.register(cx.waker()); + Poll::Pending + } + }) + .await; + + self.0.borrow_mut().ioctl = IoctlState::Sent { buf: pending.buf }; + pending + } + + pub fn ioctl_cancel(&self) { + self.0.borrow_mut().ioctl = IoctlState::Done { resp_len: 0 }; + } + + pub async fn ioctl(&self, buf: &mut [u8], req_len: usize) -> usize { + trace!("ioctl req bytes: {:02x}", Bytes(&buf[..req_len])); + + { + let mut this = self.0.borrow_mut(); + this.ioctl = IoctlState::Pending(PendingIoctl { buf, req_len }); + this.runner_waker.wake(); + } + + self.ioctl_wait_complete().await + } + + pub fn ioctl_done(&self, response: &[u8]) { + let mut this = self.0.borrow_mut(); + if let IoctlState::Sent { buf } = this.ioctl { + trace!("ioctl resp bytes: {:02x}", Bytes(response)); + + // TODO fix this + (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); + + this.ioctl = IoctlState::Done { + resp_len: response.len(), + }; + this.control_waker.wake(); + } else { + warn!("IOCTL Response but no pending Ioctl"); + } + } + + // // // // // // // // // // // // // // // // // // // // + + pub fn init_done(&self) { + let mut this = self.0.borrow_mut(); + this.is_init = true; + this.control_waker.wake(); + } + + pub async fn init_wait(&self) { + poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if this.is_init { + Poll::Ready(()) + } else { + this.control_waker.register(cx.waker()); + Poll::Pending + } + }) + .await + } +} diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs new file mode 100644 index 00000000..44dfbe89 --- /dev/null +++ b/embassy-net-esp-hosted/src/lib.rs @@ -0,0 +1,337 @@ +#![no_std] + +use control::Control; +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel as ch; +use embassy_time::{Duration, Instant, Timer}; +use embedded_hal::digital::{InputPin, OutputPin}; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; +use ioctl::Shared; +use proto::CtrlMsg; + +use crate::ioctl::PendingIoctl; +use crate::proto::CtrlMsgPayload; + +mod proto; + +// must be first +mod fmt; + +mod control; +mod ioctl; + +const MTU: usize = 1514; + +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + pub const SIZE: usize = core::mem::size_of::(); + + #[allow(unused)] + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + #[allow(unused)] + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + unsafe { core::mem::transmute(bytes) } + } + + #[allow(unused)] + pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + + unsafe { core::mem::transmute(bytes) } + } + } + }; +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +struct PayloadHeader { + /// InterfaceType on lower 4 bits, number on higher 4 bits. + if_type_and_num: u8, + + /// Flags. + /// + /// bit 0: more fragments. + flags: u8, + + len: u16, + offset: u16, + checksum: u16, + seq_num: u16, + reserved2: u8, + + /// Packet type for HCI or PRIV interface, reserved otherwise + hci_priv_packet_type: u8, +} +impl_bytes!(PayloadHeader); + +#[allow(unused)] +#[repr(u8)] +enum InterfaceType { + Sta = 0, + Ap = 1, + Serial = 2, + Hci = 3, + Priv = 4, + Test = 5, +} + +const MAX_SPI_BUFFER_SIZE: usize = 1600; + +pub struct State { + shared: Shared, + ch: ch::State, +} + +impl State { + pub fn new() -> Self { + Self { + shared: Shared::new(), + ch: ch::State::new(), + } + } +} + +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +pub async fn new<'a, SPI, IN, OUT>( + state: &'a mut State, + spi: SPI, + handshake: IN, + ready: IN, + reset: OUT, +) -> (NetDriver<'a>, Control<'a>, Runner<'a, SPI, IN, OUT>) +where + SPI: SpiDevice, + IN: InputPin + Wait, + OUT: OutputPin, +{ + let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); + let state_ch = ch_runner.state_runner(); + + let mut runner = Runner { + ch: ch_runner, + shared: &state.shared, + next_seq: 1, + handshake, + ready, + reset, + spi, + }; + runner.init().await; + + (device, Control::new(state_ch, &state.shared), runner) +} + +pub struct Runner<'a, SPI, IN, OUT> { + ch: ch::Runner<'a, MTU>, + shared: &'a Shared, + + next_seq: u16, + + spi: SPI, + handshake: IN, + ready: IN, + reset: OUT, +} + +impl<'a, SPI, IN, OUT> Runner<'a, SPI, IN, OUT> +where + SPI: SpiDevice, + IN: InputPin + Wait, + OUT: OutputPin, +{ + async fn init(&mut self) {} + + pub async fn run(mut self) -> ! { + debug!("resetting..."); + self.reset.set_low().unwrap(); + Timer::after(Duration::from_millis(100)).await; + self.reset.set_high().unwrap(); + Timer::after(Duration::from_millis(1000)).await; + + let mut tx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; + let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; + + loop { + self.handshake.wait_for_high().await.unwrap(); + + let ioctl = self.shared.ioctl_wait_pending(); + let tx = self.ch.tx_buf(); + let ev = async { self.ready.wait_for_high().await.unwrap() }; + + match select3(ioctl, tx, ev).await { + Either3::First(PendingIoctl { buf, req_len }) => { + tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02"); + tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes()); + tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]); + + let mut header = PayloadHeader { + if_type_and_num: InterfaceType::Serial as _, + len: (req_len + 14) as _, + offset: PayloadHeader::SIZE as _, + seq_num: self.next_seq, + ..Default::default() + }; + self.next_seq = self.next_seq.wrapping_add(1); + + // Calculate checksum + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + header.checksum = checksum(&tx_buf[..26 + req_len]); + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + } + Either3::Second(packet) => { + tx_buf[12..][..packet.len()].copy_from_slice(packet); + + let mut header = PayloadHeader { + if_type_and_num: InterfaceType::Sta as _, + len: packet.len() as _, + offset: PayloadHeader::SIZE as _, + seq_num: self.next_seq, + ..Default::default() + }; + self.next_seq = self.next_seq.wrapping_add(1); + + // Calculate checksum + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + header.checksum = checksum(&tx_buf[..12 + packet.len()]); + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + + self.ch.tx_done(); + } + Either3::Third(()) => { + tx_buf[..PayloadHeader::SIZE].fill(0); + } + } + + if tx_buf[0] != 0 { + trace!("tx: {:02x}", &tx_buf[..40]); + } + + self.spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + // The esp-hosted firmware deasserts the HANSHAKE pin a few us AFTER ending the SPI transfer + // If we check it again too fast, we'll see it's high from the previous transfer, and if we send it + // data it will get lost. + // Make sure we check it after 100us at minimum. + let delay_until = Instant::now() + Duration::from_micros(100); + self.handle_rx(&mut rx_buf); + Timer::at(delay_until).await; + } + } + + fn handle_rx(&mut self, buf: &mut [u8]) { + trace!("rx: {:02x}", &buf[..40]); + + let buf_len = buf.len(); + let h = PayloadHeader::from_bytes_mut((&mut buf[..PayloadHeader::SIZE]).try_into().unwrap()); + + if h.len == 0 || h.offset as usize != PayloadHeader::SIZE { + return; + } + + let payload_len = h.len as usize; + if buf_len < PayloadHeader::SIZE + payload_len { + warn!("rx: len too big"); + return; + } + + let if_type_and_num = h.if_type_and_num; + let want_checksum = h.checksum; + h.checksum = 0; + let got_checksum = checksum(&buf[..PayloadHeader::SIZE + payload_len]); + if want_checksum != got_checksum { + warn!("rx: bad checksum. Got {:04x}, want {:04x}", got_checksum, want_checksum); + return; + } + + let payload = &mut buf[PayloadHeader::SIZE..][..payload_len]; + + match if_type_and_num & 0x0f { + // STA + 0 => match self.ch.try_rx_buf() { + Some(buf) => { + buf[..payload.len()].copy_from_slice(payload); + self.ch.rx_done(payload.len()) + } + None => warn!("failed to push rxd packet to the channel."), + }, + // serial + 2 => { + trace!("serial rx: {:02x}", payload); + if payload.len() < 14 { + warn!("serial rx: too short"); + return; + } + + let is_event = match &payload[..12] { + b"\x01\x08\x00ctrlResp\x02" => false, + b"\x01\x08\x00ctrlEvnt\x02" => true, + _ => { + warn!("serial rx: bad tlv"); + return; + } + }; + + let len = u16::from_le_bytes(payload[12..14].try_into().unwrap()) as usize; + if payload.len() < 14 + len { + warn!("serial rx: too short 2"); + return; + } + let data = &payload[14..][..len]; + + if is_event { + self.handle_event(data); + } else { + self.shared.ioctl_done(data); + } + } + _ => warn!("unknown iftype {}", if_type_and_num), + } + } + + fn handle_event(&self, data: &[u8]) { + let Ok(event) = noproto::read::(data) else { + warn!("failed to parse event"); + return + }; + + debug!("event: {:?}", &event); + + let Some(payload) = &event.payload else { + warn!("event without payload?"); + return + }; + + match payload { + CtrlMsgPayload::EventEspInit(_) => self.shared.init_done(), + _ => {} + } + } +} + +fn checksum(buf: &[u8]) -> u16 { + let mut res = 0u16; + for &b in buf { + res = res.wrapping_add(b as _); + } + res +} diff --git a/embassy-net-esp-hosted/src/proto.rs b/embassy-net-esp-hosted/src/proto.rs new file mode 100644 index 00000000..8ceb1579 --- /dev/null +++ b/embassy-net-esp-hosted/src/proto.rs @@ -0,0 +1,652 @@ +use heapless::{String, Vec}; + +/// internal supporting structures for CtrlMsg + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanResult { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub chnl: u32, + #[noproto(tag = "3")] + pub rssi: u32, + #[noproto(tag = "4")] + pub bssid: String<32>, + #[noproto(tag = "5")] + pub sec_prot: CtrlWifiSecProt, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ConnectedStaList { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub rssi: u32, +} +/// * Req/Resp structure * + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetMacAddress { + #[noproto(tag = "1")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetMacAddress { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetMode {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetMode { + #[noproto(tag = "1")] + pub mode: u32, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSetMode { + #[noproto(tag = "1")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSetMode { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetStatus {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetStatus { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSetMacAddress { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSetMacAddress { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetApConfig {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetApConfig { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub bssid: String<32>, + #[noproto(tag = "3")] + pub rssi: u32, + #[noproto(tag = "4")] + pub chnl: u32, + #[noproto(tag = "5")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "6")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqConnectAp { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub bssid: String<32>, + #[noproto(tag = "4")] + pub is_wpa3_supported: bool, + #[noproto(tag = "5")] + pub listen_interval: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespConnectAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetSoftApConfig {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetSoftApConfig { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub chnl: u32, + #[noproto(tag = "4")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "5")] + pub max_conn: u32, + #[noproto(tag = "6")] + pub ssid_hidden: bool, + #[noproto(tag = "7")] + pub bw: u32, + #[noproto(tag = "8")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqStartSoftAp { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub chnl: u32, + #[noproto(tag = "4")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "5")] + pub max_conn: u32, + #[noproto(tag = "6")] + pub ssid_hidden: bool, + #[noproto(tag = "7")] + pub bw: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespStartSoftAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqScanResult {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespScanResult { + #[noproto(tag = "1")] + pub count: u32, + #[noproto(repeated, tag = "2")] + pub entries: Vec, + #[noproto(tag = "3")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSoftApConnectedSta {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSoftApConnectedSta { + #[noproto(tag = "1")] + pub num: u32, + #[noproto(repeated, tag = "2")] + pub stations: Vec, + #[noproto(tag = "3")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqOtaBegin {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespOtaBegin { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqOtaWrite { + #[noproto(tag = "1")] + pub ota_data: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespOtaWrite { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqOtaEnd {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespOtaEnd { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqVendorIeData { + #[noproto(tag = "1")] + pub element_id: u32, + #[noproto(tag = "2")] + pub length: u32, + #[noproto(tag = "3")] + pub vendor_oui: Vec, + #[noproto(tag = "4")] + pub vendor_oui_type: u32, + #[noproto(tag = "5")] + pub payload: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSetSoftApVendorSpecificIe { + #[noproto(tag = "1")] + pub enable: bool, + #[noproto(tag = "2")] + pub r#type: CtrlVendorIeType, + #[noproto(tag = "3")] + pub idx: CtrlVendorIeid, + #[noproto(optional, tag = "4")] + pub vendor_ie_data: Option, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSetSoftApVendorSpecificIe { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSetWifiMaxTxPower { + #[noproto(tag = "1")] + pub wifi_max_tx_power: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSetWifiMaxTxPower { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetWifiCurrTxPower {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetWifiCurrTxPower { + #[noproto(tag = "1")] + pub wifi_curr_tx_power: u32, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqConfigHeartbeat { + #[noproto(tag = "1")] + pub enable: bool, + #[noproto(tag = "2")] + pub duration: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespConfigHeartbeat { + #[noproto(tag = "1")] + pub resp: u32, +} +/// * Event structure * + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgEventEspInit { + #[noproto(tag = "1")] + pub init_data: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgEventHeartbeat { + #[noproto(tag = "1")] + pub hb_num: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgEventStationDisconnectFromAp { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgEventStationDisconnectFromEspSoftAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsg { + /// msg_type could be req, resp or Event + #[noproto(tag = "1")] + pub msg_type: CtrlMsgType, + /// msg id + #[noproto(tag = "2")] + pub msg_id: CtrlMsgId, + /// union of all msg ids + #[noproto( + oneof, + tags = "101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 301, 302, 303, 304" + )] + pub payload: Option, +} + +/// union of all msg ids +#[derive(Debug, Clone, Eq, PartialEq, noproto::Oneof)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlMsgPayload { + /// * Requests * + #[noproto(tag = "101")] + ReqGetMacAddress(CtrlMsgReqGetMacAddress), + #[noproto(tag = "102")] + ReqSetMacAddress(CtrlMsgReqSetMacAddress), + #[noproto(tag = "103")] + ReqGetWifiMode(CtrlMsgReqGetMode), + #[noproto(tag = "104")] + ReqSetWifiMode(CtrlMsgReqSetMode), + #[noproto(tag = "105")] + ReqScanApList(CtrlMsgReqScanResult), + #[noproto(tag = "106")] + ReqGetApConfig(CtrlMsgReqGetApConfig), + #[noproto(tag = "107")] + ReqConnectAp(CtrlMsgReqConnectAp), + #[noproto(tag = "108")] + ReqDisconnectAp(CtrlMsgReqGetStatus), + #[noproto(tag = "109")] + ReqGetSoftapConfig(CtrlMsgReqGetSoftApConfig), + #[noproto(tag = "110")] + ReqSetSoftapVendorSpecificIe(CtrlMsgReqSetSoftApVendorSpecificIe), + #[noproto(tag = "111")] + ReqStartSoftap(CtrlMsgReqStartSoftAp), + #[noproto(tag = "112")] + ReqSoftapConnectedStasList(CtrlMsgReqSoftApConnectedSta), + #[noproto(tag = "113")] + ReqStopSoftap(CtrlMsgReqGetStatus), + #[noproto(tag = "114")] + ReqSetPowerSaveMode(CtrlMsgReqSetMode), + #[noproto(tag = "115")] + ReqGetPowerSaveMode(CtrlMsgReqGetMode), + #[noproto(tag = "116")] + ReqOtaBegin(CtrlMsgReqOtaBegin), + #[noproto(tag = "117")] + ReqOtaWrite(CtrlMsgReqOtaWrite), + #[noproto(tag = "118")] + ReqOtaEnd(CtrlMsgReqOtaEnd), + #[noproto(tag = "119")] + ReqSetWifiMaxTxPower(CtrlMsgReqSetWifiMaxTxPower), + #[noproto(tag = "120")] + ReqGetWifiCurrTxPower(CtrlMsgReqGetWifiCurrTxPower), + #[noproto(tag = "121")] + ReqConfigHeartbeat(CtrlMsgReqConfigHeartbeat), + /// * Responses * + #[noproto(tag = "201")] + RespGetMacAddress(CtrlMsgRespGetMacAddress), + #[noproto(tag = "202")] + RespSetMacAddress(CtrlMsgRespSetMacAddress), + #[noproto(tag = "203")] + RespGetWifiMode(CtrlMsgRespGetMode), + #[noproto(tag = "204")] + RespSetWifiMode(CtrlMsgRespSetMode), + #[noproto(tag = "205")] + RespScanApList(CtrlMsgRespScanResult), + #[noproto(tag = "206")] + RespGetApConfig(CtrlMsgRespGetApConfig), + #[noproto(tag = "207")] + RespConnectAp(CtrlMsgRespConnectAp), + #[noproto(tag = "208")] + RespDisconnectAp(CtrlMsgRespGetStatus), + #[noproto(tag = "209")] + RespGetSoftapConfig(CtrlMsgRespGetSoftApConfig), + #[noproto(tag = "210")] + RespSetSoftapVendorSpecificIe(CtrlMsgRespSetSoftApVendorSpecificIe), + #[noproto(tag = "211")] + RespStartSoftap(CtrlMsgRespStartSoftAp), + #[noproto(tag = "212")] + RespSoftapConnectedStasList(CtrlMsgRespSoftApConnectedSta), + #[noproto(tag = "213")] + RespStopSoftap(CtrlMsgRespGetStatus), + #[noproto(tag = "214")] + RespSetPowerSaveMode(CtrlMsgRespSetMode), + #[noproto(tag = "215")] + RespGetPowerSaveMode(CtrlMsgRespGetMode), + #[noproto(tag = "216")] + RespOtaBegin(CtrlMsgRespOtaBegin), + #[noproto(tag = "217")] + RespOtaWrite(CtrlMsgRespOtaWrite), + #[noproto(tag = "218")] + RespOtaEnd(CtrlMsgRespOtaEnd), + #[noproto(tag = "219")] + RespSetWifiMaxTxPower(CtrlMsgRespSetWifiMaxTxPower), + #[noproto(tag = "220")] + RespGetWifiCurrTxPower(CtrlMsgRespGetWifiCurrTxPower), + #[noproto(tag = "221")] + RespConfigHeartbeat(CtrlMsgRespConfigHeartbeat), + /// * Notifications * + #[noproto(tag = "301")] + EventEspInit(CtrlMsgEventEspInit), + #[noproto(tag = "302")] + EventHeartbeat(CtrlMsgEventHeartbeat), + #[noproto(tag = "303")] + EventStationDisconnectFromAp(CtrlMsgEventStationDisconnectFromAp), + #[noproto(tag = "304")] + EventStationDisconnectFromEspSoftAp(CtrlMsgEventStationDisconnectFromEspSoftAp), +} + +/// Enums similar to ESP IDF +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlVendorIeType { + #[default] + Beacon = 0, + ProbeReq = 1, + ProbeResp = 2, + AssocReq = 3, + AssocResp = 4, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlVendorIeid { + #[default] + Id0 = 0, + Id1 = 1, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiMode { + #[default] + None = 0, + Sta = 1, + Ap = 2, + Apsta = 3, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiBw { + #[default] + BwInvalid = 0, + Ht20 = 1, + Ht40 = 2, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiPowerSave { + #[default] + PsInvalid = 0, + MinModem = 1, + MaxModem = 2, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiSecProt { + #[default] + Open = 0, + Wep = 1, + WpaPsk = 2, + Wpa2Psk = 3, + WpaWpa2Psk = 4, + Wpa2Enterprise = 5, + Wpa3Psk = 6, + Wpa2Wpa3Psk = 7, +} + +/// enums for Control path +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlStatus { + #[default] + Connected = 0, + NotConnected = 1, + NoApFound = 2, + ConnectionFail = 3, + InvalidArgument = 4, + OutOfRange = 5, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlMsgType { + #[default] + MsgTypeInvalid = 0, + Req = 1, + Resp = 2, + Event = 3, + MsgTypeMax = 4, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlMsgId { + #[default] + MsgIdInvalid = 0, + /// * Request Msgs * + ReqBase = 100, + ReqGetMacAddress = 101, + ReqSetMacAddress = 102, + ReqGetWifiMode = 103, + ReqSetWifiMode = 104, + ReqGetApScanList = 105, + ReqGetApConfig = 106, + ReqConnectAp = 107, + ReqDisconnectAp = 108, + ReqGetSoftApConfig = 109, + ReqSetSoftApVendorSpecificIe = 110, + ReqStartSoftAp = 111, + ReqGetSoftApConnectedStaList = 112, + ReqStopSoftAp = 113, + ReqSetPowerSaveMode = 114, + ReqGetPowerSaveMode = 115, + ReqOtaBegin = 116, + ReqOtaWrite = 117, + ReqOtaEnd = 118, + ReqSetWifiMaxTxPower = 119, + ReqGetWifiCurrTxPower = 120, + ReqConfigHeartbeat = 121, + /// Add new control path command response before Req_Max + /// and update Req_Max + ReqMax = 122, + /// * Response Msgs * + RespBase = 200, + RespGetMacAddress = 201, + RespSetMacAddress = 202, + RespGetWifiMode = 203, + RespSetWifiMode = 204, + RespGetApScanList = 205, + RespGetApConfig = 206, + RespConnectAp = 207, + RespDisconnectAp = 208, + RespGetSoftApConfig = 209, + RespSetSoftApVendorSpecificIe = 210, + RespStartSoftAp = 211, + RespGetSoftApConnectedStaList = 212, + RespStopSoftAp = 213, + RespSetPowerSaveMode = 214, + RespGetPowerSaveMode = 215, + RespOtaBegin = 216, + RespOtaWrite = 217, + RespOtaEnd = 218, + RespSetWifiMaxTxPower = 219, + RespGetWifiCurrTxPower = 220, + RespConfigHeartbeat = 221, + /// Add new control path command response before Resp_Max + /// and update Resp_Max + RespMax = 222, + /// * Event Msgs * + EventBase = 300, + EventEspInit = 301, + EventHeartbeat = 302, + EventStationDisconnectFromAp = 303, + EventStationDisconnectFromEspSoftAp = 304, + /// Add new control path command notification before Event_Max + /// and update Event_Max + EventMax = 305, +} diff --git a/embassy-stm32-wpan/Cargo.toml b/embassy-stm32-wpan/Cargo.toml index 1c1b57b3..6d78ca57 100644 --- a/embassy-stm32-wpan/Cargo.toml +++ b/embassy-stm32-wpan/Cargo.toml @@ -23,10 +23,15 @@ cortex-m = "0.7.6" heapless = "0.7.16" bit_field = "0.10.2" +stm32-device-signature = { version = "0.3.3", features = ["stm32wb5x"] } +stm32wb-hci = { version = "0.1.2", features = ["version-5-0"], optional = true } [features] defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt"] +ble = ["dep:stm32wb-hci"] +mac = [] + stm32wb10cc = [ "embassy-stm32/stm32wb10cc" ] stm32wb15cc = [ "embassy-stm32/stm32wb15cc" ] stm32wb30ce = [ "embassy-stm32/stm32wb30ce" ] diff --git a/embassy-stm32-wpan/src/ble.rs b/embassy-stm32-wpan/src/ble.rs index f0bd6f48..619cd66a 100644 --- a/embassy-stm32-wpan/src/ble.rs +++ b/embassy-stm32-wpan/src/ble.rs @@ -1,13 +1,14 @@ use core::marker::PhantomData; use embassy_stm32::ipcc::Ipcc; +use hci::Opcode; +use crate::channels; use crate::cmd::CmdPacket; use crate::consts::TlPacketType; use crate::evt::EvtBox; -use crate::tables::BleTable; +use crate::tables::{BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE}; use crate::unsafe_linked_list::LinkedListNode; -use crate::{channels, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE}; pub struct Ble { phantom: PhantomData, @@ -29,7 +30,7 @@ impl Ble { Self { phantom: PhantomData } } /// `HW_IPCC_BLE_EvtNot` - pub async fn read(&self) -> EvtBox { + pub async fn tl_read(&self) -> EvtBox { Ipcc::receive(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, || unsafe { if let Some(node_ptr) = LinkedListNode::remove_head(EVT_QUEUE.as_mut_ptr()) { Some(EvtBox::new(node_ptr.cast())) @@ -41,7 +42,7 @@ impl Ble { } /// `TL_BLE_SendCmd` - pub async fn write(&self, opcode: u16, payload: &[u8]) { + pub async fn tl_write(&self, opcode: u16, payload: &[u8]) { Ipcc::send(channels::cpu1::IPCC_BLE_CMD_CHANNEL, || unsafe { CmdPacket::write_into(BLE_CMD_BUFFER.as_mut_ptr(), TlPacketType::BleCmd, opcode, payload); }) @@ -61,3 +62,18 @@ impl Ble { .await; } } + +pub extern crate stm32wb_hci as hci; + +impl hci::Controller for Ble { + async fn controller_write(&mut self, opcode: Opcode, payload: &[u8]) { + self.tl_write(opcode.0, payload).await; + } + + async fn controller_read_into(&self, buf: &mut [u8]) { + let evt_box = self.tl_read().await; + let evt_serial = evt_box.serial(); + + buf[..evt_serial.len()].copy_from_slice(evt_serial); + } +} diff --git a/embassy-stm32-wpan/src/cmd.rs b/embassy-stm32-wpan/src/cmd.rs index edca8239..8428b6ff 100644 --- a/embassy-stm32-wpan/src/cmd.rs +++ b/embassy-stm32-wpan/src/cmd.rs @@ -37,7 +37,7 @@ pub struct CmdSerialStub { } #[derive(Copy, Clone, Default)] -#[repr(C, packed)] +#[repr(C, packed(4))] pub struct CmdPacket { pub header: PacketHeader, pub cmdserial: CmdSerial, @@ -52,7 +52,7 @@ impl CmdPacket { p_cmd_serial, CmdSerialStub { ty: packet_type as u8, - cmd_code: cmd_code, + cmd_code, payload_len: payload.len() as u8, }, ); diff --git a/embassy-stm32-wpan/src/consts.rs b/embassy-stm32-wpan/src/consts.rs index caf26c06..9a107306 100644 --- a/embassy-stm32-wpan/src/consts.rs +++ b/embassy-stm32-wpan/src/consts.rs @@ -1,5 +1,8 @@ use core::convert::TryFrom; +use crate::evt::CsEvt; +use crate::PacketHeader; + #[derive(Debug)] #[repr(C)] pub enum TlPacketType { @@ -53,3 +56,34 @@ impl TryFrom for TlPacketType { } } } + +pub const TL_PACKET_HEADER_SIZE: usize = core::mem::size_of::(); +pub const TL_EVT_HEADER_SIZE: usize = 3; +pub const TL_CS_EVT_SIZE: usize = core::mem::size_of::(); + +/** + * Queue length of BLE Event + * This parameter defines the number of asynchronous events that can be stored in the HCI layer before + * being reported to the application. When a command is sent to the BLE core coprocessor, the HCI layer + * is waiting for the event with the Num_HCI_Command_Packets set to 1. The receive queue shall be large + * enough to store all asynchronous events received in between. + * When CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE is set to 27, this allow to store three 255 bytes long asynchronous events + * between the HCI command and its event. + * This parameter depends on the value given to CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE. When the queue size is to small, + * the system may hang if the queue is full with asynchronous events and the HCI layer is still waiting + * for a CC/CS event, In that case, the notification TL_BLE_HCI_ToNot() is called to indicate + * to the application a HCI command did not receive its command event within 30s (Default HCI Timeout). + */ +pub const CFG_TL_BLE_EVT_QUEUE_LENGTH: usize = 5; +pub const CFG_TL_BLE_MOST_EVENT_PAYLOAD_SIZE: usize = 255; +pub const TL_BLE_EVENT_FRAME_SIZE: usize = TL_EVT_HEADER_SIZE + CFG_TL_BLE_MOST_EVENT_PAYLOAD_SIZE; + +pub const POOL_SIZE: usize = CFG_TL_BLE_EVT_QUEUE_LENGTH * 4 * divc(TL_PACKET_HEADER_SIZE + TL_BLE_EVENT_FRAME_SIZE, 4); + +pub const fn divc(x: usize, y: usize) -> usize { + (x + y - 1) / y +} + +pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; +#[allow(dead_code)] +pub const TL_BLE_EVT_CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_BLE_EVT_CS_PACKET_SIZE; diff --git a/embassy-stm32-wpan/src/evt.rs b/embassy-stm32-wpan/src/evt.rs index 3a9d0357..25249a13 100644 --- a/embassy-stm32-wpan/src/evt.rs +++ b/embassy-stm32-wpan/src/evt.rs @@ -1,7 +1,7 @@ use core::{ptr, slice}; use super::PacketHeader; -use crate::mm; +use crate::consts::TL_EVT_HEADER_SIZE; /** * The payload of `Evt` for a command status event @@ -46,15 +46,15 @@ pub struct AsynchEvt { payload: [u8; 1], } -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone)] #[repr(C, packed)] pub struct Evt { pub evt_code: u8, pub payload_len: u8, - pub payload: [u8; 1], + pub payload: [u8; 255], } -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone)] #[repr(C, packed)] pub struct EvtSerial { pub kind: u8, @@ -62,6 +62,7 @@ pub struct EvtSerial { } #[derive(Copy, Clone, Default)] +#[repr(C, packed)] pub struct EvtStub { pub kind: u8, pub evt_code: u8, @@ -75,7 +76,7 @@ pub struct EvtStub { /// Be careful that the asynchronous events reported by the CPU2 on the system channel do /// include the header and shall use `EvtPacket` format. Only the command response format on the /// system channel is different. -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone)] #[repr(C, packed)] pub struct EvtPacket { pub header: PacketHeader, @@ -114,7 +115,7 @@ impl EvtBox { } } - pub fn payload<'a>(&self) -> &'a [u8] { + pub fn payload<'a>(&'a self) -> &'a [u8] { unsafe { let p_payload_len = &(*self.ptr).evt_serial.evt.payload_len as *const u8; let p_payload = &(*self.ptr).evt_serial.evt.payload as *const u8; @@ -125,71 +126,35 @@ impl EvtBox { } } - // TODO: bring back acl + /// writes an underlying [`EvtPacket`] into the provided buffer. + /// Returns the number of bytes that were written. + /// Returns an error if event kind is unknown or if provided buffer size is not enough. + pub fn serial<'a>(&'a self) -> &'a [u8] { + unsafe { + let evt_serial: *const EvtSerial = &(*self.ptr).evt_serial; + let evt_serial_buf: *const u8 = evt_serial.cast(); - // /// writes an underlying [`EvtPacket`] into the provided buffer. - // /// Returns the number of bytes that were written. - // /// Returns an error if event kind is unknown or if provided buffer size is not enough. - // #[allow(clippy::result_unit_err)] - // pub fn write(&self, buf: &mut [u8]) -> Result { - // unsafe { - // let evt_kind = TlPacketType::try_from((*self.ptr).evt_serial.kind)?; - // - // let evt_data: *const EvtPacket = self.ptr.cast(); - // let evt_serial: *const EvtSerial = &(*evt_data).evt_serial; - // let evt_serial_buf: *const u8 = evt_serial.cast(); - // - // let acl_data: *const AclDataPacket = self.ptr.cast(); - // let acl_serial: *const AclDataSerial = &(*acl_data).acl_data_serial; - // let acl_serial_buf: *const u8 = acl_serial.cast(); - // - // if let TlPacketType::AclData = evt_kind { - // let len = (*acl_serial).length as usize + 5; - // if len > buf.len() { - // return Err(()); - // } - // - // core::ptr::copy(evt_serial_buf, buf.as_mut_ptr(), len); - // - // Ok(len) - // } else { - // let len = (*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE; - // if len > buf.len() { - // return Err(()); - // } - // - // core::ptr::copy(acl_serial_buf, buf.as_mut_ptr(), len); - // - // Ok(len) - // } - // } - // } - // - // /// returns the size of a buffer required to hold this event - // #[allow(clippy::result_unit_err)] - // pub fn size(&self) -> Result { - // unsafe { - // let evt_kind = TlPacketType::try_from((*self.ptr).evt_serial.kind)?; - // - // let evt_data: *const EvtPacket = self.ptr.cast(); - // let evt_serial: *const EvtSerial = &(*evt_data).evt_serial; - // - // let acl_data: *const AclDataPacket = self.ptr.cast(); - // let acl_serial: *const AclDataSerial = &(*acl_data).acl_data_serial; - // - // if let TlPacketType::AclData = evt_kind { - // Ok((*acl_serial).length as usize + 5) - // } else { - // Ok((*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE) - // } - // } - // } + let len = (*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE; + + slice::from_raw_parts(evt_serial_buf, len) + } + } } impl Drop for EvtBox { fn drop(&mut self) { - trace!("evt box drop packet"); + #[cfg(feature = "ble")] + unsafe { + use crate::mm; - unsafe { mm::MemoryManager::drop_event_packet(self.ptr) }; + mm::MemoryManager::drop_event_packet(self.ptr) + }; + + #[cfg(feature = "mac")] + unsafe { + use crate::mac; + + mac::Mac::drop_event_packet(self.ptr) + } } } diff --git a/embassy-stm32-wpan/src/lhci.rs b/embassy-stm32-wpan/src/lhci.rs new file mode 100644 index 00000000..62116a69 --- /dev/null +++ b/embassy-stm32-wpan/src/lhci.rs @@ -0,0 +1,111 @@ +use crate::cmd::CmdPacket; +use crate::consts::{TlPacketType, TL_EVT_HEADER_SIZE}; +use crate::evt::{CcEvt, EvtPacket, EvtSerial}; +use crate::tables::{DeviceInfoTable, RssInfoTable, SafeBootInfoTable, WirelessFwInfoTable}; +use crate::TL_REF_TABLE; + +const TL_BLEEVT_CC_OPCODE: u8 = 0x0e; +const LHCI_OPCODE_C1_DEVICE_INF: u16 = 0xfd62; + +const PACKAGE_DATA_PTR: *const u8 = 0x1FFF_7500 as _; +const UID64_PTR: *const u32 = 0x1FFF_7580 as _; + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct LhciC1DeviceInformationCcrp { + pub status: u8, + pub rev_id: u16, + pub dev_code_id: u16, + pub package_type: u8, + pub device_type_id: u8, + pub st_company_id: u32, + pub uid64: u32, + + pub uid96_0: u32, + pub uid96_1: u32, + pub uid96_2: u32, + + pub safe_boot_info_table: SafeBootInfoTable, + pub rss_info_table: RssInfoTable, + pub wireless_fw_info_table: WirelessFwInfoTable, + + pub app_fw_inf: u32, +} + +impl Default for LhciC1DeviceInformationCcrp { + fn default() -> Self { + let DeviceInfoTable { + safe_boot_info_table, + rss_info_table, + wireless_fw_info_table, + } = unsafe { &*(*TL_REF_TABLE.as_ptr()).device_info_table }.clone(); + + let device_id = stm32_device_signature::device_id(); + let uid96_0 = (device_id[3] as u32) << 24 + | (device_id[2] as u32) << 16 + | (device_id[1] as u32) << 8 + | device_id[0] as u32; + let uid96_1 = (device_id[7] as u32) << 24 + | (device_id[6] as u32) << 16 + | (device_id[5] as u32) << 8 + | device_id[4] as u32; + let uid96_2 = (device_id[11] as u32) << 24 + | (device_id[10] as u32) << 16 + | (device_id[9] as u32) << 8 + | device_id[8] as u32; + + let package_type = unsafe { *PACKAGE_DATA_PTR }; + let uid64 = unsafe { *UID64_PTR }; + let st_company_id = unsafe { *UID64_PTR.offset(1) } >> 8 & 0x00FF_FFFF; + let device_type_id = (unsafe { *UID64_PTR.offset(1) } & 0x000000FF) as u8; + + LhciC1DeviceInformationCcrp { + status: 0, + rev_id: 0, + dev_code_id: 0, + package_type, + device_type_id, + st_company_id, + uid64, + uid96_0, + uid96_1, + uid96_2, + safe_boot_info_table, + rss_info_table, + wireless_fw_info_table, + app_fw_inf: (1 << 8), // 0.0.1 + } + } +} + +impl LhciC1DeviceInformationCcrp { + pub fn new() -> Self { + Self::default() + } + + pub fn write(&self, cmd_packet: &mut CmdPacket) { + let self_size = core::mem::size_of::(); + + unsafe { + let cmd_packet_ptr: *mut CmdPacket = cmd_packet; + let evet_packet_ptr: *mut EvtPacket = cmd_packet_ptr.cast(); + + let evt_serial: *mut EvtSerial = &mut (*evet_packet_ptr).evt_serial; + let evt_payload = (*evt_serial).evt.payload.as_mut_ptr(); + let evt_cc: *mut CcEvt = evt_payload.cast(); + let evt_cc_payload_buf = (*evt_cc).payload.as_mut_ptr(); + + (*evt_serial).kind = TlPacketType::LocRsp as u8; + (*evt_serial).evt.evt_code = TL_BLEEVT_CC_OPCODE; + (*evt_serial).evt.payload_len = TL_EVT_HEADER_SIZE as u8 + self_size as u8; + + (*evt_cc).cmd_code = LHCI_OPCODE_C1_DEVICE_INF; + (*evt_cc).num_cmd = 1; + + let self_ptr: *const LhciC1DeviceInformationCcrp = self; + let self_buf = self_ptr.cast(); + + core::ptr::copy(self_buf, evt_cc_payload_buf, self_size); + } + } +} diff --git a/embassy-stm32-wpan/src/lib.rs b/embassy-stm32-wpan/src/lib.rs index 833db0df..bf0f0466 100644 --- a/embassy-stm32-wpan/src/lib.rs +++ b/embassy-stm32-wpan/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![cfg_attr(feature = "ble", feature(async_fn_in_trait))] // This must go FIRST so that all the other modules see its macros. pub mod fmt; @@ -6,148 +7,41 @@ pub mod fmt; use core::mem::MaybeUninit; use core::sync::atomic::{compiler_fence, Ordering}; -use ble::Ble; -use cmd::CmdPacket; use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_stm32::interrupt; -use embassy_stm32::interrupt::typelevel::Interrupt; use embassy_stm32::ipcc::{Config, Ipcc, ReceiveInterruptHandler, TransmitInterruptHandler}; use embassy_stm32::peripherals::IPCC; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::channel::Channel; -use embassy_sync::signal::Signal; -use evt::{CcEvt, EvtBox}; use mm::MemoryManager; use sys::Sys; -use tables::{ - BleTable, DeviceInfoTable, Mac802_15_4Table, MemManagerTable, RefTable, SysTable, ThreadTable, TracesTable, -}; +use tables::*; use unsafe_linked_list::LinkedListNode; +#[cfg(feature = "ble")] pub mod ble; pub mod channels; pub mod cmd; pub mod consts; pub mod evt; +pub mod lhci; +#[cfg(feature = "mac")] +pub mod mac; pub mod mm; pub mod shci; pub mod sys; pub mod tables; pub mod unsafe_linked_list; -#[link_section = "TL_REF_TABLE"] -pub static mut TL_REF_TABLE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM1"] -static mut TL_DEVICE_INFO_TABLE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM1"] -static mut TL_BLE_TABLE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM1"] -static mut TL_THREAD_TABLE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM1"] -static mut TL_SYS_TABLE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM1"] -static mut TL_MEM_MANAGER_TABLE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM1"] -static mut TL_TRACES_TABLE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM1"] -static mut TL_MAC_802_15_4_TABLE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM2"] -static mut FREE_BUF_QUEUE: MaybeUninit = MaybeUninit::uninit(); - -// Not in shared RAM -static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit = MaybeUninit::uninit(); - -#[allow(dead_code)] // Not used currently but reserved -#[link_section = "MB_MEM2"] -static mut TRACES_EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); - type PacketHeader = LinkedListNode; -const TL_PACKET_HEADER_SIZE: usize = core::mem::size_of::(); -const TL_EVT_HEADER_SIZE: usize = 3; -const TL_CS_EVT_SIZE: usize = core::mem::size_of::(); - -#[link_section = "MB_MEM2"] -static mut CS_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE]> = - MaybeUninit::uninit(); - -#[link_section = "MB_MEM2"] -static mut EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM2"] -static mut SYSTEM_EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM2"] -pub static mut SYS_CMD_BUF: MaybeUninit = MaybeUninit::uninit(); - -/** - * Queue length of BLE Event - * This parameter defines the number of asynchronous events that can be stored in the HCI layer before - * being reported to the application. When a command is sent to the BLE core coprocessor, the HCI layer - * is waiting for the event with the Num_HCI_Command_Packets set to 1. The receive queue shall be large - * enough to store all asynchronous events received in between. - * When CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE is set to 27, this allow to store three 255 bytes long asynchronous events - * between the HCI command and its event. - * This parameter depends on the value given to CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE. When the queue size is to small, - * the system may hang if the queue is full with asynchronous events and the HCI layer is still waiting - * for a CC/CS event, In that case, the notification TL_BLE_HCI_ToNot() is called to indicate - * to the application a HCI command did not receive its command event within 30s (Default HCI Timeout). - */ -const CFG_TLBLE_EVT_QUEUE_LENGTH: usize = 5; -const CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE: usize = 255; -const TL_BLE_EVENT_FRAME_SIZE: usize = TL_EVT_HEADER_SIZE + CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE; - -const fn divc(x: usize, y: usize) -> usize { - ((x) + (y) - 1) / (y) -} - -const POOL_SIZE: usize = CFG_TLBLE_EVT_QUEUE_LENGTH * 4 * divc(TL_PACKET_HEADER_SIZE + TL_BLE_EVENT_FRAME_SIZE, 4); - -#[link_section = "MB_MEM2"] -static mut EVT_POOL: MaybeUninit<[u8; POOL_SIZE]> = MaybeUninit::uninit(); - -#[link_section = "MB_MEM2"] -static mut SYS_SPARE_EVT_BUF: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]> = - MaybeUninit::uninit(); - -#[link_section = "MB_MEM2"] -static mut BLE_SPARE_EVT_BUF: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]> = - MaybeUninit::uninit(); - -#[link_section = "MB_MEM2"] -static mut BLE_CMD_BUFFER: MaybeUninit = MaybeUninit::uninit(); - -#[link_section = "MB_MEM2"] -// fuck these "magic" numbers from ST ---v---v -static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251]> = MaybeUninit::uninit(); - -// TODO: remove these items - -#[allow(dead_code)] -/// current event that is produced during IPCC IRQ handler execution -/// on SYS channel -static EVT_CHANNEL: Channel = Channel::new(); - -#[allow(dead_code)] -/// last received Command Complete event -static LAST_CC_EVT: Signal = Signal::new(); - -static STATE: Signal = Signal::new(); - pub struct TlMbox<'d> { _ipcc: PeripheralRef<'d, IPCC>, pub sys_subsystem: Sys, pub mm_subsystem: MemoryManager, - pub ble_subsystem: Ble, + #[cfg(feature = "ble")] + pub ble_subsystem: ble::Ble, + #[cfg(feature = "mac")] + pub mac_subsystem: mac::Mac, } impl<'d> TlMbox<'d> { @@ -232,24 +126,14 @@ impl<'d> TlMbox<'d> { Ipcc::enable(config); - let sys = sys::Sys::new(); - let ble = ble::Ble::new(); - let mm = mm::MemoryManager::new(); - - // enable interrupts - interrupt::typelevel::IPCC_C1_RX::unpend(); - interrupt::typelevel::IPCC_C1_TX::unpend(); - - unsafe { interrupt::typelevel::IPCC_C1_RX::enable() }; - unsafe { interrupt::typelevel::IPCC_C1_TX::enable() }; - - STATE.reset(); - Self { _ipcc: ipcc, - sys_subsystem: sys, - ble_subsystem: ble, - mm_subsystem: mm, + sys_subsystem: sys::Sys::new(), + #[cfg(feature = "ble")] + ble_subsystem: ble::Ble::new(), + #[cfg(feature = "mac")] + mac_subsystem: mac::Mac::new(), + mm_subsystem: mm::MemoryManager::new(), } } } diff --git a/embassy-stm32-wpan/src/mac.rs b/embassy-stm32-wpan/src/mac.rs new file mode 100644 index 00000000..d2be1b85 --- /dev/null +++ b/embassy-stm32-wpan/src/mac.rs @@ -0,0 +1,111 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ptr; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_futures::poll_once; +use embassy_stm32::ipcc::Ipcc; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::channels; +use crate::cmd::CmdPacket; +use crate::consts::TlPacketType; +use crate::evt::{EvtBox, EvtPacket}; +use crate::tables::{ + Mac802_15_4Table, MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER, TL_MAC_802_15_4_TABLE, +}; + +static MAC_WAKER: AtomicWaker = AtomicWaker::new(); +static MAC_EVT_OUT: AtomicBool = AtomicBool::new(false); + +pub struct Mac { + phantom: PhantomData, +} + +impl Mac { + pub(crate) fn new() -> Self { + unsafe { + TL_MAC_802_15_4_TABLE.as_mut_ptr().write_volatile(Mac802_15_4Table { + p_cmdrsp_buffer: MAC_802_15_4_CMD_BUFFER.as_mut_ptr().cast(), + p_notack_buffer: MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr().cast(), + evt_queue: ptr::null_mut(), + }); + } + + Self { phantom: PhantomData } + } + + /// SAFETY: passing a pointer to something other than a managed event packet is UB + pub(crate) unsafe fn drop_event_packet(_: *mut EvtPacket) { + // Write the ack + CmdPacket::write_into( + MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _, + TlPacketType::OtAck, + 0, + &[], + ); + + // Clear the rx flag + let _ = poll_once(Ipcc::receive::( + channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL, + || None, + )); + + // Allow a new read call + MAC_EVT_OUT.store(false, Ordering::SeqCst); + MAC_WAKER.wake(); + } + + /// `HW_IPCC_MAC_802_15_4_EvtNot` + /// + /// This function will stall if the previous `EvtBox` has not been dropped + pub async fn read(&self) -> EvtBox { + // Wait for the last event box to be dropped + poll_fn(|cx| { + MAC_WAKER.register(cx.waker()); + if MAC_EVT_OUT.load(Ordering::SeqCst) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + // Return a new event box + Ipcc::receive(channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL, || unsafe { + // The closure is not async, therefore the closure must execute to completion (cannot be dropped) + // Therefore, the event box is guaranteed to be cleaned up if it's not leaked + MAC_EVT_OUT.store(true, Ordering::SeqCst); + + Some(EvtBox::new(MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _)) + }) + .await + } + + /// `HW_IPCC_MAC_802_15_4_CmdEvtNot` + pub async fn write_and_get_response(&self, opcode: u16, payload: &[u8]) -> u8 { + self.write(opcode, payload).await; + Ipcc::flush(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL).await; + + unsafe { + let p_event_packet = MAC_802_15_4_CMD_BUFFER.as_ptr() as *const EvtPacket; + let p_mac_rsp_evt = &((*p_event_packet).evt_serial.evt.payload) as *const u8; + + ptr::read_volatile(p_mac_rsp_evt) + } + } + + /// `TL_MAC_802_15_4_SendCmd` + pub async fn write(&self, opcode: u16, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_MAC_802_15_4_CMD_RSP_CHANNEL, || unsafe { + CmdPacket::write_into( + MAC_802_15_4_CMD_BUFFER.as_mut_ptr(), + TlPacketType::OtCmd, + opcode, + payload, + ); + }) + .await; + } +} diff --git a/embassy-stm32-wpan/src/mm.rs b/embassy-stm32-wpan/src/mm.rs index 21f42409..047fddcd 100644 --- a/embassy-stm32-wpan/src/mm.rs +++ b/embassy-stm32-wpan/src/mm.rs @@ -1,22 +1,23 @@ //! Memory manager routines - use core::future::poll_fn; use core::marker::PhantomData; +use core::mem::MaybeUninit; use core::task::Poll; use cortex_m::interrupt; use embassy_stm32::ipcc::Ipcc; use embassy_sync::waitqueue::AtomicWaker; +use crate::channels; +use crate::consts::POOL_SIZE; use crate::evt::EvtPacket; -use crate::tables::MemManagerTable; -use crate::unsafe_linked_list::LinkedListNode; -use crate::{ - channels, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUF_QUEUE, LOCAL_FREE_BUF_QUEUE, POOL_SIZE, SYS_SPARE_EVT_BUF, - TL_MEM_MANAGER_TABLE, +use crate::tables::{ + MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUF_QUEUE, SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE, }; +use crate::unsafe_linked_list::LinkedListNode; static MM_WAKER: AtomicWaker = AtomicWaker::new(); +static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit = MaybeUninit::uninit(); pub struct MemoryManager { phantom: PhantomData, @@ -42,7 +43,8 @@ impl MemoryManager { Self { phantom: PhantomData } } - /// SAFETY: passing a pointer to something other than an event packet is UB + #[allow(dead_code)] + /// SAFETY: passing a pointer to something other than a managed event packet is UB pub(crate) unsafe fn drop_event_packet(evt: *mut EvtPacket) { interrupt::free(|_| unsafe { LinkedListNode::insert_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), evt as *mut _); diff --git a/embassy-stm32-wpan/src/shci.rs b/embassy-stm32-wpan/src/shci.rs index cdf027d5..30d68971 100644 --- a/embassy-stm32-wpan/src/shci.rs +++ b/embassy-stm32-wpan/src/shci.rs @@ -1,39 +1,346 @@ use core::{mem, slice}; -use super::{TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE}; +use crate::consts::{TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE}; -pub const SCHI_OPCODE_BLE_INIT: u16 = 0xfc66; +const SHCI_OGF: u16 = 0x3F; -#[derive(Debug, Clone, Copy)] +const fn opcode(ogf: u16, ocf: u16) -> isize { + ((ogf << 10) + ocf) as isize +} + +#[allow(dead_code)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SchiCommandStatus { + ShciSuccess = 0x00, + ShciUnknownCmd = 0x01, + ShciMemoryCapacityExceededErrCode = 0x07, + ShciErrUnsupportedFeature = 0x11, + ShciErrInvalidHciCmdParams = 0x12, + ShciErrInvalidParams = 0x42, /* only used for release < v1.13.0 */ + ShciErrInvalidParamsV2 = 0x92, /* available for release >= v1.13.0 */ + ShciFusCmdNotSupported = 0xFF, +} + +impl TryFrom for SchiCommandStatus { + type Error = (); + + fn try_from(v: u8) -> Result { + match v { + x if x == SchiCommandStatus::ShciSuccess as u8 => Ok(SchiCommandStatus::ShciSuccess), + x if x == SchiCommandStatus::ShciUnknownCmd as u8 => Ok(SchiCommandStatus::ShciUnknownCmd), + x if x == SchiCommandStatus::ShciMemoryCapacityExceededErrCode as u8 => { + Ok(SchiCommandStatus::ShciMemoryCapacityExceededErrCode) + } + x if x == SchiCommandStatus::ShciErrUnsupportedFeature as u8 => { + Ok(SchiCommandStatus::ShciErrUnsupportedFeature) + } + x if x == SchiCommandStatus::ShciErrInvalidHciCmdParams as u8 => { + Ok(SchiCommandStatus::ShciErrInvalidHciCmdParams) + } + x if x == SchiCommandStatus::ShciErrInvalidParams as u8 => Ok(SchiCommandStatus::ShciErrInvalidParams), /* only used for release < v1.13.0 */ + x if x == SchiCommandStatus::ShciErrInvalidParamsV2 as u8 => Ok(SchiCommandStatus::ShciErrInvalidParamsV2), /* available for release >= v1.13.0 */ + x if x == SchiCommandStatus::ShciFusCmdNotSupported as u8 => Ok(SchiCommandStatus::ShciFusCmdNotSupported), + _ => Err(()), + } + } +} + +#[allow(dead_code)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ShciOpcode { + // 0x50 reserved + // 0x51 reserved + FusGetState = opcode(SHCI_OGF, 0x52), + // 0x53 reserved + FusFirmwareUpgrade = opcode(SHCI_OGF, 0x54), + FusFirmwareDelete = opcode(SHCI_OGF, 0x55), + FusUpdateAuthKey = opcode(SHCI_OGF, 0x56), + FusLockAuthKey = opcode(SHCI_OGF, 0x57), + FusStoreUserKey = opcode(SHCI_OGF, 0x58), + FusLoadUserKey = opcode(SHCI_OGF, 0x59), + FusStartWirelessStack = opcode(SHCI_OGF, 0x5a), + // 0x5b reserved + // 0x5c reserved + FusLockUserKey = opcode(SHCI_OGF, 0x5d), + FusUnloadUserKey = opcode(SHCI_OGF, 0x5e), + FusActivateAntirollback = opcode(SHCI_OGF, 0x5f), + // 0x60 reserved + // 0x61 reserved + // 0x62 reserved + // 0x63 reserved + // 0x64 reserved + // 0x65 reserved + BleInit = opcode(SHCI_OGF, 0x66), + ThreadInit = opcode(SHCI_OGF, 0x67), + DebugInit = opcode(SHCI_OGF, 0x68), + FlashEraseActivity = opcode(SHCI_OGF, 0x69), + ConcurrentSetMode = opcode(SHCI_OGF, 0x6a), + FlashStoreData = opcode(SHCI_OGF, 0x6b), + FlashEraseData = opcode(SHCI_OGF, 0x6c), + RadioAllowLowPower = opcode(SHCI_OGF, 0x6d), + Mac802_15_4Init = opcode(SHCI_OGF, 0x6e), + ReInit = opcode(SHCI_OGF, 0x6f), + ZigbeeInit = opcode(SHCI_OGF, 0x70), + LldTestsInit = opcode(SHCI_OGF, 0x71), + ExtraConfig = opcode(SHCI_OGF, 0x72), + SetFlashActivityControl = opcode(SHCI_OGF, 0x73), + BleLldInit = opcode(SHCI_OGF, 0x74), + Config = opcode(SHCI_OGF, 0x75), + ConcurrentGetNextBleEvtTime = opcode(SHCI_OGF, 0x76), + ConcurrentEnableNext802_15_4EvtNotification = opcode(SHCI_OGF, 0x77), + Mac802_15_4DeInit = opcode(SHCI_OGF, 0x78), +} + +pub const SHCI_C2_CONFIG_EVTMASK1_BIT0_ERROR_NOTIF_ENABLE: u8 = 1 << 0; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE: u8 = 1 << 1; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT2_THREAD_NVM_RAM_UPDATE_ENABLE: u8 = 1 << 2; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT3_NVM_START_WRITE_ENABLE: u8 = 1 << 3; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT4_NVM_END_WRITE_ENABLE: u8 = 1 << 4; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT5_NVM_START_ERASE_ENABLE: u8 = 1 << 5; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT6_NVM_END_ERASE_ENABLE: u8 = 1 << 6; + +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct ShciConfigParam { + pub payload_cmd_size: u8, + pub config: u8, + pub event_mask: u8, + pub spare: u8, + pub ble_nvm_ram_address: u32, + pub thread_nvm_ram_address: u32, + pub revision_id: u16, + pub device_id: u16, +} + +impl ShciConfigParam { + pub fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } + } +} + +impl Default for ShciConfigParam { + fn default() -> Self { + Self { + payload_cmd_size: (mem::size_of::() - 1) as u8, + config: 0, + event_mask: SHCI_C2_CONFIG_EVTMASK1_BIT0_ERROR_NOTIF_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT2_THREAD_NVM_RAM_UPDATE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT3_NVM_START_WRITE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT4_NVM_END_WRITE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT5_NVM_START_ERASE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT6_NVM_END_ERASE_ENABLE, + spare: 0, + ble_nvm_ram_address: 0, + thread_nvm_ram_address: 0, + revision_id: 0, + device_id: 0, + } + } +} + +#[derive(Clone, Copy)] #[repr(C, packed)] pub struct ShciBleInitCmdParam { - /// NOT USED CURRENTLY + /// NOT USED - shall be set to 0 pub p_ble_buffer_address: u32, - - /// Size of the Buffer allocated in pBleBufferAddress + /// NOT USED - shall be set to 0 pub ble_buffer_size: u32, - + /// Maximum number of attribute records related to all the required characteristics (excluding the services) + /// that can be stored in the GATT database, for the specific BLE user application. + /// For each characteristic, the number of attribute records goes from two to five depending on the characteristic properties: + /// - minimum of two (one for declaration and one for the value) + /// - add one more record for each additional property: notify or indicate, broadcast, extended property. + /// The total calculated value must be increased by 9, due to the records related to the standard attribute profile and + /// GAP service characteristics, and automatically added when initializing GATT and GAP layers + /// - Min value: + 9 + /// - Max value: depending on the GATT database defined by user application pub num_attr_record: u16, + /// Defines the maximum number of services that can be stored in the GATT database. Note that the GAP and GATT services + /// are automatically added at initialization so this parameter must be the number of user services increased by two. + /// - Min value: + 2 + /// - Max value: depending GATT database defined by user application pub num_attr_serv: u16, + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Size of the storage area for the attribute values. + /// Each characteristic contributes to the attrValueArrSize value as follows: + /// - Characteristic value length plus: + /// + 5 bytes if characteristic UUID is 16 bits + /// + 19 bytes if characteristic UUID is 128 bits + /// + 2 bytes if characteristic has a server configuration descriptor + /// + 2 bytes * NumOfLinks if the characteristic has a client configuration descriptor + /// + 2 bytes if the characteristic has extended properties + /// Each descriptor contributes to the attrValueArrSize value as follows: + /// - Descriptor length pub attr_value_arr_size: u16, + /// Maximum number of BLE links supported + /// - Min value: 1 + /// - Max value: 8 pub num_of_links: u8, + /// Disable/enable the extended packet length BLE 5.0 feature + /// - Disable: 0 + /// - Enable: 1 pub extended_packet_length_enable: u8, - pub pr_write_list_size: u8, - pub mb_lock_count: u8, - + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Maximum number of supported "prepare write request" + /// - Min value: given by the macro DEFAULT_PREP_WRITE_LIST_SIZE + /// - Max value: a value higher than the minimum required can be specified, but it is not recommended + pub prepare_write_list_size: u8, + /// NOTE: This parameter is overwritten by the CPU2 with an hardcoded optimal value when the parameter "Options" is set to "LL_only" + /// ( see Options description in that structure ) + /// + /// Number of allocated memory blocks for the BLE stack + /// - Min value: given by the macro MBLOCKS_CALC + /// - Max value: a higher value can improve data throughput performance, but uses more memory + pub block_count: u8, + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Maximum ATT MTU size supported + /// - Min value: 23 + /// - Max value: 512 pub att_mtu: u16, + /// The sleep clock accuracy (ppm value) that used in BLE connected slave mode to calculate the window widening + /// (in combination with the sleep clock accuracy sent by master in CONNECT_REQ PDU), + /// refer to BLE 5.0 specifications - Vol 6 - Part B - chap 4.5.7 and 4.2.2 + /// - Min value: 0 + /// - Max value: 500 (worst possible admitted by specification) pub slave_sca: u16, + /// The sleep clock accuracy handled in master mode. It is used to determine the connection and advertising events timing. + /// It is transmitted to the slave in CONNEC_REQ PDU used by the slave to calculate the window widening, + /// see SlaveSca and Bluetooth Core Specification v5.0 Vol 6 - Part B - chap 4.5.7 and 4.2.2 + /// Possible values: + /// - 251 ppm to 500 ppm: 0 + /// - 151 ppm to 250 ppm: 1 + /// - 101 ppm to 150 ppm: 2 + /// - 76 ppm to 100 ppm: 3 + /// - 51 ppm to 75 ppm: 4 + /// - 31 ppm to 50 ppm: 5 + /// - 21 ppm to 30 ppm: 6 + /// - 0 ppm to 20 ppm: 7 pub master_sca: u8, + /// Some information for Low speed clock mapped in bits field + /// - bit 0: + /// - 1: Calibration for the RF system wakeup clock source + /// - 0: No calibration for the RF system wakeup clock source + /// - bit 1: + /// - 1: STM32W5M Module device + /// - 0: Other devices as STM32WBxx SOC, STM32WB1M module + /// - bit 2: + /// - 1: HSE/1024 Clock config + /// - 0: LSE Clock config pub ls_source: u8, + /// This parameter determines the maximum duration of a slave connection event. When this duration is reached the slave closes + /// the current connections event (whatever is the CE_length parameter specified by the master in HCI_CREATE_CONNECTION HCI command), + /// expressed in units of 625/256 µs (~2.44 µs) + /// - Min value: 0 (if 0 is specified, the master and slave perform only a single TX-RX exchange per connection event) + /// - Max value: 1638400 (4000 ms). A higher value can be specified (max 0xFFFFFFFF) but results in a maximum connection time + /// of 4000 ms as specified. In this case the parameter is not applied, and the predicted CE length calculated on slave is not shortened pub max_conn_event_length: u32, + /// Startup time of the high speed (16 or 32 MHz) crystal oscillator in units of 625/256 µs (~2.44 µs). + /// - Min value: 0 + /// - Max value: 820 (~2 ms). A higher value can be specified, but the value that implemented in stack is forced to ~2 ms pub hs_startup_time: u16, + /// Viterbi implementation in BLE LL reception. + /// - 0: Enable + /// - 1: Disable pub viterbi_enable: u8, - pub ll_only: u8, + /// - bit 0: + /// - 1: LL only + /// - 0: LL + host + /// - bit 1: + /// - 1: no service change desc. + /// - 0: with service change desc. + /// - bit 2: + /// - 1: device name Read-Only + /// - 0: device name R/W + /// - bit 3: + /// - 1: extended advertizing supported + /// - 0: extended advertizing not supported + /// - bit 4: + /// - 1: CS Algo #2 supported + /// - 0: CS Algo #2 not supported + /// - bit 5: + /// - 1: Reduced GATT database in NVM + /// - 0: Full GATT database in NVM + /// - bit 6: + /// - 1: GATT caching is used + /// - 0: GATT caching is not used + /// - bit 7: + /// - 1: LE Power Class 1 + /// - 0: LE Power Classe 2-3 + /// - other bits: complete with Options_extension flag + pub options: u8, + /// Reserved for future use - shall be set to 0 pub hw_version: u8, + // /** + // * Maximum number of connection-oriented channels in initiator mode. + // * Range: 0 .. 64 + // */ + // pub max_coc_initiator_nbr: u8, + // + // /** + // * Minimum transmit power in dBm supported by the Controller. + // * Range: -127 .. 20 + // */ + // pub min_tx_power: i8, + // + // /** + // * Maximum transmit power in dBm supported by the Controller. + // * Range: -127 .. 20 + // */ + // pub max_tx_power: i8, + // + // /** + // * RX model configuration + // * - bit 0: 1: agc_rssi model improved vs RF blockers 0: Legacy agc_rssi model + // * - other bits: reserved ( shall be set to 0) + // */ + // pub rx_model_config: u8, + // + // /* Maximum number of advertising sets. + // * Range: 1 .. 8 with limitation: + // * This parameter is linked to max_adv_data_len such as both compliant with allocated Total memory computed with BLE_EXT_ADV_BUFFER_SIZE based + // * on Max Extended advertising configuration supported. + // * This parameter is considered by the CPU2 when Options has SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV flag set + // */ + // pub max_adv_set_nbr: u8, + // + // /* Maximum advertising data length (in bytes) + // * Range: 31 .. 1650 with limitation: + // * This parameter is linked to max_adv_set_nbr such as both compliant with allocated Total memory computed with BLE_EXT_ADV_BUFFER_SIZE based + // * on Max Extended advertising configuration supported. + // * This parameter is considered by the CPU2 when Options has SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV flag set + // */ + // pub max_adv_data_len: u16, + // + // /* RF TX Path Compensation Value (16-bit signed integer). Units: 0.1 dB. + // * Range: -1280 .. 1280 + // */ + // pub tx_path_compens: i16, + // + // /* RF RX Path Compensation Value (16-bit signed integer). Units: 0.1 dB. + // * Range: -1280 .. 1280 + // */ + // pub rx_path_compens: i16, + // + // /* BLE core specification version (8-bit unsigned integer). + // * values as: 11(5.2), 12(5.3) + // */ + // pub ble_core_version: u8, + // + // /** + // * Options flags extension + // * - bit 0: 1: appearance Writable 0: appearance Read-Only + // * - bit 1: 1: Enhanced ATT supported 0: Enhanced ATT not supported + // * - other bits: reserved ( shall be set to 0) + // */ + // pub options_extension: u8, } impl ShciBleInitCmdParam { - pub fn payload<'a>(&self) -> &'a [u8] { + pub fn payload<'a>(&'a self) -> &'a [u8] { unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } } } @@ -48,8 +355,8 @@ impl Default for ShciBleInitCmdParam { attr_value_arr_size: 1344, num_of_links: 2, extended_packet_length_enable: 1, - pr_write_list_size: 0x3A, - mb_lock_count: 0x79, + prepare_write_list_size: 0x3A, + block_count: 0x79, att_mtu: 156, slave_sca: 500, master_sca: 0, @@ -57,25 +364,12 @@ impl Default for ShciBleInitCmdParam { max_conn_event_length: 0xFFFFFFFF, hs_startup_time: 0x148, viterbi_enable: 1, - ll_only: 0, + options: 0, hw_version: 0, } } } -#[derive(Debug, Clone, Copy, Default)] -#[repr(C, packed)] -pub struct ShciHeader { - metadata: [u32; 3], -} - -#[derive(Debug, Clone, Copy)] -#[repr(C, packed)] -pub struct ShciBleInitCmdPacket { - pub header: ShciHeader, - pub param: ShciBleInitCmdParam, -} - pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; #[allow(dead_code)] // Not used currently but reserved const TL_BLE_EVT_CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_BLE_EVT_CS_PACKET_SIZE; diff --git a/embassy-stm32-wpan/src/sys.rs b/embassy-stm32-wpan/src/sys.rs index a185cd4f..2b699b72 100644 --- a/embassy-stm32-wpan/src/sys.rs +++ b/embassy-stm32-wpan/src/sys.rs @@ -1,9 +1,11 @@ use core::marker::PhantomData; +use core::ptr; use crate::cmd::CmdPacket; use crate::consts::TlPacketType; -use crate::evt::EvtBox; -use crate::shci::{ShciBleInitCmdParam, SCHI_OPCODE_BLE_INIT}; +use crate::evt::{CcEvt, EvtBox, EvtPacket}; +#[allow(unused_imports)] +use crate::shci::{SchiCommandStatus, ShciBleInitCmdParam, ShciOpcode}; use crate::tables::{SysTable, WirelessFwInfoTable}; use crate::unsafe_linked_list::LinkedListNode; use crate::{channels, Ipcc, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_DEVICE_INFO_TABLE, TL_SYS_TABLE}; @@ -39,21 +41,35 @@ impl Sys { } } - pub fn write(&self, opcode: u16, payload: &[u8]) { + pub async fn write(&self, opcode: ShciOpcode, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, || unsafe { + CmdPacket::write_into(SYS_CMD_BUF.as_mut_ptr(), TlPacketType::SysCmd, opcode as u16, payload); + }) + .await; + } + + /// `HW_IPCC_SYS_CmdEvtNot` + pub async fn write_and_get_response(&self, opcode: ShciOpcode, payload: &[u8]) -> SchiCommandStatus { + self.write(opcode, payload).await; + Ipcc::flush(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL).await; + unsafe { - CmdPacket::write_into(SYS_CMD_BUF.as_mut_ptr(), TlPacketType::SysCmd, opcode, payload); + let p_event_packet = SYS_CMD_BUF.as_ptr() as *const EvtPacket; + let p_command_event = &((*p_event_packet).evt_serial.evt.payload) as *const _ as *const CcEvt; + let p_payload = &((*p_command_event).payload) as *const u8; + + ptr::read_volatile(p_payload).try_into().unwrap() } } - pub async fn shci_c2_ble_init(&self, param: ShciBleInitCmdParam) { - debug!("sending SHCI"); + #[cfg(feature = "mac")] + pub async fn shci_c2_mac_802_15_4_init(&self) -> SchiCommandStatus { + self.write_and_get_response(ShciOpcode::Mac802_15_4Init, &[]).await + } - Ipcc::send(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, || { - self.write(SCHI_OPCODE_BLE_INIT, param.payload()); - }) - .await; - - Ipcc::flush(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL).await; + #[cfg(feature = "ble")] + pub async fn shci_c2_ble_init(&self, param: ShciBleInitCmdParam) -> SchiCommandStatus { + self.write_and_get_response(ShciOpcode::BleInit, param.payload()).await } /// `HW_IPCC_SYS_EvtNot` diff --git a/embassy-stm32-wpan/src/tables.rs b/embassy-stm32-wpan/src/tables.rs index 15121695..3f26282c 100644 --- a/embassy-stm32-wpan/src/tables.rs +++ b/embassy-stm32-wpan/src/tables.rs @@ -1,6 +1,9 @@ +use core::mem::MaybeUninit; + use bit_field::BitField; use crate::cmd::{AclDataPacket, CmdPacket}; +use crate::consts::{POOL_SIZE, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE}; use crate::unsafe_linked_list::LinkedListNode; #[derive(Debug, Copy, Clone)] @@ -161,6 +164,9 @@ pub struct Mac802_15_4Table { pub evt_queue: *const u8, } +#[repr(C, align(4))] +pub struct AlignedData([u8; L]); + /// Reference table. Contains pointers to all other tables. #[derive(Debug, Copy, Clone)] #[repr(C)] @@ -173,3 +179,94 @@ pub struct RefTable { pub traces_table: *const TracesTable, pub mac_802_15_4_table: *const Mac802_15_4Table, } + +// --------------------- ref table --------------------- +#[link_section = "TL_REF_TABLE"] +pub static mut TL_REF_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_DEVICE_INFO_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_BLE_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_THREAD_TABLE: MaybeUninit = MaybeUninit::uninit(); + +// #[link_section = "MB_MEM1"] +// pub static mut TL_LLD_TESTS_TABLE: MaybeUninit = MaybeUninit::uninit(); + +// #[link_section = "MB_MEM1"] +// pub static mut TL_BLE_LLD_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_SYS_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_MEM_MANAGER_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_TRACES_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_MAC_802_15_4_TABLE: MaybeUninit = MaybeUninit::uninit(); + +// #[link_section = "MB_MEM1"] +// pub static mut TL_ZIGBEE_TABLE: MaybeUninit = MaybeUninit::uninit(); + +// --------------------- tables --------------------- +#[link_section = "MB_MEM1"] +pub static mut FREE_BUF_QUEUE: MaybeUninit = MaybeUninit::uninit(); + +#[allow(dead_code)] +#[link_section = "MB_MEM1"] +pub static mut TRACES_EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); + +const CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; + +#[link_section = "MB_MEM2"] +pub static mut CS_BUFFER: MaybeUninit> = MaybeUninit::uninit(); + +#[link_section = "MB_MEM2"] +pub static mut EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM2"] +pub static mut SYSTEM_EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); + +// --------------------- app tables --------------------- +#[cfg(feature = "mac")] +#[link_section = "MB_MEM2"] +pub static mut MAC_802_15_4_CMD_BUFFER: MaybeUninit = MaybeUninit::uninit(); + +#[cfg(feature = "mac")] +const MAC_802_15_4_NOTIF_RSP_EVT_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255; + +#[cfg(feature = "mac")] +#[link_section = "MB_MEM2"] +pub static mut MAC_802_15_4_NOTIF_RSP_EVT_BUFFER: MaybeUninit> = + MaybeUninit::uninit(); + +#[link_section = "MB_MEM2"] +pub static mut EVT_POOL: MaybeUninit<[u8; POOL_SIZE]> = MaybeUninit::uninit(); + +#[link_section = "MB_MEM2"] +pub static mut SYS_CMD_BUF: MaybeUninit = MaybeUninit::uninit(); + +const SYS_SPARE_EVT_BUF_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255; + +#[link_section = "MB_MEM2"] +pub static mut SYS_SPARE_EVT_BUF: MaybeUninit> = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut BLE_CMD_BUFFER: MaybeUninit = MaybeUninit::uninit(); + +const BLE_SPARE_EVT_BUF_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255; + +#[link_section = "MB_MEM2"] +pub static mut BLE_SPARE_EVT_BUF: MaybeUninit> = MaybeUninit::uninit(); + +const HCI_ACL_DATA_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + 5 + 251; + +#[link_section = "MB_MEM2"] +// fuck these "magic" numbers from ST ---v---v +pub static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; HCI_ACL_DATA_BUFFER_SIZE]> = MaybeUninit::uninit(); diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/ipcc.rs index 3062226c..37f840c7 100644 --- a/embassy-stm32/src/ipcc.rs +++ b/embassy-stm32/src/ipcc.rs @@ -120,7 +120,6 @@ impl Ipcc { let regs = IPCC::regs(); Self::flush(channel).await; - compiler_fence(Ordering::SeqCst); f(); @@ -136,7 +135,7 @@ impl Ipcc { // This is a race, but is nice for debugging if regs.cpu(0).sr().read().chf(channel as usize) { - trace!("ipcc: ch {}: wait for tx free", channel as u8); + trace!("ipcc: ch {}: wait for tx free", channel as u8); } poll_fn(|cx| { @@ -165,7 +164,7 @@ impl Ipcc { loop { // This is a race, but is nice for debugging if !regs.cpu(1).sr().read().chf(channel as usize) { - trace!("ipcc: ch {}: wait for rx occupied", channel as u8); + trace!("ipcc: ch {}: wait for rx occupied", channel as u8); } poll_fn(|cx| { @@ -186,8 +185,7 @@ impl Ipcc { }) .await; - trace!("ipcc: ch {}: read data", channel as u8); - compiler_fence(Ordering::SeqCst); + trace!("ipcc: ch {}: read data", channel as u8); match f() { Some(ret) => return ret, @@ -237,7 +235,7 @@ pub(crate) mod sealed { } } - pub fn rx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker { + pub const fn rx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker { match channel { IpccChannel::Channel1 => &self.rx_wakers[0], IpccChannel::Channel2 => &self.rx_wakers[1], @@ -248,7 +246,7 @@ pub(crate) mod sealed { } } - pub fn tx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker { + pub const fn tx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker { match channel { IpccChannel::Channel1 => &self.tx_wakers[0], IpccChannel::Channel2 => &self.tx_wakers[1], diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 6627b786..8c417596 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -6,8 +6,24 @@ license = "MIT OR Apache-2.0" [features] default = ["nightly"] -nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-time/nightly", "embassy-time/unstable-traits", "static_cell/nightly", - "embassy-usb", "embedded-io/async", "embassy-net", "embassy-lora", "lora-phy", "lorawan-device", "lorawan"] +nightly = [ + "embedded-hal-async", + "embassy-executor/nightly", + "embassy-nrf/nightly", + "embassy-net/nightly", + "embassy-net-esp-hosted", + "embassy-nrf/unstable-traits", + "embassy-time/nightly", + "embassy-time/unstable-traits", + "static_cell/nightly", + "embassy-usb", + "embedded-io/async", + "embassy-net", + "embassy-lora", + "lora-phy", + "lorawan-device", + "lorawan", +] [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } @@ -22,6 +38,7 @@ embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["ti lora-phy = { version = "1", optional = true } lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true } lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true } +embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"], optional = true } defmt = "0.3" defmt-rtt = "0.4" @@ -35,3 +52,4 @@ rand = { version = "0.8.4", default-features = false } embedded-storage = "0.3.0" usbd-hid = "0.6.0" serde = { version = "1.0.136", default-features = false } +embedded-hal-async = { version = "0.2.0-alpha.1", optional = true } diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs new file mode 100644 index 00000000..4eb31b10 --- /dev/null +++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs @@ -0,0 +1,139 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{info, unwrap, warn}; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use static_cell::make_static; +use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: hosted::Runner< + 'static, + ExclusiveDevice, Output<'static, peripherals::P0_31>>, + Input<'static, AnyPin>, + Output<'static, peripherals::P1_05>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_nrf::init(Default::default()); + + let miso = p.P0_28; + let sck = p.P0_29; + let mosi = p.P0_30; + let cs = Output::new(p.P0_31, Level::High, OutputDrive::HighDrive); + let handshake = Input::new(p.P1_01.degrade(), Pull::Up); + let ready = Input::new(p.P1_04.degrade(), Pull::None); + let reset = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M32; + config.mode = spim::MODE_2; // !!! + let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); + let spi = ExclusiveDevice::new(spi, cs); + + let (device, mut control, runner) = embassy_net_esp_hosted::new( + make_static!(embassy_net_esp_hosted::State::new()), + spi, + handshake, + ready, + reset, + ) + .await; + + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init().await; + control.join(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/stm32wb/.cargo/config.toml b/examples/stm32wb/.cargo/config.toml index d23fdc51..35317a29 100644 --- a/examples/stm32wb/.cargo/config.toml +++ b/examples/stm32wb/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] # replace STM32WB55CCUx with your chip as listed in `probe-rs-cli chip list` -# runner = "probe-rs-cli run --chip STM32WB55CCUx --speed 1000 --connect-under-reset" +# runner = "probe-rs-cli run --chip STM32WB55RGVx --speed 1000 --connect-under-reset" runner = "teleprobe local run --chip STM32WB55RG --elf" [build] diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 83a44375..fbb2d918 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -20,3 +20,24 @@ embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } + + +[features] +default = ["ble"] +mac = ["embassy-stm32-wpan/mac"] +ble = ["embassy-stm32-wpan/ble"] + +[[bin]] +name = "tl_mbox_ble" +required-features = ["ble"] + +[[bin]] +name = "tl_mbox_mac" +required-features = ["mac"] + +[[bin]] +name = "eddystone_beacon" +required-features = ["ble"] + +[patch.crates-io] +stm32wb-hci = { git = "https://github.com/OueslatiGhaith/stm32wb-hci", rev = "9f663be"} \ No newline at end of file diff --git a/examples/stm32wb/src/bin/eddystone_beacon.rs b/examples/stm32wb/src/bin/eddystone_beacon.rs new file mode 100644 index 00000000..fdd5be4a --- /dev/null +++ b/examples/stm32wb/src/bin/eddystone_beacon.rs @@ -0,0 +1,249 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::time::Duration; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::ble::hci::host::uart::UartHci; +use embassy_stm32_wpan::ble::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType}; +use embassy_stm32_wpan::ble::hci::types::AdvertisingType; +use embassy_stm32_wpan::ble::hci::vendor::stm32wb::command::gap::{ + AdvertisingDataType, DiscoverableParameters, GapCommands, Role, +}; +use embassy_stm32_wpan::ble::hci::vendor::stm32wb::command::gatt::GattCommands; +use embassy_stm32_wpan::ble::hci::vendor::stm32wb::command::hal::{ConfigData, HalCommands, PowerLevel}; +use embassy_stm32_wpan::ble::hci::BdAddr; +use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut mbox = TlMbox::init(p.IPCC, Irqs, config); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("resetting BLE..."); + mbox.ble_subsystem.reset().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config public address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::public_address(get_bd_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config random address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::random_address(get_random_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config identity root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::identity_root(&get_irk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config encryption root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::encryption_root(&get_erk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config tx power level..."); + mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("GATT init..."); + mbox.ble_subsystem.init_gatt().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("GAP init..."); + mbox.ble_subsystem + .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + // info!("set scan response..."); + // mbox.ble_subsystem.le_set_scan_response_data(&[]).await.unwrap(); + // let response = mbox.ble_subsystem.read().await.unwrap(); + // defmt::info!("{}", response); + + info!("set discoverable..."); + mbox.ble_subsystem + .set_discoverable(&DiscoverableParameters { + advertising_type: AdvertisingType::NonConnectableUndirected, + advertising_interval: Some((Duration::from_millis(250), Duration::from_millis(250))), + address_type: OwnAddressType::Public, + filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan, + local_name: None, + advertising_data: &[], + conn_interval: (None, None), + }) + .await + .unwrap(); + + let response = mbox.ble_subsystem.read().await; + defmt::info!("{}", response); + + // remove some advertisement to decrease the packet size + info!("delete tx power ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::TxPowerLevel) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("delete conn interval ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::PeripheralConnectionInterval) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data..."); + mbox.ble_subsystem + .update_advertising_data(&eddystone_advertising_data()) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data type..."); + mbox.ble_subsystem + .update_advertising_data(&[3, AdvertisingDataType::UuidCompleteList16 as u8, 0xaa, 0xfe]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data flags..."); + mbox.ble_subsystem + .update_advertising_data(&[ + 2, + AdvertisingDataType::Flags as u8, + (0x02 | 0x04) as u8, // BLE general discoverable, without BR/EDR support + ]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + cortex_m::asm::wfi(); +} + +fn get_bd_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = lhci_info.device_type_id; + bytes[4] = (lhci_info.st_company_id & 0xff) as u8; + bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8; + + BdAddr(bytes) +} + +fn get_random_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = 0; + bytes[4] = 0x6E; + bytes[5] = 0xED; + + BdAddr(bytes) +} + +const BLE_CFG_IRK: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, +]; +const BLE_CFG_ERK: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, +]; + +fn get_irk() -> EncryptionKey { + EncryptionKey(BLE_CFG_IRK) +} + +fn get_erk() -> EncryptionKey { + EncryptionKey(BLE_CFG_ERK) +} + +fn eddystone_advertising_data() -> [u8; 24] { + const EDDYSTONE_URL: &[u8] = b"www.rust-lang.com"; + + let mut service_data = [0u8; 24]; + let url_len = EDDYSTONE_URL.len(); + + service_data[0] = 6 + url_len as u8; + service_data[1] = AdvertisingDataType::ServiceData as u8; + + // 16-bit eddystone uuid + service_data[2] = 0xaa; + service_data[3] = 0xFE; + + service_data[4] = 0x10; // URL frame type + service_data[5] = 22_i8 as u8; // calibrated TX power at 0m + service_data[6] = 0x03; // eddystone url prefix = https + + service_data[7..(7 + url_len)].copy_from_slice(EDDYSTONE_URL); + + service_data +} diff --git a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs b/examples/stm32wb/src/bin/tl_mbox_ble.rs similarity index 95% rename from examples/stm32wb/src/bin/tl_mbox_tx_rx.rs rename to examples/stm32wb/src/bin/tl_mbox_ble.rs index 439bd01a..a511e89a 100644 --- a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs +++ b/examples/stm32wb/src/bin/tl_mbox_ble.rs @@ -52,10 +52,10 @@ async fn main(_spawner: Spawner) { mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; info!("starting ble..."); - mbox.ble_subsystem.write(0x0c, &[]).await; + mbox.ble_subsystem.tl_write(0x0c, &[]).await; info!("waiting for ble..."); - let ble_event = mbox.ble_subsystem.read().await; + let ble_event = mbox.ble_subsystem.tl_read().await; info!("ble event: {}", ble_event.payload()); diff --git a/examples/stm32wb/src/bin/tl_mbox_mac.rs b/examples/stm32wb/src/bin/tl_mbox_mac.rs new file mode 100644 index 00000000..afd319a4 --- /dev/null +++ b/examples/stm32wb/src/bin/tl_mbox_mac.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + // + // info!("starting ble..."); + // mbox.ble_subsystem.t_write(0x0c, &[]).await; + // + // info!("waiting for ble..."); + // let ble_event = mbox.ble_subsystem.tl_read().await; + // + // info!("ble event: {}", ble_event.payload()); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml index 9735c87d..4f9ecc47 100644 --- a/tests/nrf/Cargo.toml +++ b/tests/nrf/Cargo.toml @@ -13,6 +13,10 @@ embassy-executor = { version = "0.2.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "nightly", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nightly", "unstable-traits", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } embedded-io = { version = "0.4.0", features = ["async"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] } +embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } +embedded-hal-async = { version = "0.2.0-alpha.1" } +static_cell = { version = "1.1", features = [ "nightly" ] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs new file mode 100644 index 00000000..277b985c --- /dev/null +++ b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs @@ -0,0 +1,270 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../common.rs"] +mod common; + +use defmt::{error, info, unwrap}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Ipv4Address, Stack, StackResources}; +use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::{with_timeout, Duration, Timer}; +use embedded_hal_async::spi::ExclusiveDevice; +use static_cell::make_static; +use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; + +teleprobe_meta::timeout!(120); + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: hosted::Runner< + 'static, + ExclusiveDevice, Output<'static, peripherals::P0_31>>, + Input<'static, AnyPin>, + Output<'static, peripherals::P1_05>, + >, +) -> ! { + runner.run().await +} + +type MyDriver = hosted::NetDriver<'static>; + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_nrf::init(Default::default()); + + let miso = p.P0_28; + let sck = p.P0_29; + let mosi = p.P0_30; + let cs = Output::new(p.P0_31, Level::High, OutputDrive::HighDrive); + let handshake = Input::new(p.P1_01.degrade(), Pull::Up); + let ready = Input::new(p.P1_04.degrade(), Pull::None); + let reset = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M32; + config.mode = spim::MODE_2; // !!! + let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); + let spi = ExclusiveDevice::new(spi, cs); + + let (device, mut control, runner) = embassy_net_esp_hosted::new( + make_static!(embassy_net_esp_hosted::State::new()), + spi, + handshake, + ready, + reset, + ) + .await; + + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init().await; + control.join(WIFI_NETWORK, WIFI_PASSWORD).await; + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + Config::dhcpv4(Default::default()), + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + info!("Waiting for DHCP up..."); + while stack.config_v4().is_none() { + Timer::after(Duration::from_millis(100)).await; + } + info!("IP addressing up!"); + + let down = test_download(stack).await; + let up = test_upload(stack).await; + let updown = test_upload_download(stack).await; + + assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS); + assert!(up > TEST_EXPECTED_UPLOAD_KBPS); + assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +// Test-only wifi network, no internet access! +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +const TEST_DURATION: usize = 10; +const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 150; +const TEST_EXPECTED_UPLOAD_KBPS: usize = 150; +const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 150; +const RX_BUFFER_SIZE: usize = 4096; +const TX_BUFFER_SIZE: usize = 4096; +const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2); +const DOWNLOAD_PORT: u16 = 4321; +const UPLOAD_PORT: u16 = 4322; +const UPLOAD_DOWNLOAD_PORT: u16 = 4323; + +async fn test_download(stack: &'static Stack) -> usize { + info!("Testing download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("download: {} kB/s", kbps); + kbps +} + +async fn test_upload(stack: &'static Stack) -> usize { + info!("Testing upload..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.write(&buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload: {} kB/s", kbps); + kbps +} + +async fn test_upload_download(stack: &'static Stack) -> usize { + info!("Testing upload+download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let (mut reader, mut writer) = socket.split(); + + let tx_buf = [0; 4096]; + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + let tx_fut = async { + loop { + match writer.write(&tx_buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(_) => {} + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }; + + let rx_fut = async { + loop { + match reader.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }; + + with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut)) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload+download: {} kB/s", kbps); + kbps +} diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs index 7a94ea19..9fc537a4 100644 --- a/tests/rp/src/bin/cyw43-perf.rs +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -63,20 +63,13 @@ async fn main(spawner: Spawner) { .set_power_management(cyw43::PowerManagementMode::PowerSave) .await; - let config = Config::dhcpv4(Default::default()); - //let config = embassy_net::Config::Static(embassy_net::Config { - // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), - // dns_servers: Vec::new(), - // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), - //}); - // Generate random seed let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. // Init network stack let stack = &*make_static!(Stack::new( net_device, - config, + Config::dhcpv4(Default::default()), make_static!(StackResources::<2>::new()), seed )); diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index 365f631b..c2422f7b 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -30,7 +30,7 @@ embassy-executor = { version = "0.2.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768", "defmt-timestamp-uptime"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-any"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg"] } +embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg", "ble"] } defmt = "0.3.0" defmt-rtt = "0.4" @@ -46,6 +46,9 @@ rand_chacha = { version = "0.3", default-features = false } chrono = { version = "^0.4", default-features = false, optional = true} +[patch.crates-io] +stm32wb-hci = { git = "https://github.com/OueslatiGhaith/stm32wb-hci", rev = "9f663be"} + # BEGIN TESTS # Generated by gen_test.py. DO NOT EDIT. [[bin]] diff --git a/tests/stm32/src/bin/tl_mbox.rs b/tests/stm32/src/bin/tl_mbox.rs index f6641ae3..76c736a5 100644 --- a/tests/stm32/src/bin/tl_mbox.rs +++ b/tests/stm32/src/bin/tl_mbox.rs @@ -6,43 +6,49 @@ #[path = "../common.rs"] mod common; -use core::mem; +use core::time::Duration; use common::*; use embassy_executor::Spawner; -use embassy_futures::poll_once; use embassy_stm32::bind_interrupts; use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::ble::hci::host::uart::UartHci; +use embassy_stm32_wpan::ble::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType}; +use embassy_stm32_wpan::ble::hci::types::AdvertisingType; +use embassy_stm32_wpan::ble::hci::vendor::stm32wb::command::gap::{ + AdvertisingDataType, DiscoverableParameters, GapCommands, Role, +}; +use embassy_stm32_wpan::ble::hci::vendor::stm32wb::command::gatt::GattCommands; +use embassy_stm32_wpan::ble::hci::vendor::stm32wb::command::hal::{ConfigData, HalCommands, PowerLevel}; +use embassy_stm32_wpan::ble::hci::BdAddr; +use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; use embassy_stm32_wpan::{mm, TlMbox}; -use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs{ IPCC_C1_RX => ReceiveInterruptHandler; IPCC_C1_TX => TransmitInterruptHandler; }); +const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; + #[embassy_executor::task] async fn run_mm_queue(memory_manager: mm::MemoryManager) { memory_manager.run_queue().await; } #[embassy_executor::main] -async fn main(spawner: Spawner) { +async fn main(_spawner: Spawner) { let p = embassy_stm32::init(config()); info!("Hello World!"); let config = Config::default(); - let mbox = TlMbox::init(p.IPCC, Irqs, config); + let mut mbox = TlMbox::init(p.IPCC, Irqs, config); - spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + // spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); - let ready_event = mbox.sys_subsystem.read().await; - let _ = poll_once(mbox.sys_subsystem.read()); // clear rx not - - info!("coprocessor ready {}", ready_event.payload()); - - // test memory manager - mem::drop(ready_event); + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); let fw_info = mbox.sys_subsystem.wireless_fw_info().unwrap(); let version_major = fw_info.version_major(); @@ -57,19 +63,188 @@ async fn main(spawner: Spawner) { version_major, version_minor, subversion, sram2a_size, sram2b_size ); - Timer::after(Duration::from_millis(50)).await; - mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; - info!("starting ble..."); - mbox.ble_subsystem.write(0x0c, &[]).await; + info!("resetting BLE..."); + mbox.ble_subsystem.reset().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); - info!("waiting for ble..."); - let ble_event = mbox.ble_subsystem.read().await; + info!("config public address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::public_address(get_bd_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); - info!("ble event: {}", ble_event.payload()); + info!("config random address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::random_address(get_random_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config identity root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::identity_root(&get_irk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config encryption root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::encryption_root(&get_erk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config tx power level..."); + mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("GATT init..."); + mbox.ble_subsystem.init_gatt().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("GAP init..."); + mbox.ble_subsystem + .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + // info!("set scan response..."); + // mbox.ble_subsystem.le_set_scan_response_data(&[]).await.unwrap(); + // let response = mbox.ble_subsystem.read().await.unwrap(); + // info!("{}", response); + + info!("set discoverable..."); + mbox.ble_subsystem + .set_discoverable(&DiscoverableParameters { + advertising_type: AdvertisingType::NonConnectableUndirected, + advertising_interval: Some((Duration::from_millis(250), Duration::from_millis(250))), + address_type: OwnAddressType::Public, + filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan, + local_name: None, + advertising_data: &[], + conn_interval: (None, None), + }) + .await + .unwrap(); + + let response = mbox.ble_subsystem.read().await; + info!("{}", response); + + // remove some advertisement to decrease the packet size + info!("delete tx power ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::TxPowerLevel) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("delete conn interval ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::PeripheralConnectionInterval) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data..."); + mbox.ble_subsystem + .update_advertising_data(&eddystone_advertising_data()) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data type..."); + mbox.ble_subsystem + .update_advertising_data(&[3, AdvertisingDataType::UuidCompleteList16 as u8, 0xaa, 0xfe]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data flags..."); + mbox.ble_subsystem + .update_advertising_data(&[ + 2, + AdvertisingDataType::Flags as u8, + (0x02 | 0x04) as u8, // BLE general discoverable, without BR/EDR support + ]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); - Timer::after(Duration::from_millis(150)).await; info!("Test OK"); cortex_m::asm::bkpt(); } + +fn get_bd_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = lhci_info.device_type_id; + bytes[4] = (lhci_info.st_company_id & 0xff) as u8; + bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8; + + BdAddr(bytes) +} + +fn get_random_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = 0; + bytes[4] = 0x6E; + bytes[5] = 0xED; + + BdAddr(bytes) +} + +const BLE_CFG_IRK: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, +]; +const BLE_CFG_ERK: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, +]; + +fn get_irk() -> EncryptionKey { + EncryptionKey(BLE_CFG_IRK) +} + +fn get_erk() -> EncryptionKey { + EncryptionKey(BLE_CFG_ERK) +} + +fn eddystone_advertising_data() -> [u8; 24] { + const EDDYSTONE_URL: &[u8] = b"www.rust-lang.com"; + + let mut service_data = [0u8; 24]; + let url_len = EDDYSTONE_URL.len(); + + service_data[0] = 6 + url_len as u8; + service_data[1] = AdvertisingDataType::ServiceData as u8; + + // 16-bit eddystone uuid + service_data[2] = 0xaa; + service_data[3] = 0xFE; + + service_data[4] = 0x10; // URL frame type + service_data[5] = 22_i8 as u8; // calibrated TX power at 0m + service_data[6] = 0x03; // eddystone url prefix = https + + service_data[7..(7 + url_len)].copy_from_slice(EDDYSTONE_URL); + + service_data +}